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/canvas-bpath.h"
19 #include "display/curve.h"
20 #include "display/sp-ctrlline.h"
21 #include "display/sodipodi-ctrl.h"
22 #include "display/sp-canvas-util.h"
23 #include <glibmm/i18n.h>
24 #include <2geom/pathvector.h>
25 #include <2geom/sbasis-to-bezier.h>
26 #include <2geom/bezier-curve.h>
27 #include <2geom/hvlinesegment.h>
28 #include "helper/units.h"
29 #include "knot.h"
30 #include "inkscape.h"
31 #include "document.h"
32 #include "sp-namedview.h"
33 #include "desktop.h"
34 #include "desktop-handles.h"
35 #include "snap.h"
36 #include "message-stack.h"
37 #include "message-context.h"
38 #include "node-context.h"
39 #include "shape-editor.h"
40 #include "selection-chemistry.h"
41 #include "selection.h"
42 #include "xml/repr.h"
43 #include "prefs-utils.h"
44 #include "sp-metrics.h"
45 #include "sp-path.h"
46 #include "libnr/nr-matrix-ops.h"
47 #include "svg/svg.h"
48 #include "verbs.h"
49 #include "display/bezier-utils.h"
50 #include <vector>
51 #include <algorithm>
52 #include <cstring>
53 #include <cmath>
54 #include <string>
55 #include "live_effects/lpeobject.h"
56 #include "live_effects/lpeobject-reference.h"
57 #include "live_effects/effect.h"
58 #include "live_effects/parameter/parameter.h"
59 #include "live_effects/parameter/path.h"
60 #include "util/mathfns.h"
61 #include "display/snap-indicator.h"
62 #include "snapped-point.h"
64 class NR::Matrix;
66 /// \todo
67 /// evil evil evil. FIXME: conflict of two different Path classes!
68 /// There is a conflict in the namespace between two classes named Path.
69 /// #include "sp-flowtext.h"
70 /// #include "sp-flowregion.h"
72 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
73 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
74 GType sp_flowregion_get_type (void);
75 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
76 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
77 GType sp_flowtext_get_type (void);
78 // end evil workaround
80 #include "helper/stlport.h"
83 /// \todo fixme: Implement these via preferences */
85 #define NODE_FILL 0xbfbfbf00
86 #define NODE_STROKE 0x000000ff
87 #define NODE_FILL_HI 0xff000000
88 #define NODE_STROKE_HI 0x000000ff
89 #define NODE_FILL_SEL 0x0000ffff
90 #define NODE_STROKE_SEL 0x000000ff
91 #define NODE_FILL_SEL_HI 0xff000000
92 #define NODE_STROKE_SEL_HI 0x000000ff
93 #define KNOT_FILL 0xffffffff
94 #define KNOT_STROKE 0x000000ff
95 #define KNOT_FILL_HI 0xff000000
96 #define KNOT_STROKE_HI 0x000000ff
98 static GMemChunk *nodechunk = NULL;
100 /* Creation from object */
102 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
103 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
104 Geom::PathVector sp_nodepath_sanitize_path(Geom::PathVector const &pathv_in);
106 /* Object updating */
108 static void stamp_repr(Inkscape::NodePath::Path *np);
109 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
110 static gchar *create_typestr(Inkscape::NodePath::Path *np);
112 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
114 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
116 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
118 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
120 /* Adjust handle placement, if the node or the other handle is moved */
121 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
122 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
124 /* Node event callbacks */
125 static void node_clicked(SPKnot *knot, guint state, gpointer data);
126 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
127 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
128 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
130 /* Handle event callbacks */
131 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
132 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
133 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
134 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
135 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
136 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
138 /* Constructors and destructors */
140 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
141 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
142 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
143 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
144 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
145 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
146 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
148 /* Helpers */
150 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
151 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
152 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
154 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
155 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
157 // active_node indicates mouseover node
158 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
160 static SPCanvasItem *
161 sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false) {
162 SPCurve *helper_curve = curve->copy();
163 helper_curve->transform(to_2geom(np->i2d));
164 SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
165 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
166 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO);
167 sp_canvas_item_move_to_z(helper_path, 0);
168 if (show) {
169 sp_canvas_item_show(helper_path);
170 }
171 helper_curve->unref();
172 return helper_path;
173 }
175 static SPCanvasItem *
176 canvasitem_from_pathvec(Inkscape::NodePath::Path *np, Geom::PathVector const &pathv, bool show) {
177 SPCurve *helper_curve = new SPCurve(pathv);
178 return sp_nodepath_make_helper_item(np, helper_curve, show);
179 }
181 static void
182 sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) {
183 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
184 if (!SP_IS_LPE_ITEM(np->item)) {
185 g_print ("Only LPEItems can have helperpaths!\n");
186 return;
187 }
189 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
190 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
191 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
192 Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i);
193 Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->lpe;
194 // create new canvas items from the effect's helper paths
195 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
196 for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
197 (*np->helper_path_vec)[lpe].push_back(canvasitem_from_pathvec(np, *j, true));
198 }
199 }
200 }
202 void
203 sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
204 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
205 if (!SP_IS_LPE_ITEM(np->item)) {
206 g_print ("Only LPEItems can have helperpaths!\n");
207 return;
208 }
210 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
211 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
212 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
213 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->lpe;
214 /* update canvas items from the effect's helper paths; note that this code relies on the
215 * fact that getHelperPaths() will always return the same number of helperpaths in the same
216 * order as during their creation in sp_nodepath_create_helperpaths
217 */
218 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
219 for (unsigned int j = 0; j < hpaths.size(); ++j) {
220 SPCurve *curve = new SPCurve(hpaths[j]);
221 curve->transform(to_2geom(np->i2d));
222 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(((*np->helper_path_vec)[lpe])[j]), curve);
223 curve = curve->unref();
224 }
225 }
226 }
228 static void
229 sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
230 for (HelperPathList::iterator i = np->helper_path_vec->begin(); i != np->helper_path_vec->end(); ++i) {
231 for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
232 GtkObject *temp = *j;
233 *j = NULL;
234 gtk_object_destroy(temp);
235 }
236 }
237 }
240 /**
241 * \brief Creates new nodepath from item
242 */
243 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
244 {
245 Inkscape::XML::Node *repr = object->repr;
247 /** \todo
248 * FIXME: remove this. We don't want to edit paths inside flowtext.
249 * Instead we will build our flowtext with cloned paths, so that the
250 * real paths are outside the flowtext and thus editable as usual.
251 */
252 if (SP_IS_FLOWTEXT(object)) {
253 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
254 if SP_IS_FLOWREGION(child) {
255 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
256 if (grandchild && SP_IS_PATH(grandchild)) {
257 object = SP_ITEM(grandchild);
258 break;
259 }
260 }
261 }
262 }
264 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
266 if (curve == NULL)
267 return NULL;
269 if (curve->get_segment_count() < 1) {
270 curve->unref();
271 return NULL; // prevent crash for one-node paths
272 }
274 //Create new nodepath
275 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
276 if (!np) {
277 curve->unref();
278 return NULL;
279 }
281 // Set defaults
282 np->desktop = desktop;
283 np->object = object;
284 np->subpaths = NULL;
285 np->selected = NULL;
286 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
287 np->local_change = 0;
288 np->show_handles = show_handles;
289 np->helper_path = NULL;
290 np->helper_path_vec = new HelperPathList;
291 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
292 np->helperpath_width = 1.0;
293 np->curve = curve->copy();
294 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
295 if (SP_IS_LPE_ITEM(object)) {
296 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
297 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
298 np->show_helperpath = true;
299 }
300 }
301 np->straight_path = false;
302 if (IS_LIVEPATHEFFECT(object) && item) {
303 np->item = item;
304 } else {
305 np->item = SP_ITEM(object);
306 }
308 // we need to update item's transform from the repr here,
309 // because they may be out of sync when we respond
310 // to a change in repr by regenerating nodepath --bb
311 sp_object_read_attr(SP_OBJECT(np->item), "transform");
313 np->i2d = from_2geom(sp_item_i2d_affine(np->item));
314 np->d2i = np->i2d.inverse();
316 np->repr = repr;
317 if (repr_key_in) { // apparantly the object is an LPEObject
318 np->repr_key = g_strdup(repr_key_in);
319 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
320 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
321 if (lpeparam) {
322 lpeparam->param_setup_nodepath(np);
323 }
324 } else {
325 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
326 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
327 np->repr_key = g_strdup("inkscape:original-d");
329 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
330 if (lpe) {
331 lpe->setup_nodepath(np);
332 }
333 } else {
334 np->repr_key = g_strdup("d");
335 }
336 }
338 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
339 * So for example a closed rectangle has a nodetypestring of length 5.
340 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
341 Geom::PathVector pathv_sanitized = sp_nodepath_sanitize_path(np->curve->get_pathvector());
342 np->curve->set_pathvector(pathv_sanitized);
343 guint length = np->curve->get_segment_count();
344 for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
345 length += pit->empty() ? 0 : 1;
346 }
348 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
349 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
351 // create the subpath(s) from the bpath
352 subpaths_from_pathvector(np, pathv_sanitized, typestr);
354 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
355 np->subpaths = g_list_reverse(np->subpaths);
357 delete[] typestr;
358 curve->unref();
360 // Draw helper curve
361 if (np->show_helperpath) {
362 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
363 }
365 sp_nodepath_create_helperpaths(np);
367 return np;
368 }
370 /**
371 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
372 */
373 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
375 if (!np) //soft fail, like delete
376 return;
378 while (np->subpaths) {
379 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
380 }
382 //Inform the ShapeEditor that made me, if any, that I am gone.
383 if (np->shape_editor)
384 np->shape_editor->nodepath_destroyed();
386 g_assert(!np->selected);
388 if (np->helper_path) {
389 GtkObject *temp = np->helper_path;
390 np->helper_path = NULL;
391 gtk_object_destroy(temp);
392 }
393 if (np->curve) {
394 np->curve->unref();
395 np->curve = NULL;
396 }
398 if (np->repr_key) {
399 g_free(np->repr_key);
400 np->repr_key = NULL;
401 }
402 if (np->repr_nodetypes_key) {
403 g_free(np->repr_nodetypes_key);
404 np->repr_nodetypes_key = NULL;
405 }
407 sp_nodepath_destroy_helperpaths(np);
408 delete np->helper_path_vec;
409 np->helper_path_vec = NULL;
411 np->desktop = NULL;
413 g_free(np);
414 }
416 /**
417 * Return the node count of a given NodeSubPath.
418 */
419 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
420 {
421 if (!subpath)
422 return 0;
423 gint nodeCount = g_list_length(subpath->nodes);
424 return nodeCount;
425 }
427 /**
428 * Return the node count of a given NodePath.
429 */
430 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
431 {
432 if (!np)
433 return 0;
434 gint nodeCount = 0;
435 for (GList *item = np->subpaths ; item ; item=item->next) {
436 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
437 nodeCount += g_list_length(subpath->nodes);
438 }
439 return nodeCount;
440 }
442 /**
443 * Return the subpath count of a given NodePath.
444 */
445 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
446 {
447 if (!np)
448 return 0;
449 return g_list_length (np->subpaths);
450 }
452 /**
453 * Return the selected node count of a given NodePath.
454 */
455 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
456 {
457 if (!np)
458 return 0;
459 return g_list_length (np->selected);
460 }
462 /**
463 * Return the number of subpaths where nodes are selected in a given NodePath.
464 */
465 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
466 {
467 if (!np)
468 return 0;
469 if (!np->selected)
470 return 0;
471 if (!np->selected->next)
472 return 1;
473 gint count = 0;
474 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
475 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
476 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
477 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
478 if (node->selected) {
479 count ++;
480 break;
481 }
482 }
483 }
484 return count;
485 }
487 /**
488 * Clean up a nodepath after editing.
489 *
490 * Currently we are deleting trivial subpaths.
491 */
492 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
493 {
494 GList *badSubPaths = NULL;
496 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
497 for (GList *l = nodepath->subpaths; l ; l=l->next) {
498 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
499 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
500 badSubPaths = g_list_append(badSubPaths, sp);
501 }
503 //Delete them. This second step is because sp_nodepath_subpath_destroy()
504 //also removes the subpath from nodepath->subpaths
505 for (GList *l = badSubPaths; l ; l=l->next) {
506 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
507 sp_nodepath_subpath_destroy(sp);
508 }
510 g_list_free(badSubPaths);
511 }
513 /**
514 * Create new nodepaths from pathvector, make it subpaths of np.
515 * \param t The node type array.
516 */
517 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
518 {
519 guint i = 0; // index into node type array
520 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
521 if (pit->empty())
522 continue; // don't add single knot paths
524 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
526 NR::Point ppos = from_2geom(pit->initialPoint()) * np->i2d;
527 NRPathcode pcode = NR_MOVETO;
529 /* Johan: Note that this is pretty arcane code. I am pretty sure it is working correctly, be very certain to change it! (better to just rewrite this whole method)*/
530 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
531 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
532 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
533 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
534 {
535 NR::Point pos = from_2geom(cit->initialPoint()) * np->i2d;
536 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
537 ppos = from_2geom(cit->finalPoint());
538 pcode = NR_LINETO;
539 }
540 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
541 std::vector<Geom::Point> points = cubic_bezier->points();
542 NR::Point pos = from_2geom(points[0]) * np->i2d;
543 NR::Point npos = from_2geom(points[1]) * np->i2d;
544 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
545 ppos = from_2geom(points[2]) * np->i2d;
546 pcode = NR_CURVETO;
547 }
548 }
550 if (pit->closed()) {
551 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
552 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
553 * If the length is zero, don't add it to the nodepath. */
554 Geom::Curve const &closing_seg = pit->back_closed();
555 if ( ! closing_seg.isDegenerate() ) {
556 NR::Point pos = from_2geom(closing_seg.finalPoint()) * np->i2d;
557 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
558 }
560 sp_nodepath_subpath_close(sp);
561 }
562 }
563 }
565 /**
566 * Convert from sodipodi:nodetypes to new style type array.
567 */
568 static
569 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
570 {
571 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
573 guint pos = 0;
575 if (types) {
576 for (guint i = 0; types[i] && ( i < length ); i++) {
577 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
578 if (types[i] != '\0') {
579 switch (types[i]) {
580 case 's':
581 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
582 break;
583 case 'z':
584 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
585 break;
586 case 'c':
587 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
588 break;
589 default:
590 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
591 break;
592 }
593 }
594 }
595 }
597 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
599 return typestr;
600 }
602 /**
603 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
604 * updated but repr is not (for speed). Used during curve and node drag.
605 */
606 static void update_object(Inkscape::NodePath::Path *np)
607 {
608 g_assert(np);
610 np->curve->unref();
611 np->curve = create_curve(np);
613 sp_nodepath_set_curve(np, np->curve);
615 if (np->show_helperpath) {
616 SPCurve * helper_curve = np->curve->copy();
617 helper_curve->transform(to_2geom(np->i2d));
618 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
619 helper_curve->unref();
620 }
622 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
623 //sp_nodepath_update_helperpaths(np);
625 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
626 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
627 np->shape_editor->update_knotholder();
628 }
630 /**
631 * Update XML path node with data from path object.
632 */
633 static void update_repr_internal(Inkscape::NodePath::Path *np)
634 {
635 g_assert(np);
637 Inkscape::XML::Node *repr = np->object->repr;
639 np->curve->unref();
640 np->curve = create_curve(np);
642 gchar *typestr = create_typestr(np);
643 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
645 // determine if path has an effect applied and write to correct "d" attribute.
646 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
647 np->local_change++;
648 repr->setAttribute(np->repr_key, svgpath);
649 }
651 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
652 np->local_change++;
653 repr->setAttribute(np->repr_nodetypes_key, typestr);
654 }
656 g_free(svgpath);
657 g_free(typestr);
659 if (np->show_helperpath) {
660 SPCurve * helper_curve = np->curve->copy();
661 helper_curve->transform(to_2geom(np->i2d));
662 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
663 helper_curve->unref();
664 }
666 // TODO: do we need this call here? after all, update_object() should have been called just before
667 //sp_nodepath_update_helperpaths(np);
668 }
670 /**
671 * Update XML path node with data from path object, commit changes forever.
672 */
673 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
674 {
675 //fixme: np can be NULL, so check before proceeding
676 g_return_if_fail(np != NULL);
678 update_repr_internal(np);
679 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
681 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
682 annotation);
683 }
685 /**
686 * Update XML path node with data from path object, commit changes with undo.
687 */
688 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
689 {
690 update_repr_internal(np);
691 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
692 annotation);
693 }
695 /**
696 * Make duplicate of path, replace corresponding XML node in tree, commit.
697 */
698 static void stamp_repr(Inkscape::NodePath::Path *np)
699 {
700 g_assert(np);
702 Inkscape::XML::Node *old_repr = np->object->repr;
703 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
705 // remember the position of the item
706 gint pos = old_repr->position();
707 // remember parent
708 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
710 SPCurve *curve = create_curve(np);
711 gchar *typestr = create_typestr(np);
713 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
715 new_repr->setAttribute(np->repr_key, svgpath);
716 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
718 // add the new repr to the parent
719 parent->appendChild(new_repr);
720 // move to the saved position
721 new_repr->setPosition(pos > 0 ? pos : 0);
723 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
724 _("Stamp"));
726 Inkscape::GC::release(new_repr);
727 g_free(svgpath);
728 g_free(typestr);
729 curve->unref();
730 }
732 /**
733 * Create curve from path.
734 */
735 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
736 {
737 SPCurve *curve = new SPCurve();
739 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
740 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
741 curve->moveto(sp->first->pos * np->d2i);
742 Inkscape::NodePath::Node *n = sp->first->n.other;
743 while (n) {
744 NR::Point const end_pt = n->pos * np->d2i;
745 switch (n->code) {
746 case NR_LINETO:
747 curve->lineto(end_pt);
748 break;
749 case NR_CURVETO:
750 curve->curveto(n->p.other->n.pos * np->d2i,
751 n->p.pos * np->d2i,
752 end_pt);
753 break;
754 default:
755 g_assert_not_reached();
756 break;
757 }
758 if (n != sp->last) {
759 n = n->n.other;
760 } else {
761 n = NULL;
762 }
763 }
764 if (sp->closed) {
765 curve->closepath();
766 }
767 }
769 return curve;
770 }
772 /**
773 * Convert path type string to sodipodi:nodetypes style.
774 */
775 static gchar *create_typestr(Inkscape::NodePath::Path *np)
776 {
777 gchar *typestr = g_new(gchar, 32);
778 gint len = 32;
779 gint pos = 0;
781 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
782 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
784 if (pos >= len) {
785 typestr = g_renew(gchar, typestr, len + 32);
786 len += 32;
787 }
789 typestr[pos++] = 'c';
791 Inkscape::NodePath::Node *n;
792 n = sp->first->n.other;
793 while (n) {
794 gchar code;
796 switch (n->type) {
797 case Inkscape::NodePath::NODE_CUSP:
798 code = 'c';
799 break;
800 case Inkscape::NodePath::NODE_SMOOTH:
801 code = 's';
802 break;
803 case Inkscape::NodePath::NODE_SYMM:
804 code = 'z';
805 break;
806 default:
807 g_assert_not_reached();
808 code = '\0';
809 break;
810 }
812 if (pos >= len) {
813 typestr = g_renew(gchar, typestr, len + 32);
814 len += 32;
815 }
817 typestr[pos++] = code;
819 if (n != sp->last) {
820 n = n->n.other;
821 } else {
822 n = NULL;
823 }
824 }
825 }
827 if (pos >= len) {
828 typestr = g_renew(gchar, typestr, len + 1);
829 len += 1;
830 }
832 typestr[pos++] = '\0';
834 return typestr;
835 }
837 /**
838 * Returns current path in context. // later eliminate this function at all!
839 */
840 static Inkscape::NodePath::Path *sp_nodepath_current()
841 {
842 if (!SP_ACTIVE_DESKTOP) {
843 return NULL;
844 }
846 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
848 if (!SP_IS_NODE_CONTEXT(event_context)) {
849 return NULL;
850 }
852 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
853 }
857 /**
858 \brief Fills node and handle positions for three nodes, splitting line
859 marked by end at distance t.
860 */
861 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
862 {
863 g_assert(new_path != NULL);
864 g_assert(end != NULL);
866 g_assert(end->p.other == new_path);
867 Inkscape::NodePath::Node *start = new_path->p.other;
868 g_assert(start);
870 if (end->code == NR_LINETO) {
871 new_path->type =Inkscape::NodePath::NODE_CUSP;
872 new_path->code = NR_LINETO;
873 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
874 } else {
875 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
876 new_path->code = NR_CURVETO;
877 gdouble s = 1 - t;
878 for (int dim = 0; dim < 2; dim++) {
879 NR::Coord const f000 = start->pos[dim];
880 NR::Coord const f001 = start->n.pos[dim];
881 NR::Coord const f011 = end->p.pos[dim];
882 NR::Coord const f111 = end->pos[dim];
883 NR::Coord const f00t = s * f000 + t * f001;
884 NR::Coord const f01t = s * f001 + t * f011;
885 NR::Coord const f11t = s * f011 + t * f111;
886 NR::Coord const f0tt = s * f00t + t * f01t;
887 NR::Coord const f1tt = s * f01t + t * f11t;
888 NR::Coord const fttt = s * f0tt + t * f1tt;
889 start->n.pos[dim] = f00t;
890 new_path->p.pos[dim] = f0tt;
891 new_path->pos[dim] = fttt;
892 new_path->n.pos[dim] = f1tt;
893 end->p.pos[dim] = f11t;
894 }
895 }
896 }
898 /**
899 * Adds new node on direct line between two nodes, activates handles of all
900 * three nodes.
901 */
902 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
903 {
904 g_assert(end);
905 g_assert(end->subpath);
906 g_assert(g_list_find(end->subpath->nodes, end));
908 Inkscape::NodePath::Node *start = end->p.other;
909 g_assert( start->n.other == end );
910 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
911 end,
912 (NRPathcode)end->code == NR_LINETO?
913 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
914 (NRPathcode)end->code,
915 &start->pos, &start->pos, &start->n.pos);
916 sp_nodepath_line_midpoint(newnode, end, t);
918 sp_node_adjust_handles(start);
919 sp_node_update_handles(start);
920 sp_node_update_handles(newnode);
921 sp_node_adjust_handles(end);
922 sp_node_update_handles(end);
924 return newnode;
925 }
927 /**
928 \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
929 */
930 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
931 {
932 g_assert(node);
933 g_assert(node->subpath);
934 g_assert(g_list_find(node->subpath->nodes, node));
936 Inkscape::NodePath::SubPath *sp = node->subpath;
937 Inkscape::NodePath::Path *np = sp->nodepath;
939 if (sp->closed) {
940 sp_nodepath_subpath_open(sp, node);
941 return sp->first;
942 } else {
943 // no break for end nodes
944 if (node == sp->first) return NULL;
945 if (node == sp->last ) return NULL;
947 // create a new subpath
948 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
950 // duplicate the break node as start of the new subpath
951 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
953 // attach rest of curve to new node
954 g_assert(node->n.other);
955 newnode->n.other = node->n.other; node->n.other = NULL;
956 newnode->n.other->p.other = newnode;
957 newsubpath->last = sp->last;
958 sp->last = node;
959 node = newnode;
960 while (node->n.other) {
961 node = node->n.other;
962 node->subpath = newsubpath;
963 sp->nodes = g_list_remove(sp->nodes, node);
964 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
965 }
968 return newnode;
969 }
970 }
972 /**
973 * Duplicate node and connect to neighbours.
974 */
975 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
976 {
977 g_assert(node);
978 g_assert(node->subpath);
979 g_assert(g_list_find(node->subpath->nodes, node));
981 Inkscape::NodePath::SubPath *sp = node->subpath;
983 NRPathcode code = (NRPathcode) node->code;
984 if (code == NR_MOVETO) { // if node is the endnode,
985 node->code = NR_LINETO; // new one is inserted before it, so change that to line
986 }
988 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
990 if (!node->n.other || !node->p.other) // if node is an endnode, select it
991 return node;
992 else
993 return newnode; // otherwise select the newly created node
994 }
996 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
997 {
998 node->p.pos = (node->pos + (node->pos - node->n.pos));
999 }
1001 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1002 {
1003 node->n.pos = (node->pos + (node->pos - node->p.pos));
1004 }
1006 /**
1007 * Change line type at node, with side effects on neighbours.
1008 */
1009 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1010 {
1011 g_assert(end);
1012 g_assert(end->subpath);
1013 g_assert(end->p.other);
1015 if (end->code == static_cast< guint > ( code ) )
1016 return;
1018 Inkscape::NodePath::Node *start = end->p.other;
1020 end->code = code;
1022 if (code == NR_LINETO) {
1023 if (start->code == NR_LINETO) {
1024 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1025 }
1026 if (end->n.other) {
1027 if (end->n.other->code == NR_LINETO) {
1028 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1029 }
1030 }
1031 } else {
1032 NR::Point delta = end->pos - start->pos;
1033 start->n.pos = start->pos + delta / 3;
1034 end->p.pos = end->pos - delta / 3;
1035 sp_node_adjust_handle(start, 1);
1036 sp_node_adjust_handle(end, -1);
1037 }
1039 sp_node_update_handles(start);
1040 sp_node_update_handles(end);
1041 }
1043 /**
1044 * Change node type, and its handles accordingly.
1045 */
1046 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1047 {
1048 g_assert(node);
1049 g_assert(node->subpath);
1051 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1052 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1053 type =Inkscape::NodePath::NODE_CUSP;
1054 }
1055 }
1057 node->type = type;
1059 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1060 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1061 node->knot->setSize (node->selected? 11 : 9);
1062 sp_knot_update_ctrl(node->knot);
1063 } else {
1064 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1065 node->knot->setSize (node->selected? 9 : 7);
1066 sp_knot_update_ctrl(node->knot);
1067 }
1069 // if one of handles is mouseovered, preserve its position
1070 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1071 sp_node_adjust_handle(node, 1);
1072 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1073 sp_node_adjust_handle(node, -1);
1074 } else {
1075 sp_node_adjust_handles(node);
1076 }
1078 sp_node_update_handles(node);
1080 sp_nodepath_update_statusbar(node->subpath->nodepath);
1082 return node;
1083 }
1085 bool
1086 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1087 {
1088 Inkscape::NodePath::Node *othernode = side->other;
1089 if (!othernode)
1090 return false;
1091 NRPathcode const code = sp_node_path_code_from_side(node, side);
1092 if (code == NR_LINETO)
1093 return true;
1094 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1095 if (&node->p == side) {
1096 other_to_me = &othernode->n;
1097 } else if (&node->n == side) {
1098 other_to_me = &othernode->p;
1099 }
1100 if (!other_to_me)
1101 return false;
1102 bool is_line =
1103 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1104 NR::L2(node->pos - side->pos) < 1e-6);
1105 return is_line;
1106 }
1108 /**
1109 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1110 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1111 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1112 * If already cusp and set to cusp, retracts handles.
1113 */
1114 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1115 {
1116 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1118 /*
1119 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1121 if (two_handles) {
1122 // do nothing, adjust_handles called via set_node_type will line them up
1123 } else if (one_handle) {
1124 if (opposite_to_handle_is_line) {
1125 if (lined_up) {
1126 // already half-smooth; pull opposite handle too making it fully smooth
1127 } else {
1128 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1129 }
1130 } else {
1131 // pull opposite handle in line with the existing one
1132 }
1133 } else if (no_handles) {
1134 if (both_segments_are_lines OR both_segments_are_curves) {
1135 //pull both handles
1136 } else {
1137 // pull the handle opposite to line segment, making node half-smooth
1138 }
1139 }
1140 */
1141 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1142 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1143 bool p_is_line = sp_node_side_is_line(node, &node->p);
1144 bool n_is_line = sp_node_side_is_line(node, &node->n);
1146 if (p_has_handle && n_has_handle) {
1147 // do nothing, adjust_handles will line them up
1148 } else if (p_has_handle || n_has_handle) {
1149 if (p_has_handle && n_is_line) {
1150 Radial line (node->n.other->pos - node->pos);
1151 Radial handle (node->pos - node->p.pos);
1152 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1153 // already half-smooth; pull opposite handle too making it fully smooth
1154 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1155 } else {
1156 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1157 }
1158 } else if (n_has_handle && p_is_line) {
1159 Radial line (node->p.other->pos - node->pos);
1160 Radial handle (node->pos - node->n.pos);
1161 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1162 // already half-smooth; pull opposite handle too making it fully smooth
1163 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1164 } else {
1165 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1166 }
1167 } else if (p_has_handle && node->n.other) {
1168 // pull n handle
1169 node->n.other->code = NR_CURVETO;
1170 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1171 NR::L2(node->p.pos - node->pos) :
1172 NR::L2(node->n.other->pos - node->pos) / 3;
1173 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1174 } else if (n_has_handle && node->p.other) {
1175 // pull p handle
1176 node->code = NR_CURVETO;
1177 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1178 NR::L2(node->n.pos - node->pos) :
1179 NR::L2(node->p.other->pos - node->pos) / 3;
1180 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1181 }
1182 } else if (!p_has_handle && !n_has_handle) {
1183 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1184 // no handles, but both segments are either lnes or curves:
1185 //pull both handles
1187 // convert both to curves:
1188 node->code = NR_CURVETO;
1189 node->n.other->code = NR_CURVETO;
1191 NR::Point leg_prev = node->pos - node->p.other->pos;
1192 NR::Point leg_next = node->pos - node->n.other->pos;
1194 double norm_leg_prev = L2(leg_prev);
1195 double norm_leg_next = L2(leg_next);
1197 NR::Point delta;
1198 if (norm_leg_next > 0.0) {
1199 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1200 (&delta)->normalize();
1201 }
1203 if (type == Inkscape::NodePath::NODE_SYMM) {
1204 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1205 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1206 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1207 } else {
1208 // length of handle is proportional to distance to adjacent node
1209 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1210 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1211 }
1213 } else {
1214 // pull the handle opposite to line segment, making it half-smooth
1215 if (p_is_line && node->n.other) {
1216 if (type != Inkscape::NodePath::NODE_SYMM) {
1217 // pull n handle
1218 node->n.other->code = NR_CURVETO;
1219 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1220 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1221 }
1222 } else if (n_is_line && node->p.other) {
1223 if (type != Inkscape::NodePath::NODE_SYMM) {
1224 // pull p handle
1225 node->code = NR_CURVETO;
1226 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1227 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1228 }
1229 }
1230 }
1231 }
1232 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1233 // cusping a cusp: retract nodes
1234 node->p.pos = node->pos;
1235 node->n.pos = node->pos;
1236 }
1238 sp_nodepath_set_node_type (node, type);
1239 }
1241 /**
1242 * Move node to point, and adjust its and neighbouring handles.
1243 */
1244 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1245 {
1246 NR::Point delta = p - node->pos;
1247 node->pos = p;
1249 node->p.pos += delta;
1250 node->n.pos += delta;
1252 Inkscape::NodePath::Node *node_p = NULL;
1253 Inkscape::NodePath::Node *node_n = NULL;
1255 if (node->p.other) {
1256 if (node->code == NR_LINETO) {
1257 sp_node_adjust_handle(node, 1);
1258 sp_node_adjust_handle(node->p.other, -1);
1259 node_p = node->p.other;
1260 }
1261 }
1262 if (node->n.other) {
1263 if (node->n.other->code == NR_LINETO) {
1264 sp_node_adjust_handle(node, -1);
1265 sp_node_adjust_handle(node->n.other, 1);
1266 node_n = node->n.other;
1267 }
1268 }
1270 // this function is only called from batch movers that will update display at the end
1271 // themselves, so here we just move all the knots without emitting move signals, for speed
1272 sp_node_update_handles(node, false);
1273 if (node_n) {
1274 sp_node_update_handles(node_n, false);
1275 }
1276 if (node_p) {
1277 sp_node_update_handles(node_p, false);
1278 }
1279 }
1281 /**
1282 * Call sp_node_moveto() for node selection and handle possible snapping.
1283 */
1284 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1285 bool const snap, bool constrained = false,
1286 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1287 {
1288 NR::Coord best = NR_HUGE;
1289 NR::Point delta(dx, dy);
1290 NR::Point best_pt = delta;
1291 Inkscape::SnappedPoint best_abs;
1293 if (snap) {
1294 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1295 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1296 * must provide that information. */
1298 // Build a list of the unselected nodes to which the snapper should snap
1299 std::vector<NR::Point> unselected_nodes;
1300 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1301 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1302 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1303 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1304 if (!node->selected) {
1305 unselected_nodes.push_back(node->pos);
1306 }
1307 }
1308 }
1310 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1312 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1313 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1314 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1315 Inkscape::SnappedPoint s;
1316 if (constrained) {
1317 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1318 dedicated_constraint.setPoint(n->pos);
1319 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1320 } else {
1321 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1322 }
1323 if (s.getSnapped() && (s.getDistance() < best)) {
1324 best = s.getDistance();
1325 best_abs = s;
1326 best_pt = s.getPoint() - n->pos;
1327 }
1328 }
1330 if (best_abs.getSnapped()) {
1331 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1332 } else {
1333 nodepath->desktop->snapindicator->remove_snappoint();
1334 }
1335 }
1337 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1338 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1339 sp_node_moveto(n, n->pos + best_pt);
1340 }
1342 // do not update repr here so that node dragging is acceptably fast
1343 update_object(nodepath);
1344 }
1346 /**
1347 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1348 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1349 near x = 0.
1350 */
1351 double
1352 sculpt_profile (double x, double alpha, guint profile)
1353 {
1354 if (x >= 1)
1355 return 0;
1356 if (x <= 0)
1357 return 1;
1359 switch (profile) {
1360 case SCULPT_PROFILE_LINEAR:
1361 return 1 - x;
1362 case SCULPT_PROFILE_BELL:
1363 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1364 case SCULPT_PROFILE_ELLIPTIC:
1365 return sqrt(1 - x*x);
1366 }
1368 return 1;
1369 }
1371 double
1372 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1373 {
1374 // extremely primitive for now, don't have time to look for the real one
1375 double lower = NR::L2(b - a);
1376 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1377 return (lower + upper)/2;
1378 }
1380 void
1381 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1382 {
1383 n->pos = n->origin + delta;
1384 n->n.pos = n->n.origin + delta_n;
1385 n->p.pos = n->p.origin + delta_p;
1386 sp_node_adjust_handles(n);
1387 sp_node_update_handles(n, false);
1388 }
1390 /**
1391 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1392 * on how far they are from the dragged node n.
1393 */
1394 static void
1395 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1396 {
1397 g_assert (n);
1398 g_assert (nodepath);
1399 g_assert (n->subpath->nodepath == nodepath);
1401 double pressure = n->knot->pressure;
1402 if (pressure == 0)
1403 pressure = 0.5; // default
1404 pressure = CLAMP (pressure, 0.2, 0.8);
1406 // map pressure to alpha = 1/5 ... 5
1407 double alpha = 1 - 2 * fabs(pressure - 0.5);
1408 if (pressure > 0.5)
1409 alpha = 1/alpha;
1411 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1413 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1414 // Only one subpath has selected nodes:
1415 // use linear mode, where the distance from n to node being dragged is calculated along the path
1417 double n_sel_range = 0, p_sel_range = 0;
1418 guint n_nodes = 0, p_nodes = 0;
1419 guint n_sel_nodes = 0, p_sel_nodes = 0;
1421 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1422 {
1423 double n_range = 0, p_range = 0;
1424 bool n_going = true, p_going = true;
1425 Inkscape::NodePath::Node *n_node = n;
1426 Inkscape::NodePath::Node *p_node = n;
1427 do {
1428 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1429 if (n_node && n_going)
1430 n_node = n_node->n.other;
1431 if (n_node == NULL) {
1432 n_going = false;
1433 } else {
1434 n_nodes ++;
1435 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1436 if (n_node->selected) {
1437 n_sel_nodes ++;
1438 n_sel_range = n_range;
1439 }
1440 if (n_node == p_node) {
1441 n_going = false;
1442 p_going = false;
1443 }
1444 }
1445 if (p_node && p_going)
1446 p_node = p_node->p.other;
1447 if (p_node == NULL) {
1448 p_going = false;
1449 } else {
1450 p_nodes ++;
1451 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1452 if (p_node->selected) {
1453 p_sel_nodes ++;
1454 p_sel_range = p_range;
1455 }
1456 if (p_node == n_node) {
1457 n_going = false;
1458 p_going = false;
1459 }
1460 }
1461 } while (n_going || p_going);
1462 }
1464 // Second pass: actually move nodes in this subpath
1465 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1466 {
1467 double n_range = 0, p_range = 0;
1468 bool n_going = true, p_going = true;
1469 Inkscape::NodePath::Node *n_node = n;
1470 Inkscape::NodePath::Node *p_node = n;
1471 do {
1472 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1473 if (n_node && n_going)
1474 n_node = n_node->n.other;
1475 if (n_node == NULL) {
1476 n_going = false;
1477 } else {
1478 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1479 if (n_node->selected) {
1480 sp_nodepath_move_node_and_handles (n_node,
1481 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1482 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1483 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1484 }
1485 if (n_node == p_node) {
1486 n_going = false;
1487 p_going = false;
1488 }
1489 }
1490 if (p_node && p_going)
1491 p_node = p_node->p.other;
1492 if (p_node == NULL) {
1493 p_going = false;
1494 } else {
1495 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1496 if (p_node->selected) {
1497 sp_nodepath_move_node_and_handles (p_node,
1498 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1499 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1500 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1501 }
1502 if (p_node == n_node) {
1503 n_going = false;
1504 p_going = false;
1505 }
1506 }
1507 } while (n_going || p_going);
1508 }
1510 } else {
1511 // Multiple subpaths have selected nodes:
1512 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1513 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1514 // fix the pear-like shape when sculpting e.g. a ring
1516 // First pass: calculate range
1517 gdouble direct_range = 0;
1518 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1519 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1520 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1521 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1522 if (node->selected) {
1523 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1524 }
1525 }
1526 }
1528 // Second pass: actually move nodes
1529 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1530 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1531 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1532 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1533 if (node->selected) {
1534 if (direct_range > 1e-6) {
1535 sp_nodepath_move_node_and_handles (node,
1536 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1537 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1538 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1539 } else {
1540 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1541 }
1543 }
1544 }
1545 }
1546 }
1548 // do not update repr here so that node dragging is acceptably fast
1549 update_object(nodepath);
1550 }
1553 /**
1554 * Move node selection to point, adjust its and neighbouring handles,
1555 * handle possible snapping, and commit the change with possible undo.
1556 */
1557 void
1558 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1559 {
1560 if (!nodepath) return;
1562 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1564 if (dx == 0) {
1565 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1566 } else if (dy == 0) {
1567 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1568 } else {
1569 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1570 }
1571 }
1573 /**
1574 * Move node selection off screen and commit the change.
1575 */
1576 void
1577 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1578 {
1579 // borrowed from sp_selection_move_screen in selection-chemistry.c
1580 // we find out the current zoom factor and divide deltas by it
1581 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1583 gdouble zoom = desktop->current_zoom();
1584 gdouble zdx = dx / zoom;
1585 gdouble zdy = dy / zoom;
1587 if (!nodepath) return;
1589 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1591 if (dx == 0) {
1592 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1593 } else if (dy == 0) {
1594 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1595 } else {
1596 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1597 }
1598 }
1600 /**
1601 * Move selected nodes to the absolute position given
1602 */
1603 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1604 {
1605 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1606 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1607 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1608 sp_node_moveto(n, npos);
1609 }
1611 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1612 }
1614 /**
1615 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1616 */
1617 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1618 {
1619 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1620 g_return_val_if_fail(nodepath->selected, no_coord);
1622 // determine coordinate of first selected node
1623 GList *nsel = nodepath->selected;
1624 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1625 NR::Coord coord = n->pos[axis];
1626 bool coincide = true;
1628 // compare it to the coordinates of all the other selected nodes
1629 for (GList *l = nsel->next; l != NULL; l = l->next) {
1630 n = (Inkscape::NodePath::Node *) l->data;
1631 if (n->pos[axis] != coord) {
1632 coincide = false;
1633 }
1634 }
1635 if (coincide) {
1636 return coord;
1637 } else {
1638 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1639 // currently we return the coordinate of the bounding box midpoint because I don't know how
1640 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1641 return bbox.midpoint()[axis];
1642 }
1643 }
1645 /** If they don't yet exist, creates knot and line for the given side of the node */
1646 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1647 {
1648 if (!side->knot) {
1649 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"));
1651 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1652 side->knot->setSize (7);
1653 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1654 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1655 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1656 sp_knot_update_ctrl(side->knot);
1658 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1659 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1660 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1661 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1662 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1663 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1664 }
1666 if (!side->line) {
1667 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1668 SP_TYPE_CTRLLINE, NULL);
1669 }
1670 }
1672 /**
1673 * Ensure the given handle of the node is visible/invisible, update its screen position
1674 */
1675 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1676 {
1677 g_assert(node != NULL);
1679 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1680 NRPathcode code = sp_node_path_code_from_side(node, side);
1682 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1684 if (show_handle) {
1685 if (!side->knot) { // No handle knot at all
1686 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1687 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1688 side->knot->pos = side->pos;
1689 if (side->knot->item)
1690 SP_CTRL(side->knot->item)->moveto(side->pos);
1691 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1692 sp_knot_show(side->knot);
1693 } else {
1694 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1695 if (fire_move_signals) {
1696 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1697 } else {
1698 sp_knot_moveto(side->knot, side->pos);
1699 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1700 }
1701 }
1702 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1703 sp_knot_show(side->knot);
1704 }
1705 }
1706 sp_canvas_item_show(side->line);
1707 } else {
1708 if (side->knot) {
1709 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1710 sp_knot_hide(side->knot);
1711 }
1712 }
1713 if (side->line) {
1714 sp_canvas_item_hide(side->line);
1715 }
1716 }
1717 }
1719 /**
1720 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1721 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1722 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1723 * updated; otherwise, just move the knots silently (used in batch moves).
1724 */
1725 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1726 {
1727 g_assert(node != NULL);
1729 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1730 sp_knot_show(node->knot);
1731 }
1733 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1734 if (fire_move_signals)
1735 sp_knot_set_position(node->knot, node->pos, 0);
1736 else
1737 sp_knot_moveto(node->knot, node->pos);
1738 }
1740 gboolean show_handles = node->selected;
1741 if (node->p.other != NULL) {
1742 if (node->p.other->selected) show_handles = TRUE;
1743 }
1744 if (node->n.other != NULL) {
1745 if (node->n.other->selected) show_handles = TRUE;
1746 }
1748 if (node->subpath->nodepath->show_handles == false)
1749 show_handles = FALSE;
1751 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1752 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1753 }
1755 /**
1756 * Call sp_node_update_handles() for all nodes on subpath.
1757 */
1758 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1759 {
1760 g_assert(subpath != NULL);
1762 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1763 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1764 }
1765 }
1767 /**
1768 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1769 */
1770 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1771 {
1772 g_assert(nodepath != NULL);
1774 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1775 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1776 }
1777 }
1779 void
1780 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1781 {
1782 if (nodepath == NULL) return;
1784 nodepath->show_handles = show;
1785 sp_nodepath_update_handles(nodepath);
1786 }
1788 /**
1789 * Adds all selected nodes in nodepath to list.
1790 */
1791 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1792 {
1793 StlConv<Node *>::list(l, selected);
1794 /// \todo this adds a copying, rework when the selection becomes a stl list
1795 }
1797 /**
1798 * Align selected nodes on the specified axis.
1799 */
1800 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1801 {
1802 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1803 return;
1804 }
1806 if ( !nodepath->selected->next ) { // only one node selected
1807 return;
1808 }
1809 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1810 NR::Point dest(pNode->pos);
1811 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1812 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1813 if (pNode) {
1814 dest[axis] = pNode->pos[axis];
1815 sp_node_moveto(pNode, dest);
1816 }
1817 }
1819 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1820 }
1822 /// Helper struct.
1823 struct NodeSort
1824 {
1825 Inkscape::NodePath::Node *_node;
1826 NR::Coord _coord;
1827 /// \todo use vectorof pointers instead of calling copy ctor
1828 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1829 _node(node), _coord(node->pos[axis])
1830 {}
1832 };
1834 static bool operator<(NodeSort const &a, NodeSort const &b)
1835 {
1836 return (a._coord < b._coord);
1837 }
1839 /**
1840 * Distribute selected nodes on the specified axis.
1841 */
1842 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1843 {
1844 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1845 return;
1846 }
1848 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1849 return;
1850 }
1852 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1853 std::vector<NodeSort> sorted;
1854 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1855 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1856 if (pNode) {
1857 NodeSort n(pNode, axis);
1858 sorted.push_back(n);
1859 //dest[axis] = pNode->pos[axis];
1860 //sp_node_moveto(pNode, dest);
1861 }
1862 }
1863 std::sort(sorted.begin(), sorted.end());
1864 unsigned int len = sorted.size();
1865 //overall bboxes span
1866 float dist = (sorted.back()._coord -
1867 sorted.front()._coord);
1868 //new distance between each bbox
1869 float step = (dist) / (len - 1);
1870 float pos = sorted.front()._coord;
1871 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1872 it < sorted.end();
1873 it ++ )
1874 {
1875 NR::Point dest((*it)._node->pos);
1876 dest[axis] = pos;
1877 sp_node_moveto((*it)._node, dest);
1878 pos += step;
1879 }
1881 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1882 }
1885 /**
1886 * Call sp_nodepath_line_add_node() for all selected segments.
1887 */
1888 void
1889 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1890 {
1891 if (!nodepath) {
1892 return;
1893 }
1895 GList *nl = NULL;
1897 int n_added = 0;
1899 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1900 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1901 g_assert(t->selected);
1902 if (t->p.other && t->p.other->selected) {
1903 nl = g_list_prepend(nl, t);
1904 }
1905 }
1907 while (nl) {
1908 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1909 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1910 sp_nodepath_node_select(n, TRUE, FALSE);
1911 n_added ++;
1912 nl = g_list_remove(nl, t);
1913 }
1915 /** \todo fixme: adjust ? */
1916 sp_nodepath_update_handles(nodepath);
1918 if (n_added > 1) {
1919 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1920 } else if (n_added > 0) {
1921 sp_nodepath_update_repr(nodepath, _("Add node"));
1922 }
1924 sp_nodepath_update_statusbar(nodepath);
1925 }
1927 /**
1928 * Select segment nearest to point
1929 */
1930 void
1931 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1932 {
1933 if (!nodepath) {
1934 return;
1935 }
1937 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1938 Geom::PathVector const &pathv = curve->get_pathvector();
1939 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1941 // calculate index for nodepath's representation.
1942 unsigned int segment_index = floor(pvpos.t) + 1;
1943 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1944 segment_index += pathv[i].size() + 1;
1945 if (pathv[i].closed()) {
1946 segment_index += 1;
1947 }
1948 }
1950 curve->unref();
1952 //find segment to segment
1953 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1955 //fixme: this can return NULL, so check before proceeding.
1956 g_return_if_fail(e != NULL);
1958 gboolean force = FALSE;
1959 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1960 force = TRUE;
1961 }
1962 sp_nodepath_node_select(e, (gboolean) toggle, force);
1963 if (e->p.other)
1964 sp_nodepath_node_select(e->p.other, TRUE, force);
1966 sp_nodepath_update_handles(nodepath);
1968 sp_nodepath_update_statusbar(nodepath);
1969 }
1971 /**
1972 * Add a node nearest to point
1973 */
1974 void
1975 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1976 {
1977 if (!nodepath) {
1978 return;
1979 }
1981 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1982 Geom::PathVector const &pathv = curve->get_pathvector();
1983 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1985 // calculate index for nodepath's representation.
1986 double int_part;
1987 double t = std::modf(pvpos.t, &int_part);
1988 unsigned int segment_index = (unsigned int)int_part + 1;
1989 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1990 segment_index += pathv[i].size() + 1;
1991 if (pathv[i].closed()) {
1992 segment_index += 1;
1993 }
1994 }
1996 curve->unref();
1998 //find segment to split
1999 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
2001 //don't know why but t seems to flip for lines
2002 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2003 t = 1.0 - t;
2004 }
2006 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2007 sp_nodepath_node_select(n, FALSE, TRUE);
2009 /* fixme: adjust ? */
2010 sp_nodepath_update_handles(nodepath);
2012 sp_nodepath_update_repr(nodepath, _("Add node"));
2014 sp_nodepath_update_statusbar(nodepath);
2015 }
2017 /*
2018 * Adjusts a segment so that t moves by a certain delta for dragging
2019 * converts lines to curves
2020 *
2021 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2022 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2023 */
2024 void
2025 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
2026 {
2027 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
2029 //fixme: e and e->p can be NULL, so check for those before proceeding
2030 g_return_if_fail(e != NULL);
2031 g_return_if_fail(&e->p != NULL);
2033 /* feel good is an arbitrary parameter that distributes the delta between handles
2034 * if t of the drag point is less than 1/6 distance form the endpoint only
2035 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2036 */
2037 double feel_good;
2038 if (t <= 1.0 / 6.0)
2039 feel_good = 0;
2040 else if (t <= 0.5)
2041 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2042 else if (t <= 5.0 / 6.0)
2043 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2044 else
2045 feel_good = 1;
2047 //if we're dragging a line convert it to a curve
2048 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2049 sp_nodepath_set_line_type(e, NR_CURVETO);
2050 }
2052 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2053 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2054 e->p.other->n.pos += offsetcoord0;
2055 e->p.pos += offsetcoord1;
2057 // adjust handles of adjacent nodes where necessary
2058 sp_node_adjust_handle(e,1);
2059 sp_node_adjust_handle(e->p.other,-1);
2061 sp_nodepath_update_handles(e->subpath->nodepath);
2063 update_object(e->subpath->nodepath);
2065 sp_nodepath_update_statusbar(e->subpath->nodepath);
2066 }
2069 /**
2070 * Call sp_nodepath_break() for all selected segments.
2071 */
2072 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2073 {
2074 if (!nodepath) return;
2076 GList *tempin = g_list_copy(nodepath->selected);
2077 GList *temp = NULL;
2078 for (GList *l = tempin; l != NULL; l = l->next) {
2079 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2080 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2081 if (nn == NULL) continue; // no break, no new node
2082 temp = g_list_prepend(temp, nn);
2083 }
2084 g_list_free(tempin);
2086 if (temp) {
2087 sp_nodepath_deselect(nodepath);
2088 }
2089 for (GList *l = temp; l != NULL; l = l->next) {
2090 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2091 }
2093 sp_nodepath_update_handles(nodepath);
2095 sp_nodepath_update_repr(nodepath, _("Break path"));
2096 }
2098 /**
2099 * Duplicate the selected node(s).
2100 */
2101 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2102 {
2103 if (!nodepath) {
2104 return;
2105 }
2107 GList *temp = NULL;
2108 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2109 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2110 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2111 if (nn == NULL) continue; // could not duplicate
2112 temp = g_list_prepend(temp, nn);
2113 }
2115 if (temp) {
2116 sp_nodepath_deselect(nodepath);
2117 }
2118 for (GList *l = temp; l != NULL; l = l->next) {
2119 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2120 }
2122 sp_nodepath_update_handles(nodepath);
2124 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2125 }
2127 /**
2128 * Internal function to join two nodes by merging them into one.
2129 */
2130 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2131 {
2132 /* a and b are endpoints */
2134 // if one of the two nodes is mouseovered, fix its position
2135 NR::Point c;
2136 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2137 c = a->pos;
2138 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2139 c = b->pos;
2140 } else {
2141 // otherwise, move joined node to the midpoint
2142 c = (a->pos + b->pos) / 2;
2143 }
2145 if (a->subpath == b->subpath) {
2146 Inkscape::NodePath::SubPath *sp = a->subpath;
2147 sp_nodepath_subpath_close(sp);
2148 sp_node_moveto (sp->first, c);
2150 sp_nodepath_update_handles(sp->nodepath);
2151 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2152 return;
2153 }
2155 /* a and b are separate subpaths */
2156 Inkscape::NodePath::SubPath *sa = a->subpath;
2157 Inkscape::NodePath::SubPath *sb = b->subpath;
2158 NR::Point p;
2159 Inkscape::NodePath::Node *n;
2160 NRPathcode code;
2161 if (a == sa->first) {
2162 // we will now reverse sa, so that a is its last node, not first, and drop that node
2163 p = sa->first->n.pos;
2164 code = (NRPathcode)sa->first->n.other->code;
2165 // create new subpath
2166 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2167 // create a first moveto node on it
2168 n = sa->last;
2169 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2170 n = n->p.other;
2171 if (n == sa->first) n = NULL;
2172 while (n) {
2173 // copy the rest of the nodes from sa to t, going backwards
2174 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2175 n = n->p.other;
2176 if (n == sa->first) n = NULL;
2177 }
2178 // replace sa with t
2179 sp_nodepath_subpath_destroy(sa);
2180 sa = t;
2181 } else if (a == sa->last) {
2182 // a is already last, just drop it
2183 p = sa->last->p.pos;
2184 code = (NRPathcode)sa->last->code;
2185 sp_nodepath_node_destroy(sa->last);
2186 } else {
2187 code = NR_END;
2188 g_assert_not_reached();
2189 }
2191 if (b == sb->first) {
2192 // copy all nodes from b to a, forward
2193 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2194 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2195 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2196 }
2197 } else if (b == sb->last) {
2198 // copy all nodes from b to a, backward
2199 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2200 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2201 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2202 }
2203 } else {
2204 g_assert_not_reached();
2205 }
2206 /* and now destroy sb */
2208 sp_nodepath_subpath_destroy(sb);
2210 sp_nodepath_update_handles(sa->nodepath);
2212 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2214 sp_nodepath_update_statusbar(nodepath);
2215 }
2217 /**
2218 * Internal function to join two nodes by adding a segment between them.
2219 */
2220 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2221 {
2222 if (a->subpath == b->subpath) {
2223 Inkscape::NodePath::SubPath *sp = a->subpath;
2225 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2226 sp->closed = TRUE;
2228 sp->first->p.other = sp->last;
2229 sp->last->n.other = sp->first;
2231 sp_node_handle_mirror_p_to_n(sp->last);
2232 sp_node_handle_mirror_n_to_p(sp->first);
2234 sp->first->code = sp->last->code;
2235 sp->first = sp->last;
2237 sp_nodepath_update_handles(sp->nodepath);
2239 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2241 return;
2242 }
2244 /* a and b are separate subpaths */
2245 Inkscape::NodePath::SubPath *sa = a->subpath;
2246 Inkscape::NodePath::SubPath *sb = b->subpath;
2248 Inkscape::NodePath::Node *n;
2249 NR::Point p;
2250 NRPathcode code;
2251 if (a == sa->first) {
2252 code = (NRPathcode) sa->first->n.other->code;
2253 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2254 n = sa->last;
2255 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2256 for (n = n->p.other; n != NULL; n = n->p.other) {
2257 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2258 }
2259 sp_nodepath_subpath_destroy(sa);
2260 sa = t;
2261 } else if (a == sa->last) {
2262 code = (NRPathcode)sa->last->code;
2263 } else {
2264 code = NR_END;
2265 g_assert_not_reached();
2266 }
2268 if (b == sb->first) {
2269 n = sb->first;
2270 sp_node_handle_mirror_p_to_n(sa->last);
2271 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2272 sp_node_handle_mirror_n_to_p(sa->last);
2273 for (n = n->n.other; n != NULL; n = n->n.other) {
2274 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2275 }
2276 } else if (b == sb->last) {
2277 n = sb->last;
2278 sp_node_handle_mirror_p_to_n(sa->last);
2279 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2280 sp_node_handle_mirror_n_to_p(sa->last);
2281 for (n = n->p.other; n != NULL; n = n->p.other) {
2282 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2283 }
2284 } else {
2285 g_assert_not_reached();
2286 }
2287 /* and now destroy sb */
2289 sp_nodepath_subpath_destroy(sb);
2291 sp_nodepath_update_handles(sa->nodepath);
2293 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2294 }
2296 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2298 /**
2299 * Internal function to handle joining two nodes.
2300 */
2301 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2302 {
2303 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2305 if (g_list_length(nodepath->selected) != 2) {
2306 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2307 return;
2308 }
2310 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2311 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2313 g_assert(a != b);
2314 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2315 // someone tried to join an orphan node (i.e. a single-node subpath).
2316 // this is not worth an error message, just fail silently.
2317 return;
2318 }
2320 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2321 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2322 return;
2323 }
2325 switch(mode) {
2326 case NODE_JOIN_ENDPOINTS:
2327 do_node_selected_join(nodepath, a, b);
2328 break;
2329 case NODE_JOIN_SEGMENT:
2330 do_node_selected_join_segment(nodepath, a, b);
2331 break;
2332 }
2333 }
2335 /**
2336 * Join two nodes by merging them into one.
2337 */
2338 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2339 {
2340 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2341 }
2343 /**
2344 * Join two nodes by adding a segment between them.
2345 */
2346 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2347 {
2348 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2349 }
2351 /**
2352 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2353 */
2354 void sp_node_delete_preserve(GList *nodes_to_delete)
2355 {
2356 GSList *nodepaths = NULL;
2358 while (nodes_to_delete) {
2359 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2360 Inkscape::NodePath::SubPath *sp = node->subpath;
2361 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2362 Inkscape::NodePath::Node *sample_cursor = NULL;
2363 Inkscape::NodePath::Node *sample_end = NULL;
2364 Inkscape::NodePath::Node *delete_cursor = node;
2365 bool just_delete = false;
2367 //find the start of this contiguous selection
2368 //move left to the first node that is not selected
2369 //or the start of the non-closed path
2370 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2371 delete_cursor = curr;
2372 }
2374 //just delete at the beginning of an open path
2375 if (!delete_cursor->p.other) {
2376 sample_cursor = delete_cursor;
2377 just_delete = true;
2378 } else {
2379 sample_cursor = delete_cursor->p.other;
2380 }
2382 //calculate points for each segment
2383 int rate = 5;
2384 float period = 1.0 / rate;
2385 std::vector<NR::Point> data;
2386 if (!just_delete) {
2387 data.push_back(sample_cursor->pos);
2388 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2389 //just delete at the end of an open path
2390 if (!sp->closed && curr == sp->last) {
2391 just_delete = true;
2392 break;
2393 }
2395 //sample points on the contiguous selected segment
2396 NR::Point *bez;
2397 bez = new NR::Point [4];
2398 bez[0] = curr->pos;
2399 bez[1] = curr->n.pos;
2400 bez[2] = curr->n.other->p.pos;
2401 bez[3] = curr->n.other->pos;
2402 for (int i=1; i<rate; i++) {
2403 gdouble t = i * period;
2404 NR::Point p = bezier_pt(3, bez, t);
2405 data.push_back(p);
2406 }
2407 data.push_back(curr->n.other->pos);
2409 sample_end = curr->n.other;
2410 //break if we've come full circle or hit the end of the selection
2411 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2412 break;
2413 }
2414 }
2415 }
2417 if (!just_delete) {
2418 //calculate the best fitting single segment and adjust the endpoints
2419 NR::Point *adata;
2420 adata = new NR::Point [data.size()];
2421 copy(data.begin(), data.end(), adata);
2423 NR::Point *bez;
2424 bez = new NR::Point [4];
2425 //would decreasing error create a better fitting approximation?
2426 gdouble error = 1.0;
2427 gint ret;
2428 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2430 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2431 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2432 //the resulting nodes behave as expected.
2433 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2434 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2435 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2436 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2438 //adjust endpoints
2439 sample_cursor->n.pos = bez[1];
2440 sample_end->p.pos = bez[2];
2441 }
2443 //destroy this contiguous selection
2444 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2445 Inkscape::NodePath::Node *temp = delete_cursor;
2446 if (delete_cursor->n.other == delete_cursor) {
2447 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2448 delete_cursor = NULL;
2449 } else {
2450 delete_cursor = delete_cursor->n.other;
2451 }
2452 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2453 sp_nodepath_node_destroy(temp);
2454 }
2456 sp_nodepath_update_handles(nodepath);
2458 if (!g_slist_find(nodepaths, nodepath))
2459 nodepaths = g_slist_prepend (nodepaths, nodepath);
2460 }
2462 for (GSList *i = nodepaths; i; i = i->next) {
2463 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2464 // different nodepaths will give us one undo event per nodepath
2465 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2467 // if the entire nodepath is removed, delete the selected object.
2468 if (nodepath->subpaths == NULL ||
2469 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2470 //at least 2
2471 sp_nodepath_get_node_count(nodepath) < 2) {
2472 SPDocument *document = sp_desktop_document (nodepath->desktop);
2473 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2474 //delete this nodepath's object, not the entire selection! (though at this time, this
2475 //does not matter)
2476 sp_selection_delete();
2477 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2478 _("Delete nodes"));
2479 } else {
2480 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2481 sp_nodepath_update_statusbar(nodepath);
2482 }
2483 }
2485 g_slist_free (nodepaths);
2486 }
2488 /**
2489 * Delete one or more selected nodes.
2490 */
2491 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2492 {
2493 if (!nodepath) return;
2494 if (!nodepath->selected) return;
2496 /** \todo fixme: do it the right way */
2497 while (nodepath->selected) {
2498 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2499 sp_nodepath_node_destroy(node);
2500 }
2503 //clean up the nodepath (such as for trivial subpaths)
2504 sp_nodepath_cleanup(nodepath);
2506 sp_nodepath_update_handles(nodepath);
2508 // if the entire nodepath is removed, delete the selected object.
2509 if (nodepath->subpaths == NULL ||
2510 sp_nodepath_get_node_count(nodepath) < 2) {
2511 SPDocument *document = sp_desktop_document (nodepath->desktop);
2512 sp_selection_delete();
2513 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2514 _("Delete nodes"));
2515 return;
2516 }
2518 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2520 sp_nodepath_update_statusbar(nodepath);
2521 }
2523 /**
2524 * Delete one or more segments between two selected nodes.
2525 * This is the code for 'split'.
2526 */
2527 void
2528 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2529 {
2530 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2531 Inkscape::NodePath::Node *curr, *next; //Iterators
2533 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2535 if (g_list_length(nodepath->selected) != 2) {
2536 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2537 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2538 return;
2539 }
2541 //Selected nodes, not inclusive
2542 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2543 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2545 if ( ( a==b) || //same node
2546 (a->subpath != b->subpath ) || //not the same path
2547 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2548 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2549 {
2550 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2551 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2552 return;
2553 }
2555 //###########################################
2556 //# BEGIN EDITS
2557 //###########################################
2558 //##################################
2559 //# CLOSED PATH
2560 //##################################
2561 if (a->subpath->closed) {
2564 gboolean reversed = FALSE;
2566 //Since we can go in a circle, we need to find the shorter distance.
2567 // a->b or b->a
2568 start = end = NULL;
2569 int distance = 0;
2570 int minDistance = 0;
2571 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2572 if (curr==b) {
2573 //printf("a to b:%d\n", distance);
2574 start = a;//go from a to b
2575 end = b;
2576 minDistance = distance;
2577 //printf("A to B :\n");
2578 break;
2579 }
2580 distance++;
2581 }
2583 //try again, the other direction
2584 distance = 0;
2585 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2586 if (curr==a) {
2587 //printf("b to a:%d\n", distance);
2588 if (distance < minDistance) {
2589 start = b; //we go from b to a
2590 end = a;
2591 reversed = TRUE;
2592 //printf("B to A\n");
2593 }
2594 break;
2595 }
2596 distance++;
2597 }
2600 //Copy everything from 'end' to 'start' to a new subpath
2601 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2602 for (curr=end ; curr ; curr=curr->n.other) {
2603 NRPathcode code = (NRPathcode) curr->code;
2604 if (curr == end)
2605 code = NR_MOVETO;
2606 sp_nodepath_node_new(t, NULL,
2607 (Inkscape::NodePath::NodeType)curr->type, code,
2608 &curr->p.pos, &curr->pos, &curr->n.pos);
2609 if (curr == start)
2610 break;
2611 }
2612 sp_nodepath_subpath_destroy(a->subpath);
2615 }
2619 //##################################
2620 //# OPEN PATH
2621 //##################################
2622 else {
2624 //We need to get the direction of the list between A and B
2625 //Can we walk from a to b?
2626 start = end = NULL;
2627 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2628 if (curr==b) {
2629 start = a; //did it! we go from a to b
2630 end = b;
2631 //printf("A to B\n");
2632 break;
2633 }
2634 }
2635 if (!start) {//didn't work? let's try the other direction
2636 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2637 if (curr==a) {
2638 start = b; //did it! we go from b to a
2639 end = a;
2640 //printf("B to A\n");
2641 break;
2642 }
2643 }
2644 }
2645 if (!start) {
2646 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2647 _("Cannot find path between nodes."));
2648 return;
2649 }
2653 //Copy everything after 'end' to a new subpath
2654 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2655 for (curr=end ; curr ; curr=curr->n.other) {
2656 NRPathcode code = (NRPathcode) curr->code;
2657 if (curr == end)
2658 code = NR_MOVETO;
2659 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2660 &curr->p.pos, &curr->pos, &curr->n.pos);
2661 }
2663 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2664 for (curr = start->n.other ; curr ; curr=next) {
2665 next = curr->n.other;
2666 sp_nodepath_node_destroy(curr);
2667 }
2669 }
2670 //###########################################
2671 //# END EDITS
2672 //###########################################
2674 //clean up the nodepath (such as for trivial subpaths)
2675 sp_nodepath_cleanup(nodepath);
2677 sp_nodepath_update_handles(nodepath);
2679 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2681 sp_nodepath_update_statusbar(nodepath);
2682 }
2684 /**
2685 * Call sp_nodepath_set_line() for all selected segments.
2686 */
2687 void
2688 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2689 {
2690 if (nodepath == NULL) return;
2692 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2694 g_assert(n->selected);
2695 if (n->p.other && n->p.other->selected) {
2696 sp_nodepath_set_line_type(n, code);
2697 }
2698 }
2700 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2701 }
2703 /**
2704 * Call sp_nodepath_convert_node_type() for all selected nodes.
2705 */
2706 void
2707 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2708 {
2709 if (nodepath == NULL) return;
2711 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2713 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2714 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2715 }
2717 sp_nodepath_update_repr(nodepath, _("Change node type"));
2718 }
2720 /**
2721 * Change select status of node, update its own and neighbour handles.
2722 */
2723 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2724 {
2725 node->selected = selected;
2727 if (selected) {
2728 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2729 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2730 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2731 sp_knot_update_ctrl(node->knot);
2732 } else {
2733 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2734 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2735 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2736 sp_knot_update_ctrl(node->knot);
2737 }
2739 sp_node_update_handles(node);
2740 if (node->n.other) sp_node_update_handles(node->n.other);
2741 if (node->p.other) sp_node_update_handles(node->p.other);
2742 }
2744 /**
2745 \brief Select a node
2746 \param node The node to select
2747 \param incremental If true, add to selection, otherwise deselect others
2748 \param override If true, always select this node, otherwise toggle selected status
2749 */
2750 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2751 {
2752 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2754 if (incremental) {
2755 if (override) {
2756 if (!g_list_find(nodepath->selected, node)) {
2757 nodepath->selected = g_list_prepend(nodepath->selected, node);
2758 }
2759 sp_node_set_selected(node, TRUE);
2760 } else { // toggle
2761 if (node->selected) {
2762 g_assert(g_list_find(nodepath->selected, node));
2763 nodepath->selected = g_list_remove(nodepath->selected, node);
2764 } else {
2765 g_assert(!g_list_find(nodepath->selected, node));
2766 nodepath->selected = g_list_prepend(nodepath->selected, node);
2767 }
2768 sp_node_set_selected(node, !node->selected);
2769 }
2770 } else {
2771 sp_nodepath_deselect(nodepath);
2772 nodepath->selected = g_list_prepend(nodepath->selected, node);
2773 sp_node_set_selected(node, TRUE);
2774 }
2776 sp_nodepath_update_statusbar(nodepath);
2777 }
2780 /**
2781 \brief Deselect all nodes in the nodepath
2782 */
2783 void
2784 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2785 {
2786 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2788 while (nodepath->selected) {
2789 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2790 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2791 }
2792 sp_nodepath_update_statusbar(nodepath);
2793 }
2795 /**
2796 \brief Select or invert selection of all nodes in the nodepath
2797 */
2798 void
2799 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2800 {
2801 if (!nodepath) return;
2803 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2804 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2805 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2806 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2807 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2808 }
2809 }
2810 }
2812 /**
2813 * If nothing selected, does the same as sp_nodepath_select_all();
2814 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2815 * (i.e., similar to "select all in layer", with the "selected" subpaths
2816 * being treated as "layers" in the path).
2817 */
2818 void
2819 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2820 {
2821 if (!nodepath) return;
2823 if (g_list_length (nodepath->selected) == 0) {
2824 sp_nodepath_select_all (nodepath, invert);
2825 return;
2826 }
2828 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2829 GSList *subpaths = NULL;
2831 for (GList *l = copy; l != NULL; l = l->next) {
2832 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2833 Inkscape::NodePath::SubPath *subpath = n->subpath;
2834 if (!g_slist_find (subpaths, subpath))
2835 subpaths = g_slist_prepend (subpaths, subpath);
2836 }
2838 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2839 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2840 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2841 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2842 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2843 }
2844 }
2846 g_slist_free (subpaths);
2847 g_list_free (copy);
2848 }
2850 /**
2851 * \brief Select the node after the last selected; if none is selected,
2852 * select the first within path.
2853 */
2854 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2855 {
2856 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2858 Inkscape::NodePath::Node *last = NULL;
2859 if (nodepath->selected) {
2860 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2861 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2862 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2863 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2864 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2865 if (node->selected) {
2866 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2867 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2868 if (spl->next) { // there's a next subpath
2869 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2870 last = subpath_next->first;
2871 } else if (spl->prev) { // there's a previous subpath
2872 last = NULL; // to be set later to the first node of first subpath
2873 } else {
2874 last = node->n.other;
2875 }
2876 } else {
2877 last = node->n.other;
2878 }
2879 } else {
2880 if (node->n.other) {
2881 last = node->n.other;
2882 } else {
2883 if (spl->next) { // there's a next subpath
2884 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2885 last = subpath_next->first;
2886 } else if (spl->prev) { // there's a previous subpath
2887 last = NULL; // to be set later to the first node of first subpath
2888 } else {
2889 last = (Inkscape::NodePath::Node *) subpath->first;
2890 }
2891 }
2892 }
2893 }
2894 }
2895 }
2896 sp_nodepath_deselect(nodepath);
2897 }
2899 if (last) { // there's at least one more node after selected
2900 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2901 } else { // no more nodes, select the first one in first subpath
2902 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2903 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2904 }
2905 }
2907 /**
2908 * \brief Select the node before the first selected; if none is selected,
2909 * select the last within path
2910 */
2911 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2912 {
2913 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2915 Inkscape::NodePath::Node *last = NULL;
2916 if (nodepath->selected) {
2917 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2918 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2919 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2920 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2921 if (node->selected) {
2922 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2923 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2924 if (spl->prev) { // there's a prev subpath
2925 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2926 last = subpath_prev->last;
2927 } else if (spl->next) { // there's a next subpath
2928 last = NULL; // to be set later to the last node of last subpath
2929 } else {
2930 last = node->p.other;
2931 }
2932 } else {
2933 last = node->p.other;
2934 }
2935 } else {
2936 if (node->p.other) {
2937 last = node->p.other;
2938 } else {
2939 if (spl->prev) { // there's a prev subpath
2940 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2941 last = subpath_prev->last;
2942 } else if (spl->next) { // there's a next subpath
2943 last = NULL; // to be set later to the last node of last subpath
2944 } else {
2945 last = (Inkscape::NodePath::Node *) subpath->last;
2946 }
2947 }
2948 }
2949 }
2950 }
2951 }
2952 sp_nodepath_deselect(nodepath);
2953 }
2955 if (last) { // there's at least one more node before selected
2956 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2957 } else { // no more nodes, select the last one in last subpath
2958 GList *spl = g_list_last(nodepath->subpaths);
2959 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2960 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2961 }
2962 }
2964 /**
2965 * \brief Select all nodes that are within the rectangle.
2966 */
2967 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2968 {
2969 if (!incremental) {
2970 sp_nodepath_deselect(nodepath);
2971 }
2973 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2974 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2975 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2976 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2978 if (b.contains(node->pos)) {
2979 sp_nodepath_node_select(node, TRUE, TRUE);
2980 }
2981 }
2982 }
2983 }
2986 void
2987 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
2988 {
2989 g_assert (n);
2990 g_assert (nodepath);
2991 g_assert (n->subpath->nodepath == nodepath);
2993 if (g_list_length (nodepath->selected) == 0) {
2994 if (grow > 0) {
2995 sp_nodepath_node_select(n, TRUE, TRUE);
2996 }
2997 return;
2998 }
3000 if (g_list_length (nodepath->selected) == 1) {
3001 if (grow < 0) {
3002 sp_nodepath_deselect (nodepath);
3003 return;
3004 }
3005 }
3007 double n_sel_range = 0, p_sel_range = 0;
3008 Inkscape::NodePath::Node *farthest_n_node = n;
3009 Inkscape::NodePath::Node *farthest_p_node = n;
3011 // Calculate ranges
3012 {
3013 double n_range = 0, p_range = 0;
3014 bool n_going = true, p_going = true;
3015 Inkscape::NodePath::Node *n_node = n;
3016 Inkscape::NodePath::Node *p_node = n;
3017 do {
3018 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3019 if (n_node && n_going)
3020 n_node = n_node->n.other;
3021 if (n_node == NULL) {
3022 n_going = false;
3023 } else {
3024 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3025 if (n_node->selected) {
3026 n_sel_range = n_range;
3027 farthest_n_node = n_node;
3028 }
3029 if (n_node == p_node) {
3030 n_going = false;
3031 p_going = false;
3032 }
3033 }
3034 if (p_node && p_going)
3035 p_node = p_node->p.other;
3036 if (p_node == NULL) {
3037 p_going = false;
3038 } else {
3039 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3040 if (p_node->selected) {
3041 p_sel_range = p_range;
3042 farthest_p_node = p_node;
3043 }
3044 if (p_node == n_node) {
3045 n_going = false;
3046 p_going = false;
3047 }
3048 }
3049 } while (n_going || p_going);
3050 }
3052 if (grow > 0) {
3053 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3054 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3055 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3056 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3057 }
3058 } else {
3059 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3060 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3061 } else if (farthest_p_node && farthest_p_node->selected) {
3062 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3063 }
3064 }
3065 }
3067 void
3068 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3069 {
3070 g_assert (n);
3071 g_assert (nodepath);
3072 g_assert (n->subpath->nodepath == nodepath);
3074 if (g_list_length (nodepath->selected) == 0) {
3075 if (grow > 0) {
3076 sp_nodepath_node_select(n, TRUE, TRUE);
3077 }
3078 return;
3079 }
3081 if (g_list_length (nodepath->selected) == 1) {
3082 if (grow < 0) {
3083 sp_nodepath_deselect (nodepath);
3084 return;
3085 }
3086 }
3088 Inkscape::NodePath::Node *farthest_selected = NULL;
3089 double farthest_dist = 0;
3091 Inkscape::NodePath::Node *closest_unselected = NULL;
3092 double closest_dist = NR_HUGE;
3094 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3095 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3096 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3097 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3098 if (node == n)
3099 continue;
3100 if (node->selected) {
3101 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3102 farthest_dist = NR::L2(node->pos - n->pos);
3103 farthest_selected = node;
3104 }
3105 } else {
3106 if (NR::L2(node->pos - n->pos) < closest_dist) {
3107 closest_dist = NR::L2(node->pos - n->pos);
3108 closest_unselected = node;
3109 }
3110 }
3111 }
3112 }
3114 if (grow > 0) {
3115 if (closest_unselected) {
3116 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3117 }
3118 } else {
3119 if (farthest_selected) {
3120 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3121 }
3122 }
3123 }
3126 /**
3127 \brief Saves all nodes' and handles' current positions in their origin members
3128 */
3129 void
3130 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3131 {
3132 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3133 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3134 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3135 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3136 n->origin = n->pos;
3137 n->p.origin = n->p.pos;
3138 n->n.origin = n->n.pos;
3139 }
3140 }
3141 }
3143 /**
3144 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3145 */
3146 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3147 {
3148 if (!nodepath->selected) {
3149 return NULL;
3150 }
3152 GList *r = NULL;
3153 guint i = 0;
3154 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3155 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3156 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3157 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3158 i++;
3159 if (node->selected) {
3160 r = g_list_append(r, GINT_TO_POINTER(i));
3161 }
3162 }
3163 }
3164 return r;
3165 }
3167 /**
3168 \brief Restores selection by selecting nodes whose positions are in the list
3169 */
3170 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3171 {
3172 sp_nodepath_deselect(nodepath);
3174 guint i = 0;
3175 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3176 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3177 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3178 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3179 i++;
3180 if (g_list_find(r, GINT_TO_POINTER(i))) {
3181 sp_nodepath_node_select(node, TRUE, TRUE);
3182 }
3183 }
3184 }
3185 }
3188 /**
3189 \brief Adjusts handle according to node type and line code.
3190 */
3191 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3192 {
3193 g_assert(node);
3195 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3196 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3198 // nothing to do if we are an end node
3199 if (me->other == NULL) return;
3200 if (other->other == NULL) return;
3202 // nothing to do if we are a cusp node
3203 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3205 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3206 NRPathcode mecode;
3207 if (which_adjust == 1) {
3208 mecode = (NRPathcode)me->other->code;
3209 } else {
3210 mecode = (NRPathcode)node->code;
3211 }
3212 if (mecode == NR_LINETO) return;
3214 if (sp_node_side_is_line(node, other)) {
3215 // other is a line, and we are either smooth or symm
3216 Inkscape::NodePath::Node *othernode = other->other;
3217 double len = NR::L2(me->pos - node->pos);
3218 NR::Point delta = node->pos - othernode->pos;
3219 double linelen = NR::L2(delta);
3220 if (linelen < 1e-18)
3221 return;
3222 me->pos = node->pos + (len / linelen)*delta;
3223 return;
3224 }
3226 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3227 // symmetrize
3228 me->pos = 2 * node->pos - other->pos;
3229 return;
3230 } else {
3231 // smoothify
3232 double len = NR::L2(me->pos - node->pos);
3233 NR::Point delta = other->pos - node->pos;
3234 double otherlen = NR::L2(delta);
3235 if (otherlen < 1e-18) return;
3236 me->pos = node->pos - (len / otherlen) * delta;
3237 }
3238 }
3240 /**
3241 \brief Adjusts both handles according to node type and line code
3242 */
3243 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3244 {
3245 g_assert(node);
3247 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3249 /* we are either smooth or symm */
3251 if (node->p.other == NULL) return;
3252 if (node->n.other == NULL) return;
3254 if (sp_node_side_is_line(node, &node->p)) {
3255 sp_node_adjust_handle(node, 1);
3256 return;
3257 }
3259 if (sp_node_side_is_line(node, &node->n)) {
3260 sp_node_adjust_handle(node, -1);
3261 return;
3262 }
3264 /* both are curves */
3265 NR::Point const delta( node->n.pos - node->p.pos );
3267 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3268 node->p.pos = node->pos - delta / 2;
3269 node->n.pos = node->pos + delta / 2;
3270 return;
3271 }
3273 /* We are smooth */
3274 double plen = NR::L2(node->p.pos - node->pos);
3275 if (plen < 1e-18) return;
3276 double nlen = NR::L2(node->n.pos - node->pos);
3277 if (nlen < 1e-18) return;
3278 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3279 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3280 }
3282 /**
3283 * Node event callback.
3284 */
3285 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3286 {
3287 gboolean ret = FALSE;
3288 switch (event->type) {
3289 case GDK_ENTER_NOTIFY:
3290 Inkscape::NodePath::Path::active_node = n;
3291 break;
3292 case GDK_LEAVE_NOTIFY:
3293 Inkscape::NodePath::Path::active_node = NULL;
3294 break;
3295 case GDK_SCROLL:
3296 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3297 switch (event->scroll.direction) {
3298 case GDK_SCROLL_UP:
3299 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3300 break;
3301 case GDK_SCROLL_DOWN:
3302 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3303 break;
3304 default:
3305 break;
3306 }
3307 ret = TRUE;
3308 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3309 switch (event->scroll.direction) {
3310 case GDK_SCROLL_UP:
3311 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3312 break;
3313 case GDK_SCROLL_DOWN:
3314 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3315 break;
3316 default:
3317 break;
3318 }
3319 ret = TRUE;
3320 }
3321 break;
3322 case GDK_KEY_PRESS:
3323 switch (get_group0_keyval (&event->key)) {
3324 case GDK_space:
3325 if (event->key.state & GDK_BUTTON1_MASK) {
3326 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3327 stamp_repr(nodepath);
3328 ret = TRUE;
3329 }
3330 break;
3331 case GDK_Page_Up:
3332 if (event->key.state & GDK_CONTROL_MASK) {
3333 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3334 } else {
3335 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3336 }
3337 break;
3338 case GDK_Page_Down:
3339 if (event->key.state & GDK_CONTROL_MASK) {
3340 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3341 } else {
3342 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3343 }
3344 break;
3345 default:
3346 break;
3347 }
3348 break;
3349 default:
3350 break;
3351 }
3353 return ret;
3354 }
3356 /**
3357 * Handle keypress on node; directly called.
3358 */
3359 gboolean node_key(GdkEvent *event)
3360 {
3361 Inkscape::NodePath::Path *np;
3363 // there is no way to verify nodes so set active_node to nil when deleting!!
3364 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3366 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3367 gint ret = FALSE;
3368 switch (get_group0_keyval (&event->key)) {
3369 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3370 case GDK_BackSpace:
3371 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3372 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3373 sp_nodepath_update_repr(np, _("Delete node"));
3374 Inkscape::NodePath::Path::active_node = NULL;
3375 ret = TRUE;
3376 break;
3377 case GDK_c:
3378 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3379 ret = TRUE;
3380 break;
3381 case GDK_s:
3382 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3383 ret = TRUE;
3384 break;
3385 case GDK_y:
3386 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3387 ret = TRUE;
3388 break;
3389 case GDK_b:
3390 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3391 ret = TRUE;
3392 break;
3393 }
3394 return ret;
3395 }
3396 return FALSE;
3397 }
3399 /**
3400 * Mouseclick on node callback.
3401 */
3402 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3403 {
3404 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3406 if (state & GDK_CONTROL_MASK) {
3407 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3409 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3410 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3411 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3412 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3413 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3414 } else {
3415 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3416 }
3417 sp_nodepath_update_repr(nodepath, _("Change node type"));
3418 sp_nodepath_update_statusbar(nodepath);
3420 } else { //ctrl+alt+click: delete node
3421 GList *node_to_delete = NULL;
3422 node_to_delete = g_list_append(node_to_delete, n);
3423 sp_node_delete_preserve(node_to_delete);
3424 }
3426 } else {
3427 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3428 }
3429 }
3431 /**
3432 * Mouse grabbed node callback.
3433 */
3434 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3435 {
3436 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3438 if (!n->selected) {
3439 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3440 }
3442 n->is_dragging = true;
3443 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3445 sp_nodepath_remember_origins (n->subpath->nodepath);
3446 }
3448 /**
3449 * Mouse ungrabbed node callback.
3450 */
3451 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3452 {
3453 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3455 n->dragging_out = NULL;
3456 n->is_dragging = false;
3457 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3459 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3460 }
3462 /**
3463 * The point on a line, given by its angle, closest to the given point.
3464 * \param p A point.
3465 * \param a Angle of the line; it is assumed to go through coordinate origin.
3466 * \param closest Pointer to the point struct where the result is stored.
3467 * \todo FIXME: use dot product perhaps?
3468 */
3469 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3470 {
3471 if (a == HUGE_VAL) { // vertical
3472 *closest = NR::Point(0, (*p)[NR::Y]);
3473 } else {
3474 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3475 (*closest)[NR::Y] = a * (*closest)[NR::X];
3476 }
3477 }
3479 /**
3480 * Distance from the point to a line given by its angle.
3481 * \param p A point.
3482 * \param a Angle of the line; it is assumed to go through coordinate origin.
3483 */
3484 static double point_line_distance(NR::Point *p, double a)
3485 {
3486 NR::Point c;
3487 point_line_closest(p, a, &c);
3488 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]));
3489 }
3491 /**
3492 * Callback for node "request" signal.
3493 * \todo fixme: This goes to "moved" event? (lauris)
3494 */
3495 static gboolean
3496 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3497 {
3498 double yn, xn, yp, xp;
3499 double an, ap, na, pa;
3500 double d_an, d_ap, d_na, d_pa;
3501 gboolean collinear = FALSE;
3502 NR::Point c;
3503 NR::Point pr;
3505 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3507 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3509 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3510 if ( (!n->subpath->nodepath->straight_path) &&
3511 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3512 || n->dragging_out ) )
3513 {
3514 NR::Point mouse = (*p);
3516 if (!n->dragging_out) {
3517 // This is the first drag-out event; find out which handle to drag out
3518 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3519 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3521 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3522 return FALSE;
3524 Inkscape::NodePath::NodeSide *opposite;
3525 if (appr_p > appr_n) { // closer to p
3526 n->dragging_out = &n->p;
3527 opposite = &n->n;
3528 n->code = NR_CURVETO;
3529 } else if (appr_p < appr_n) { // closer to n
3530 n->dragging_out = &n->n;
3531 opposite = &n->p;
3532 n->n.other->code = NR_CURVETO;
3533 } else { // p and n nodes are the same
3534 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3535 n->dragging_out = &n->p;
3536 opposite = &n->n;
3537 n->code = NR_CURVETO;
3538 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3539 n->dragging_out = &n->n;
3540 opposite = &n->p;
3541 n->n.other->code = NR_CURVETO;
3542 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3543 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);
3544 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);
3545 if (appr_other_p > appr_other_n) { // closer to other's p handle
3546 n->dragging_out = &n->n;
3547 opposite = &n->p;
3548 n->n.other->code = NR_CURVETO;
3549 } else { // closer to other's n handle
3550 n->dragging_out = &n->p;
3551 opposite = &n->n;
3552 n->code = NR_CURVETO;
3553 }
3554 }
3555 }
3557 // if there's another handle, make sure the one we drag out starts parallel to it
3558 if (opposite->pos != n->pos) {
3559 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3560 }
3562 // knots might not be created yet!
3563 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3564 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3565 }
3567 // pass this on to the handle-moved callback
3568 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3569 sp_node_update_handles(n);
3570 return TRUE;
3571 }
3573 if (state & GDK_CONTROL_MASK) { // constrained motion
3575 // calculate relative distances of handles
3576 // n handle:
3577 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3578 xn = n->n.pos[NR::X] - n->pos[NR::X];
3579 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3580 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3581 if (n->n.other) { // if there is the next point
3582 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3583 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3584 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3585 }
3586 }
3587 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3588 if (yn < 0) { xn = -xn; yn = -yn; }
3590 // p handle:
3591 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3592 xp = n->p.pos[NR::X] - n->pos[NR::X];
3593 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3594 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3595 if (n->p.other) {
3596 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3597 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3598 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3599 }
3600 }
3601 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3602 if (yp < 0) { xp = -xp; yp = -yp; }
3604 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3605 // sliding on handles, only if at least one of the handles is non-vertical
3606 // (otherwise it's the same as ctrl+drag anyway)
3608 // calculate angles of the handles
3609 if (xn == 0) {
3610 if (yn == 0) { // no handle, consider it the continuation of the other one
3611 an = 0;
3612 collinear = TRUE;
3613 }
3614 else an = 0; // vertical; set the angle to horizontal
3615 } else an = yn/xn;
3617 if (xp == 0) {
3618 if (yp == 0) { // no handle, consider it the continuation of the other one
3619 ap = an;
3620 }
3621 else ap = 0; // vertical; set the angle to horizontal
3622 } else ap = yp/xp;
3624 if (collinear) an = ap;
3626 // angles of the perpendiculars; HUGE_VAL means vertical
3627 if (an == 0) na = HUGE_VAL; else na = -1/an;
3628 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3630 // mouse point relative to the node's original pos
3631 pr = (*p) - n->origin;
3633 // distances to the four lines (two handles and two perpendiculars)
3634 d_an = point_line_distance(&pr, an);
3635 d_na = point_line_distance(&pr, na);
3636 d_ap = point_line_distance(&pr, ap);
3637 d_pa = point_line_distance(&pr, pa);
3639 // find out which line is the closest, save its closest point in c
3640 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3641 point_line_closest(&pr, an, &c);
3642 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3643 point_line_closest(&pr, ap, &c);
3644 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3645 point_line_closest(&pr, na, &c);
3646 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3647 point_line_closest(&pr, pa, &c);
3648 }
3650 // move the node to the closest point
3651 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3652 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3653 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3654 true);
3656 } else { // constraining to hor/vert
3658 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3659 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3660 (*p)[NR::X] - n->pos[NR::X],
3661 n->origin[NR::Y] - n->pos[NR::Y],
3662 true,
3663 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3664 } else { // snap to vert
3665 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3666 n->origin[NR::X] - n->pos[NR::X],
3667 (*p)[NR::Y] - n->pos[NR::Y],
3668 true,
3669 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3670 }
3671 }
3672 } else { // move freely
3673 if (n->is_dragging) {
3674 if (state & GDK_MOD1_MASK) { // sculpt
3675 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3676 } else {
3677 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3678 (*p)[NR::X] - n->pos[NR::X],
3679 (*p)[NR::Y] - n->pos[NR::Y],
3680 (state & GDK_SHIFT_MASK) == 0);
3681 }
3682 }
3683 }
3685 n->subpath->nodepath->desktop->scroll_to_point(p);
3687 return TRUE;
3688 }
3690 /**
3691 * Node handle clicked callback.
3692 */
3693 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3694 {
3695 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3697 if (state & GDK_CONTROL_MASK) { // "delete" handle
3698 if (n->p.knot == knot) {
3699 n->p.pos = n->pos;
3700 } else if (n->n.knot == knot) {
3701 n->n.pos = n->pos;
3702 }
3703 sp_node_update_handles(n);
3704 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3705 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3706 sp_nodepath_update_statusbar(nodepath);
3708 } else { // just select or add to selection, depending in Shift
3709 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3710 }
3711 }
3713 /**
3714 * Node handle grabbed callback.
3715 */
3716 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3717 {
3718 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3720 if (!n->selected) {
3721 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3722 }
3724 // remember the origin point of the handle
3725 if (n->p.knot == knot) {
3726 n->p.origin_radial = n->p.pos - n->pos;
3727 } else if (n->n.knot == knot) {
3728 n->n.origin_radial = n->n.pos - n->pos;
3729 } else {
3730 g_assert_not_reached();
3731 }
3733 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3734 }
3736 /**
3737 * Node handle ungrabbed callback.
3738 */
3739 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3740 {
3741 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3743 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3744 if (n->p.knot == knot) {
3745 n->p.origin_radial.a = 0;
3746 sp_knot_set_position(knot, n->p.pos, state);
3747 } else if (n->n.knot == knot) {
3748 n->n.origin_radial.a = 0;
3749 sp_knot_set_position(knot, n->n.pos, state);
3750 } else {
3751 g_assert_not_reached();
3752 }
3754 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3755 }
3757 /**
3758 * Node handle "request" signal callback.
3759 */
3760 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3761 {
3762 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3764 Inkscape::NodePath::NodeSide *me, *opposite;
3765 gint which;
3766 if (n->p.knot == knot) {
3767 me = &n->p;
3768 opposite = &n->n;
3769 which = -1;
3770 } else if (n->n.knot == knot) {
3771 me = &n->n;
3772 opposite = &n->p;
3773 which = 1;
3774 } else {
3775 me = opposite = NULL;
3776 which = 0;
3777 g_assert_not_reached();
3778 }
3780 SPDesktop *desktop = n->subpath->nodepath->desktop;
3781 SnapManager &m = desktop->namedview->snap_manager;
3782 m.setup(desktop, n->subpath->nodepath->item);
3783 Inkscape::SnappedPoint s;
3785 if ((state & GDK_SHIFT_MASK) != 0) {
3786 // We will not try to snap when the shift-key is pressed
3787 // so remove the old snap indicator and don't wait for it to time-out
3788 desktop->snapindicator->remove_snappoint();
3789 }
3791 Inkscape::NodePath::Node *othernode = opposite->other;
3792 if (othernode) {
3793 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3794 /* We are smooth node adjacent with line */
3795 NR::Point const delta = *p - n->pos;
3796 NR::Coord const len = NR::L2(delta);
3797 Inkscape::NodePath::Node *othernode = opposite->other;
3798 NR::Point const ndelta = n->pos - othernode->pos;
3799 NR::Coord const linelen = NR::L2(ndelta);
3800 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3801 NR::Coord const scal = dot(delta, ndelta) / linelen;
3802 (*p) = n->pos + (scal / linelen) * ndelta;
3803 }
3804 if ((state & GDK_SHIFT_MASK) == 0) {
3805 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3806 }
3807 } else {
3808 if ((state & GDK_SHIFT_MASK) == 0) {
3809 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3810 }
3811 }
3812 } else {
3813 if ((state & GDK_SHIFT_MASK) == 0) {
3814 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3815 }
3816 }
3818 s.getPoint(*p);
3820 sp_node_adjust_handle(n, -which);
3822 return FALSE;
3823 }
3825 /**
3826 * Node handle moved callback.
3827 */
3828 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3829 {
3830 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3832 Inkscape::NodePath::NodeSide *me;
3833 Inkscape::NodePath::NodeSide *other;
3834 if (n->p.knot == knot) {
3835 me = &n->p;
3836 other = &n->n;
3837 } else if (n->n.knot == knot) {
3838 me = &n->n;
3839 other = &n->p;
3840 } else {
3841 me = NULL;
3842 other = NULL;
3843 g_assert_not_reached();
3844 }
3846 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3847 Radial rme(me->pos - n->pos);
3848 Radial rother(other->pos - n->pos);
3849 Radial rnew(*p - n->pos);
3851 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3852 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3853 /* 0 interpreted as "no snapping". */
3855 // 1. Snap to the closest PI/snaps angle, starting from zero.
3856 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3858 // 2. Snap to the original angle, its opposite and perpendiculars
3859 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3860 /* The closest PI/2 angle, starting from original angle */
3861 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3863 // Snap to the closest.
3864 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3865 ? a_snapped
3866 : a_ortho );
3867 }
3869 // 3. Snap to the angle of the opposite line, if any
3870 Inkscape::NodePath::Node *othernode = other->other;
3871 if (othernode) {
3872 NR::Point other_to_snap(0,0);
3873 if (sp_node_side_is_line(n, other)) {
3874 other_to_snap = othernode->pos - n->pos;
3875 } else {
3876 other_to_snap = other->pos - n->pos;
3877 }
3878 if (NR::L2(other_to_snap) > 1e-3) {
3879 Radial rother_to_snap(other_to_snap);
3880 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3881 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3883 // Snap to the closest.
3884 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3885 ? a_snapped
3886 : a_oppo );
3887 }
3888 }
3890 rnew.a = a_snapped;
3891 }
3893 if (state & GDK_MOD1_MASK) {
3894 // lock handle length
3895 rnew.r = me->origin_radial.r;
3896 }
3898 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3899 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3900 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3901 rother.a += rnew.a - rme.a;
3902 other->pos = NR::Point(rother) + n->pos;
3903 if (other->knot) {
3904 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3905 sp_knot_moveto(other->knot, other->pos);
3906 }
3907 }
3909 me->pos = NR::Point(rnew) + n->pos;
3910 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3912 // move knot, but without emitting the signal:
3913 // we cannot emit a "moved" signal because we're now processing it
3914 sp_knot_moveto(me->knot, me->pos);
3916 update_object(n->subpath->nodepath);
3918 /* status text */
3919 SPDesktop *desktop = n->subpath->nodepath->desktop;
3920 if (!desktop) return;
3921 SPEventContext *ec = desktop->event_context;
3922 if (!ec) return;
3923 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3924 if (!mc) return;
3926 double degrees = 180 / M_PI * rnew.a;
3927 if (degrees > 180) degrees -= 360;
3928 if (degrees < -180) degrees += 360;
3929 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3930 degrees = angle_to_compass (degrees);
3932 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3934 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3935 _("<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);
3937 g_string_free(length, TRUE);
3938 }
3940 /**
3941 * Node handle event callback.
3942 */
3943 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3944 {
3945 gboolean ret = FALSE;
3946 switch (event->type) {
3947 case GDK_KEY_PRESS:
3948 switch (get_group0_keyval (&event->key)) {
3949 case GDK_space:
3950 if (event->key.state & GDK_BUTTON1_MASK) {
3951 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3952 stamp_repr(nodepath);
3953 ret = TRUE;
3954 }
3955 break;
3956 default:
3957 break;
3958 }
3959 break;
3960 case GDK_ENTER_NOTIFY:
3961 // we use an experimentally determined threshold that seems to work fine
3962 if (NR::L2(n->pos - knot->pos) < 0.75)
3963 Inkscape::NodePath::Path::active_node = n;
3964 break;
3965 case GDK_LEAVE_NOTIFY:
3966 // we use an experimentally determined threshold that seems to work fine
3967 if (NR::L2(n->pos - knot->pos) < 0.75)
3968 Inkscape::NodePath::Path::active_node = NULL;
3969 break;
3970 default:
3971 break;
3972 }
3974 return ret;
3975 }
3977 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3978 Radial &rme, Radial &rother, gboolean const both)
3979 {
3980 rme.a += angle;
3981 if ( both
3982 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3983 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3984 {
3985 rother.a += angle;
3986 }
3987 }
3989 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
3990 Radial &rme, Radial &rother, gboolean const both)
3991 {
3992 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
3994 gdouble r;
3995 if ( both
3996 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3997 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3998 {
3999 r = MAX(rme.r, rother.r);
4000 } else {
4001 r = rme.r;
4002 }
4004 gdouble const weird_angle = atan2(norm_angle, r);
4005 /* Bulia says norm_angle is just the visible distance that the
4006 * object's end must travel on the screen. Left as 'angle' for want of
4007 * a better name.*/
4009 rme.a += weird_angle;
4010 if ( both
4011 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4012 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4013 {
4014 rother.a += weird_angle;
4015 }
4016 }
4018 /**
4019 * Rotate one node.
4020 */
4021 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4022 {
4023 Inkscape::NodePath::NodeSide *me, *other;
4024 bool both = false;
4026 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4027 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4029 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4030 me = &(n->p);
4031 other = &(n->n);
4032 } else if (!n->p.other) {
4033 me = &(n->n);
4034 other = &(n->p);
4035 } else {
4036 if (which > 0) { // right handle
4037 if (xn > xp) {
4038 me = &(n->n);
4039 other = &(n->p);
4040 } else {
4041 me = &(n->p);
4042 other = &(n->n);
4043 }
4044 } else if (which < 0){ // left handle
4045 if (xn <= xp) {
4046 me = &(n->n);
4047 other = &(n->p);
4048 } else {
4049 me = &(n->p);
4050 other = &(n->n);
4051 }
4052 } else { // both handles
4053 me = &(n->n);
4054 other = &(n->p);
4055 both = true;
4056 }
4057 }
4059 Radial rme(me->pos - n->pos);
4060 Radial rother(other->pos - n->pos);
4062 if (screen) {
4063 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4064 } else {
4065 node_rotate_one_internal (*n, angle, rme, rother, both);
4066 }
4068 me->pos = n->pos + NR::Point(rme);
4070 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4071 other->pos = n->pos + NR::Point(rother);
4072 }
4074 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4075 // so here we just move all the knots without emitting move signals, for speed
4076 sp_node_update_handles(n, false);
4077 }
4079 /**
4080 * Rotate selected nodes.
4081 */
4082 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4083 {
4084 if (!nodepath || !nodepath->selected) return;
4086 if (g_list_length(nodepath->selected) == 1) {
4087 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4088 node_rotate_one (n, angle, which, screen);
4089 } else {
4090 // rotate as an object:
4092 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4093 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4094 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4095 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4096 box.expandTo (n->pos); // contain all selected nodes
4097 }
4099 gdouble rot;
4100 if (screen) {
4101 gdouble const zoom = nodepath->desktop->current_zoom();
4102 gdouble const zmove = angle / zoom;
4103 gdouble const r = NR::L2(box.max() - box.midpoint());
4104 rot = atan2(zmove, r);
4105 } else {
4106 rot = angle;
4107 }
4109 NR::Point rot_center;
4110 if (Inkscape::NodePath::Path::active_node == NULL)
4111 rot_center = box.midpoint();
4112 else
4113 rot_center = Inkscape::NodePath::Path::active_node->pos;
4115 NR::Matrix t =
4116 NR::Matrix (NR::translate(-rot_center)) *
4117 NR::Matrix (NR::rotate(rot)) *
4118 NR::Matrix (NR::translate(rot_center));
4120 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4121 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4122 n->pos *= t;
4123 n->n.pos *= t;
4124 n->p.pos *= t;
4125 sp_node_update_handles(n, false);
4126 }
4127 }
4129 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4130 }
4132 /**
4133 * Scale one node.
4134 */
4135 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4136 {
4137 bool both = false;
4138 Inkscape::NodePath::NodeSide *me, *other;
4140 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4141 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4143 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4144 me = &(n->p);
4145 other = &(n->n);
4146 n->code = NR_CURVETO;
4147 } else if (!n->p.other) {
4148 me = &(n->n);
4149 other = &(n->p);
4150 if (n->n.other)
4151 n->n.other->code = NR_CURVETO;
4152 } else {
4153 if (which > 0) { // right handle
4154 if (xn > xp) {
4155 me = &(n->n);
4156 other = &(n->p);
4157 if (n->n.other)
4158 n->n.other->code = NR_CURVETO;
4159 } else {
4160 me = &(n->p);
4161 other = &(n->n);
4162 n->code = NR_CURVETO;
4163 }
4164 } else if (which < 0){ // left handle
4165 if (xn <= xp) {
4166 me = &(n->n);
4167 other = &(n->p);
4168 if (n->n.other)
4169 n->n.other->code = NR_CURVETO;
4170 } else {
4171 me = &(n->p);
4172 other = &(n->n);
4173 n->code = NR_CURVETO;
4174 }
4175 } else { // both handles
4176 me = &(n->n);
4177 other = &(n->p);
4178 both = true;
4179 n->code = NR_CURVETO;
4180 if (n->n.other)
4181 n->n.other->code = NR_CURVETO;
4182 }
4183 }
4185 Radial rme(me->pos - n->pos);
4186 Radial rother(other->pos - n->pos);
4188 rme.r += grow;
4189 if (rme.r < 0) rme.r = 0;
4190 if (rme.a == HUGE_VAL) {
4191 if (me->other) { // if direction is unknown, initialize it towards the next node
4192 Radial rme_next(me->other->pos - n->pos);
4193 rme.a = rme_next.a;
4194 } else { // if there's no next, initialize to 0
4195 rme.a = 0;
4196 }
4197 }
4198 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4199 rother.r += grow;
4200 if (rother.r < 0) rother.r = 0;
4201 if (rother.a == HUGE_VAL) {
4202 rother.a = rme.a + M_PI;
4203 }
4204 }
4206 me->pos = n->pos + NR::Point(rme);
4208 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4209 other->pos = n->pos + NR::Point(rother);
4210 }
4212 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4213 // so here we just move all the knots without emitting move signals, for speed
4214 sp_node_update_handles(n, false);
4215 }
4217 /**
4218 * Scale selected nodes.
4219 */
4220 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4221 {
4222 if (!nodepath || !nodepath->selected) return;
4224 if (g_list_length(nodepath->selected) == 1) {
4225 // scale handles of the single selected node
4226 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4227 node_scale_one (n, grow, which);
4228 } else {
4229 // scale nodes as an "object":
4231 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4232 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4233 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4234 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4235 box.expandTo (n->pos); // contain all selected nodes
4236 }
4238 double scale = (box.maxExtent() + grow)/box.maxExtent();
4240 NR::Point scale_center;
4241 if (Inkscape::NodePath::Path::active_node == NULL)
4242 scale_center = box.midpoint();
4243 else
4244 scale_center = Inkscape::NodePath::Path::active_node->pos;
4246 NR::Matrix t =
4247 NR::Matrix (NR::translate(-scale_center)) *
4248 NR::Matrix (NR::scale(scale, scale)) *
4249 NR::Matrix (NR::translate(scale_center));
4251 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4252 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4253 n->pos *= t;
4254 n->n.pos *= t;
4255 n->p.pos *= t;
4256 sp_node_update_handles(n, false);
4257 }
4258 }
4260 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4261 }
4263 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4264 {
4265 if (!nodepath) return;
4266 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4267 }
4269 /**
4270 * Flip selected nodes horizontally/vertically.
4271 */
4272 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4273 {
4274 if (!nodepath || !nodepath->selected) return;
4276 if (g_list_length(nodepath->selected) == 1 && !center) {
4277 // flip handles of the single selected node
4278 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4279 double temp = n->p.pos[axis];
4280 n->p.pos[axis] = n->n.pos[axis];
4281 n->n.pos[axis] = temp;
4282 sp_node_update_handles(n, false);
4283 } else {
4284 // scale nodes as an "object":
4286 NR::Rect box = sp_node_selected_bbox (nodepath);
4287 if (!center) {
4288 center = box.midpoint();
4289 }
4290 NR::Matrix t =
4291 NR::Matrix (NR::translate(- *center)) *
4292 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4293 NR::Matrix (NR::translate(*center));
4295 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4296 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4297 n->pos *= t;
4298 n->n.pos *= t;
4299 n->p.pos *= t;
4300 sp_node_update_handles(n, false);
4301 }
4302 }
4304 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4305 }
4307 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4308 {
4309 g_assert (nodepath->selected);
4311 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4312 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4313 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4314 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4315 box.expandTo (n->pos); // contain all selected nodes
4316 }
4317 return box;
4318 }
4320 //-----------------------------------------------
4321 /**
4322 * Return new subpath under given nodepath.
4323 */
4324 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4325 {
4326 g_assert(nodepath);
4327 g_assert(nodepath->desktop);
4329 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4331 s->nodepath = nodepath;
4332 s->closed = FALSE;
4333 s->nodes = NULL;
4334 s->first = NULL;
4335 s->last = NULL;
4337 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4338 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4339 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4341 return s;
4342 }
4344 /**
4345 * Destroy nodes in subpath, then subpath itself.
4346 */
4347 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4348 {
4349 g_assert(subpath);
4350 g_assert(subpath->nodepath);
4351 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4353 while (subpath->nodes) {
4354 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4355 }
4357 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4359 g_free(subpath);
4360 }
4362 /**
4363 * Link head to tail in subpath.
4364 */
4365 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4366 {
4367 g_assert(!sp->closed);
4368 g_assert(sp->last != sp->first);
4369 g_assert(sp->first->code == NR_MOVETO);
4371 sp->closed = TRUE;
4373 //Link the head to the tail
4374 sp->first->p.other = sp->last;
4375 sp->last->n.other = sp->first;
4376 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4377 sp->first = sp->last;
4379 //Remove the extra end node
4380 sp_nodepath_node_destroy(sp->last->n.other);
4381 }
4383 /**
4384 * Open closed (loopy) subpath at node.
4385 */
4386 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4387 {
4388 g_assert(sp->closed);
4389 g_assert(n->subpath == sp);
4390 g_assert(sp->first == sp->last);
4392 /* We create new startpoint, current node will become last one */
4394 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4395 &n->pos, &n->pos, &n->n.pos);
4398 sp->closed = FALSE;
4400 //Unlink to make a head and tail
4401 sp->first = new_path;
4402 sp->last = n;
4403 n->n.other = NULL;
4404 new_path->p.other = NULL;
4405 }
4407 /**
4408 * Return new node in subpath with given properties.
4409 * \param pos Position of node.
4410 * \param ppos Handle position in previous direction
4411 * \param npos Handle position in previous direction
4412 */
4413 Inkscape::NodePath::Node *
4414 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)
4415 {
4416 g_assert(sp);
4417 g_assert(sp->nodepath);
4418 g_assert(sp->nodepath->desktop);
4420 if (nodechunk == NULL)
4421 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4423 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4425 n->subpath = sp;
4427 if (type != Inkscape::NodePath::NODE_NONE) {
4428 // use the type from sodipodi:nodetypes
4429 n->type = type;
4430 } else {
4431 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4432 // points are (almost) collinear
4433 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4434 // endnode, or a node with a retracted handle
4435 n->type = Inkscape::NodePath::NODE_CUSP;
4436 } else {
4437 n->type = Inkscape::NodePath::NODE_SMOOTH;
4438 }
4439 } else {
4440 n->type = Inkscape::NodePath::NODE_CUSP;
4441 }
4442 }
4444 n->code = code;
4445 n->selected = FALSE;
4446 n->pos = *pos;
4447 n->p.pos = *ppos;
4448 n->n.pos = *npos;
4450 n->dragging_out = NULL;
4452 Inkscape::NodePath::Node *prev;
4453 if (next) {
4454 //g_assert(g_list_find(sp->nodes, next));
4455 prev = next->p.other;
4456 } else {
4457 prev = sp->last;
4458 }
4460 if (prev)
4461 prev->n.other = n;
4462 else
4463 sp->first = n;
4465 if (next)
4466 next->p.other = n;
4467 else
4468 sp->last = n;
4470 n->p.other = prev;
4471 n->n.other = next;
4473 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"));
4474 sp_knot_set_position(n->knot, *pos, 0);
4476 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4477 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4478 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4479 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4480 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4481 sp_knot_update_ctrl(n->knot);
4483 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4484 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4485 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4486 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4487 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4488 sp_knot_show(n->knot);
4490 // We only create handle knots and lines on demand
4491 n->p.knot = NULL;
4492 n->p.line = NULL;
4493 n->n.knot = NULL;
4494 n->n.line = NULL;
4496 sp->nodes = g_list_prepend(sp->nodes, n);
4498 return n;
4499 }
4501 /**
4502 * Destroy node and its knots, link neighbors in subpath.
4503 */
4504 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4505 {
4506 g_assert(node);
4507 g_assert(node->subpath);
4508 g_assert(SP_IS_KNOT(node->knot));
4510 Inkscape::NodePath::SubPath *sp = node->subpath;
4512 if (node->selected) { // first, deselect
4513 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4514 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4515 }
4517 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4519 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4520 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4521 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4522 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4523 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4524 g_object_unref(G_OBJECT(node->knot));
4526 if (node->p.knot) {
4527 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4528 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4529 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4530 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4531 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4532 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4533 g_object_unref(G_OBJECT(node->p.knot));
4534 node->p.knot = NULL;
4535 }
4537 if (node->n.knot) {
4538 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4539 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4540 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4541 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4542 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4543 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4544 g_object_unref(G_OBJECT(node->n.knot));
4545 node->n.knot = NULL;
4546 }
4548 if (node->p.line)
4549 gtk_object_destroy(GTK_OBJECT(node->p.line));
4550 if (node->n.line)
4551 gtk_object_destroy(GTK_OBJECT(node->n.line));
4553 if (sp->nodes) { // there are others nodes on the subpath
4554 if (sp->closed) {
4555 if (sp->first == node) {
4556 g_assert(sp->last == node);
4557 sp->first = node->n.other;
4558 sp->last = sp->first;
4559 }
4560 node->p.other->n.other = node->n.other;
4561 node->n.other->p.other = node->p.other;
4562 } else {
4563 if (sp->first == node) {
4564 sp->first = node->n.other;
4565 sp->first->code = NR_MOVETO;
4566 }
4567 if (sp->last == node) sp->last = node->p.other;
4568 if (node->p.other) node->p.other->n.other = node->n.other;
4569 if (node->n.other) node->n.other->p.other = node->p.other;
4570 }
4571 } else { // this was the last node on subpath
4572 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4573 }
4575 g_mem_chunk_free(nodechunk, node);
4576 }
4578 /**
4579 * Returns one of the node's two sides.
4580 * \param which Indicates which side.
4581 * \return Pointer to previous node side if which==-1, next if which==1.
4582 */
4583 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4584 {
4585 g_assert(node);
4587 switch (which) {
4588 case -1:
4589 return &node->p;
4590 case 1:
4591 return &node->n;
4592 default:
4593 break;
4594 }
4596 g_assert_not_reached();
4598 return NULL;
4599 }
4601 /**
4602 * Return the other side of the node, given one of its sides.
4603 */
4604 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4605 {
4606 g_assert(node);
4608 if (me == &node->p) return &node->n;
4609 if (me == &node->n) return &node->p;
4611 g_assert_not_reached();
4613 return NULL;
4614 }
4616 /**
4617 * Return NRPathcode on the given side of the node.
4618 */
4619 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4620 {
4621 g_assert(node);
4623 if (me == &node->p) {
4624 if (node->p.other) return (NRPathcode)node->code;
4625 return NR_MOVETO;
4626 }
4628 if (me == &node->n) {
4629 if (node->n.other) return (NRPathcode)node->n.other->code;
4630 return NR_MOVETO;
4631 }
4633 g_assert_not_reached();
4635 return NR_END;
4636 }
4638 /**
4639 * Return node with the given index
4640 */
4641 Inkscape::NodePath::Node *
4642 sp_nodepath_get_node_by_index(int index)
4643 {
4644 Inkscape::NodePath::Node *e = NULL;
4646 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4647 if (!nodepath) {
4648 return e;
4649 }
4651 //find segment
4652 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4654 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4655 int n = g_list_length(sp->nodes);
4656 if (sp->closed) {
4657 n++;
4658 }
4660 //if the piece belongs to this subpath grab it
4661 //otherwise move onto the next subpath
4662 if (index < n) {
4663 e = sp->first;
4664 for (int i = 0; i < index; ++i) {
4665 e = e->n.other;
4666 }
4667 break;
4668 } else {
4669 if (sp->closed) {
4670 index -= (n+1);
4671 } else {
4672 index -= n;
4673 }
4674 }
4675 }
4677 return e;
4678 }
4680 /**
4681 * Returns plain text meaning of node type.
4682 */
4683 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4684 {
4685 unsigned retracted = 0;
4686 bool endnode = false;
4688 for (int which = -1; which <= 1; which += 2) {
4689 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4690 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4691 retracted ++;
4692 if (!side->other)
4693 endnode = true;
4694 }
4696 if (retracted == 0) {
4697 if (endnode) {
4698 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4699 return _("end node");
4700 } else {
4701 switch (node->type) {
4702 case Inkscape::NodePath::NODE_CUSP:
4703 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4704 return _("cusp");
4705 case Inkscape::NodePath::NODE_SMOOTH:
4706 // TRANSLATORS: "smooth" is an adjective here
4707 return _("smooth");
4708 case Inkscape::NodePath::NODE_SYMM:
4709 return _("symmetric");
4710 }
4711 }
4712 } else if (retracted == 1) {
4713 if (endnode) {
4714 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4715 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4716 } else {
4717 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4718 }
4719 } else {
4720 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4721 }
4723 return NULL;
4724 }
4726 /**
4727 * Handles content of statusbar as long as node tool is active.
4728 */
4729 void
4730 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4731 {
4732 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");
4733 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4735 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4736 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4737 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4738 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4740 SPDesktop *desktop = NULL;
4741 if (nodepath) {
4742 desktop = nodepath->desktop;
4743 } else {
4744 desktop = SP_ACTIVE_DESKTOP;
4745 }
4747 SPEventContext *ec = desktop->event_context;
4748 if (!ec) return;
4749 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4750 if (!mc) return;
4752 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4754 if (selected_nodes == 0) {
4755 Inkscape::Selection *sel = desktop->selection;
4756 if (!sel || sel->isEmpty()) {
4757 mc->setF(Inkscape::NORMAL_MESSAGE,
4758 _("Select a single object to edit its nodes or handles."));
4759 } else {
4760 if (nodepath) {
4761 mc->setF(Inkscape::NORMAL_MESSAGE,
4762 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.",
4763 "<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.",
4764 total_nodes),
4765 total_nodes);
4766 } else {
4767 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4768 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4769 } else {
4770 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4771 }
4772 }
4773 }
4774 } else if (nodepath && selected_nodes == 1) {
4775 mc->setF(Inkscape::NORMAL_MESSAGE,
4776 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4777 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4778 total_nodes),
4779 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4780 } else {
4781 if (selected_subpaths > 1) {
4782 mc->setF(Inkscape::NORMAL_MESSAGE,
4783 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4784 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4785 total_nodes),
4786 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4787 } else {
4788 mc->setF(Inkscape::NORMAL_MESSAGE,
4789 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4790 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4791 total_nodes),
4792 selected_nodes, total_nodes, when_selected);
4793 }
4794 }
4795 }
4797 /*
4798 * returns a *copy* of the curve of that object.
4799 */
4800 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4801 if (!object)
4802 return NULL;
4804 SPCurve *curve = NULL;
4805 if (SP_IS_PATH(object)) {
4806 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4807 curve = curve_new->copy();
4808 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4809 const gchar *svgd = object->repr->attribute(key);
4810 if (svgd) {
4811 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4812 SPCurve *curve_new = new SPCurve(pv);
4813 if (curve_new) {
4814 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4815 }
4816 }
4817 }
4819 return curve;
4820 }
4822 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4823 if (!np || !np->object || !curve)
4824 return;
4826 if (SP_IS_PATH(np->object)) {
4827 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4828 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4829 } else {
4830 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4831 }
4832 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4833 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4834 if (pathparam) {
4835 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4836 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4837 }
4838 }
4839 }
4841 /**
4842 SPCanvasItem *
4843 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4844 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4845 }
4846 **/
4848 /**
4849 SPCanvasItem *
4850 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4851 SPCurve *flash_curve = curve->copy();
4852 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4853 flash_curve->transform(i2d);
4854 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4855 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4856 // unless we also flash the nodes...
4857 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4858 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4859 sp_canvas_item_show(canvasitem);
4860 flash_curve->unref();
4861 return canvasitem;
4862 }
4864 SPCanvasItem *
4865 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4866 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4867 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4868 }
4869 **/
4871 SPCanvasItem *
4872 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4873 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4874 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4875 flash_curve->transform(i2d);
4876 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4877 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4878 // unless we also flash the nodes...
4879 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4880 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4881 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4882 sp_canvas_item_show(canvasitem);
4883 flash_curve->unref();
4884 return canvasitem;
4885 }
4887 // TODO: Merge this with sp_nodepath_make_helper_item()!
4888 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4889 np->show_helperpath = show;
4891 if (show) {
4892 SPCurve *helper_curve = np->curve->copy();
4893 helper_curve->transform(to_2geom(np->i2d));
4894 if (!np->helper_path) {
4895 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4897 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4898 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4899 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4900 sp_canvas_item_move_to_z(np->helper_path, 0);
4901 sp_canvas_item_show(np->helper_path);
4902 } else {
4903 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4904 }
4905 helper_curve->unref();
4906 } else {
4907 if (np->helper_path) {
4908 GtkObject *temp = np->helper_path;
4909 np->helper_path = NULL;
4910 gtk_object_destroy(temp);
4911 }
4912 }
4913 }
4915 /* sp_nodepath_make_straight_path:
4916 * Prevents user from curving the path by dragging a segment or activating handles etc.
4917 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4918 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4919 */
4920 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4921 np->straight_path = true;
4922 np->show_handles = false;
4923 g_message("add code to make the path straight.");
4924 // do sp_nodepath_convert_node_type on all nodes?
4925 // coding tip: search for this text : "Make selected segments lines"
4926 }
4928 /* convert all curve types to LineSegment or CubicBezier, because nodepath cannot handle other segment types */
4929 Geom::PathVector sp_nodepath_sanitize_path(Geom::PathVector const &pathv_in) {
4930 Geom::PathVector pathv;
4932 for (Geom::PathVector::const_iterator pit = pathv_in.begin(); pit != pathv_in.end(); ++pit) {
4933 pathv.push_back( Geom::Path() );
4934 Geom::Path &newpath = pathv.back();
4935 newpath.start(pit->initialPoint());
4936 newpath.close(pit->closed());
4938 for (Geom::Path::const_iterator c = pit->begin(); c != pit->end_open(); ++c) {
4939 if( dynamic_cast<Geom::CubicBezier const*>(&*c) ||
4940 dynamic_cast<Geom::LineSegment const*>(&*c) ||
4941 dynamic_cast<Geom::HLineSegment const*>(&*c) ||
4942 dynamic_cast<Geom::VLineSegment const*>(&*c) )
4943 {
4944 newpath.append(*c);
4945 }
4946 else {
4947 //this case handles sbasis as well as all other curve types
4948 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c->toSBasis(), 0.1);
4949 newpath.append(sbasis_path);
4950 }
4951 }
4952 }
4954 return pathv;
4955 }
4957 /*
4958 Local Variables:
4959 mode:c++
4960 c-file-style:"stroustrup"
4961 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4962 indent-tabs-mode:nil
4963 fill-column:99
4964 End:
4965 */
4966 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :