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 void add_curve_to_subpath( Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c,
104 Inkscape::NodePath::NodeType const *t, guint & i, NR::Point & ppos, NRPathcode & pcode );
105 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
107 /* Object updating */
109 static void stamp_repr(Inkscape::NodePath::Path *np);
110 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
111 static gchar *create_typestr(Inkscape::NodePath::Path *np);
113 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
115 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
117 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
119 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
121 /* Adjust handle placement, if the node or the other handle is moved */
122 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
123 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
125 /* Node event callbacks */
126 static void node_clicked(SPKnot *knot, guint state, gpointer data);
127 static void node_grabbed(SPKnot *knot, guint state, gpointer data);
128 static void node_ungrabbed(SPKnot *knot, guint state, gpointer data);
129 static gboolean node_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
131 /* Handle event callbacks */
132 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data);
133 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data);
134 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data);
135 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data);
136 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data);
137 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n);
139 /* Constructors and destructors */
141 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath);
142 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath);
143 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp);
144 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n);
145 static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code,
146 NR::Point *ppos, NR::Point *pos, NR::Point *npos);
147 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node);
149 /* Helpers */
151 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which);
152 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
153 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
155 static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
156 static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve);
158 // active_node indicates mouseover node
159 Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
161 static SPCanvasItem *
162 sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false) {
163 SPCurve *helper_curve = curve->copy();
164 helper_curve->transform(to_2geom(np->i2d));
165 SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
166 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
167 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO);
168 sp_canvas_item_move_to_z(helper_path, 0);
169 if (show) {
170 sp_canvas_item_show(helper_path);
171 }
172 helper_curve->unref();
173 return helper_path;
174 }
176 static SPCanvasItem *
177 canvasitem_from_pathvec(Inkscape::NodePath::Path *np, Geom::PathVector const &pathv, bool show) {
178 SPCurve *helper_curve = new SPCurve(pathv);
179 return sp_nodepath_make_helper_item(np, helper_curve, show);
180 }
182 static void
183 sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) {
184 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
185 if (!SP_IS_LPE_ITEM(np->item)) {
186 g_print ("Only LPEItems can have helperpaths!\n");
187 return;
188 }
190 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
191 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
192 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
193 Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i);
194 Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->lpe;
195 // create new canvas items from the effect's helper paths
196 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
197 for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
198 (*np->helper_path_vec)[lpe].push_back(canvasitem_from_pathvec(np, *j, true));
199 }
200 }
201 }
203 void
204 sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
205 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> >* helper_path_vec;
206 if (!SP_IS_LPE_ITEM(np->item)) {
207 g_print ("Only LPEItems can have helperpaths!\n");
208 return;
209 }
211 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
212 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
213 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
214 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->lpe;
215 /* update canvas items from the effect's helper paths; note that this code relies on the
216 * fact that getHelperPaths() will always return the same number of helperpaths in the same
217 * order as during their creation in sp_nodepath_create_helperpaths
218 */
219 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
220 for (unsigned int j = 0; j < hpaths.size(); ++j) {
221 SPCurve *curve = new SPCurve(hpaths[j]);
222 curve->transform(to_2geom(np->i2d));
223 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(((*np->helper_path_vec)[lpe])[j]), curve);
224 curve = curve->unref();
225 }
226 }
227 }
229 static void
230 sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
231 for (HelperPathList::iterator i = np->helper_path_vec->begin(); i != np->helper_path_vec->end(); ++i) {
232 for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
233 GtkObject *temp = *j;
234 *j = NULL;
235 gtk_object_destroy(temp);
236 }
237 }
238 }
241 /**
242 * \brief Creates new nodepath from item
243 */
244 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
245 {
246 Inkscape::XML::Node *repr = object->repr;
248 /** \todo
249 * FIXME: remove this. We don't want to edit paths inside flowtext.
250 * Instead we will build our flowtext with cloned paths, so that the
251 * real paths are outside the flowtext and thus editable as usual.
252 */
253 if (SP_IS_FLOWTEXT(object)) {
254 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
255 if SP_IS_FLOWREGION(child) {
256 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
257 if (grandchild && SP_IS_PATH(grandchild)) {
258 object = SP_ITEM(grandchild);
259 break;
260 }
261 }
262 }
263 }
265 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
267 if (curve == NULL)
268 return NULL;
270 if (curve->get_segment_count() < 1) {
271 curve->unref();
272 return NULL; // prevent crash for one-node paths
273 }
275 //Create new nodepath
276 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
277 if (!np) {
278 curve->unref();
279 return NULL;
280 }
282 // Set defaults
283 np->desktop = desktop;
284 np->object = object;
285 np->subpaths = NULL;
286 np->selected = NULL;
287 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
288 np->local_change = 0;
289 np->show_handles = show_handles;
290 np->helper_path = NULL;
291 np->helper_path_vec = new HelperPathList;
292 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
293 np->helperpath_width = 1.0;
294 np->curve = curve->copy();
295 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
296 if (SP_IS_LPE_ITEM(object)) {
297 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
298 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
299 np->show_helperpath = true;
300 }
301 }
302 np->straight_path = false;
303 if (IS_LIVEPATHEFFECT(object) && item) {
304 np->item = item;
305 } else {
306 np->item = SP_ITEM(object);
307 }
309 // we need to update item's transform from the repr here,
310 // because they may be out of sync when we respond
311 // to a change in repr by regenerating nodepath --bb
312 sp_object_read_attr(SP_OBJECT(np->item), "transform");
314 np->i2d = from_2geom(sp_item_i2d_affine(np->item));
315 np->d2i = np->i2d.inverse();
317 np->repr = repr;
318 if (repr_key_in) { // apparantly the object is an LPEObject
319 np->repr_key = g_strdup(repr_key_in);
320 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
321 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
322 if (lpeparam) {
323 lpeparam->param_setup_nodepath(np);
324 }
325 } else {
326 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
327 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
328 np->repr_key = g_strdup("inkscape:original-d");
330 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
331 if (lpe) {
332 lpe->setup_nodepath(np);
333 }
334 } else {
335 np->repr_key = g_strdup("d");
336 }
337 }
339 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
340 * So for example a closed rectangle has a nodetypestring of length 5.
341 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
342 Geom::PathVector const &pathv = curve->get_pathvector();
343 guint length = curve->get_segment_count();
344 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.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, 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 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
530 add_curve_to_subpath(np, sp, *cit, t, i, ppos, pcode);
531 }
533 if (pit->closed()) {
534 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
535 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
536 * If the length is zero, don't add it to the nodepath. */
537 Geom::Curve const &closing_seg = pit->back_closed();
538 if ( ! closing_seg.isDegenerate() ) {
539 NR::Point pos = from_2geom(closing_seg.finalPoint()) * np->i2d;
540 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
541 }
543 sp_nodepath_subpath_close(sp);
544 }
545 }
546 }
547 // should add initial point of curve with type of previous curve:
548 static void add_curve_to_subpath(Inkscape::NodePath::Path *np, Inkscape::NodePath::SubPath *sp, Geom::Curve const & c, Inkscape::NodePath::NodeType const *t, guint & i,
549 NR::Point & ppos, NRPathcode & pcode)
550 {
551 if( dynamic_cast<Geom::LineSegment const*>(&c) ||
552 dynamic_cast<Geom::HLineSegment const*>(&c) ||
553 dynamic_cast<Geom::VLineSegment const*>(&c) )
554 {
555 NR::Point pos = from_2geom(c.initialPoint()) * np->i2d;
556 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &pos, &pos, &pos);
557 ppos = from_2geom(c.finalPoint());
558 pcode = NR_LINETO;
559 }
560 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
561 std::vector<Geom::Point> points = cubic_bezier->points();
562 NR::Point pos = from_2geom(points[0]) * np->i2d;
563 NR::Point npos = from_2geom(points[1]) * np->i2d;
564 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
565 ppos = from_2geom(points[2]) * np->i2d;
566 pcode = NR_CURVETO;
567 }
568 else {
569 //this case handles sbasis as well as all other curve types
570 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
572 for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
573 add_curve_to_subpath(np, sp, *iter, t, i, ppos, pcode);
574 }
575 }
576 }
579 /**
580 * Convert from sodipodi:nodetypes to new style type array.
581 */
582 static
583 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
584 {
585 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
587 guint pos = 0;
589 if (types) {
590 for (guint i = 0; types[i] && ( i < length ); i++) {
591 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
592 if (types[i] != '\0') {
593 switch (types[i]) {
594 case 's':
595 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
596 break;
597 case 'z':
598 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
599 break;
600 case 'c':
601 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
602 break;
603 default:
604 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
605 break;
606 }
607 }
608 }
609 }
611 while (pos < length) typestr[pos++] =Inkscape::NodePath::NODE_NONE;
613 return typestr;
614 }
616 /**
617 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
618 * updated but repr is not (for speed). Used during curve and node drag.
619 */
620 static void update_object(Inkscape::NodePath::Path *np)
621 {
622 g_assert(np);
624 np->curve->unref();
625 np->curve = create_curve(np);
627 sp_nodepath_set_curve(np, np->curve);
629 if (np->show_helperpath) {
630 SPCurve * helper_curve = np->curve->copy();
631 helper_curve->transform(to_2geom(np->i2d));
632 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
633 helper_curve->unref();
634 }
636 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
637 //sp_nodepath_update_helperpaths(np);
639 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
640 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
641 np->shape_editor->update_knotholder();
642 }
644 /**
645 * Update XML path node with data from path object.
646 */
647 static void update_repr_internal(Inkscape::NodePath::Path *np)
648 {
649 g_assert(np);
651 Inkscape::XML::Node *repr = np->object->repr;
653 np->curve->unref();
654 np->curve = create_curve(np);
656 gchar *typestr = create_typestr(np);
657 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
659 // determine if path has an effect applied and write to correct "d" attribute.
660 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
661 np->local_change++;
662 repr->setAttribute(np->repr_key, svgpath);
663 }
665 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
666 np->local_change++;
667 repr->setAttribute(np->repr_nodetypes_key, typestr);
668 }
670 g_free(svgpath);
671 g_free(typestr);
673 if (np->show_helperpath) {
674 SPCurve * helper_curve = np->curve->copy();
675 helper_curve->transform(to_2geom(np->i2d));
676 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
677 helper_curve->unref();
678 }
680 // TODO: do we need this call here? after all, update_object() should have been called just before
681 //sp_nodepath_update_helperpaths(np);
682 }
684 /**
685 * Update XML path node with data from path object, commit changes forever.
686 */
687 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
688 {
689 //fixme: np can be NULL, so check before proceeding
690 g_return_if_fail(np != NULL);
692 update_repr_internal(np);
693 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
695 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
696 annotation);
697 }
699 /**
700 * Update XML path node with data from path object, commit changes with undo.
701 */
702 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
703 {
704 update_repr_internal(np);
705 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
706 annotation);
707 }
709 /**
710 * Make duplicate of path, replace corresponding XML node in tree, commit.
711 */
712 static void stamp_repr(Inkscape::NodePath::Path *np)
713 {
714 g_assert(np);
716 Inkscape::XML::Node *old_repr = np->object->repr;
717 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
719 // remember the position of the item
720 gint pos = old_repr->position();
721 // remember parent
722 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
724 SPCurve *curve = create_curve(np);
725 gchar *typestr = create_typestr(np);
727 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
729 new_repr->setAttribute(np->repr_key, svgpath);
730 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
732 // add the new repr to the parent
733 parent->appendChild(new_repr);
734 // move to the saved position
735 new_repr->setPosition(pos > 0 ? pos : 0);
737 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
738 _("Stamp"));
740 Inkscape::GC::release(new_repr);
741 g_free(svgpath);
742 g_free(typestr);
743 curve->unref();
744 }
746 /**
747 * Create curve from path.
748 */
749 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
750 {
751 SPCurve *curve = new SPCurve();
753 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
754 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
755 curve->moveto(sp->first->pos * np->d2i);
756 Inkscape::NodePath::Node *n = sp->first->n.other;
757 while (n) {
758 NR::Point const end_pt = n->pos * np->d2i;
759 switch (n->code) {
760 case NR_LINETO:
761 curve->lineto(end_pt);
762 break;
763 case NR_CURVETO:
764 curve->curveto(n->p.other->n.pos * np->d2i,
765 n->p.pos * np->d2i,
766 end_pt);
767 break;
768 default:
769 g_assert_not_reached();
770 break;
771 }
772 if (n != sp->last) {
773 n = n->n.other;
774 } else {
775 n = NULL;
776 }
777 }
778 if (sp->closed) {
779 curve->closepath();
780 }
781 }
783 return curve;
784 }
786 /**
787 * Convert path type string to sodipodi:nodetypes style.
788 */
789 static gchar *create_typestr(Inkscape::NodePath::Path *np)
790 {
791 gchar *typestr = g_new(gchar, 32);
792 gint len = 32;
793 gint pos = 0;
795 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
796 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
798 if (pos >= len) {
799 typestr = g_renew(gchar, typestr, len + 32);
800 len += 32;
801 }
803 typestr[pos++] = 'c';
805 Inkscape::NodePath::Node *n;
806 n = sp->first->n.other;
807 while (n) {
808 gchar code;
810 switch (n->type) {
811 case Inkscape::NodePath::NODE_CUSP:
812 code = 'c';
813 break;
814 case Inkscape::NodePath::NODE_SMOOTH:
815 code = 's';
816 break;
817 case Inkscape::NodePath::NODE_SYMM:
818 code = 'z';
819 break;
820 default:
821 g_assert_not_reached();
822 code = '\0';
823 break;
824 }
826 if (pos >= len) {
827 typestr = g_renew(gchar, typestr, len + 32);
828 len += 32;
829 }
831 typestr[pos++] = code;
833 if (n != sp->last) {
834 n = n->n.other;
835 } else {
836 n = NULL;
837 }
838 }
839 }
841 if (pos >= len) {
842 typestr = g_renew(gchar, typestr, len + 1);
843 len += 1;
844 }
846 typestr[pos++] = '\0';
848 return typestr;
849 }
851 /**
852 * Returns current path in context. // later eliminate this function at all!
853 */
854 static Inkscape::NodePath::Path *sp_nodepath_current()
855 {
856 if (!SP_ACTIVE_DESKTOP) {
857 return NULL;
858 }
860 SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context;
862 if (!SP_IS_NODE_CONTEXT(event_context)) {
863 return NULL;
864 }
866 return SP_NODE_CONTEXT(event_context)->shape_editor->get_nodepath();
867 }
871 /**
872 \brief Fills node and handle positions for three nodes, splitting line
873 marked by end at distance t.
874 */
875 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
876 {
877 g_assert(new_path != NULL);
878 g_assert(end != NULL);
880 g_assert(end->p.other == new_path);
881 Inkscape::NodePath::Node *start = new_path->p.other;
882 g_assert(start);
884 if (end->code == NR_LINETO) {
885 new_path->type =Inkscape::NodePath::NODE_CUSP;
886 new_path->code = NR_LINETO;
887 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
888 } else {
889 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
890 new_path->code = NR_CURVETO;
891 gdouble s = 1 - t;
892 for (int dim = 0; dim < 2; dim++) {
893 NR::Coord const f000 = start->pos[dim];
894 NR::Coord const f001 = start->n.pos[dim];
895 NR::Coord const f011 = end->p.pos[dim];
896 NR::Coord const f111 = end->pos[dim];
897 NR::Coord const f00t = s * f000 + t * f001;
898 NR::Coord const f01t = s * f001 + t * f011;
899 NR::Coord const f11t = s * f011 + t * f111;
900 NR::Coord const f0tt = s * f00t + t * f01t;
901 NR::Coord const f1tt = s * f01t + t * f11t;
902 NR::Coord const fttt = s * f0tt + t * f1tt;
903 start->n.pos[dim] = f00t;
904 new_path->p.pos[dim] = f0tt;
905 new_path->pos[dim] = fttt;
906 new_path->n.pos[dim] = f1tt;
907 end->p.pos[dim] = f11t;
908 }
909 }
910 }
912 /**
913 * Adds new node on direct line between two nodes, activates handles of all
914 * three nodes.
915 */
916 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
917 {
918 g_assert(end);
919 g_assert(end->subpath);
920 g_assert(g_list_find(end->subpath->nodes, end));
922 Inkscape::NodePath::Node *start = end->p.other;
923 g_assert( start->n.other == end );
924 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
925 end,
926 (NRPathcode)end->code == NR_LINETO?
927 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
928 (NRPathcode)end->code,
929 &start->pos, &start->pos, &start->n.pos);
930 sp_nodepath_line_midpoint(newnode, end, t);
932 sp_node_adjust_handles(start);
933 sp_node_update_handles(start);
934 sp_node_update_handles(newnode);
935 sp_node_adjust_handles(end);
936 sp_node_update_handles(end);
938 return newnode;
939 }
941 /**
942 \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
943 */
944 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
945 {
946 g_assert(node);
947 g_assert(node->subpath);
948 g_assert(g_list_find(node->subpath->nodes, node));
950 Inkscape::NodePath::SubPath *sp = node->subpath;
951 Inkscape::NodePath::Path *np = sp->nodepath;
953 if (sp->closed) {
954 sp_nodepath_subpath_open(sp, node);
955 return sp->first;
956 } else {
957 // no break for end nodes
958 if (node == sp->first) return NULL;
959 if (node == sp->last ) return NULL;
961 // create a new subpath
962 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
964 // duplicate the break node as start of the new subpath
965 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, (Inkscape::NodePath::NodeType)node->type, NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
967 // attach rest of curve to new node
968 g_assert(node->n.other);
969 newnode->n.other = node->n.other; node->n.other = NULL;
970 newnode->n.other->p.other = newnode;
971 newsubpath->last = sp->last;
972 sp->last = node;
973 node = newnode;
974 while (node->n.other) {
975 node = node->n.other;
976 node->subpath = newsubpath;
977 sp->nodes = g_list_remove(sp->nodes, node);
978 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
979 }
982 return newnode;
983 }
984 }
986 /**
987 * Duplicate node and connect to neighbours.
988 */
989 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
990 {
991 g_assert(node);
992 g_assert(node->subpath);
993 g_assert(g_list_find(node->subpath->nodes, node));
995 Inkscape::NodePath::SubPath *sp = node->subpath;
997 NRPathcode code = (NRPathcode) node->code;
998 if (code == NR_MOVETO) { // if node is the endnode,
999 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1000 }
1002 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1004 if (!node->n.other || !node->p.other) // if node is an endnode, select it
1005 return node;
1006 else
1007 return newnode; // otherwise select the newly created node
1008 }
1010 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1011 {
1012 node->p.pos = (node->pos + (node->pos - node->n.pos));
1013 }
1015 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1016 {
1017 node->n.pos = (node->pos + (node->pos - node->p.pos));
1018 }
1020 /**
1021 * Change line type at node, with side effects on neighbours.
1022 */
1023 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1024 {
1025 g_assert(end);
1026 g_assert(end->subpath);
1027 g_assert(end->p.other);
1029 if (end->code == static_cast< guint > ( code ) )
1030 return;
1032 Inkscape::NodePath::Node *start = end->p.other;
1034 end->code = code;
1036 if (code == NR_LINETO) {
1037 if (start->code == NR_LINETO) {
1038 sp_nodepath_set_node_type (start, Inkscape::NodePath::NODE_CUSP);
1039 }
1040 if (end->n.other) {
1041 if (end->n.other->code == NR_LINETO) {
1042 sp_nodepath_set_node_type (end, Inkscape::NodePath::NODE_CUSP);
1043 }
1044 }
1045 } else {
1046 NR::Point delta = end->pos - start->pos;
1047 start->n.pos = start->pos + delta / 3;
1048 end->p.pos = end->pos - delta / 3;
1049 sp_node_adjust_handle(start, 1);
1050 sp_node_adjust_handle(end, -1);
1051 }
1053 sp_node_update_handles(start);
1054 sp_node_update_handles(end);
1055 }
1057 /**
1058 * Change node type, and its handles accordingly.
1059 */
1060 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1061 {
1062 g_assert(node);
1063 g_assert(node->subpath);
1065 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1066 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1067 type =Inkscape::NodePath::NODE_CUSP;
1068 }
1069 }
1071 node->type = type;
1073 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1074 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1075 node->knot->setSize (node->selected? 11 : 9);
1076 sp_knot_update_ctrl(node->knot);
1077 } else {
1078 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1079 node->knot->setSize (node->selected? 9 : 7);
1080 sp_knot_update_ctrl(node->knot);
1081 }
1083 // if one of handles is mouseovered, preserve its position
1084 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1085 sp_node_adjust_handle(node, 1);
1086 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1087 sp_node_adjust_handle(node, -1);
1088 } else {
1089 sp_node_adjust_handles(node);
1090 }
1092 sp_node_update_handles(node);
1094 sp_nodepath_update_statusbar(node->subpath->nodepath);
1096 return node;
1097 }
1099 bool
1100 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1101 {
1102 Inkscape::NodePath::Node *othernode = side->other;
1103 if (!othernode)
1104 return false;
1105 NRPathcode const code = sp_node_path_code_from_side(node, side);
1106 if (code == NR_LINETO)
1107 return true;
1108 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1109 if (&node->p == side) {
1110 other_to_me = &othernode->n;
1111 } else if (&node->n == side) {
1112 other_to_me = &othernode->p;
1113 }
1114 if (!other_to_me)
1115 return false;
1116 bool is_line =
1117 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1118 NR::L2(node->pos - side->pos) < 1e-6);
1119 return is_line;
1120 }
1122 /**
1123 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1124 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1125 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1126 * If already cusp and set to cusp, retracts handles.
1127 */
1128 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1129 {
1130 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1132 /*
1133 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1135 if (two_handles) {
1136 // do nothing, adjust_handles called via set_node_type will line them up
1137 } else if (one_handle) {
1138 if (opposite_to_handle_is_line) {
1139 if (lined_up) {
1140 // already half-smooth; pull opposite handle too making it fully smooth
1141 } else {
1142 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1143 }
1144 } else {
1145 // pull opposite handle in line with the existing one
1146 }
1147 } else if (no_handles) {
1148 if (both_segments_are_lines OR both_segments_are_curves) {
1149 //pull both handles
1150 } else {
1151 // pull the handle opposite to line segment, making node half-smooth
1152 }
1153 }
1154 */
1155 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1156 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1157 bool p_is_line = sp_node_side_is_line(node, &node->p);
1158 bool n_is_line = sp_node_side_is_line(node, &node->n);
1160 if (p_has_handle && n_has_handle) {
1161 // do nothing, adjust_handles will line them up
1162 } else if (p_has_handle || n_has_handle) {
1163 if (p_has_handle && n_is_line) {
1164 Radial line (node->n.other->pos - node->pos);
1165 Radial handle (node->pos - node->p.pos);
1166 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1167 // already half-smooth; pull opposite handle too making it fully smooth
1168 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1169 } else {
1170 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1171 }
1172 } else if (n_has_handle && p_is_line) {
1173 Radial line (node->p.other->pos - node->pos);
1174 Radial handle (node->pos - node->n.pos);
1175 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1176 // already half-smooth; pull opposite handle too making it fully smooth
1177 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1178 } else {
1179 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1180 }
1181 } else if (p_has_handle && node->n.other) {
1182 // pull n handle
1183 node->n.other->code = NR_CURVETO;
1184 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1185 NR::L2(node->p.pos - node->pos) :
1186 NR::L2(node->n.other->pos - node->pos) / 3;
1187 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1188 } else if (n_has_handle && node->p.other) {
1189 // pull p handle
1190 node->code = NR_CURVETO;
1191 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1192 NR::L2(node->n.pos - node->pos) :
1193 NR::L2(node->p.other->pos - node->pos) / 3;
1194 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1195 }
1196 } else if (!p_has_handle && !n_has_handle) {
1197 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1198 // no handles, but both segments are either lnes or curves:
1199 //pull both handles
1201 // convert both to curves:
1202 node->code = NR_CURVETO;
1203 node->n.other->code = NR_CURVETO;
1205 NR::Point leg_prev = node->pos - node->p.other->pos;
1206 NR::Point leg_next = node->pos - node->n.other->pos;
1208 double norm_leg_prev = L2(leg_prev);
1209 double norm_leg_next = L2(leg_next);
1211 NR::Point delta;
1212 if (norm_leg_next > 0.0) {
1213 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1214 (&delta)->normalize();
1215 }
1217 if (type == Inkscape::NodePath::NODE_SYMM) {
1218 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1219 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1220 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1221 } else {
1222 // length of handle is proportional to distance to adjacent node
1223 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1224 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1225 }
1227 } else {
1228 // pull the handle opposite to line segment, making it half-smooth
1229 if (p_is_line && node->n.other) {
1230 if (type != Inkscape::NodePath::NODE_SYMM) {
1231 // pull n handle
1232 node->n.other->code = NR_CURVETO;
1233 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1234 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1235 }
1236 } else if (n_is_line && node->p.other) {
1237 if (type != Inkscape::NodePath::NODE_SYMM) {
1238 // pull p handle
1239 node->code = NR_CURVETO;
1240 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1241 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1242 }
1243 }
1244 }
1245 }
1246 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1247 // cusping a cusp: retract nodes
1248 node->p.pos = node->pos;
1249 node->n.pos = node->pos;
1250 }
1252 sp_nodepath_set_node_type (node, type);
1253 }
1255 /**
1256 * Move node to point, and adjust its and neighbouring handles.
1257 */
1258 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1259 {
1260 NR::Point delta = p - node->pos;
1261 node->pos = p;
1263 node->p.pos += delta;
1264 node->n.pos += delta;
1266 Inkscape::NodePath::Node *node_p = NULL;
1267 Inkscape::NodePath::Node *node_n = NULL;
1269 if (node->p.other) {
1270 if (node->code == NR_LINETO) {
1271 sp_node_adjust_handle(node, 1);
1272 sp_node_adjust_handle(node->p.other, -1);
1273 node_p = node->p.other;
1274 }
1275 }
1276 if (node->n.other) {
1277 if (node->n.other->code == NR_LINETO) {
1278 sp_node_adjust_handle(node, -1);
1279 sp_node_adjust_handle(node->n.other, 1);
1280 node_n = node->n.other;
1281 }
1282 }
1284 // this function is only called from batch movers that will update display at the end
1285 // themselves, so here we just move all the knots without emitting move signals, for speed
1286 sp_node_update_handles(node, false);
1287 if (node_n) {
1288 sp_node_update_handles(node_n, false);
1289 }
1290 if (node_p) {
1291 sp_node_update_handles(node_p, false);
1292 }
1293 }
1295 /**
1296 * Call sp_node_moveto() for node selection and handle possible snapping.
1297 */
1298 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1299 bool const snap, bool constrained = false,
1300 Inkscape::Snapper::ConstraintLine const &constraint = NR::Point())
1301 {
1302 NR::Coord best = NR_HUGE;
1303 NR::Point delta(dx, dy);
1304 NR::Point best_pt = delta;
1305 Inkscape::SnappedPoint best_abs;
1307 if (snap) {
1308 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1309 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1310 * must provide that information. */
1312 // Build a list of the unselected nodes to which the snapper should snap
1313 std::vector<NR::Point> unselected_nodes;
1314 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1315 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1316 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1317 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1318 if (!node->selected) {
1319 unselected_nodes.push_back(node->pos);
1320 }
1321 }
1322 }
1324 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1326 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1327 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1328 m.setup(NULL, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1329 Inkscape::SnappedPoint s;
1330 if (constrained) {
1331 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1332 dedicated_constraint.setPoint(n->pos);
1333 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, dedicated_constraint);
1334 } else {
1335 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta);
1336 }
1337 if (s.getSnapped() && (s.getDistance() < best)) {
1338 best = s.getDistance();
1339 best_abs = s;
1340 best_pt = s.getPoint() - n->pos;
1341 }
1342 }
1344 if (best_abs.getSnapped()) {
1345 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1346 } else {
1347 nodepath->desktop->snapindicator->remove_snappoint();
1348 }
1349 }
1351 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1352 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1353 sp_node_moveto(n, n->pos + best_pt);
1354 }
1356 // do not update repr here so that node dragging is acceptably fast
1357 update_object(nodepath);
1358 }
1360 /**
1361 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1362 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1363 near x = 0.
1364 */
1365 double
1366 sculpt_profile (double x, double alpha, guint profile)
1367 {
1368 if (x >= 1)
1369 return 0;
1370 if (x <= 0)
1371 return 1;
1373 switch (profile) {
1374 case SCULPT_PROFILE_LINEAR:
1375 return 1 - x;
1376 case SCULPT_PROFILE_BELL:
1377 return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1378 case SCULPT_PROFILE_ELLIPTIC:
1379 return sqrt(1 - x*x);
1380 }
1382 return 1;
1383 }
1385 double
1386 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1387 {
1388 // extremely primitive for now, don't have time to look for the real one
1389 double lower = NR::L2(b - a);
1390 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1391 return (lower + upper)/2;
1392 }
1394 void
1395 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1396 {
1397 n->pos = n->origin + delta;
1398 n->n.pos = n->n.origin + delta_n;
1399 n->p.pos = n->p.origin + delta_p;
1400 sp_node_adjust_handles(n);
1401 sp_node_update_handles(n, false);
1402 }
1404 /**
1405 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1406 * on how far they are from the dragged node n.
1407 */
1408 static void
1409 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1410 {
1411 g_assert (n);
1412 g_assert (nodepath);
1413 g_assert (n->subpath->nodepath == nodepath);
1415 double pressure = n->knot->pressure;
1416 if (pressure == 0)
1417 pressure = 0.5; // default
1418 pressure = CLAMP (pressure, 0.2, 0.8);
1420 // map pressure to alpha = 1/5 ... 5
1421 double alpha = 1 - 2 * fabs(pressure - 0.5);
1422 if (pressure > 0.5)
1423 alpha = 1/alpha;
1425 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1427 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1428 // Only one subpath has selected nodes:
1429 // use linear mode, where the distance from n to node being dragged is calculated along the path
1431 double n_sel_range = 0, p_sel_range = 0;
1432 guint n_nodes = 0, p_nodes = 0;
1433 guint n_sel_nodes = 0, p_sel_nodes = 0;
1435 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1436 {
1437 double n_range = 0, p_range = 0;
1438 bool n_going = true, p_going = true;
1439 Inkscape::NodePath::Node *n_node = n;
1440 Inkscape::NodePath::Node *p_node = n;
1441 do {
1442 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1443 if (n_node && n_going)
1444 n_node = n_node->n.other;
1445 if (n_node == NULL) {
1446 n_going = false;
1447 } else {
1448 n_nodes ++;
1449 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1450 if (n_node->selected) {
1451 n_sel_nodes ++;
1452 n_sel_range = n_range;
1453 }
1454 if (n_node == p_node) {
1455 n_going = false;
1456 p_going = false;
1457 }
1458 }
1459 if (p_node && p_going)
1460 p_node = p_node->p.other;
1461 if (p_node == NULL) {
1462 p_going = false;
1463 } else {
1464 p_nodes ++;
1465 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1466 if (p_node->selected) {
1467 p_sel_nodes ++;
1468 p_sel_range = p_range;
1469 }
1470 if (p_node == n_node) {
1471 n_going = false;
1472 p_going = false;
1473 }
1474 }
1475 } while (n_going || p_going);
1476 }
1478 // Second pass: actually move nodes in this subpath
1479 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1480 {
1481 double n_range = 0, p_range = 0;
1482 bool n_going = true, p_going = true;
1483 Inkscape::NodePath::Node *n_node = n;
1484 Inkscape::NodePath::Node *p_node = n;
1485 do {
1486 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1487 if (n_node && n_going)
1488 n_node = n_node->n.other;
1489 if (n_node == NULL) {
1490 n_going = false;
1491 } else {
1492 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1493 if (n_node->selected) {
1494 sp_nodepath_move_node_and_handles (n_node,
1495 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1496 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1497 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1498 }
1499 if (n_node == p_node) {
1500 n_going = false;
1501 p_going = false;
1502 }
1503 }
1504 if (p_node && p_going)
1505 p_node = p_node->p.other;
1506 if (p_node == NULL) {
1507 p_going = false;
1508 } else {
1509 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1510 if (p_node->selected) {
1511 sp_nodepath_move_node_and_handles (p_node,
1512 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1513 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1514 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1515 }
1516 if (p_node == n_node) {
1517 n_going = false;
1518 p_going = false;
1519 }
1520 }
1521 } while (n_going || p_going);
1522 }
1524 } else {
1525 // Multiple subpaths have selected nodes:
1526 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1527 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1528 // fix the pear-like shape when sculpting e.g. a ring
1530 // First pass: calculate range
1531 gdouble direct_range = 0;
1532 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1533 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1534 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1535 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1536 if (node->selected) {
1537 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1538 }
1539 }
1540 }
1542 // Second pass: actually move nodes
1543 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1544 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1545 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1546 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1547 if (node->selected) {
1548 if (direct_range > 1e-6) {
1549 sp_nodepath_move_node_and_handles (node,
1550 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1551 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1552 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1553 } else {
1554 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1555 }
1557 }
1558 }
1559 }
1560 }
1562 // do not update repr here so that node dragging is acceptably fast
1563 update_object(nodepath);
1564 }
1567 /**
1568 * Move node selection to point, adjust its and neighbouring handles,
1569 * handle possible snapping, and commit the change with possible undo.
1570 */
1571 void
1572 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1573 {
1574 if (!nodepath) return;
1576 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1578 if (dx == 0) {
1579 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1580 } else if (dy == 0) {
1581 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1582 } else {
1583 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1584 }
1585 }
1587 /**
1588 * Move node selection off screen and commit the change.
1589 */
1590 void
1591 sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1592 {
1593 // borrowed from sp_selection_move_screen in selection-chemistry.c
1594 // we find out the current zoom factor and divide deltas by it
1595 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1597 gdouble zoom = desktop->current_zoom();
1598 gdouble zdx = dx / zoom;
1599 gdouble zdy = dy / zoom;
1601 if (!nodepath) return;
1603 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1605 if (dx == 0) {
1606 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1607 } else if (dy == 0) {
1608 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1609 } else {
1610 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1611 }
1612 }
1614 /**
1615 * Move selected nodes to the absolute position given
1616 */
1617 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
1618 {
1619 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1620 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1621 NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
1622 sp_node_moveto(n, npos);
1623 }
1625 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1626 }
1628 /**
1629 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1630 */
1631 NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1632 {
1633 NR::Maybe<NR::Coord> no_coord = NR::Nothing();
1634 g_return_val_if_fail(nodepath->selected, no_coord);
1636 // determine coordinate of first selected node
1637 GList *nsel = nodepath->selected;
1638 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1639 NR::Coord coord = n->pos[axis];
1640 bool coincide = true;
1642 // compare it to the coordinates of all the other selected nodes
1643 for (GList *l = nsel->next; l != NULL; l = l->next) {
1644 n = (Inkscape::NodePath::Node *) l->data;
1645 if (n->pos[axis] != coord) {
1646 coincide = false;
1647 }
1648 }
1649 if (coincide) {
1650 return coord;
1651 } else {
1652 NR::Rect bbox = sp_node_selected_bbox(nodepath);
1653 // currently we return the coordinate of the bounding box midpoint because I don't know how
1654 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1655 return bbox.midpoint()[axis];
1656 }
1657 }
1659 /** If they don't yet exist, creates knot and line for the given side of the node */
1660 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1661 {
1662 if (!side->knot) {
1663 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"));
1665 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1666 side->knot->setSize (7);
1667 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1668 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1669 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1670 sp_knot_update_ctrl(side->knot);
1672 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1673 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1674 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1675 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1676 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1677 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1678 }
1680 if (!side->line) {
1681 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1682 SP_TYPE_CTRLLINE, NULL);
1683 }
1684 }
1686 /**
1687 * Ensure the given handle of the node is visible/invisible, update its screen position
1688 */
1689 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1690 {
1691 g_assert(node != NULL);
1693 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1694 NRPathcode code = sp_node_path_code_from_side(node, side);
1696 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1698 if (show_handle) {
1699 if (!side->knot) { // No handle knot at all
1700 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1701 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1702 side->knot->pos = side->pos;
1703 if (side->knot->item)
1704 SP_CTRL(side->knot->item)->moveto(side->pos);
1705 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1706 sp_knot_show(side->knot);
1707 } else {
1708 if (side->knot->pos != side->pos) { // only if it's really moved
1709 if (fire_move_signals) {
1710 sp_knot_set_position(side->knot, &side->pos, 0); // this will set coords of the line as well
1711 } else {
1712 sp_knot_moveto(side->knot, &side->pos);
1713 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1714 }
1715 }
1716 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1717 sp_knot_show(side->knot);
1718 }
1719 }
1720 sp_canvas_item_show(side->line);
1721 } else {
1722 if (side->knot) {
1723 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1724 sp_knot_hide(side->knot);
1725 }
1726 }
1727 if (side->line) {
1728 sp_canvas_item_hide(side->line);
1729 }
1730 }
1731 }
1733 /**
1734 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1735 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1736 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1737 * updated; otherwise, just move the knots silently (used in batch moves).
1738 */
1739 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1740 {
1741 g_assert(node != NULL);
1743 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1744 sp_knot_show(node->knot);
1745 }
1747 if (node->knot->pos != node->pos) { // visible knot is in a different position, need to update
1748 if (fire_move_signals)
1749 sp_knot_set_position(node->knot, &node->pos, 0);
1750 else
1751 sp_knot_moveto(node->knot, &node->pos);
1752 }
1754 gboolean show_handles = node->selected;
1755 if (node->p.other != NULL) {
1756 if (node->p.other->selected) show_handles = TRUE;
1757 }
1758 if (node->n.other != NULL) {
1759 if (node->n.other->selected) show_handles = TRUE;
1760 }
1762 if (node->subpath->nodepath->show_handles == false)
1763 show_handles = FALSE;
1765 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1766 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1767 }
1769 /**
1770 * Call sp_node_update_handles() for all nodes on subpath.
1771 */
1772 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1773 {
1774 g_assert(subpath != NULL);
1776 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1777 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1778 }
1779 }
1781 /**
1782 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1783 */
1784 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1785 {
1786 g_assert(nodepath != NULL);
1788 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1789 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1790 }
1791 }
1793 void
1794 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1795 {
1796 if (nodepath == NULL) return;
1798 nodepath->show_handles = show;
1799 sp_nodepath_update_handles(nodepath);
1800 }
1802 /**
1803 * Adds all selected nodes in nodepath to list.
1804 */
1805 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1806 {
1807 StlConv<Node *>::list(l, selected);
1808 /// \todo this adds a copying, rework when the selection becomes a stl list
1809 }
1811 /**
1812 * Align selected nodes on the specified axis.
1813 */
1814 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1815 {
1816 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1817 return;
1818 }
1820 if ( !nodepath->selected->next ) { // only one node selected
1821 return;
1822 }
1823 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1824 NR::Point dest(pNode->pos);
1825 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1826 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1827 if (pNode) {
1828 dest[axis] = pNode->pos[axis];
1829 sp_node_moveto(pNode, dest);
1830 }
1831 }
1833 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1834 }
1836 /// Helper struct.
1837 struct NodeSort
1838 {
1839 Inkscape::NodePath::Node *_node;
1840 NR::Coord _coord;
1841 /// \todo use vectorof pointers instead of calling copy ctor
1842 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1843 _node(node), _coord(node->pos[axis])
1844 {}
1846 };
1848 static bool operator<(NodeSort const &a, NodeSort const &b)
1849 {
1850 return (a._coord < b._coord);
1851 }
1853 /**
1854 * Distribute selected nodes on the specified axis.
1855 */
1856 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1857 {
1858 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1859 return;
1860 }
1862 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1863 return;
1864 }
1866 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1867 std::vector<NodeSort> sorted;
1868 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1869 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1870 if (pNode) {
1871 NodeSort n(pNode, axis);
1872 sorted.push_back(n);
1873 //dest[axis] = pNode->pos[axis];
1874 //sp_node_moveto(pNode, dest);
1875 }
1876 }
1877 std::sort(sorted.begin(), sorted.end());
1878 unsigned int len = sorted.size();
1879 //overall bboxes span
1880 float dist = (sorted.back()._coord -
1881 sorted.front()._coord);
1882 //new distance between each bbox
1883 float step = (dist) / (len - 1);
1884 float pos = sorted.front()._coord;
1885 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1886 it < sorted.end();
1887 it ++ )
1888 {
1889 NR::Point dest((*it)._node->pos);
1890 dest[axis] = pos;
1891 sp_node_moveto((*it)._node, dest);
1892 pos += step;
1893 }
1895 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1896 }
1899 /**
1900 * Call sp_nodepath_line_add_node() for all selected segments.
1901 */
1902 void
1903 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1904 {
1905 if (!nodepath) {
1906 return;
1907 }
1909 GList *nl = NULL;
1911 int n_added = 0;
1913 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1914 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1915 g_assert(t->selected);
1916 if (t->p.other && t->p.other->selected) {
1917 nl = g_list_prepend(nl, t);
1918 }
1919 }
1921 while (nl) {
1922 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1923 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1924 sp_nodepath_node_select(n, TRUE, FALSE);
1925 n_added ++;
1926 nl = g_list_remove(nl, t);
1927 }
1929 /** \todo fixme: adjust ? */
1930 sp_nodepath_update_handles(nodepath);
1932 if (n_added > 1) {
1933 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1934 } else if (n_added > 0) {
1935 sp_nodepath_update_repr(nodepath, _("Add node"));
1936 }
1938 sp_nodepath_update_statusbar(nodepath);
1939 }
1941 /**
1942 * Select segment nearest to point
1943 */
1944 void
1945 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1946 {
1947 if (!nodepath) {
1948 return;
1949 }
1951 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1952 Geom::PathVector const &pathv = curve->get_pathvector();
1953 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1955 // calculate index for nodepath's representation.
1956 unsigned int segment_index = floor(pvpos.t) + 1;
1957 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
1958 segment_index += pathv[i].size() + 1;
1959 if (pathv[i].closed()) {
1960 segment_index += 1;
1961 }
1962 }
1964 curve->unref();
1966 //find segment to segment
1967 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
1969 //fixme: this can return NULL, so check before proceeding.
1970 g_return_if_fail(e != NULL);
1972 gboolean force = FALSE;
1973 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1974 force = TRUE;
1975 }
1976 sp_nodepath_node_select(e, (gboolean) toggle, force);
1977 if (e->p.other)
1978 sp_nodepath_node_select(e->p.other, TRUE, force);
1980 sp_nodepath_update_handles(nodepath);
1982 sp_nodepath_update_statusbar(nodepath);
1983 }
1985 /**
1986 * Add a node nearest to point
1987 */
1988 void
1989 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
1990 {
1991 if (!nodepath) {
1992 return;
1993 }
1995 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1996 Geom::PathVector const &pathv = curve->get_pathvector();
1997 Geom::PathVectorPosition pvpos = Geom::nearestPoint(pathv, p);
1999 // calculate index for nodepath's representation.
2000 double int_part;
2001 double t = std::modf(pvpos.t, &int_part);
2002 unsigned int segment_index = (unsigned int)int_part + 1;
2003 for (unsigned int i = 0; i < pvpos.path_nr; ++i) {
2004 segment_index += pathv[i].size() + 1;
2005 if (pathv[i].closed()) {
2006 segment_index += 1;
2007 }
2008 }
2010 curve->unref();
2012 //find segment to split
2013 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(segment_index);
2015 //don't know why but t seems to flip for lines
2016 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2017 t = 1.0 - t;
2018 }
2020 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2021 sp_nodepath_node_select(n, FALSE, TRUE);
2023 /* fixme: adjust ? */
2024 sp_nodepath_update_handles(nodepath);
2026 sp_nodepath_update_repr(nodepath, _("Add node"));
2028 sp_nodepath_update_statusbar(nodepath);
2029 }
2031 /*
2032 * Adjusts a segment so that t moves by a certain delta for dragging
2033 * converts lines to curves
2034 *
2035 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2036 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2037 */
2038 void
2039 sp_nodepath_curve_drag(int node, double t, NR::Point delta)
2040 {
2041 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(node);
2043 //fixme: e and e->p can be NULL, so check for those before proceeding
2044 g_return_if_fail(e != NULL);
2045 g_return_if_fail(&e->p != NULL);
2047 /* feel good is an arbitrary parameter that distributes the delta between handles
2048 * if t of the drag point is less than 1/6 distance form the endpoint only
2049 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2050 */
2051 double feel_good;
2052 if (t <= 1.0 / 6.0)
2053 feel_good = 0;
2054 else if (t <= 0.5)
2055 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2056 else if (t <= 5.0 / 6.0)
2057 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2058 else
2059 feel_good = 1;
2061 //if we're dragging a line convert it to a curve
2062 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2063 sp_nodepath_set_line_type(e, NR_CURVETO);
2064 }
2066 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2067 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2068 e->p.other->n.pos += offsetcoord0;
2069 e->p.pos += offsetcoord1;
2071 // adjust handles of adjacent nodes where necessary
2072 sp_node_adjust_handle(e,1);
2073 sp_node_adjust_handle(e->p.other,-1);
2075 sp_nodepath_update_handles(e->subpath->nodepath);
2077 update_object(e->subpath->nodepath);
2079 sp_nodepath_update_statusbar(e->subpath->nodepath);
2080 }
2083 /**
2084 * Call sp_nodepath_break() for all selected segments.
2085 */
2086 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2087 {
2088 if (!nodepath) return;
2090 GList *tempin = g_list_copy(nodepath->selected);
2091 GList *temp = NULL;
2092 for (GList *l = tempin; l != NULL; l = l->next) {
2093 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2094 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2095 if (nn == NULL) continue; // no break, no new node
2096 temp = g_list_prepend(temp, nn);
2097 }
2098 g_list_free(tempin);
2100 if (temp) {
2101 sp_nodepath_deselect(nodepath);
2102 }
2103 for (GList *l = temp; l != NULL; l = l->next) {
2104 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2105 }
2107 sp_nodepath_update_handles(nodepath);
2109 sp_nodepath_update_repr(nodepath, _("Break path"));
2110 }
2112 /**
2113 * Duplicate the selected node(s).
2114 */
2115 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2116 {
2117 if (!nodepath) {
2118 return;
2119 }
2121 GList *temp = NULL;
2122 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2123 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2124 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2125 if (nn == NULL) continue; // could not duplicate
2126 temp = g_list_prepend(temp, nn);
2127 }
2129 if (temp) {
2130 sp_nodepath_deselect(nodepath);
2131 }
2132 for (GList *l = temp; l != NULL; l = l->next) {
2133 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2134 }
2136 sp_nodepath_update_handles(nodepath);
2138 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2139 }
2141 /**
2142 * Internal function to join two nodes by merging them into one.
2143 */
2144 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2145 {
2146 /* a and b are endpoints */
2148 // if one of the two nodes is mouseovered, fix its position
2149 NR::Point c;
2150 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2151 c = a->pos;
2152 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2153 c = b->pos;
2154 } else {
2155 // otherwise, move joined node to the midpoint
2156 c = (a->pos + b->pos) / 2;
2157 }
2159 if (a->subpath == b->subpath) {
2160 Inkscape::NodePath::SubPath *sp = a->subpath;
2161 sp_nodepath_subpath_close(sp);
2162 sp_node_moveto (sp->first, c);
2164 sp_nodepath_update_handles(sp->nodepath);
2165 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2166 return;
2167 }
2169 /* a and b are separate subpaths */
2170 Inkscape::NodePath::SubPath *sa = a->subpath;
2171 Inkscape::NodePath::SubPath *sb = b->subpath;
2172 NR::Point p;
2173 Inkscape::NodePath::Node *n;
2174 NRPathcode code;
2175 if (a == sa->first) {
2176 // we will now reverse sa, so that a is its last node, not first, and drop that node
2177 p = sa->first->n.pos;
2178 code = (NRPathcode)sa->first->n.other->code;
2179 // create new subpath
2180 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2181 // create a first moveto node on it
2182 n = sa->last;
2183 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2184 n = n->p.other;
2185 if (n == sa->first) n = NULL;
2186 while (n) {
2187 // copy the rest of the nodes from sa to t, going backwards
2188 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2189 n = n->p.other;
2190 if (n == sa->first) n = NULL;
2191 }
2192 // replace sa with t
2193 sp_nodepath_subpath_destroy(sa);
2194 sa = t;
2195 } else if (a == sa->last) {
2196 // a is already last, just drop it
2197 p = sa->last->p.pos;
2198 code = (NRPathcode)sa->last->code;
2199 sp_nodepath_node_destroy(sa->last);
2200 } else {
2201 code = NR_END;
2202 g_assert_not_reached();
2203 }
2205 if (b == sb->first) {
2206 // copy all nodes from b to a, forward
2207 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2208 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2209 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2210 }
2211 } else if (b == sb->last) {
2212 // copy all nodes from b to a, backward
2213 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2214 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2215 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2216 }
2217 } else {
2218 g_assert_not_reached();
2219 }
2220 /* and now destroy sb */
2222 sp_nodepath_subpath_destroy(sb);
2224 sp_nodepath_update_handles(sa->nodepath);
2226 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2228 sp_nodepath_update_statusbar(nodepath);
2229 }
2231 /**
2232 * Internal function to join two nodes by adding a segment between them.
2233 */
2234 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2235 {
2236 if (a->subpath == b->subpath) {
2237 Inkscape::NodePath::SubPath *sp = a->subpath;
2239 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2240 sp->closed = TRUE;
2242 sp->first->p.other = sp->last;
2243 sp->last->n.other = sp->first;
2245 sp_node_handle_mirror_p_to_n(sp->last);
2246 sp_node_handle_mirror_n_to_p(sp->first);
2248 sp->first->code = sp->last->code;
2249 sp->first = sp->last;
2251 sp_nodepath_update_handles(sp->nodepath);
2253 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2255 return;
2256 }
2258 /* a and b are separate subpaths */
2259 Inkscape::NodePath::SubPath *sa = a->subpath;
2260 Inkscape::NodePath::SubPath *sb = b->subpath;
2262 Inkscape::NodePath::Node *n;
2263 NR::Point p;
2264 NRPathcode code;
2265 if (a == sa->first) {
2266 code = (NRPathcode) sa->first->n.other->code;
2267 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2268 n = sa->last;
2269 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2270 for (n = n->p.other; n != NULL; n = n->p.other) {
2271 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2272 }
2273 sp_nodepath_subpath_destroy(sa);
2274 sa = t;
2275 } else if (a == sa->last) {
2276 code = (NRPathcode)sa->last->code;
2277 } else {
2278 code = NR_END;
2279 g_assert_not_reached();
2280 }
2282 if (b == sb->first) {
2283 n = sb->first;
2284 sp_node_handle_mirror_p_to_n(sa->last);
2285 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2286 sp_node_handle_mirror_n_to_p(sa->last);
2287 for (n = n->n.other; n != NULL; n = n->n.other) {
2288 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2289 }
2290 } else if (b == sb->last) {
2291 n = sb->last;
2292 sp_node_handle_mirror_p_to_n(sa->last);
2293 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2294 sp_node_handle_mirror_n_to_p(sa->last);
2295 for (n = n->p.other; n != NULL; n = n->p.other) {
2296 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2297 }
2298 } else {
2299 g_assert_not_reached();
2300 }
2301 /* and now destroy sb */
2303 sp_nodepath_subpath_destroy(sb);
2305 sp_nodepath_update_handles(sa->nodepath);
2307 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2308 }
2310 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2312 /**
2313 * Internal function to handle joining two nodes.
2314 */
2315 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2316 {
2317 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2319 if (g_list_length(nodepath->selected) != 2) {
2320 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2321 return;
2322 }
2324 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2325 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2327 g_assert(a != b);
2328 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2329 // someone tried to join an orphan node (i.e. a single-node subpath).
2330 // this is not worth an error message, just fail silently.
2331 return;
2332 }
2334 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2335 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2336 return;
2337 }
2339 switch(mode) {
2340 case NODE_JOIN_ENDPOINTS:
2341 do_node_selected_join(nodepath, a, b);
2342 break;
2343 case NODE_JOIN_SEGMENT:
2344 do_node_selected_join_segment(nodepath, a, b);
2345 break;
2346 }
2347 }
2349 /**
2350 * Join two nodes by merging them into one.
2351 */
2352 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2353 {
2354 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2355 }
2357 /**
2358 * Join two nodes by adding a segment between them.
2359 */
2360 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2361 {
2362 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2363 }
2365 /**
2366 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2367 */
2368 void sp_node_delete_preserve(GList *nodes_to_delete)
2369 {
2370 GSList *nodepaths = NULL;
2372 while (nodes_to_delete) {
2373 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2374 Inkscape::NodePath::SubPath *sp = node->subpath;
2375 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2376 Inkscape::NodePath::Node *sample_cursor = NULL;
2377 Inkscape::NodePath::Node *sample_end = NULL;
2378 Inkscape::NodePath::Node *delete_cursor = node;
2379 bool just_delete = false;
2381 //find the start of this contiguous selection
2382 //move left to the first node that is not selected
2383 //or the start of the non-closed path
2384 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2385 delete_cursor = curr;
2386 }
2388 //just delete at the beginning of an open path
2389 if (!delete_cursor->p.other) {
2390 sample_cursor = delete_cursor;
2391 just_delete = true;
2392 } else {
2393 sample_cursor = delete_cursor->p.other;
2394 }
2396 //calculate points for each segment
2397 int rate = 5;
2398 float period = 1.0 / rate;
2399 std::vector<NR::Point> data;
2400 if (!just_delete) {
2401 data.push_back(sample_cursor->pos);
2402 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2403 //just delete at the end of an open path
2404 if (!sp->closed && curr == sp->last) {
2405 just_delete = true;
2406 break;
2407 }
2409 //sample points on the contiguous selected segment
2410 NR::Point *bez;
2411 bez = new NR::Point [4];
2412 bez[0] = curr->pos;
2413 bez[1] = curr->n.pos;
2414 bez[2] = curr->n.other->p.pos;
2415 bez[3] = curr->n.other->pos;
2416 for (int i=1; i<rate; i++) {
2417 gdouble t = i * period;
2418 NR::Point p = bezier_pt(3, bez, t);
2419 data.push_back(p);
2420 }
2421 data.push_back(curr->n.other->pos);
2423 sample_end = curr->n.other;
2424 //break if we've come full circle or hit the end of the selection
2425 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2426 break;
2427 }
2428 }
2429 }
2431 if (!just_delete) {
2432 //calculate the best fitting single segment and adjust the endpoints
2433 NR::Point *adata;
2434 adata = new NR::Point [data.size()];
2435 copy(data.begin(), data.end(), adata);
2437 NR::Point *bez;
2438 bez = new NR::Point [4];
2439 //would decreasing error create a better fitting approximation?
2440 gdouble error = 1.0;
2441 gint ret;
2442 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2444 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2445 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2446 //the resulting nodes behave as expected.
2447 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2448 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2449 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2450 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2452 //adjust endpoints
2453 sample_cursor->n.pos = bez[1];
2454 sample_end->p.pos = bez[2];
2455 }
2457 //destroy this contiguous selection
2458 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2459 Inkscape::NodePath::Node *temp = delete_cursor;
2460 if (delete_cursor->n.other == delete_cursor) {
2461 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2462 delete_cursor = NULL;
2463 } else {
2464 delete_cursor = delete_cursor->n.other;
2465 }
2466 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2467 sp_nodepath_node_destroy(temp);
2468 }
2470 sp_nodepath_update_handles(nodepath);
2472 if (!g_slist_find(nodepaths, nodepath))
2473 nodepaths = g_slist_prepend (nodepaths, nodepath);
2474 }
2476 for (GSList *i = nodepaths; i; i = i->next) {
2477 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2478 // different nodepaths will give us one undo event per nodepath
2479 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2481 // if the entire nodepath is removed, delete the selected object.
2482 if (nodepath->subpaths == NULL ||
2483 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2484 //at least 2
2485 sp_nodepath_get_node_count(nodepath) < 2) {
2486 SPDocument *document = sp_desktop_document (nodepath->desktop);
2487 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2488 //delete this nodepath's object, not the entire selection! (though at this time, this
2489 //does not matter)
2490 sp_selection_delete();
2491 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2492 _("Delete nodes"));
2493 } else {
2494 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2495 sp_nodepath_update_statusbar(nodepath);
2496 }
2497 }
2499 g_slist_free (nodepaths);
2500 }
2502 /**
2503 * Delete one or more selected nodes.
2504 */
2505 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2506 {
2507 if (!nodepath) return;
2508 if (!nodepath->selected) return;
2510 /** \todo fixme: do it the right way */
2511 while (nodepath->selected) {
2512 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2513 sp_nodepath_node_destroy(node);
2514 }
2517 //clean up the nodepath (such as for trivial subpaths)
2518 sp_nodepath_cleanup(nodepath);
2520 sp_nodepath_update_handles(nodepath);
2522 // if the entire nodepath is removed, delete the selected object.
2523 if (nodepath->subpaths == NULL ||
2524 sp_nodepath_get_node_count(nodepath) < 2) {
2525 SPDocument *document = sp_desktop_document (nodepath->desktop);
2526 sp_selection_delete();
2527 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2528 _("Delete nodes"));
2529 return;
2530 }
2532 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2534 sp_nodepath_update_statusbar(nodepath);
2535 }
2537 /**
2538 * Delete one or more segments between two selected nodes.
2539 * This is the code for 'split'.
2540 */
2541 void
2542 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2543 {
2544 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2545 Inkscape::NodePath::Node *curr, *next; //Iterators
2547 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2549 if (g_list_length(nodepath->selected) != 2) {
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 //Selected nodes, not inclusive
2556 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2557 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2559 if ( ( a==b) || //same node
2560 (a->subpath != b->subpath ) || //not the same path
2561 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2562 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2563 {
2564 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2565 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2566 return;
2567 }
2569 //###########################################
2570 //# BEGIN EDITS
2571 //###########################################
2572 //##################################
2573 //# CLOSED PATH
2574 //##################################
2575 if (a->subpath->closed) {
2578 gboolean reversed = FALSE;
2580 //Since we can go in a circle, we need to find the shorter distance.
2581 // a->b or b->a
2582 start = end = NULL;
2583 int distance = 0;
2584 int minDistance = 0;
2585 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2586 if (curr==b) {
2587 //printf("a to b:%d\n", distance);
2588 start = a;//go from a to b
2589 end = b;
2590 minDistance = distance;
2591 //printf("A to B :\n");
2592 break;
2593 }
2594 distance++;
2595 }
2597 //try again, the other direction
2598 distance = 0;
2599 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2600 if (curr==a) {
2601 //printf("b to a:%d\n", distance);
2602 if (distance < minDistance) {
2603 start = b; //we go from b to a
2604 end = a;
2605 reversed = TRUE;
2606 //printf("B to A\n");
2607 }
2608 break;
2609 }
2610 distance++;
2611 }
2614 //Copy everything from 'end' to 'start' to a new subpath
2615 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2616 for (curr=end ; curr ; curr=curr->n.other) {
2617 NRPathcode code = (NRPathcode) curr->code;
2618 if (curr == end)
2619 code = NR_MOVETO;
2620 sp_nodepath_node_new(t, NULL,
2621 (Inkscape::NodePath::NodeType)curr->type, code,
2622 &curr->p.pos, &curr->pos, &curr->n.pos);
2623 if (curr == start)
2624 break;
2625 }
2626 sp_nodepath_subpath_destroy(a->subpath);
2629 }
2633 //##################################
2634 //# OPEN PATH
2635 //##################################
2636 else {
2638 //We need to get the direction of the list between A and B
2639 //Can we walk from a to b?
2640 start = end = NULL;
2641 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2642 if (curr==b) {
2643 start = a; //did it! we go from a to b
2644 end = b;
2645 //printf("A to B\n");
2646 break;
2647 }
2648 }
2649 if (!start) {//didn't work? let's try the other direction
2650 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2651 if (curr==a) {
2652 start = b; //did it! we go from b to a
2653 end = a;
2654 //printf("B to A\n");
2655 break;
2656 }
2657 }
2658 }
2659 if (!start) {
2660 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2661 _("Cannot find path between nodes."));
2662 return;
2663 }
2667 //Copy everything after 'end' to a new subpath
2668 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2669 for (curr=end ; curr ; curr=curr->n.other) {
2670 NRPathcode code = (NRPathcode) curr->code;
2671 if (curr == end)
2672 code = NR_MOVETO;
2673 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2674 &curr->p.pos, &curr->pos, &curr->n.pos);
2675 }
2677 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2678 for (curr = start->n.other ; curr ; curr=next) {
2679 next = curr->n.other;
2680 sp_nodepath_node_destroy(curr);
2681 }
2683 }
2684 //###########################################
2685 //# END EDITS
2686 //###########################################
2688 //clean up the nodepath (such as for trivial subpaths)
2689 sp_nodepath_cleanup(nodepath);
2691 sp_nodepath_update_handles(nodepath);
2693 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2695 sp_nodepath_update_statusbar(nodepath);
2696 }
2698 /**
2699 * Call sp_nodepath_set_line() for all selected segments.
2700 */
2701 void
2702 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2703 {
2704 if (nodepath == NULL) return;
2706 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2707 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2708 g_assert(n->selected);
2709 if (n->p.other && n->p.other->selected) {
2710 sp_nodepath_set_line_type(n, code);
2711 }
2712 }
2714 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2715 }
2717 /**
2718 * Call sp_nodepath_convert_node_type() for all selected nodes.
2719 */
2720 void
2721 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2722 {
2723 if (nodepath == NULL) return;
2725 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2727 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2728 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2729 }
2731 sp_nodepath_update_repr(nodepath, _("Change node type"));
2732 }
2734 /**
2735 * Change select status of node, update its own and neighbour handles.
2736 */
2737 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2738 {
2739 node->selected = selected;
2741 if (selected) {
2742 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2743 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2744 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2745 sp_knot_update_ctrl(node->knot);
2746 } else {
2747 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2748 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2749 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2750 sp_knot_update_ctrl(node->knot);
2751 }
2753 sp_node_update_handles(node);
2754 if (node->n.other) sp_node_update_handles(node->n.other);
2755 if (node->p.other) sp_node_update_handles(node->p.other);
2756 }
2758 /**
2759 \brief Select a node
2760 \param node The node to select
2761 \param incremental If true, add to selection, otherwise deselect others
2762 \param override If true, always select this node, otherwise toggle selected status
2763 */
2764 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2765 {
2766 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2768 if (incremental) {
2769 if (override) {
2770 if (!g_list_find(nodepath->selected, node)) {
2771 nodepath->selected = g_list_prepend(nodepath->selected, node);
2772 }
2773 sp_node_set_selected(node, TRUE);
2774 } else { // toggle
2775 if (node->selected) {
2776 g_assert(g_list_find(nodepath->selected, node));
2777 nodepath->selected = g_list_remove(nodepath->selected, node);
2778 } else {
2779 g_assert(!g_list_find(nodepath->selected, node));
2780 nodepath->selected = g_list_prepend(nodepath->selected, node);
2781 }
2782 sp_node_set_selected(node, !node->selected);
2783 }
2784 } else {
2785 sp_nodepath_deselect(nodepath);
2786 nodepath->selected = g_list_prepend(nodepath->selected, node);
2787 sp_node_set_selected(node, TRUE);
2788 }
2790 sp_nodepath_update_statusbar(nodepath);
2791 }
2794 /**
2795 \brief Deselect all nodes in the nodepath
2796 */
2797 void
2798 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2799 {
2800 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2802 while (nodepath->selected) {
2803 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2804 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2805 }
2806 sp_nodepath_update_statusbar(nodepath);
2807 }
2809 /**
2810 \brief Select or invert selection of all nodes in the nodepath
2811 */
2812 void
2813 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2814 {
2815 if (!nodepath) return;
2817 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2818 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2819 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2820 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2821 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2822 }
2823 }
2824 }
2826 /**
2827 * If nothing selected, does the same as sp_nodepath_select_all();
2828 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2829 * (i.e., similar to "select all in layer", with the "selected" subpaths
2830 * being treated as "layers" in the path).
2831 */
2832 void
2833 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2834 {
2835 if (!nodepath) return;
2837 if (g_list_length (nodepath->selected) == 0) {
2838 sp_nodepath_select_all (nodepath, invert);
2839 return;
2840 }
2842 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2843 GSList *subpaths = NULL;
2845 for (GList *l = copy; l != NULL; l = l->next) {
2846 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2847 Inkscape::NodePath::SubPath *subpath = n->subpath;
2848 if (!g_slist_find (subpaths, subpath))
2849 subpaths = g_slist_prepend (subpaths, subpath);
2850 }
2852 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2853 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2854 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2855 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2856 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2857 }
2858 }
2860 g_slist_free (subpaths);
2861 g_list_free (copy);
2862 }
2864 /**
2865 * \brief Select the node after the last selected; if none is selected,
2866 * select the first within path.
2867 */
2868 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2869 {
2870 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2872 Inkscape::NodePath::Node *last = NULL;
2873 if (nodepath->selected) {
2874 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2875 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2876 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2877 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2878 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2879 if (node->selected) {
2880 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2881 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2882 if (spl->next) { // there's a next subpath
2883 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2884 last = subpath_next->first;
2885 } else if (spl->prev) { // there's a previous subpath
2886 last = NULL; // to be set later to the first node of first subpath
2887 } else {
2888 last = node->n.other;
2889 }
2890 } else {
2891 last = node->n.other;
2892 }
2893 } else {
2894 if (node->n.other) {
2895 last = node->n.other;
2896 } else {
2897 if (spl->next) { // there's a next subpath
2898 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2899 last = subpath_next->first;
2900 } else if (spl->prev) { // there's a previous subpath
2901 last = NULL; // to be set later to the first node of first subpath
2902 } else {
2903 last = (Inkscape::NodePath::Node *) subpath->first;
2904 }
2905 }
2906 }
2907 }
2908 }
2909 }
2910 sp_nodepath_deselect(nodepath);
2911 }
2913 if (last) { // there's at least one more node after selected
2914 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2915 } else { // no more nodes, select the first one in first subpath
2916 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2917 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2918 }
2919 }
2921 /**
2922 * \brief Select the node before the first selected; if none is selected,
2923 * select the last within path
2924 */
2925 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2926 {
2927 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2929 Inkscape::NodePath::Node *last = NULL;
2930 if (nodepath->selected) {
2931 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2932 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2933 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2934 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2935 if (node->selected) {
2936 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2937 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2938 if (spl->prev) { // there's a prev subpath
2939 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2940 last = subpath_prev->last;
2941 } else if (spl->next) { // there's a next subpath
2942 last = NULL; // to be set later to the last node of last subpath
2943 } else {
2944 last = node->p.other;
2945 }
2946 } else {
2947 last = node->p.other;
2948 }
2949 } else {
2950 if (node->p.other) {
2951 last = node->p.other;
2952 } else {
2953 if (spl->prev) { // there's a prev subpath
2954 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2955 last = subpath_prev->last;
2956 } else if (spl->next) { // there's a next subpath
2957 last = NULL; // to be set later to the last node of last subpath
2958 } else {
2959 last = (Inkscape::NodePath::Node *) subpath->last;
2960 }
2961 }
2962 }
2963 }
2964 }
2965 }
2966 sp_nodepath_deselect(nodepath);
2967 }
2969 if (last) { // there's at least one more node before selected
2970 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2971 } else { // no more nodes, select the last one in last subpath
2972 GList *spl = g_list_last(nodepath->subpaths);
2973 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2974 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2975 }
2976 }
2978 /**
2979 * \brief Select all nodes that are within the rectangle.
2980 */
2981 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
2982 {
2983 if (!incremental) {
2984 sp_nodepath_deselect(nodepath);
2985 }
2987 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2988 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2989 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2990 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2992 if (b.contains(node->pos)) {
2993 sp_nodepath_node_select(node, TRUE, TRUE);
2994 }
2995 }
2996 }
2997 }
3000 void
3001 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3002 {
3003 g_assert (n);
3004 g_assert (nodepath);
3005 g_assert (n->subpath->nodepath == nodepath);
3007 if (g_list_length (nodepath->selected) == 0) {
3008 if (grow > 0) {
3009 sp_nodepath_node_select(n, TRUE, TRUE);
3010 }
3011 return;
3012 }
3014 if (g_list_length (nodepath->selected) == 1) {
3015 if (grow < 0) {
3016 sp_nodepath_deselect (nodepath);
3017 return;
3018 }
3019 }
3021 double n_sel_range = 0, p_sel_range = 0;
3022 Inkscape::NodePath::Node *farthest_n_node = n;
3023 Inkscape::NodePath::Node *farthest_p_node = n;
3025 // Calculate ranges
3026 {
3027 double n_range = 0, p_range = 0;
3028 bool n_going = true, p_going = true;
3029 Inkscape::NodePath::Node *n_node = n;
3030 Inkscape::NodePath::Node *p_node = n;
3031 do {
3032 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3033 if (n_node && n_going)
3034 n_node = n_node->n.other;
3035 if (n_node == NULL) {
3036 n_going = false;
3037 } else {
3038 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3039 if (n_node->selected) {
3040 n_sel_range = n_range;
3041 farthest_n_node = n_node;
3042 }
3043 if (n_node == p_node) {
3044 n_going = false;
3045 p_going = false;
3046 }
3047 }
3048 if (p_node && p_going)
3049 p_node = p_node->p.other;
3050 if (p_node == NULL) {
3051 p_going = false;
3052 } else {
3053 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3054 if (p_node->selected) {
3055 p_sel_range = p_range;
3056 farthest_p_node = p_node;
3057 }
3058 if (p_node == n_node) {
3059 n_going = false;
3060 p_going = false;
3061 }
3062 }
3063 } while (n_going || p_going);
3064 }
3066 if (grow > 0) {
3067 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3068 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3069 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3070 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3071 }
3072 } else {
3073 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3074 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3075 } else if (farthest_p_node && farthest_p_node->selected) {
3076 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3077 }
3078 }
3079 }
3081 void
3082 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3083 {
3084 g_assert (n);
3085 g_assert (nodepath);
3086 g_assert (n->subpath->nodepath == nodepath);
3088 if (g_list_length (nodepath->selected) == 0) {
3089 if (grow > 0) {
3090 sp_nodepath_node_select(n, TRUE, TRUE);
3091 }
3092 return;
3093 }
3095 if (g_list_length (nodepath->selected) == 1) {
3096 if (grow < 0) {
3097 sp_nodepath_deselect (nodepath);
3098 return;
3099 }
3100 }
3102 Inkscape::NodePath::Node *farthest_selected = NULL;
3103 double farthest_dist = 0;
3105 Inkscape::NodePath::Node *closest_unselected = NULL;
3106 double closest_dist = NR_HUGE;
3108 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3109 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3110 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3111 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3112 if (node == n)
3113 continue;
3114 if (node->selected) {
3115 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3116 farthest_dist = NR::L2(node->pos - n->pos);
3117 farthest_selected = node;
3118 }
3119 } else {
3120 if (NR::L2(node->pos - n->pos) < closest_dist) {
3121 closest_dist = NR::L2(node->pos - n->pos);
3122 closest_unselected = node;
3123 }
3124 }
3125 }
3126 }
3128 if (grow > 0) {
3129 if (closest_unselected) {
3130 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3131 }
3132 } else {
3133 if (farthest_selected) {
3134 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3135 }
3136 }
3137 }
3140 /**
3141 \brief Saves all nodes' and handles' current positions in their origin members
3142 */
3143 void
3144 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3145 {
3146 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3147 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3148 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3149 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3150 n->origin = n->pos;
3151 n->p.origin = n->p.pos;
3152 n->n.origin = n->n.pos;
3153 }
3154 }
3155 }
3157 /**
3158 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3159 */
3160 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3161 {
3162 if (!nodepath->selected) {
3163 return NULL;
3164 }
3166 GList *r = NULL;
3167 guint i = 0;
3168 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3169 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3170 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3171 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3172 i++;
3173 if (node->selected) {
3174 r = g_list_append(r, GINT_TO_POINTER(i));
3175 }
3176 }
3177 }
3178 return r;
3179 }
3181 /**
3182 \brief Restores selection by selecting nodes whose positions are in the list
3183 */
3184 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3185 {
3186 sp_nodepath_deselect(nodepath);
3188 guint i = 0;
3189 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3190 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3191 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3192 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3193 i++;
3194 if (g_list_find(r, GINT_TO_POINTER(i))) {
3195 sp_nodepath_node_select(node, TRUE, TRUE);
3196 }
3197 }
3198 }
3199 }
3202 /**
3203 \brief Adjusts handle according to node type and line code.
3204 */
3205 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3206 {
3207 g_assert(node);
3209 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3210 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3212 // nothing to do if we are an end node
3213 if (me->other == NULL) return;
3214 if (other->other == NULL) return;
3216 // nothing to do if we are a cusp node
3217 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3219 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3220 NRPathcode mecode;
3221 if (which_adjust == 1) {
3222 mecode = (NRPathcode)me->other->code;
3223 } else {
3224 mecode = (NRPathcode)node->code;
3225 }
3226 if (mecode == NR_LINETO) return;
3228 if (sp_node_side_is_line(node, other)) {
3229 // other is a line, and we are either smooth or symm
3230 Inkscape::NodePath::Node *othernode = other->other;
3231 double len = NR::L2(me->pos - node->pos);
3232 NR::Point delta = node->pos - othernode->pos;
3233 double linelen = NR::L2(delta);
3234 if (linelen < 1e-18)
3235 return;
3236 me->pos = node->pos + (len / linelen)*delta;
3237 return;
3238 }
3240 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3241 // symmetrize
3242 me->pos = 2 * node->pos - other->pos;
3243 return;
3244 } else {
3245 // smoothify
3246 double len = NR::L2(me->pos - node->pos);
3247 NR::Point delta = other->pos - node->pos;
3248 double otherlen = NR::L2(delta);
3249 if (otherlen < 1e-18) return;
3250 me->pos = node->pos - (len / otherlen) * delta;
3251 }
3252 }
3254 /**
3255 \brief Adjusts both handles according to node type and line code
3256 */
3257 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3258 {
3259 g_assert(node);
3261 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3263 /* we are either smooth or symm */
3265 if (node->p.other == NULL) return;
3266 if (node->n.other == NULL) return;
3268 if (sp_node_side_is_line(node, &node->p)) {
3269 sp_node_adjust_handle(node, 1);
3270 return;
3271 }
3273 if (sp_node_side_is_line(node, &node->n)) {
3274 sp_node_adjust_handle(node, -1);
3275 return;
3276 }
3278 /* both are curves */
3279 NR::Point const delta( node->n.pos - node->p.pos );
3281 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3282 node->p.pos = node->pos - delta / 2;
3283 node->n.pos = node->pos + delta / 2;
3284 return;
3285 }
3287 /* We are smooth */
3288 double plen = NR::L2(node->p.pos - node->pos);
3289 if (plen < 1e-18) return;
3290 double nlen = NR::L2(node->n.pos - node->pos);
3291 if (nlen < 1e-18) return;
3292 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3293 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3294 }
3296 /**
3297 * Node event callback.
3298 */
3299 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3300 {
3301 gboolean ret = FALSE;
3302 switch (event->type) {
3303 case GDK_ENTER_NOTIFY:
3304 Inkscape::NodePath::Path::active_node = n;
3305 break;
3306 case GDK_LEAVE_NOTIFY:
3307 Inkscape::NodePath::Path::active_node = NULL;
3308 break;
3309 case GDK_SCROLL:
3310 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3311 switch (event->scroll.direction) {
3312 case GDK_SCROLL_UP:
3313 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3314 break;
3315 case GDK_SCROLL_DOWN:
3316 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3317 break;
3318 default:
3319 break;
3320 }
3321 ret = TRUE;
3322 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3323 switch (event->scroll.direction) {
3324 case GDK_SCROLL_UP:
3325 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3326 break;
3327 case GDK_SCROLL_DOWN:
3328 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3329 break;
3330 default:
3331 break;
3332 }
3333 ret = TRUE;
3334 }
3335 break;
3336 case GDK_KEY_PRESS:
3337 switch (get_group0_keyval (&event->key)) {
3338 case GDK_space:
3339 if (event->key.state & GDK_BUTTON1_MASK) {
3340 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3341 stamp_repr(nodepath);
3342 ret = TRUE;
3343 }
3344 break;
3345 case GDK_Page_Up:
3346 if (event->key.state & GDK_CONTROL_MASK) {
3347 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3348 } else {
3349 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3350 }
3351 break;
3352 case GDK_Page_Down:
3353 if (event->key.state & GDK_CONTROL_MASK) {
3354 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3355 } else {
3356 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3357 }
3358 break;
3359 default:
3360 break;
3361 }
3362 break;
3363 default:
3364 break;
3365 }
3367 return ret;
3368 }
3370 /**
3371 * Handle keypress on node; directly called.
3372 */
3373 gboolean node_key(GdkEvent *event)
3374 {
3375 Inkscape::NodePath::Path *np;
3377 // there is no way to verify nodes so set active_node to nil when deleting!!
3378 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3380 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3381 gint ret = FALSE;
3382 switch (get_group0_keyval (&event->key)) {
3383 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3384 case GDK_BackSpace:
3385 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3386 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3387 sp_nodepath_update_repr(np, _("Delete node"));
3388 Inkscape::NodePath::Path::active_node = NULL;
3389 ret = TRUE;
3390 break;
3391 case GDK_c:
3392 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3393 ret = TRUE;
3394 break;
3395 case GDK_s:
3396 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3397 ret = TRUE;
3398 break;
3399 case GDK_y:
3400 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3401 ret = TRUE;
3402 break;
3403 case GDK_b:
3404 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3405 ret = TRUE;
3406 break;
3407 }
3408 return ret;
3409 }
3410 return FALSE;
3411 }
3413 /**
3414 * Mouseclick on node callback.
3415 */
3416 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3417 {
3418 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3420 if (state & GDK_CONTROL_MASK) {
3421 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3423 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3424 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3425 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3426 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3427 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3428 } else {
3429 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3430 }
3431 sp_nodepath_update_repr(nodepath, _("Change node type"));
3432 sp_nodepath_update_statusbar(nodepath);
3434 } else { //ctrl+alt+click: delete node
3435 GList *node_to_delete = NULL;
3436 node_to_delete = g_list_append(node_to_delete, n);
3437 sp_node_delete_preserve(node_to_delete);
3438 }
3440 } else {
3441 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3442 }
3443 }
3445 /**
3446 * Mouse grabbed node callback.
3447 */
3448 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3449 {
3450 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3452 if (!n->selected) {
3453 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3454 }
3456 n->is_dragging = true;
3457 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3459 sp_nodepath_remember_origins (n->subpath->nodepath);
3460 }
3462 /**
3463 * Mouse ungrabbed node callback.
3464 */
3465 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3466 {
3467 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3469 n->dragging_out = NULL;
3470 n->is_dragging = false;
3471 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3473 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3474 }
3476 /**
3477 * The point on a line, given by its angle, closest to the given point.
3478 * \param p A point.
3479 * \param a Angle of the line; it is assumed to go through coordinate origin.
3480 * \param closest Pointer to the point struct where the result is stored.
3481 * \todo FIXME: use dot product perhaps?
3482 */
3483 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3484 {
3485 if (a == HUGE_VAL) { // vertical
3486 *closest = NR::Point(0, (*p)[NR::Y]);
3487 } else {
3488 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3489 (*closest)[NR::Y] = a * (*closest)[NR::X];
3490 }
3491 }
3493 /**
3494 * Distance from the point to a line given by its angle.
3495 * \param p A point.
3496 * \param a Angle of the line; it is assumed to go through coordinate origin.
3497 */
3498 static double point_line_distance(NR::Point *p, double a)
3499 {
3500 NR::Point c;
3501 point_line_closest(p, a, &c);
3502 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]));
3503 }
3505 /**
3506 * Callback for node "request" signal.
3507 * \todo fixme: This goes to "moved" event? (lauris)
3508 */
3509 static gboolean
3510 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3511 {
3512 double yn, xn, yp, xp;
3513 double an, ap, na, pa;
3514 double d_an, d_ap, d_na, d_pa;
3515 gboolean collinear = FALSE;
3516 NR::Point c;
3517 NR::Point pr;
3519 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3521 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3523 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3524 if ( (!n->subpath->nodepath->straight_path) &&
3525 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3526 || n->dragging_out ) )
3527 {
3528 NR::Point mouse = (*p);
3530 if (!n->dragging_out) {
3531 // This is the first drag-out event; find out which handle to drag out
3532 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3533 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3535 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3536 return FALSE;
3538 Inkscape::NodePath::NodeSide *opposite;
3539 if (appr_p > appr_n) { // closer to p
3540 n->dragging_out = &n->p;
3541 opposite = &n->n;
3542 n->code = NR_CURVETO;
3543 } else if (appr_p < appr_n) { // closer to n
3544 n->dragging_out = &n->n;
3545 opposite = &n->p;
3546 n->n.other->code = NR_CURVETO;
3547 } else { // p and n nodes are the same
3548 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3549 n->dragging_out = &n->p;
3550 opposite = &n->n;
3551 n->code = NR_CURVETO;
3552 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3553 n->dragging_out = &n->n;
3554 opposite = &n->p;
3555 n->n.other->code = NR_CURVETO;
3556 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3557 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);
3558 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);
3559 if (appr_other_p > appr_other_n) { // closer to other's p handle
3560 n->dragging_out = &n->n;
3561 opposite = &n->p;
3562 n->n.other->code = NR_CURVETO;
3563 } else { // closer to other's n handle
3564 n->dragging_out = &n->p;
3565 opposite = &n->n;
3566 n->code = NR_CURVETO;
3567 }
3568 }
3569 }
3571 // if there's another handle, make sure the one we drag out starts parallel to it
3572 if (opposite->pos != n->pos) {
3573 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3574 }
3576 // knots might not be created yet!
3577 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3578 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3579 }
3581 // pass this on to the handle-moved callback
3582 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3583 sp_node_update_handles(n);
3584 return TRUE;
3585 }
3587 if (state & GDK_CONTROL_MASK) { // constrained motion
3589 // calculate relative distances of handles
3590 // n handle:
3591 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3592 xn = n->n.pos[NR::X] - n->pos[NR::X];
3593 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3594 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3595 if (n->n.other) { // if there is the next point
3596 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3597 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3598 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3599 }
3600 }
3601 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3602 if (yn < 0) { xn = -xn; yn = -yn; }
3604 // p handle:
3605 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3606 xp = n->p.pos[NR::X] - n->pos[NR::X];
3607 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3608 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3609 if (n->p.other) {
3610 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3611 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3612 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3613 }
3614 }
3615 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3616 if (yp < 0) { xp = -xp; yp = -yp; }
3618 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3619 // sliding on handles, only if at least one of the handles is non-vertical
3620 // (otherwise it's the same as ctrl+drag anyway)
3622 // calculate angles of the handles
3623 if (xn == 0) {
3624 if (yn == 0) { // no handle, consider it the continuation of the other one
3625 an = 0;
3626 collinear = TRUE;
3627 }
3628 else an = 0; // vertical; set the angle to horizontal
3629 } else an = yn/xn;
3631 if (xp == 0) {
3632 if (yp == 0) { // no handle, consider it the continuation of the other one
3633 ap = an;
3634 }
3635 else ap = 0; // vertical; set the angle to horizontal
3636 } else ap = yp/xp;
3638 if (collinear) an = ap;
3640 // angles of the perpendiculars; HUGE_VAL means vertical
3641 if (an == 0) na = HUGE_VAL; else na = -1/an;
3642 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3644 // mouse point relative to the node's original pos
3645 pr = (*p) - n->origin;
3647 // distances to the four lines (two handles and two perpendiculars)
3648 d_an = point_line_distance(&pr, an);
3649 d_na = point_line_distance(&pr, na);
3650 d_ap = point_line_distance(&pr, ap);
3651 d_pa = point_line_distance(&pr, pa);
3653 // find out which line is the closest, save its closest point in c
3654 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3655 point_line_closest(&pr, an, &c);
3656 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3657 point_line_closest(&pr, ap, &c);
3658 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3659 point_line_closest(&pr, na, &c);
3660 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3661 point_line_closest(&pr, pa, &c);
3662 }
3664 // move the node to the closest point
3665 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3666 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3667 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3668 true);
3670 } else { // constraining to hor/vert
3672 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3673 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3674 (*p)[NR::X] - n->pos[NR::X],
3675 n->origin[NR::Y] - n->pos[NR::Y],
3676 true,
3677 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3678 } else { // snap to vert
3679 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3680 n->origin[NR::X] - n->pos[NR::X],
3681 (*p)[NR::Y] - n->pos[NR::Y],
3682 true,
3683 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3684 }
3685 }
3686 } else { // move freely
3687 if (n->is_dragging) {
3688 if (state & GDK_MOD1_MASK) { // sculpt
3689 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3690 } else {
3691 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3692 (*p)[NR::X] - n->pos[NR::X],
3693 (*p)[NR::Y] - n->pos[NR::Y],
3694 (state & GDK_SHIFT_MASK) == 0);
3695 }
3696 }
3697 }
3699 n->subpath->nodepath->desktop->scroll_to_point(p);
3701 return TRUE;
3702 }
3704 /**
3705 * Node handle clicked callback.
3706 */
3707 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3708 {
3709 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3711 if (state & GDK_CONTROL_MASK) { // "delete" handle
3712 if (n->p.knot == knot) {
3713 n->p.pos = n->pos;
3714 } else if (n->n.knot == knot) {
3715 n->n.pos = n->pos;
3716 }
3717 sp_node_update_handles(n);
3718 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3719 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3720 sp_nodepath_update_statusbar(nodepath);
3722 } else { // just select or add to selection, depending in Shift
3723 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3724 }
3725 }
3727 /**
3728 * Node handle grabbed callback.
3729 */
3730 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3731 {
3732 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3734 if (!n->selected) {
3735 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3736 }
3738 // remember the origin point of the handle
3739 if (n->p.knot == knot) {
3740 n->p.origin_radial = n->p.pos - n->pos;
3741 } else if (n->n.knot == knot) {
3742 n->n.origin_radial = n->n.pos - n->pos;
3743 } else {
3744 g_assert_not_reached();
3745 }
3747 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3748 }
3750 /**
3751 * Node handle ungrabbed callback.
3752 */
3753 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3754 {
3755 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3757 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3758 if (n->p.knot == knot) {
3759 n->p.origin_radial.a = 0;
3760 sp_knot_set_position(knot, &n->p.pos, state);
3761 } else if (n->n.knot == knot) {
3762 n->n.origin_radial.a = 0;
3763 sp_knot_set_position(knot, &n->n.pos, state);
3764 } else {
3765 g_assert_not_reached();
3766 }
3768 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3769 }
3771 /**
3772 * Node handle "request" signal callback.
3773 */
3774 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3775 {
3776 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3778 Inkscape::NodePath::NodeSide *me, *opposite;
3779 gint which;
3780 if (n->p.knot == knot) {
3781 me = &n->p;
3782 opposite = &n->n;
3783 which = -1;
3784 } else if (n->n.knot == knot) {
3785 me = &n->n;
3786 opposite = &n->p;
3787 which = 1;
3788 } else {
3789 me = opposite = NULL;
3790 which = 0;
3791 g_assert_not_reached();
3792 }
3794 SPDesktop *desktop = n->subpath->nodepath->desktop;
3795 SnapManager &m = desktop->namedview->snap_manager;
3796 m.setup(desktop, n->subpath->nodepath->item);
3797 Inkscape::SnappedPoint s;
3799 if ((state & GDK_SHIFT_MASK) != 0) {
3800 // We will not try to snap when the shift-key is pressed
3801 // so remove the old snap indicator and don't wait for it to time-out
3802 desktop->snapindicator->remove_snappoint();
3803 }
3805 Inkscape::NodePath::Node *othernode = opposite->other;
3806 if (othernode) {
3807 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3808 /* We are smooth node adjacent with line */
3809 NR::Point const delta = *p - n->pos;
3810 NR::Coord const len = NR::L2(delta);
3811 Inkscape::NodePath::Node *othernode = opposite->other;
3812 NR::Point const ndelta = n->pos - othernode->pos;
3813 NR::Coord const linelen = NR::L2(ndelta);
3814 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3815 NR::Coord const scal = dot(delta, ndelta) / linelen;
3816 (*p) = n->pos + (scal / linelen) * ndelta;
3817 }
3818 if ((state & GDK_SHIFT_MASK) == 0) {
3819 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p, Inkscape::Snapper::ConstraintLine(*p, ndelta));
3820 }
3821 } else {
3822 if ((state & GDK_SHIFT_MASK) == 0) {
3823 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3824 }
3825 }
3826 } else {
3827 if ((state & GDK_SHIFT_MASK) == 0) {
3828 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, *p);
3829 }
3830 }
3832 s.getPoint(*p);
3834 sp_node_adjust_handle(n, -which);
3836 return FALSE;
3837 }
3839 /**
3840 * Node handle moved callback.
3841 */
3842 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3843 {
3844 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3846 Inkscape::NodePath::NodeSide *me;
3847 Inkscape::NodePath::NodeSide *other;
3848 if (n->p.knot == knot) {
3849 me = &n->p;
3850 other = &n->n;
3851 } else if (n->n.knot == knot) {
3852 me = &n->n;
3853 other = &n->p;
3854 } else {
3855 me = NULL;
3856 other = NULL;
3857 g_assert_not_reached();
3858 }
3860 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3861 Radial rme(me->pos - n->pos);
3862 Radial rother(other->pos - n->pos);
3863 Radial rnew(*p - n->pos);
3865 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3866 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3867 /* 0 interpreted as "no snapping". */
3869 // 1. Snap to the closest PI/snaps angle, starting from zero.
3870 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3872 // 2. Snap to the original angle, its opposite and perpendiculars
3873 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3874 /* The closest PI/2 angle, starting from original angle */
3875 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3877 // Snap to the closest.
3878 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3879 ? a_snapped
3880 : a_ortho );
3881 }
3883 // 3. Snap to the angle of the opposite line, if any
3884 Inkscape::NodePath::Node *othernode = other->other;
3885 if (othernode) {
3886 NR::Point other_to_snap(0,0);
3887 if (sp_node_side_is_line(n, other)) {
3888 other_to_snap = othernode->pos - n->pos;
3889 } else {
3890 other_to_snap = other->pos - n->pos;
3891 }
3892 if (NR::L2(other_to_snap) > 1e-3) {
3893 Radial rother_to_snap(other_to_snap);
3894 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3895 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3897 // Snap to the closest.
3898 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3899 ? a_snapped
3900 : a_oppo );
3901 }
3902 }
3904 rnew.a = a_snapped;
3905 }
3907 if (state & GDK_MOD1_MASK) {
3908 // lock handle length
3909 rnew.r = me->origin_radial.r;
3910 }
3912 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3913 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3914 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3915 rother.a += rnew.a - rme.a;
3916 other->pos = NR::Point(rother) + n->pos;
3917 if (other->knot) {
3918 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3919 sp_knot_moveto(other->knot, &other->pos);
3920 }
3921 }
3923 me->pos = NR::Point(rnew) + n->pos;
3924 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3926 // move knot, but without emitting the signal:
3927 // we cannot emit a "moved" signal because we're now processing it
3928 sp_knot_moveto(me->knot, &(me->pos));
3930 update_object(n->subpath->nodepath);
3932 /* status text */
3933 SPDesktop *desktop = n->subpath->nodepath->desktop;
3934 if (!desktop) return;
3935 SPEventContext *ec = desktop->event_context;
3936 if (!ec) return;
3937 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
3938 if (!mc) return;
3940 double degrees = 180 / M_PI * rnew.a;
3941 if (degrees > 180) degrees -= 360;
3942 if (degrees < -180) degrees += 360;
3943 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3944 degrees = angle_to_compass (degrees);
3946 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3948 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3949 _("<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);
3951 g_string_free(length, TRUE);
3952 }
3954 /**
3955 * Node handle event callback.
3956 */
3957 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3958 {
3959 gboolean ret = FALSE;
3960 switch (event->type) {
3961 case GDK_KEY_PRESS:
3962 switch (get_group0_keyval (&event->key)) {
3963 case GDK_space:
3964 if (event->key.state & GDK_BUTTON1_MASK) {
3965 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3966 stamp_repr(nodepath);
3967 ret = TRUE;
3968 }
3969 break;
3970 default:
3971 break;
3972 }
3973 break;
3974 case GDK_ENTER_NOTIFY:
3975 // we use an experimentally determined threshold that seems to work fine
3976 if (NR::L2(n->pos - knot->pos) < 0.75)
3977 Inkscape::NodePath::Path::active_node = n;
3978 break;
3979 case GDK_LEAVE_NOTIFY:
3980 // we use an experimentally determined threshold that seems to work fine
3981 if (NR::L2(n->pos - knot->pos) < 0.75)
3982 Inkscape::NodePath::Path::active_node = NULL;
3983 break;
3984 default:
3985 break;
3986 }
3988 return ret;
3989 }
3991 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
3992 Radial &rme, Radial &rother, gboolean const both)
3993 {
3994 rme.a += angle;
3995 if ( both
3996 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
3997 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
3998 {
3999 rother.a += angle;
4000 }
4001 }
4003 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4004 Radial &rme, Radial &rother, gboolean const both)
4005 {
4006 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4008 gdouble r;
4009 if ( both
4010 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4011 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4012 {
4013 r = MAX(rme.r, rother.r);
4014 } else {
4015 r = rme.r;
4016 }
4018 gdouble const weird_angle = atan2(norm_angle, r);
4019 /* Bulia says norm_angle is just the visible distance that the
4020 * object's end must travel on the screen. Left as 'angle' for want of
4021 * a better name.*/
4023 rme.a += weird_angle;
4024 if ( both
4025 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4026 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4027 {
4028 rother.a += weird_angle;
4029 }
4030 }
4032 /**
4033 * Rotate one node.
4034 */
4035 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4036 {
4037 Inkscape::NodePath::NodeSide *me, *other;
4038 bool both = false;
4040 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4041 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4043 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4044 me = &(n->p);
4045 other = &(n->n);
4046 } else if (!n->p.other) {
4047 me = &(n->n);
4048 other = &(n->p);
4049 } else {
4050 if (which > 0) { // right handle
4051 if (xn > xp) {
4052 me = &(n->n);
4053 other = &(n->p);
4054 } else {
4055 me = &(n->p);
4056 other = &(n->n);
4057 }
4058 } else if (which < 0){ // left handle
4059 if (xn <= xp) {
4060 me = &(n->n);
4061 other = &(n->p);
4062 } else {
4063 me = &(n->p);
4064 other = &(n->n);
4065 }
4066 } else { // both handles
4067 me = &(n->n);
4068 other = &(n->p);
4069 both = true;
4070 }
4071 }
4073 Radial rme(me->pos - n->pos);
4074 Radial rother(other->pos - n->pos);
4076 if (screen) {
4077 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4078 } else {
4079 node_rotate_one_internal (*n, angle, rme, rother, both);
4080 }
4082 me->pos = n->pos + NR::Point(rme);
4084 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4085 other->pos = n->pos + NR::Point(rother);
4086 }
4088 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4089 // so here we just move all the knots without emitting move signals, for speed
4090 sp_node_update_handles(n, false);
4091 }
4093 /**
4094 * Rotate selected nodes.
4095 */
4096 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4097 {
4098 if (!nodepath || !nodepath->selected) return;
4100 if (g_list_length(nodepath->selected) == 1) {
4101 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4102 node_rotate_one (n, angle, which, screen);
4103 } else {
4104 // rotate as an object:
4106 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4107 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4108 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4109 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4110 box.expandTo (n->pos); // contain all selected nodes
4111 }
4113 gdouble rot;
4114 if (screen) {
4115 gdouble const zoom = nodepath->desktop->current_zoom();
4116 gdouble const zmove = angle / zoom;
4117 gdouble const r = NR::L2(box.max() - box.midpoint());
4118 rot = atan2(zmove, r);
4119 } else {
4120 rot = angle;
4121 }
4123 NR::Point rot_center;
4124 if (Inkscape::NodePath::Path::active_node == NULL)
4125 rot_center = box.midpoint();
4126 else
4127 rot_center = Inkscape::NodePath::Path::active_node->pos;
4129 NR::Matrix t =
4130 NR::Matrix (NR::translate(-rot_center)) *
4131 NR::Matrix (NR::rotate(rot)) *
4132 NR::Matrix (NR::translate(rot_center));
4134 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4135 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4136 n->pos *= t;
4137 n->n.pos *= t;
4138 n->p.pos *= t;
4139 sp_node_update_handles(n, false);
4140 }
4141 }
4143 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4144 }
4146 /**
4147 * Scale one node.
4148 */
4149 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4150 {
4151 bool both = false;
4152 Inkscape::NodePath::NodeSide *me, *other;
4154 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4155 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4157 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4158 me = &(n->p);
4159 other = &(n->n);
4160 n->code = NR_CURVETO;
4161 } else if (!n->p.other) {
4162 me = &(n->n);
4163 other = &(n->p);
4164 if (n->n.other)
4165 n->n.other->code = NR_CURVETO;
4166 } else {
4167 if (which > 0) { // right handle
4168 if (xn > xp) {
4169 me = &(n->n);
4170 other = &(n->p);
4171 if (n->n.other)
4172 n->n.other->code = NR_CURVETO;
4173 } else {
4174 me = &(n->p);
4175 other = &(n->n);
4176 n->code = NR_CURVETO;
4177 }
4178 } else if (which < 0){ // left handle
4179 if (xn <= xp) {
4180 me = &(n->n);
4181 other = &(n->p);
4182 if (n->n.other)
4183 n->n.other->code = NR_CURVETO;
4184 } else {
4185 me = &(n->p);
4186 other = &(n->n);
4187 n->code = NR_CURVETO;
4188 }
4189 } else { // both handles
4190 me = &(n->n);
4191 other = &(n->p);
4192 both = true;
4193 n->code = NR_CURVETO;
4194 if (n->n.other)
4195 n->n.other->code = NR_CURVETO;
4196 }
4197 }
4199 Radial rme(me->pos - n->pos);
4200 Radial rother(other->pos - n->pos);
4202 rme.r += grow;
4203 if (rme.r < 0) rme.r = 0;
4204 if (rme.a == HUGE_VAL) {
4205 if (me->other) { // if direction is unknown, initialize it towards the next node
4206 Radial rme_next(me->other->pos - n->pos);
4207 rme.a = rme_next.a;
4208 } else { // if there's no next, initialize to 0
4209 rme.a = 0;
4210 }
4211 }
4212 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4213 rother.r += grow;
4214 if (rother.r < 0) rother.r = 0;
4215 if (rother.a == HUGE_VAL) {
4216 rother.a = rme.a + M_PI;
4217 }
4218 }
4220 me->pos = n->pos + NR::Point(rme);
4222 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4223 other->pos = n->pos + NR::Point(rother);
4224 }
4226 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4227 // so here we just move all the knots without emitting move signals, for speed
4228 sp_node_update_handles(n, false);
4229 }
4231 /**
4232 * Scale selected nodes.
4233 */
4234 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4235 {
4236 if (!nodepath || !nodepath->selected) return;
4238 if (g_list_length(nodepath->selected) == 1) {
4239 // scale handles of the single selected node
4240 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4241 node_scale_one (n, grow, which);
4242 } else {
4243 // scale nodes as an "object":
4245 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4246 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4247 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4248 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4249 box.expandTo (n->pos); // contain all selected nodes
4250 }
4252 double scale = (box.maxExtent() + grow)/box.maxExtent();
4254 NR::Point scale_center;
4255 if (Inkscape::NodePath::Path::active_node == NULL)
4256 scale_center = box.midpoint();
4257 else
4258 scale_center = Inkscape::NodePath::Path::active_node->pos;
4260 NR::Matrix t =
4261 NR::Matrix (NR::translate(-scale_center)) *
4262 NR::Matrix (NR::scale(scale, scale)) *
4263 NR::Matrix (NR::translate(scale_center));
4265 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4266 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4267 n->pos *= t;
4268 n->n.pos *= t;
4269 n->p.pos *= t;
4270 sp_node_update_handles(n, false);
4271 }
4272 }
4274 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4275 }
4277 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4278 {
4279 if (!nodepath) return;
4280 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4281 }
4283 /**
4284 * Flip selected nodes horizontally/vertically.
4285 */
4286 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Maybe<NR::Point> center)
4287 {
4288 if (!nodepath || !nodepath->selected) return;
4290 if (g_list_length(nodepath->selected) == 1 && !center) {
4291 // flip handles of the single selected node
4292 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4293 double temp = n->p.pos[axis];
4294 n->p.pos[axis] = n->n.pos[axis];
4295 n->n.pos[axis] = temp;
4296 sp_node_update_handles(n, false);
4297 } else {
4298 // scale nodes as an "object":
4300 NR::Rect box = sp_node_selected_bbox (nodepath);
4301 if (!center) {
4302 center = box.midpoint();
4303 }
4304 NR::Matrix t =
4305 NR::Matrix (NR::translate(- *center)) *
4306 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4307 NR::Matrix (NR::translate(*center));
4309 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4310 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4311 n->pos *= t;
4312 n->n.pos *= t;
4313 n->p.pos *= t;
4314 sp_node_update_handles(n, false);
4315 }
4316 }
4318 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4319 }
4321 NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4322 {
4323 g_assert (nodepath->selected);
4325 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4326 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4327 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4328 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4329 box.expandTo (n->pos); // contain all selected nodes
4330 }
4331 return box;
4332 }
4334 //-----------------------------------------------
4335 /**
4336 * Return new subpath under given nodepath.
4337 */
4338 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4339 {
4340 g_assert(nodepath);
4341 g_assert(nodepath->desktop);
4343 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4345 s->nodepath = nodepath;
4346 s->closed = FALSE;
4347 s->nodes = NULL;
4348 s->first = NULL;
4349 s->last = NULL;
4351 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4352 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4353 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4355 return s;
4356 }
4358 /**
4359 * Destroy nodes in subpath, then subpath itself.
4360 */
4361 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4362 {
4363 g_assert(subpath);
4364 g_assert(subpath->nodepath);
4365 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4367 while (subpath->nodes) {
4368 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4369 }
4371 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4373 g_free(subpath);
4374 }
4376 /**
4377 * Link head to tail in subpath.
4378 */
4379 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4380 {
4381 g_assert(!sp->closed);
4382 g_assert(sp->last != sp->first);
4383 g_assert(sp->first->code == NR_MOVETO);
4385 sp->closed = TRUE;
4387 //Link the head to the tail
4388 sp->first->p.other = sp->last;
4389 sp->last->n.other = sp->first;
4390 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4391 sp->first = sp->last;
4393 //Remove the extra end node
4394 sp_nodepath_node_destroy(sp->last->n.other);
4395 }
4397 /**
4398 * Open closed (loopy) subpath at node.
4399 */
4400 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4401 {
4402 g_assert(sp->closed);
4403 g_assert(n->subpath == sp);
4404 g_assert(sp->first == sp->last);
4406 /* We create new startpoint, current node will become last one */
4408 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4409 &n->pos, &n->pos, &n->n.pos);
4412 sp->closed = FALSE;
4414 //Unlink to make a head and tail
4415 sp->first = new_path;
4416 sp->last = n;
4417 n->n.other = NULL;
4418 new_path->p.other = NULL;
4419 }
4421 /**
4422 * Return new node in subpath with given properties.
4423 * \param pos Position of node.
4424 * \param ppos Handle position in previous direction
4425 * \param npos Handle position in previous direction
4426 */
4427 Inkscape::NodePath::Node *
4428 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)
4429 {
4430 g_assert(sp);
4431 g_assert(sp->nodepath);
4432 g_assert(sp->nodepath->desktop);
4434 if (nodechunk == NULL)
4435 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4437 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4439 n->subpath = sp;
4441 if (type != Inkscape::NodePath::NODE_NONE) {
4442 // use the type from sodipodi:nodetypes
4443 n->type = type;
4444 } else {
4445 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4446 // points are (almost) collinear
4447 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4448 // endnode, or a node with a retracted handle
4449 n->type = Inkscape::NodePath::NODE_CUSP;
4450 } else {
4451 n->type = Inkscape::NodePath::NODE_SMOOTH;
4452 }
4453 } else {
4454 n->type = Inkscape::NodePath::NODE_CUSP;
4455 }
4456 }
4458 n->code = code;
4459 n->selected = FALSE;
4460 n->pos = *pos;
4461 n->p.pos = *ppos;
4462 n->n.pos = *npos;
4464 n->dragging_out = NULL;
4466 Inkscape::NodePath::Node *prev;
4467 if (next) {
4468 //g_assert(g_list_find(sp->nodes, next));
4469 prev = next->p.other;
4470 } else {
4471 prev = sp->last;
4472 }
4474 if (prev)
4475 prev->n.other = n;
4476 else
4477 sp->first = n;
4479 if (next)
4480 next->p.other = n;
4481 else
4482 sp->last = n;
4484 n->p.other = prev;
4485 n->n.other = next;
4487 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"));
4488 sp_knot_set_position(n->knot, pos, 0);
4490 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4491 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4492 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4493 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4494 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4495 sp_knot_update_ctrl(n->knot);
4497 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4498 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4499 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4500 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4501 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4502 sp_knot_show(n->knot);
4504 // We only create handle knots and lines on demand
4505 n->p.knot = NULL;
4506 n->p.line = NULL;
4507 n->n.knot = NULL;
4508 n->n.line = NULL;
4510 sp->nodes = g_list_prepend(sp->nodes, n);
4512 return n;
4513 }
4515 /**
4516 * Destroy node and its knots, link neighbors in subpath.
4517 */
4518 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4519 {
4520 g_assert(node);
4521 g_assert(node->subpath);
4522 g_assert(SP_IS_KNOT(node->knot));
4524 Inkscape::NodePath::SubPath *sp = node->subpath;
4526 if (node->selected) { // first, deselect
4527 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4528 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4529 }
4531 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4533 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4534 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4535 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4536 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4537 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4538 g_object_unref(G_OBJECT(node->knot));
4540 if (node->p.knot) {
4541 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4542 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4543 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4544 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4545 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4546 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4547 g_object_unref(G_OBJECT(node->p.knot));
4548 node->p.knot = NULL;
4549 }
4551 if (node->n.knot) {
4552 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4553 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4554 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4555 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4556 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4557 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4558 g_object_unref(G_OBJECT(node->n.knot));
4559 node->n.knot = NULL;
4560 }
4562 if (node->p.line)
4563 gtk_object_destroy(GTK_OBJECT(node->p.line));
4564 if (node->n.line)
4565 gtk_object_destroy(GTK_OBJECT(node->n.line));
4567 if (sp->nodes) { // there are others nodes on the subpath
4568 if (sp->closed) {
4569 if (sp->first == node) {
4570 g_assert(sp->last == node);
4571 sp->first = node->n.other;
4572 sp->last = sp->first;
4573 }
4574 node->p.other->n.other = node->n.other;
4575 node->n.other->p.other = node->p.other;
4576 } else {
4577 if (sp->first == node) {
4578 sp->first = node->n.other;
4579 sp->first->code = NR_MOVETO;
4580 }
4581 if (sp->last == node) sp->last = node->p.other;
4582 if (node->p.other) node->p.other->n.other = node->n.other;
4583 if (node->n.other) node->n.other->p.other = node->p.other;
4584 }
4585 } else { // this was the last node on subpath
4586 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4587 }
4589 g_mem_chunk_free(nodechunk, node);
4590 }
4592 /**
4593 * Returns one of the node's two sides.
4594 * \param which Indicates which side.
4595 * \return Pointer to previous node side if which==-1, next if which==1.
4596 */
4597 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4598 {
4599 g_assert(node);
4601 switch (which) {
4602 case -1:
4603 return &node->p;
4604 case 1:
4605 return &node->n;
4606 default:
4607 break;
4608 }
4610 g_assert_not_reached();
4612 return NULL;
4613 }
4615 /**
4616 * Return the other side of the node, given one of its sides.
4617 */
4618 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4619 {
4620 g_assert(node);
4622 if (me == &node->p) return &node->n;
4623 if (me == &node->n) return &node->p;
4625 g_assert_not_reached();
4627 return NULL;
4628 }
4630 /**
4631 * Return NRPathcode on the given side of the node.
4632 */
4633 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4634 {
4635 g_assert(node);
4637 if (me == &node->p) {
4638 if (node->p.other) return (NRPathcode)node->code;
4639 return NR_MOVETO;
4640 }
4642 if (me == &node->n) {
4643 if (node->n.other) return (NRPathcode)node->n.other->code;
4644 return NR_MOVETO;
4645 }
4647 g_assert_not_reached();
4649 return NR_END;
4650 }
4652 /**
4653 * Return node with the given index
4654 */
4655 Inkscape::NodePath::Node *
4656 sp_nodepath_get_node_by_index(int index)
4657 {
4658 Inkscape::NodePath::Node *e = NULL;
4660 Inkscape::NodePath::Path *nodepath = sp_nodepath_current();
4661 if (!nodepath) {
4662 return e;
4663 }
4665 //find segment
4666 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4668 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4669 int n = g_list_length(sp->nodes);
4670 if (sp->closed) {
4671 n++;
4672 }
4674 //if the piece belongs to this subpath grab it
4675 //otherwise move onto the next subpath
4676 if (index < n) {
4677 e = sp->first;
4678 for (int i = 0; i < index; ++i) {
4679 e = e->n.other;
4680 }
4681 break;
4682 } else {
4683 if (sp->closed) {
4684 index -= (n+1);
4685 } else {
4686 index -= n;
4687 }
4688 }
4689 }
4691 return e;
4692 }
4694 /**
4695 * Returns plain text meaning of node type.
4696 */
4697 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4698 {
4699 unsigned retracted = 0;
4700 bool endnode = false;
4702 for (int which = -1; which <= 1; which += 2) {
4703 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4704 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4705 retracted ++;
4706 if (!side->other)
4707 endnode = true;
4708 }
4710 if (retracted == 0) {
4711 if (endnode) {
4712 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4713 return _("end node");
4714 } else {
4715 switch (node->type) {
4716 case Inkscape::NodePath::NODE_CUSP:
4717 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4718 return _("cusp");
4719 case Inkscape::NodePath::NODE_SMOOTH:
4720 // TRANSLATORS: "smooth" is an adjective here
4721 return _("smooth");
4722 case Inkscape::NodePath::NODE_SYMM:
4723 return _("symmetric");
4724 }
4725 }
4726 } else if (retracted == 1) {
4727 if (endnode) {
4728 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4729 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4730 } else {
4731 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4732 }
4733 } else {
4734 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4735 }
4737 return NULL;
4738 }
4740 /**
4741 * Handles content of statusbar as long as node tool is active.
4742 */
4743 void
4744 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4745 {
4746 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");
4747 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4749 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4750 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4751 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4752 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4754 SPDesktop *desktop = NULL;
4755 if (nodepath) {
4756 desktop = nodepath->desktop;
4757 } else {
4758 desktop = SP_ACTIVE_DESKTOP;
4759 }
4761 SPEventContext *ec = desktop->event_context;
4762 if (!ec) return;
4763 Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
4764 if (!mc) return;
4766 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4768 if (selected_nodes == 0) {
4769 Inkscape::Selection *sel = desktop->selection;
4770 if (!sel || sel->isEmpty()) {
4771 mc->setF(Inkscape::NORMAL_MESSAGE,
4772 _("Select a single object to edit its nodes or handles."));
4773 } else {
4774 if (nodepath) {
4775 mc->setF(Inkscape::NORMAL_MESSAGE,
4776 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.",
4777 "<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.",
4778 total_nodes),
4779 total_nodes);
4780 } else {
4781 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4782 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4783 } else {
4784 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4785 }
4786 }
4787 }
4788 } else if (nodepath && selected_nodes == 1) {
4789 mc->setF(Inkscape::NORMAL_MESSAGE,
4790 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4791 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4792 total_nodes),
4793 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4794 } else {
4795 if (selected_subpaths > 1) {
4796 mc->setF(Inkscape::NORMAL_MESSAGE,
4797 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4798 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4799 total_nodes),
4800 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4801 } else {
4802 mc->setF(Inkscape::NORMAL_MESSAGE,
4803 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4804 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4805 total_nodes),
4806 selected_nodes, total_nodes, when_selected);
4807 }
4808 }
4809 }
4811 /*
4812 * returns a *copy* of the curve of that object.
4813 */
4814 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4815 if (!object)
4816 return NULL;
4818 SPCurve *curve = NULL;
4819 if (SP_IS_PATH(object)) {
4820 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4821 curve = curve_new->copy();
4822 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4823 const gchar *svgd = object->repr->attribute(key);
4824 if (svgd) {
4825 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4826 SPCurve *curve_new = new SPCurve(pv);
4827 if (curve_new) {
4828 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4829 }
4830 }
4831 }
4833 return curve;
4834 }
4836 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4837 if (!np || !np->object || !curve)
4838 return;
4840 if (SP_IS_PATH(np->object)) {
4841 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4842 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4843 } else {
4844 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4845 }
4846 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4847 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4848 if (pathparam) {
4849 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4850 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4851 }
4852 }
4853 }
4855 /**
4856 SPCanvasItem *
4857 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4858 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4859 }
4860 **/
4862 /**
4863 SPCanvasItem *
4864 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4865 SPCurve *flash_curve = curve->copy();
4866 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4867 flash_curve->transform(i2d);
4868 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4869 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4870 // unless we also flash the nodes...
4871 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4872 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4873 sp_canvas_item_show(canvasitem);
4874 flash_curve->unref();
4875 return canvasitem;
4876 }
4878 SPCanvasItem *
4879 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4880 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4881 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4882 }
4883 **/
4885 SPCanvasItem *
4886 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4887 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4888 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4889 flash_curve->transform(i2d);
4890 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4891 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4892 // unless we also flash the nodes...
4893 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4894 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4895 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4896 sp_canvas_item_show(canvasitem);
4897 flash_curve->unref();
4898 return canvasitem;
4899 }
4901 // TODO: Merge this with sp_nodepath_make_helper_item()!
4902 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4903 np->show_helperpath = show;
4905 if (show) {
4906 SPCurve *helper_curve = np->curve->copy();
4907 helper_curve->transform(to_2geom(np->i2d));
4908 if (!np->helper_path) {
4909 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4911 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4912 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);
4913 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4914 sp_canvas_item_move_to_z(np->helper_path, 0);
4915 sp_canvas_item_show(np->helper_path);
4916 } else {
4917 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4918 }
4919 helper_curve->unref();
4920 } else {
4921 if (np->helper_path) {
4922 GtkObject *temp = np->helper_path;
4923 np->helper_path = NULL;
4924 gtk_object_destroy(temp);
4925 }
4926 }
4927 }
4929 /* sp_nodepath_make_straight_path:
4930 * Prevents user from curving the path by dragging a segment or activating handles etc.
4931 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4932 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4933 */
4934 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4935 np->straight_path = true;
4936 np->show_handles = false;
4937 g_message("add code to make the path straight.");
4938 // do sp_nodepath_convert_node_type on all nodes?
4939 // coding tip: search for this text : "Make selected segments lines"
4940 }
4943 /*
4944 Local Variables:
4945 mode:c++
4946 c-file-style:"stroustrup"
4947 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4948 indent-tabs-mode:nil
4949 fill-column:99
4950 End:
4951 */
4952 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :