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 "helper/geom.h"
30 #include "knot.h"
31 #include "inkscape.h"
32 #include "document.h"
33 #include "sp-namedview.h"
34 #include "desktop.h"
35 #include "desktop-handles.h"
36 #include "snap.h"
37 #include "message-stack.h"
38 #include "message-context.h"
39 #include "node-context.h"
40 #include "lpe-tool-context.h"
41 #include "shape-editor.h"
42 #include "selection-chemistry.h"
43 #include "selection.h"
44 #include "xml/repr.h"
45 #include "prefs-utils.h"
46 #include "sp-metrics.h"
47 #include "sp-path.h"
48 #include "libnr/nr-matrix-ops.h"
49 #include "svg/svg.h"
50 #include "verbs.h"
51 #include "display/bezier-utils.h"
52 #include <vector>
53 #include <algorithm>
54 #include <cstring>
55 #include <cmath>
56 #include <string>
57 #include "live_effects/lpeobject.h"
58 #include "live_effects/lpeobject-reference.h"
59 #include "live_effects/effect.h"
60 #include "live_effects/parameter/parameter.h"
61 #include "live_effects/parameter/path.h"
62 #include "util/mathfns.h"
63 #include "display/snap-indicator.h"
64 #include "snapped-point.h"
66 class NR::Matrix;
68 /// \todo
69 /// evil evil evil. FIXME: conflict of two different Path classes!
70 /// There is a conflict in the namespace between two classes named Path.
71 /// #include "sp-flowtext.h"
72 /// #include "sp-flowregion.h"
74 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
75 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
76 GType sp_flowregion_get_type (void);
77 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
78 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
79 GType sp_flowtext_get_type (void);
80 // end evil workaround
82 #include "helper/stlport.h"
85 /// \todo fixme: Implement these via preferences */
87 #define NODE_FILL 0xbfbfbf00
88 #define NODE_STROKE 0x000000ff
89 #define NODE_FILL_HI 0xff000000
90 #define NODE_STROKE_HI 0x000000ff
91 #define NODE_FILL_SEL 0x0000ffff
92 #define NODE_STROKE_SEL 0x000000ff
93 #define NODE_FILL_SEL_HI 0xff000000
94 #define NODE_STROKE_SEL_HI 0x000000ff
95 #define KNOT_FILL 0xffffffff
96 #define KNOT_STROKE 0x000000ff
97 #define KNOT_FILL_HI 0xff000000
98 #define KNOT_STROKE_HI 0x000000ff
100 static GMemChunk *nodechunk = NULL;
102 /* Creation from object */
104 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
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(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(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;
269 }
271 if (curve->get_segment_count() < 1) {
272 curve->unref();
273 return NULL; // prevent crash for one-node paths
274 }
276 //Create new nodepath
277 Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
278 if (!np) {
279 curve->unref();
280 return NULL;
281 }
283 // Set defaults
284 np->desktop = desktop;
285 np->object = object;
286 np->subpaths = NULL;
287 np->selected = NULL;
288 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
289 np->local_change = 0;
290 np->show_handles = show_handles;
291 np->helper_path = NULL;
292 np->helper_path_vec = new HelperPathList;
293 np->helperpath_rgba = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
294 np->helperpath_width = 1.0;
295 np->curve = curve->copy();
296 np->show_helperpath = (prefs_get_int_attribute ("tools.nodes", "show_helperpath", 0) == 1);
297 if (SP_IS_LPE_ITEM(object)) {
298 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
299 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
300 np->show_helperpath = true;
301 }
302 }
303 np->straight_path = false;
304 if (IS_LIVEPATHEFFECT(object) && item) {
305 np->item = item;
306 } else {
307 np->item = SP_ITEM(object);
308 }
310 // we need to update item's transform from the repr here,
311 // because they may be out of sync when we respond
312 // to a change in repr by regenerating nodepath --bb
313 sp_object_read_attr(SP_OBJECT(np->item), "transform");
315 np->i2d = sp_item_i2d_affine(np->item);
316 np->d2i = np->i2d.inverse();
318 np->repr = repr;
319 if (repr_key_in) { // apparantly the object is an LPEObject
320 np->repr_key = g_strdup(repr_key_in);
321 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
322 Inkscape::LivePathEffect::Parameter *lpeparam = LIVEPATHEFFECT(object)->lpe->getParameter(repr_key_in);
323 if (lpeparam) {
324 lpeparam->param_setup_nodepath(np);
325 }
326 } else {
327 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
328 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
329 np->repr_key = g_strdup("inkscape:original-d");
331 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
332 if (lpe) {
333 lpe->setup_nodepath(np);
334 }
335 } else {
336 np->repr_key = g_strdup("d");
337 }
338 }
340 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
341 * So for example a closed rectangle has a nodetypestring of length 5.
342 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
343 Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
344 np->curve->set_pathvector(pathv_sanitized);
345 guint length = np->curve->get_segment_count();
346 for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
347 length += pit->empty() ? 0 : 1;
348 }
350 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
351 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
353 // create the subpath(s) from the bpath
354 subpaths_from_pathvector(np, pathv_sanitized, typestr);
356 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
357 np->subpaths = g_list_reverse(np->subpaths);
359 delete[] typestr;
360 curve->unref();
362 // Draw helper curve
363 if (np->show_helperpath) {
364 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
365 }
367 sp_nodepath_create_helperpaths(np);
369 return np;
370 }
372 /**
373 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
374 */
375 void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
377 if (!np) { //soft fail, like delete
378 return;
379 }
381 while (np->subpaths) {
382 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) np->subpaths->data);
383 }
385 //Inform the ShapeEditor that made me, if any, that I am gone.
386 if (np->shape_editor)
387 np->shape_editor->nodepath_destroyed();
389 g_assert(!np->selected);
391 if (np->helper_path) {
392 GtkObject *temp = np->helper_path;
393 np->helper_path = NULL;
394 gtk_object_destroy(temp);
395 }
396 if (np->curve) {
397 np->curve->unref();
398 np->curve = NULL;
399 }
401 if (np->repr_key) {
402 g_free(np->repr_key);
403 np->repr_key = NULL;
404 }
405 if (np->repr_nodetypes_key) {
406 g_free(np->repr_nodetypes_key);
407 np->repr_nodetypes_key = NULL;
408 }
410 sp_nodepath_destroy_helperpaths(np);
411 delete np->helper_path_vec;
412 np->helper_path_vec = NULL;
414 np->desktop = NULL;
416 g_free(np);
417 }
419 /**
420 * Return the node count of a given NodeSubPath.
421 */
422 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
423 {
424 int nodeCount = 0;
426 if (subpath) {
427 nodeCount = g_list_length(subpath->nodes);
428 }
430 return nodeCount;
431 }
433 /**
434 * Return the node count of a given NodePath.
435 */
436 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
437 {
438 gint nodeCount = 0;
439 if (np) {
440 for (GList *item = np->subpaths ; item ; item=item->next) {
441 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
442 nodeCount += g_list_length(subpath->nodes);
443 }
444 }
445 return nodeCount;
446 }
448 /**
449 * Return the subpath count of a given NodePath.
450 */
451 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
452 {
453 gint nodeCount = 0;
454 if (np) {
455 nodeCount = g_list_length(np->subpaths);
456 }
457 return nodeCount;
458 }
460 /**
461 * Return the selected node count of a given NodePath.
462 */
463 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
464 {
465 gint nodeCount = 0;
466 if (np) {
467 nodeCount = g_list_length(np->selected);
468 }
469 return nodeCount;
470 }
472 /**
473 * Return the number of subpaths where nodes are selected in a given NodePath.
474 */
475 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
476 {
477 gint nodeCount = 0;
478 if (np && np->selected) {
479 if (!np->selected->next) {
480 nodeCount = 1;
481 } else {
482 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
483 Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
484 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
485 Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
486 if (node->selected) {
487 nodeCount++;
488 break;
489 }
490 }
491 }
492 }
493 }
494 return nodeCount;
495 }
497 /**
498 * Clean up a nodepath after editing.
499 *
500 * Currently we are deleting trivial subpaths.
501 */
502 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
503 {
504 GList *badSubPaths = NULL;
506 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
507 for (GList *l = nodepath->subpaths; l ; l=l->next) {
508 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
509 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
510 badSubPaths = g_list_append(badSubPaths, sp);
511 }
513 //Delete them. This second step is because sp_nodepath_subpath_destroy()
514 //also removes the subpath from nodepath->subpaths
515 for (GList *l = badSubPaths; l ; l=l->next) {
516 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
517 sp_nodepath_subpath_destroy(sp);
518 }
520 g_list_free(badSubPaths);
521 }
523 /**
524 * Create new nodepaths from pathvector, make it subpaths of np.
525 * \param t The node type array.
526 */
527 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
528 {
529 guint i = 0; // index into node type array
530 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
531 if (pit->empty())
532 continue; // don't add single knot paths
534 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
536 NR::Point ppos = pit->initialPoint() * (Geom::Matrix)np->i2d;
537 NRPathcode pcode = NR_MOVETO;
539 /* Johan: Note that this is pretty arcane code. I am pretty sure it is working correctly, be very certain to change it! (better to just rewrite this whole method)*/
540 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
541 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
542 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
543 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
544 {
545 NR::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
546 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
548 ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
549 pcode = NR_LINETO;
550 }
551 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
552 std::vector<Geom::Point> points = cubic_bezier->points();
553 NR::Point pos = points[0] * (Geom::Matrix)np->i2d;
554 NR::Point npos = points[1] * (Geom::Matrix)np->i2d;
555 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
557 ppos = points[2] * (Geom::Matrix)np->i2d;
558 pcode = NR_CURVETO;
559 }
560 }
562 if (pit->closed()) {
563 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
564 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
565 * If the length is zero, don't add it to the nodepath. */
566 Geom::Curve const &closing_seg = pit->back_closed();
567 if ( ! closing_seg.isDegenerate() ) {
568 NR::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
569 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
570 }
572 sp_nodepath_subpath_close(sp);
573 }
574 }
575 }
577 /**
578 * Convert from sodipodi:nodetypes to new style type array.
579 */
580 static
581 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
582 {
583 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
585 guint pos = 0;
587 if (types) {
588 for (guint i = 0; types[i] && ( i < length ); i++) {
589 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
590 if (types[i] != '\0') {
591 switch (types[i]) {
592 case 's':
593 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
594 break;
595 case 'z':
596 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
597 break;
598 case 'c':
599 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
600 break;
601 default:
602 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
603 break;
604 }
605 }
606 }
607 }
609 while (pos < length) {
610 typestr[pos++] = Inkscape::NodePath::NODE_NONE;
611 }
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(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(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 // Returns different message contexts depending on the current context. This function should only
852 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
853 // other cases.
854 static Inkscape::MessageContext *
855 get_message_context(SPEventContext *ec)
856 {
857 Inkscape::MessageContext *mc = 0;
859 if (SP_IS_NODE_CONTEXT(ec)) {
860 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
861 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
862 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
863 } else {
864 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
865 }
867 return mc;
868 }
870 /**
871 \brief Fills node and handle positions for three nodes, splitting line
872 marked by end at distance t.
873 */
874 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
875 {
876 g_assert(new_path != NULL);
877 g_assert(end != NULL);
879 g_assert(end->p.other == new_path);
880 Inkscape::NodePath::Node *start = new_path->p.other;
881 g_assert(start);
883 if (end->code == NR_LINETO) {
884 new_path->type =Inkscape::NodePath::NODE_CUSP;
885 new_path->code = NR_LINETO;
886 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
887 } else {
888 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
889 new_path->code = NR_CURVETO;
890 gdouble s = 1 - t;
891 for (int dim = 0; dim < 2; dim++) {
892 NR::Coord const f000 = start->pos[dim];
893 NR::Coord const f001 = start->n.pos[dim];
894 NR::Coord const f011 = end->p.pos[dim];
895 NR::Coord const f111 = end->pos[dim];
896 NR::Coord const f00t = s * f000 + t * f001;
897 NR::Coord const f01t = s * f001 + t * f011;
898 NR::Coord const f11t = s * f011 + t * f111;
899 NR::Coord const f0tt = s * f00t + t * f01t;
900 NR::Coord const f1tt = s * f01t + t * f11t;
901 NR::Coord const fttt = s * f0tt + t * f1tt;
902 start->n.pos[dim] = f00t;
903 new_path->p.pos[dim] = f0tt;
904 new_path->pos[dim] = fttt;
905 new_path->n.pos[dim] = f1tt;
906 end->p.pos[dim] = f11t;
907 }
908 }
909 }
911 /**
912 * Adds new node on direct line between two nodes, activates handles of all
913 * three nodes.
914 */
915 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
916 {
917 g_assert(end);
918 g_assert(end->subpath);
919 g_assert(g_list_find(end->subpath->nodes, end));
921 Inkscape::NodePath::Node *start = end->p.other;
922 g_assert( start->n.other == end );
923 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
924 end,
925 (NRPathcode)end->code == NR_LINETO?
926 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
927 (NRPathcode)end->code,
928 &start->pos, &start->pos, &start->n.pos);
929 sp_nodepath_line_midpoint(newnode, end, t);
931 sp_node_adjust_handles(start);
932 sp_node_update_handles(start);
933 sp_node_update_handles(newnode);
934 sp_node_adjust_handles(end);
935 sp_node_update_handles(end);
937 return newnode;
938 }
940 /**
941 \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
942 */
943 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
944 {
945 g_assert(node);
946 g_assert(node->subpath);
947 g_assert(g_list_find(node->subpath->nodes, node));
949 Inkscape::NodePath::Node* result = 0;
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 result = sp->first;
956 } else if ( (node == sp->first) || (node == sp->last ) ){
957 // no break for end nodes
958 result = 0;
959 } else {
960 // create a new subpath
961 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
963 // duplicate the break node as start of the new subpath
964 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
965 static_cast<Inkscape::NodePath::NodeType>(node->type),
966 NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
968 // attach rest of curve to new node
969 g_assert(node->n.other);
970 newnode->n.other = node->n.other; node->n.other = NULL;
971 newnode->n.other->p.other = newnode;
972 newsubpath->last = sp->last;
973 sp->last = node;
974 node = newnode;
975 while (node->n.other) {
976 node = node->n.other;
977 node->subpath = newsubpath;
978 sp->nodes = g_list_remove(sp->nodes, node);
979 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
980 }
983 result = newnode;
984 }
985 return result;
986 }
988 /**
989 * Duplicate node and connect to neighbours.
990 */
991 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
992 {
993 g_assert(node);
994 g_assert(node->subpath);
995 g_assert(g_list_find(node->subpath->nodes, node));
997 Inkscape::NodePath::SubPath *sp = node->subpath;
999 NRPathcode code = (NRPathcode) node->code;
1000 if (code == NR_MOVETO) { // if node is the endnode,
1001 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1002 }
1004 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1006 if (!node->n.other || !node->p.other) { // if node is an endnode, select it
1007 return node;
1008 } else {
1009 return newnode; // otherwise select the newly created node
1010 }
1011 }
1013 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1014 {
1015 node->p.pos = (node->pos + (node->pos - node->n.pos));
1016 }
1018 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1019 {
1020 node->n.pos = (node->pos + (node->pos - node->p.pos));
1021 }
1023 /**
1024 * Change line type at node, with side effects on neighbours.
1025 */
1026 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1027 {
1028 g_assert(end);
1029 g_assert(end->subpath);
1030 g_assert(end->p.other);
1032 if (end->code != static_cast<guint>(code) ) {
1033 Inkscape::NodePath::Node *start = end->p.other;
1035 end->code = code;
1037 if (code == NR_LINETO) {
1038 if (start->code == NR_LINETO) {
1039 sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1040 }
1041 if (end->n.other) {
1042 if (end->n.other->code == NR_LINETO) {
1043 sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1044 }
1045 }
1046 } else {
1047 NR::Point delta = end->pos - start->pos;
1048 start->n.pos = start->pos + delta / 3;
1049 end->p.pos = end->pos - delta / 3;
1050 sp_node_adjust_handle(start, 1);
1051 sp_node_adjust_handle(end, -1);
1052 }
1054 sp_node_update_handles(start);
1055 sp_node_update_handles(end);
1056 }
1057 }
1059 /**
1060 * Change node type, and its handles accordingly.
1061 */
1062 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1063 {
1064 g_assert(node);
1065 g_assert(node->subpath);
1067 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1068 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1069 type =Inkscape::NodePath::NODE_CUSP;
1070 }
1071 }
1073 node->type = type;
1075 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1076 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1077 node->knot->setSize (node->selected? 11 : 9);
1078 sp_knot_update_ctrl(node->knot);
1079 } else {
1080 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1081 node->knot->setSize (node->selected? 9 : 7);
1082 sp_knot_update_ctrl(node->knot);
1083 }
1085 // if one of handles is mouseovered, preserve its position
1086 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1087 sp_node_adjust_handle(node, 1);
1088 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1089 sp_node_adjust_handle(node, -1);
1090 } else {
1091 sp_node_adjust_handles(node);
1092 }
1094 sp_node_update_handles(node);
1096 sp_nodepath_update_statusbar(node->subpath->nodepath);
1098 return node;
1099 }
1101 bool
1102 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1103 {
1104 // TODO clean up multiple returns
1105 Inkscape::NodePath::Node *othernode = side->other;
1106 if (!othernode)
1107 return false;
1108 NRPathcode const code = sp_node_path_code_from_side(node, side);
1109 if (code == NR_LINETO)
1110 return true;
1111 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1112 if (&node->p == side) {
1113 other_to_me = &othernode->n;
1114 } else if (&node->n == side) {
1115 other_to_me = &othernode->p;
1116 }
1117 if (!other_to_me)
1118 return false;
1119 bool is_line =
1120 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1121 NR::L2(node->pos - side->pos) < 1e-6);
1122 return is_line;
1123 }
1125 /**
1126 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1127 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1128 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1129 * If already cusp and set to cusp, retracts handles.
1130 */
1131 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1132 {
1133 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1135 /*
1136 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1138 if (two_handles) {
1139 // do nothing, adjust_handles called via set_node_type will line them up
1140 } else if (one_handle) {
1141 if (opposite_to_handle_is_line) {
1142 if (lined_up) {
1143 // already half-smooth; pull opposite handle too making it fully smooth
1144 } else {
1145 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1146 }
1147 } else {
1148 // pull opposite handle in line with the existing one
1149 }
1150 } else if (no_handles) {
1151 if (both_segments_are_lines OR both_segments_are_curves) {
1152 //pull both handles
1153 } else {
1154 // pull the handle opposite to line segment, making node half-smooth
1155 }
1156 }
1157 */
1158 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1159 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1160 bool p_is_line = sp_node_side_is_line(node, &node->p);
1161 bool n_is_line = sp_node_side_is_line(node, &node->n);
1163 if (p_has_handle && n_has_handle) {
1164 // do nothing, adjust_handles will line them up
1165 } else if (p_has_handle || n_has_handle) {
1166 if (p_has_handle && n_is_line) {
1167 Radial line (node->n.other->pos - node->pos);
1168 Radial handle (node->pos - node->p.pos);
1169 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1170 // already half-smooth; pull opposite handle too making it fully smooth
1171 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1172 } else {
1173 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1174 }
1175 } else if (n_has_handle && p_is_line) {
1176 Radial line (node->p.other->pos - node->pos);
1177 Radial handle (node->pos - node->n.pos);
1178 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1179 // already half-smooth; pull opposite handle too making it fully smooth
1180 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1181 } else {
1182 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1183 }
1184 } else if (p_has_handle && node->n.other) {
1185 // pull n handle
1186 node->n.other->code = NR_CURVETO;
1187 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1188 NR::L2(node->p.pos - node->pos) :
1189 NR::L2(node->n.other->pos - node->pos) / 3;
1190 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1191 } else if (n_has_handle && node->p.other) {
1192 // pull p handle
1193 node->code = NR_CURVETO;
1194 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1195 NR::L2(node->n.pos - node->pos) :
1196 NR::L2(node->p.other->pos - node->pos) / 3;
1197 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1198 }
1199 } else if (!p_has_handle && !n_has_handle) {
1200 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1201 // no handles, but both segments are either lnes or curves:
1202 //pull both handles
1204 // convert both to curves:
1205 node->code = NR_CURVETO;
1206 node->n.other->code = NR_CURVETO;
1208 NR::Point leg_prev = node->pos - node->p.other->pos;
1209 NR::Point leg_next = node->pos - node->n.other->pos;
1211 double norm_leg_prev = L2(leg_prev);
1212 double norm_leg_next = L2(leg_next);
1214 NR::Point delta;
1215 if (norm_leg_next > 0.0) {
1216 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1217 (&delta)->normalize();
1218 }
1220 if (type == Inkscape::NodePath::NODE_SYMM) {
1221 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1222 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1223 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1224 } else {
1225 // length of handle is proportional to distance to adjacent node
1226 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1227 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1228 }
1230 } else {
1231 // pull the handle opposite to line segment, making it half-smooth
1232 if (p_is_line && node->n.other) {
1233 if (type != Inkscape::NodePath::NODE_SYMM) {
1234 // pull n handle
1235 node->n.other->code = NR_CURVETO;
1236 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1237 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1238 }
1239 } else if (n_is_line && node->p.other) {
1240 if (type != Inkscape::NodePath::NODE_SYMM) {
1241 // pull p handle
1242 node->code = NR_CURVETO;
1243 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1244 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1245 }
1246 }
1247 }
1248 }
1249 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1250 // cusping a cusp: retract nodes
1251 node->p.pos = node->pos;
1252 node->n.pos = node->pos;
1253 }
1255 sp_nodepath_set_node_type (node, type);
1256 }
1258 /**
1259 * Move node to point, and adjust its and neighbouring handles.
1260 */
1261 void sp_node_moveto(Inkscape::NodePath::Node *node, NR::Point p)
1262 {
1263 NR::Point delta = p - node->pos;
1264 node->pos = p;
1266 node->p.pos += delta;
1267 node->n.pos += delta;
1269 Inkscape::NodePath::Node *node_p = NULL;
1270 Inkscape::NodePath::Node *node_n = NULL;
1272 if (node->p.other) {
1273 if (node->code == NR_LINETO) {
1274 sp_node_adjust_handle(node, 1);
1275 sp_node_adjust_handle(node->p.other, -1);
1276 node_p = node->p.other;
1277 }
1278 }
1279 if (node->n.other) {
1280 if (node->n.other->code == NR_LINETO) {
1281 sp_node_adjust_handle(node, -1);
1282 sp_node_adjust_handle(node->n.other, 1);
1283 node_n = node->n.other;
1284 }
1285 }
1287 // this function is only called from batch movers that will update display at the end
1288 // themselves, so here we just move all the knots without emitting move signals, for speed
1289 sp_node_update_handles(node, false);
1290 if (node_n) {
1291 sp_node_update_handles(node_n, false);
1292 }
1293 if (node_p) {
1294 sp_node_update_handles(node_p, false);
1295 }
1296 }
1298 /**
1299 * Call sp_node_moveto() for node selection and handle possible snapping.
1300 */
1301 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1302 bool const snap, bool constrained = false,
1303 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1304 {
1305 NR::Coord best = NR_HUGE;
1306 NR::Point delta(dx, dy);
1307 NR::Point best_pt = delta;
1308 Inkscape::SnappedPoint best_abs;
1310 if (snap) {
1311 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1312 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1313 * must provide that information. */
1315 // Build a list of the unselected nodes to which the snapper should snap
1316 std::vector<Geom::Point> unselected_nodes;
1317 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1318 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1319 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1320 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1321 if (!node->selected) {
1322 unselected_nodes.push_back(to_2geom(node->pos));
1323 }
1324 }
1325 }
1327 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1329 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1330 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1331 m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1332 Inkscape::SnappedPoint s;
1333 if (constrained) {
1334 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1335 dedicated_constraint.setPoint(n->pos);
1336 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1337 } else {
1338 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1339 }
1340 if (s.getSnapped() && (s.getDistance() < best)) {
1341 best = s.getDistance();
1342 best_abs = s;
1343 best_pt = from_2geom(s.getPoint()) - n->pos;
1344 }
1345 }
1347 if (best_abs.getSnapped()) {
1348 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1349 } else {
1350 nodepath->desktop->snapindicator->remove_snappoint();
1351 }
1352 }
1354 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1355 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1356 sp_node_moveto(n, n->pos + best_pt);
1357 }
1359 // do not update repr here so that node dragging is acceptably fast
1360 update_object(nodepath);
1361 }
1363 /**
1364 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1365 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1366 near x = 0.
1367 */
1368 double
1369 sculpt_profile (double x, double alpha, guint profile)
1370 {
1371 double result = 1;
1373 if (x >= 1) {
1374 result = 0;
1375 } else if (x <= 0) {
1376 result = 1;
1377 } else {
1378 switch (profile) {
1379 case SCULPT_PROFILE_LINEAR:
1380 result = 1 - x;
1381 break;
1382 case SCULPT_PROFILE_BELL:
1383 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1384 break;
1385 case SCULPT_PROFILE_ELLIPTIC:
1386 result = sqrt(1 - x*x);
1387 break;
1388 default:
1389 g_assert_not_reached();
1390 }
1391 }
1393 return result;
1394 }
1396 double
1397 bezier_length (NR::Point a, NR::Point ah, NR::Point bh, NR::Point b)
1398 {
1399 // extremely primitive for now, don't have time to look for the real one
1400 double lower = NR::L2(b - a);
1401 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1402 return (lower + upper)/2;
1403 }
1405 void
1406 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, NR::Point delta, NR::Point delta_n, NR::Point delta_p)
1407 {
1408 n->pos = n->origin + delta;
1409 n->n.pos = n->n.origin + delta_n;
1410 n->p.pos = n->p.origin + delta_p;
1411 sp_node_adjust_handles(n);
1412 sp_node_update_handles(n, false);
1413 }
1415 /**
1416 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1417 * on how far they are from the dragged node n.
1418 */
1419 static void
1420 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, NR::Point delta)
1421 {
1422 g_assert (n);
1423 g_assert (nodepath);
1424 g_assert (n->subpath->nodepath == nodepath);
1426 double pressure = n->knot->pressure;
1427 if (pressure == 0)
1428 pressure = 0.5; // default
1429 pressure = CLAMP (pressure, 0.2, 0.8);
1431 // map pressure to alpha = 1/5 ... 5
1432 double alpha = 1 - 2 * fabs(pressure - 0.5);
1433 if (pressure > 0.5)
1434 alpha = 1/alpha;
1436 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1438 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1439 // Only one subpath has selected nodes:
1440 // use linear mode, where the distance from n to node being dragged is calculated along the path
1442 double n_sel_range = 0, p_sel_range = 0;
1443 guint n_nodes = 0, p_nodes = 0;
1444 guint n_sel_nodes = 0, p_sel_nodes = 0;
1446 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1447 {
1448 double n_range = 0, p_range = 0;
1449 bool n_going = true, p_going = true;
1450 Inkscape::NodePath::Node *n_node = n;
1451 Inkscape::NodePath::Node *p_node = n;
1452 do {
1453 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1454 if (n_node && n_going)
1455 n_node = n_node->n.other;
1456 if (n_node == NULL) {
1457 n_going = false;
1458 } else {
1459 n_nodes ++;
1460 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1461 if (n_node->selected) {
1462 n_sel_nodes ++;
1463 n_sel_range = n_range;
1464 }
1465 if (n_node == p_node) {
1466 n_going = false;
1467 p_going = false;
1468 }
1469 }
1470 if (p_node && p_going)
1471 p_node = p_node->p.other;
1472 if (p_node == NULL) {
1473 p_going = false;
1474 } else {
1475 p_nodes ++;
1476 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1477 if (p_node->selected) {
1478 p_sel_nodes ++;
1479 p_sel_range = p_range;
1480 }
1481 if (p_node == n_node) {
1482 n_going = false;
1483 p_going = false;
1484 }
1485 }
1486 } while (n_going || p_going);
1487 }
1489 // Second pass: actually move nodes in this subpath
1490 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1491 {
1492 double n_range = 0, p_range = 0;
1493 bool n_going = true, p_going = true;
1494 Inkscape::NodePath::Node *n_node = n;
1495 Inkscape::NodePath::Node *p_node = n;
1496 do {
1497 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1498 if (n_node && n_going)
1499 n_node = n_node->n.other;
1500 if (n_node == NULL) {
1501 n_going = false;
1502 } else {
1503 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1504 if (n_node->selected) {
1505 sp_nodepath_move_node_and_handles (n_node,
1506 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1507 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1508 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1509 }
1510 if (n_node == p_node) {
1511 n_going = false;
1512 p_going = false;
1513 }
1514 }
1515 if (p_node && p_going)
1516 p_node = p_node->p.other;
1517 if (p_node == NULL) {
1518 p_going = false;
1519 } else {
1520 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1521 if (p_node->selected) {
1522 sp_nodepath_move_node_and_handles (p_node,
1523 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1524 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1525 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1526 }
1527 if (p_node == n_node) {
1528 n_going = false;
1529 p_going = false;
1530 }
1531 }
1532 } while (n_going || p_going);
1533 }
1535 } else {
1536 // Multiple subpaths have selected nodes:
1537 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1538 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1539 // fix the pear-like shape when sculpting e.g. a ring
1541 // First pass: calculate range
1542 gdouble direct_range = 0;
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 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1549 }
1550 }
1551 }
1553 // Second pass: actually move nodes
1554 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1555 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1556 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1557 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1558 if (node->selected) {
1559 if (direct_range > 1e-6) {
1560 sp_nodepath_move_node_and_handles (node,
1561 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1562 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1563 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1564 } else {
1565 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1566 }
1568 }
1569 }
1570 }
1571 }
1573 // do not update repr here so that node dragging is acceptably fast
1574 update_object(nodepath);
1575 }
1578 /**
1579 * Move node selection to point, adjust its and neighbouring handles,
1580 * handle possible snapping, and commit the change with possible undo.
1581 */
1582 void
1583 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1584 {
1585 if (!nodepath) return;
1587 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1589 if (dx == 0) {
1590 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1591 } else if (dy == 0) {
1592 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1593 } else {
1594 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1595 }
1596 }
1598 /**
1599 * Move node selection off screen and commit the change.
1600 */
1601 void
1602 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1603 {
1604 // borrowed from sp_selection_move_screen in selection-chemistry.c
1605 // we find out the current zoom factor and divide deltas by it
1607 gdouble zoom = desktop->current_zoom();
1608 gdouble zdx = dx / zoom;
1609 gdouble zdy = dy / zoom;
1611 if (!nodepath) return;
1613 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1615 if (dx == 0) {
1616 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1617 } else if (dy == 0) {
1618 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1619 } else {
1620 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1621 }
1622 }
1624 /**
1625 * Move selected nodes to the absolute position given
1626 */
1627 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1628 {
1629 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1630 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1631 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1632 sp_node_moveto(n, npos);
1633 }
1635 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1636 }
1638 /**
1639 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1640 */
1641 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1642 {
1643 boost::optional<Geom::Coord> no_coord;
1644 g_return_val_if_fail(nodepath->selected, no_coord);
1646 // determine coordinate of first selected node
1647 GList *nsel = nodepath->selected;
1648 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1649 NR::Coord coord = n->pos[axis];
1650 bool coincide = true;
1652 // compare it to the coordinates of all the other selected nodes
1653 for (GList *l = nsel->next; l != NULL; l = l->next) {
1654 n = (Inkscape::NodePath::Node *) l->data;
1655 if (n->pos[axis] != coord) {
1656 coincide = false;
1657 }
1658 }
1659 if (coincide) {
1660 return coord;
1661 } else {
1662 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1663 // currently we return the coordinate of the bounding box midpoint because I don't know how
1664 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1665 return bbox.midpoint()[axis];
1666 }
1667 }
1669 /** If they don't yet exist, creates knot and line for the given side of the node */
1670 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1671 {
1672 if (!side->knot) {
1673 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"));
1675 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1676 side->knot->setSize (7);
1677 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1678 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1679 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1680 sp_knot_update_ctrl(side->knot);
1682 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1683 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1684 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1685 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1686 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1687 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1688 }
1690 if (!side->line) {
1691 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1692 SP_TYPE_CTRLLINE, NULL);
1693 }
1694 }
1696 /**
1697 * Ensure the given handle of the node is visible/invisible, update its screen position
1698 */
1699 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1700 {
1701 g_assert(node != NULL);
1703 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1704 NRPathcode code = sp_node_path_code_from_side(node, side);
1706 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1708 if (show_handle) {
1709 if (!side->knot) { // No handle knot at all
1710 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1711 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1712 side->knot->pos = side->pos;
1713 if (side->knot->item)
1714 SP_CTRL(side->knot->item)->moveto(side->pos);
1715 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1716 sp_knot_show(side->knot);
1717 } else {
1718 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1719 if (fire_move_signals) {
1720 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1721 } else {
1722 sp_knot_moveto(side->knot, side->pos);
1723 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1724 }
1725 }
1726 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1727 sp_knot_show(side->knot);
1728 }
1729 }
1730 sp_canvas_item_show(side->line);
1731 } else {
1732 if (side->knot) {
1733 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1734 sp_knot_hide(side->knot);
1735 }
1736 }
1737 if (side->line) {
1738 sp_canvas_item_hide(side->line);
1739 }
1740 }
1741 }
1743 /**
1744 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1745 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1746 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1747 * updated; otherwise, just move the knots silently (used in batch moves).
1748 */
1749 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1750 {
1751 g_assert(node != NULL);
1753 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1754 sp_knot_show(node->knot);
1755 }
1757 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1758 if (fire_move_signals)
1759 sp_knot_set_position(node->knot, node->pos, 0);
1760 else
1761 sp_knot_moveto(node->knot, node->pos);
1762 }
1764 gboolean show_handles = node->selected;
1765 if (node->p.other != NULL) {
1766 if (node->p.other->selected) show_handles = TRUE;
1767 }
1768 if (node->n.other != NULL) {
1769 if (node->n.other->selected) show_handles = TRUE;
1770 }
1772 if (node->subpath->nodepath->show_handles == false)
1773 show_handles = FALSE;
1775 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1776 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1777 }
1779 /**
1780 * Call sp_node_update_handles() for all nodes on subpath.
1781 */
1782 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1783 {
1784 g_assert(subpath != NULL);
1786 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1787 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1788 }
1789 }
1791 /**
1792 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1793 */
1794 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1795 {
1796 g_assert(nodepath != NULL);
1798 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1799 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1800 }
1801 }
1803 void
1804 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1805 {
1806 if (nodepath) {
1807 nodepath->show_handles = show;
1808 sp_nodepath_update_handles(nodepath);
1809 }
1810 }
1812 /**
1813 * Adds all selected nodes in nodepath to list.
1814 */
1815 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1816 {
1817 StlConv<Node *>::list(l, selected);
1818 /// \todo this adds a copying, rework when the selection becomes a stl list
1819 }
1821 /**
1822 * Align selected nodes on the specified axis.
1823 */
1824 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1825 {
1826 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1827 return;
1828 }
1830 if ( !nodepath->selected->next ) { // only one node selected
1831 return;
1832 }
1833 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1834 NR::Point dest(pNode->pos);
1835 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1836 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1837 if (pNode) {
1838 dest[axis] = pNode->pos[axis];
1839 sp_node_moveto(pNode, dest);
1840 }
1841 }
1843 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1844 }
1846 /// Helper struct.
1847 struct NodeSort
1848 {
1849 Inkscape::NodePath::Node *_node;
1850 NR::Coord _coord;
1851 /// \todo use vectorof pointers instead of calling copy ctor
1852 NodeSort(Inkscape::NodePath::Node *node, NR::Dim2 axis) :
1853 _node(node), _coord(node->pos[axis])
1854 {}
1856 };
1858 static bool operator<(NodeSort const &a, NodeSort const &b)
1859 {
1860 return (a._coord < b._coord);
1861 }
1863 /**
1864 * Distribute selected nodes on the specified axis.
1865 */
1866 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
1867 {
1868 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1869 return;
1870 }
1872 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1873 return;
1874 }
1876 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1877 std::vector<NodeSort> sorted;
1878 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1879 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1880 if (pNode) {
1881 NodeSort n(pNode, axis);
1882 sorted.push_back(n);
1883 //dest[axis] = pNode->pos[axis];
1884 //sp_node_moveto(pNode, dest);
1885 }
1886 }
1887 std::sort(sorted.begin(), sorted.end());
1888 unsigned int len = sorted.size();
1889 //overall bboxes span
1890 float dist = (sorted.back()._coord -
1891 sorted.front()._coord);
1892 //new distance between each bbox
1893 float step = (dist) / (len - 1);
1894 float pos = sorted.front()._coord;
1895 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1896 it < sorted.end();
1897 it ++ )
1898 {
1899 NR::Point dest((*it)._node->pos);
1900 dest[axis] = pos;
1901 sp_node_moveto((*it)._node, dest);
1902 pos += step;
1903 }
1905 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1906 }
1909 /**
1910 * Call sp_nodepath_line_add_node() for all selected segments.
1911 */
1912 void
1913 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1914 {
1915 if (!nodepath) {
1916 return;
1917 }
1919 GList *nl = NULL;
1921 int n_added = 0;
1923 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1924 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1925 g_assert(t->selected);
1926 if (t->p.other && t->p.other->selected) {
1927 nl = g_list_prepend(nl, t);
1928 }
1929 }
1931 while (nl) {
1932 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1933 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1934 sp_nodepath_node_select(n, TRUE, FALSE);
1935 n_added ++;
1936 nl = g_list_remove(nl, t);
1937 }
1939 /** \todo fixme: adjust ? */
1940 sp_nodepath_update_handles(nodepath);
1942 if (n_added > 1) {
1943 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1944 } else if (n_added > 0) {
1945 sp_nodepath_update_repr(nodepath, _("Add node"));
1946 }
1948 sp_nodepath_update_statusbar(nodepath);
1949 }
1951 /**
1952 * Select segment nearest to point
1953 */
1954 void
1955 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p, bool toggle)
1956 {
1957 if (!nodepath) {
1958 return;
1959 }
1961 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1962 Geom::PathVector const &pathv = curve->get_pathvector();
1963 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
1964 if (!pvpos) {
1965 g_print ("Possible error?\n");
1966 return;
1967 }
1969 // calculate index for nodepath's representation.
1970 unsigned int segment_index = floor(pvpos->t) + 1;
1971 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
1972 segment_index += pathv[i].size() + 1;
1973 if (pathv[i].closed()) {
1974 segment_index += 1;
1975 }
1976 }
1978 curve->unref();
1980 //find segment to segment
1981 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
1983 //fixme: this can return NULL, so check before proceeding.
1984 g_return_if_fail(e != NULL);
1986 gboolean force = FALSE;
1987 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1988 force = TRUE;
1989 }
1990 sp_nodepath_node_select(e, (gboolean) toggle, force);
1991 if (e->p.other)
1992 sp_nodepath_node_select(e->p.other, TRUE, force);
1994 sp_nodepath_update_handles(nodepath);
1996 sp_nodepath_update_statusbar(nodepath);
1997 }
1999 /**
2000 * Add a node nearest to point
2001 */
2002 void
2003 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, NR::Point p)
2004 {
2005 if (!nodepath) {
2006 return;
2007 }
2009 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2010 Geom::PathVector const &pathv = curve->get_pathvector();
2011 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2012 if (!pvpos) {
2013 g_print ("Possible error?\n");
2014 return;
2015 }
2017 // calculate index for nodepath's representation.
2018 double int_part;
2019 double t = std::modf(pvpos->t, &int_part);
2020 unsigned int segment_index = (unsigned int)int_part + 1;
2021 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2022 segment_index += pathv[i].size() + 1;
2023 if (pathv[i].closed()) {
2024 segment_index += 1;
2025 }
2026 }
2028 curve->unref();
2030 //find segment to split
2031 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2033 //don't know why but t seems to flip for lines
2034 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2035 t = 1.0 - t;
2036 }
2038 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2039 sp_nodepath_node_select(n, FALSE, TRUE);
2041 /* fixme: adjust ? */
2042 sp_nodepath_update_handles(nodepath);
2044 sp_nodepath_update_repr(nodepath, _("Add node"));
2046 sp_nodepath_update_statusbar(nodepath);
2047 }
2049 /*
2050 * Adjusts a segment so that t moves by a certain delta for dragging
2051 * converts lines to curves
2052 *
2053 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2054 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2055 */
2056 void
2057 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, NR::Point delta)
2058 {
2059 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2061 //fixme: e and e->p can be NULL, so check for those before proceeding
2062 g_return_if_fail(e != NULL);
2063 g_return_if_fail(&e->p != NULL);
2065 /* feel good is an arbitrary parameter that distributes the delta between handles
2066 * if t of the drag point is less than 1/6 distance form the endpoint only
2067 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2068 */
2069 double feel_good;
2070 if (t <= 1.0 / 6.0)
2071 feel_good = 0;
2072 else if (t <= 0.5)
2073 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2074 else if (t <= 5.0 / 6.0)
2075 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2076 else
2077 feel_good = 1;
2079 //if we're dragging a line convert it to a curve
2080 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2081 sp_nodepath_set_line_type(e, NR_CURVETO);
2082 }
2084 NR::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2085 NR::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2086 e->p.other->n.pos += offsetcoord0;
2087 e->p.pos += offsetcoord1;
2089 // adjust handles of adjacent nodes where necessary
2090 sp_node_adjust_handle(e,1);
2091 sp_node_adjust_handle(e->p.other,-1);
2093 sp_nodepath_update_handles(e->subpath->nodepath);
2095 update_object(e->subpath->nodepath);
2097 sp_nodepath_update_statusbar(e->subpath->nodepath);
2098 }
2101 /**
2102 * Call sp_nodepath_break() for all selected segments.
2103 */
2104 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2105 {
2106 if (!nodepath) return;
2108 GList *tempin = g_list_copy(nodepath->selected);
2109 GList *temp = NULL;
2110 for (GList *l = tempin; l != NULL; l = l->next) {
2111 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2112 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2113 if (nn == NULL) continue; // no break, no new node
2114 temp = g_list_prepend(temp, nn);
2115 }
2116 g_list_free(tempin);
2118 if (temp) {
2119 sp_nodepath_deselect(nodepath);
2120 }
2121 for (GList *l = temp; l != NULL; l = l->next) {
2122 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2123 }
2125 sp_nodepath_update_handles(nodepath);
2127 sp_nodepath_update_repr(nodepath, _("Break path"));
2128 }
2130 /**
2131 * Duplicate the selected node(s).
2132 */
2133 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2134 {
2135 if (!nodepath) {
2136 return;
2137 }
2139 GList *temp = NULL;
2140 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2141 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2142 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2143 if (nn == NULL) continue; // could not duplicate
2144 temp = g_list_prepend(temp, nn);
2145 }
2147 if (temp) {
2148 sp_nodepath_deselect(nodepath);
2149 }
2150 for (GList *l = temp; l != NULL; l = l->next) {
2151 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2152 }
2154 sp_nodepath_update_handles(nodepath);
2156 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2157 }
2159 /**
2160 * Internal function to join two nodes by merging them into one.
2161 */
2162 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2163 {
2164 /* a and b are endpoints */
2166 // if one of the two nodes is mouseovered, fix its position
2167 NR::Point c;
2168 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2169 c = a->pos;
2170 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2171 c = b->pos;
2172 } else {
2173 // otherwise, move joined node to the midpoint
2174 c = (a->pos + b->pos) / 2;
2175 }
2177 if (a->subpath == b->subpath) {
2178 Inkscape::NodePath::SubPath *sp = a->subpath;
2179 sp_nodepath_subpath_close(sp);
2180 sp_node_moveto (sp->first, c);
2182 sp_nodepath_update_handles(sp->nodepath);
2183 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2184 return;
2185 }
2187 /* a and b are separate subpaths */
2188 Inkscape::NodePath::SubPath *sa = a->subpath;
2189 Inkscape::NodePath::SubPath *sb = b->subpath;
2190 NR::Point p;
2191 Inkscape::NodePath::Node *n;
2192 NRPathcode code;
2193 if (a == sa->first) {
2194 // we will now reverse sa, so that a is its last node, not first, and drop that node
2195 p = sa->first->n.pos;
2196 code = (NRPathcode)sa->first->n.other->code;
2197 // create new subpath
2198 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2199 // create a first moveto node on it
2200 n = sa->last;
2201 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2202 n = n->p.other;
2203 if (n == sa->first) n = NULL;
2204 while (n) {
2205 // copy the rest of the nodes from sa to t, going backwards
2206 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2207 n = n->p.other;
2208 if (n == sa->first) n = NULL;
2209 }
2210 // replace sa with t
2211 sp_nodepath_subpath_destroy(sa);
2212 sa = t;
2213 } else if (a == sa->last) {
2214 // a is already last, just drop it
2215 p = sa->last->p.pos;
2216 code = (NRPathcode)sa->last->code;
2217 sp_nodepath_node_destroy(sa->last);
2218 } else {
2219 code = NR_END;
2220 g_assert_not_reached();
2221 }
2223 if (b == sb->first) {
2224 // copy all nodes from b to a, forward
2225 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2226 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2227 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2228 }
2229 } else if (b == sb->last) {
2230 // copy all nodes from b to a, backward
2231 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2232 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2233 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2234 }
2235 } else {
2236 g_assert_not_reached();
2237 }
2238 /* and now destroy sb */
2240 sp_nodepath_subpath_destroy(sb);
2242 sp_nodepath_update_handles(sa->nodepath);
2244 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2246 sp_nodepath_update_statusbar(nodepath);
2247 }
2249 /**
2250 * Internal function to join two nodes by adding a segment between them.
2251 */
2252 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2253 {
2254 if (a->subpath == b->subpath) {
2255 Inkscape::NodePath::SubPath *sp = a->subpath;
2257 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2258 sp->closed = TRUE;
2260 sp->first->p.other = sp->last;
2261 sp->last->n.other = sp->first;
2263 sp_node_handle_mirror_p_to_n(sp->last);
2264 sp_node_handle_mirror_n_to_p(sp->first);
2266 sp->first->code = sp->last->code;
2267 sp->first = sp->last;
2269 sp_nodepath_update_handles(sp->nodepath);
2271 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2273 return;
2274 }
2276 /* a and b are separate subpaths */
2277 Inkscape::NodePath::SubPath *sa = a->subpath;
2278 Inkscape::NodePath::SubPath *sb = b->subpath;
2280 Inkscape::NodePath::Node *n;
2281 NR::Point p;
2282 NRPathcode code;
2283 if (a == sa->first) {
2284 code = (NRPathcode) sa->first->n.other->code;
2285 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2286 n = sa->last;
2287 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2288 for (n = n->p.other; n != NULL; n = n->p.other) {
2289 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2290 }
2291 sp_nodepath_subpath_destroy(sa);
2292 sa = t;
2293 } else if (a == sa->last) {
2294 code = (NRPathcode)sa->last->code;
2295 } else {
2296 code = NR_END;
2297 g_assert_not_reached();
2298 }
2300 if (b == sb->first) {
2301 n = sb->first;
2302 sp_node_handle_mirror_p_to_n(sa->last);
2303 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2304 sp_node_handle_mirror_n_to_p(sa->last);
2305 for (n = n->n.other; n != NULL; n = n->n.other) {
2306 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2307 }
2308 } else if (b == sb->last) {
2309 n = sb->last;
2310 sp_node_handle_mirror_p_to_n(sa->last);
2311 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2312 sp_node_handle_mirror_n_to_p(sa->last);
2313 for (n = n->p.other; n != NULL; n = n->p.other) {
2314 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2315 }
2316 } else {
2317 g_assert_not_reached();
2318 }
2319 /* and now destroy sb */
2321 sp_nodepath_subpath_destroy(sb);
2323 sp_nodepath_update_handles(sa->nodepath);
2325 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2326 }
2328 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2330 /**
2331 * Internal function to handle joining two nodes.
2332 */
2333 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2334 {
2335 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2337 if (g_list_length(nodepath->selected) != 2) {
2338 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2339 return;
2340 }
2342 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2343 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2345 g_assert(a != b);
2346 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2347 // someone tried to join an orphan node (i.e. a single-node subpath).
2348 // this is not worth an error message, just fail silently.
2349 return;
2350 }
2352 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2353 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2354 return;
2355 }
2357 switch(mode) {
2358 case NODE_JOIN_ENDPOINTS:
2359 do_node_selected_join(nodepath, a, b);
2360 break;
2361 case NODE_JOIN_SEGMENT:
2362 do_node_selected_join_segment(nodepath, a, b);
2363 break;
2364 }
2365 }
2367 /**
2368 * Join two nodes by merging them into one.
2369 */
2370 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2371 {
2372 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2373 }
2375 /**
2376 * Join two nodes by adding a segment between them.
2377 */
2378 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2379 {
2380 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2381 }
2383 /**
2384 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2385 */
2386 void sp_node_delete_preserve(GList *nodes_to_delete)
2387 {
2388 GSList *nodepaths = NULL;
2390 while (nodes_to_delete) {
2391 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2392 Inkscape::NodePath::SubPath *sp = node->subpath;
2393 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2394 Inkscape::NodePath::Node *sample_cursor = NULL;
2395 Inkscape::NodePath::Node *sample_end = NULL;
2396 Inkscape::NodePath::Node *delete_cursor = node;
2397 bool just_delete = false;
2399 //find the start of this contiguous selection
2400 //move left to the first node that is not selected
2401 //or the start of the non-closed path
2402 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2403 delete_cursor = curr;
2404 }
2406 //just delete at the beginning of an open path
2407 if (!delete_cursor->p.other) {
2408 sample_cursor = delete_cursor;
2409 just_delete = true;
2410 } else {
2411 sample_cursor = delete_cursor->p.other;
2412 }
2414 //calculate points for each segment
2415 int rate = 5;
2416 float period = 1.0 / rate;
2417 std::vector<NR::Point> data;
2418 if (!just_delete) {
2419 data.push_back(sample_cursor->pos);
2420 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2421 //just delete at the end of an open path
2422 if (!sp->closed && curr == sp->last) {
2423 just_delete = true;
2424 break;
2425 }
2427 //sample points on the contiguous selected segment
2428 NR::Point *bez;
2429 bez = new NR::Point [4];
2430 bez[0] = curr->pos;
2431 bez[1] = curr->n.pos;
2432 bez[2] = curr->n.other->p.pos;
2433 bez[3] = curr->n.other->pos;
2434 for (int i=1; i<rate; i++) {
2435 gdouble t = i * period;
2436 NR::Point p = bezier_pt(3, bez, t);
2437 data.push_back(p);
2438 }
2439 data.push_back(curr->n.other->pos);
2441 sample_end = curr->n.other;
2442 //break if we've come full circle or hit the end of the selection
2443 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2444 break;
2445 }
2446 }
2447 }
2449 if (!just_delete) {
2450 //calculate the best fitting single segment and adjust the endpoints
2451 NR::Point *adata;
2452 adata = new NR::Point [data.size()];
2453 copy(data.begin(), data.end(), adata);
2455 NR::Point *bez;
2456 bez = new NR::Point [4];
2457 //would decreasing error create a better fitting approximation?
2458 gdouble error = 1.0;
2459 gint ret;
2460 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2462 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2463 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2464 //the resulting nodes behave as expected.
2465 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2466 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2467 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2468 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2470 //adjust endpoints
2471 sample_cursor->n.pos = bez[1];
2472 sample_end->p.pos = bez[2];
2473 }
2475 //destroy this contiguous selection
2476 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2477 Inkscape::NodePath::Node *temp = delete_cursor;
2478 if (delete_cursor->n.other == delete_cursor) {
2479 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2480 delete_cursor = NULL;
2481 } else {
2482 delete_cursor = delete_cursor->n.other;
2483 }
2484 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2485 sp_nodepath_node_destroy(temp);
2486 }
2488 sp_nodepath_update_handles(nodepath);
2490 if (!g_slist_find(nodepaths, nodepath))
2491 nodepaths = g_slist_prepend (nodepaths, nodepath);
2492 }
2494 for (GSList *i = nodepaths; i; i = i->next) {
2495 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2496 // different nodepaths will give us one undo event per nodepath
2497 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2499 // if the entire nodepath is removed, delete the selected object.
2500 if (nodepath->subpaths == NULL ||
2501 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2502 //at least 2
2503 sp_nodepath_get_node_count(nodepath) < 2) {
2504 SPDocument *document = sp_desktop_document (nodepath->desktop);
2505 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2506 //delete this nodepath's object, not the entire selection! (though at this time, this
2507 //does not matter)
2508 sp_selection_delete(nodepath->desktop);
2509 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2510 _("Delete nodes"));
2511 } else {
2512 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2513 sp_nodepath_update_statusbar(nodepath);
2514 }
2515 }
2517 g_slist_free (nodepaths);
2518 }
2520 /**
2521 * Delete one or more selected nodes.
2522 */
2523 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2524 {
2525 if (!nodepath) return;
2526 if (!nodepath->selected) return;
2528 /** \todo fixme: do it the right way */
2529 while (nodepath->selected) {
2530 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2531 sp_nodepath_node_destroy(node);
2532 }
2535 //clean up the nodepath (such as for trivial subpaths)
2536 sp_nodepath_cleanup(nodepath);
2538 sp_nodepath_update_handles(nodepath);
2540 // if the entire nodepath is removed, delete the selected object.
2541 if (nodepath->subpaths == NULL ||
2542 sp_nodepath_get_node_count(nodepath) < 2) {
2543 SPDocument *document = sp_desktop_document (nodepath->desktop);
2544 sp_selection_delete(nodepath->desktop);
2545 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2546 _("Delete nodes"));
2547 return;
2548 }
2550 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2552 sp_nodepath_update_statusbar(nodepath);
2553 }
2555 /**
2556 * Delete one or more segments between two selected nodes.
2557 * This is the code for 'split'.
2558 */
2559 void
2560 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2561 {
2562 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2563 Inkscape::NodePath::Node *curr, *next; //Iterators
2565 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2567 if (g_list_length(nodepath->selected) != 2) {
2568 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2569 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2570 return;
2571 }
2573 //Selected nodes, not inclusive
2574 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2575 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2577 if ( ( a==b) || //same node
2578 (a->subpath != b->subpath ) || //not the same path
2579 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2580 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2581 {
2582 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2583 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2584 return;
2585 }
2587 //###########################################
2588 //# BEGIN EDITS
2589 //###########################################
2590 //##################################
2591 //# CLOSED PATH
2592 //##################################
2593 if (a->subpath->closed) {
2596 gboolean reversed = FALSE;
2598 //Since we can go in a circle, we need to find the shorter distance.
2599 // a->b or b->a
2600 start = end = NULL;
2601 int distance = 0;
2602 int minDistance = 0;
2603 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2604 if (curr==b) {
2605 //printf("a to b:%d\n", distance);
2606 start = a;//go from a to b
2607 end = b;
2608 minDistance = distance;
2609 //printf("A to B :\n");
2610 break;
2611 }
2612 distance++;
2613 }
2615 //try again, the other direction
2616 distance = 0;
2617 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2618 if (curr==a) {
2619 //printf("b to a:%d\n", distance);
2620 if (distance < minDistance) {
2621 start = b; //we go from b to a
2622 end = a;
2623 reversed = TRUE;
2624 //printf("B to A\n");
2625 }
2626 break;
2627 }
2628 distance++;
2629 }
2632 //Copy everything from 'end' to 'start' to a new subpath
2633 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2634 for (curr=end ; curr ; curr=curr->n.other) {
2635 NRPathcode code = (NRPathcode) curr->code;
2636 if (curr == end)
2637 code = NR_MOVETO;
2638 sp_nodepath_node_new(t, NULL,
2639 (Inkscape::NodePath::NodeType)curr->type, code,
2640 &curr->p.pos, &curr->pos, &curr->n.pos);
2641 if (curr == start)
2642 break;
2643 }
2644 sp_nodepath_subpath_destroy(a->subpath);
2647 }
2651 //##################################
2652 //# OPEN PATH
2653 //##################################
2654 else {
2656 //We need to get the direction of the list between A and B
2657 //Can we walk from a to b?
2658 start = end = NULL;
2659 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2660 if (curr==b) {
2661 start = a; //did it! we go from a to b
2662 end = b;
2663 //printf("A to B\n");
2664 break;
2665 }
2666 }
2667 if (!start) {//didn't work? let's try the other direction
2668 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2669 if (curr==a) {
2670 start = b; //did it! we go from b to a
2671 end = a;
2672 //printf("B to A\n");
2673 break;
2674 }
2675 }
2676 }
2677 if (!start) {
2678 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2679 _("Cannot find path between nodes."));
2680 return;
2681 }
2685 //Copy everything after 'end' to a new subpath
2686 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2687 for (curr=end ; curr ; curr=curr->n.other) {
2688 NRPathcode code = (NRPathcode) curr->code;
2689 if (curr == end)
2690 code = NR_MOVETO;
2691 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2692 &curr->p.pos, &curr->pos, &curr->n.pos);
2693 }
2695 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2696 for (curr = start->n.other ; curr ; curr=next) {
2697 next = curr->n.other;
2698 sp_nodepath_node_destroy(curr);
2699 }
2701 }
2702 //###########################################
2703 //# END EDITS
2704 //###########################################
2706 //clean up the nodepath (such as for trivial subpaths)
2707 sp_nodepath_cleanup(nodepath);
2709 sp_nodepath_update_handles(nodepath);
2711 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2713 sp_nodepath_update_statusbar(nodepath);
2714 }
2716 /**
2717 * Call sp_nodepath_set_line() for all selected segments.
2718 */
2719 void
2720 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2721 {
2722 if (nodepath == NULL) return;
2724 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2725 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2726 g_assert(n->selected);
2727 if (n->p.other && n->p.other->selected) {
2728 sp_nodepath_set_line_type(n, code);
2729 }
2730 }
2732 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2733 }
2735 /**
2736 * Call sp_nodepath_convert_node_type() for all selected nodes.
2737 */
2738 void
2739 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2740 {
2741 if (nodepath == NULL) return;
2743 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2745 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2746 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2747 }
2749 sp_nodepath_update_repr(nodepath, _("Change node type"));
2750 }
2752 /**
2753 * Change select status of node, update its own and neighbour handles.
2754 */
2755 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2756 {
2757 node->selected = selected;
2759 if (selected) {
2760 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2761 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2762 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2763 sp_knot_update_ctrl(node->knot);
2764 } else {
2765 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2766 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2767 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2768 sp_knot_update_ctrl(node->knot);
2769 }
2771 sp_node_update_handles(node);
2772 if (node->n.other) sp_node_update_handles(node->n.other);
2773 if (node->p.other) sp_node_update_handles(node->p.other);
2774 }
2776 /**
2777 \brief Select a node
2778 \param node The node to select
2779 \param incremental If true, add to selection, otherwise deselect others
2780 \param override If true, always select this node, otherwise toggle selected status
2781 */
2782 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2783 {
2784 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2786 if (incremental) {
2787 if (override) {
2788 if (!g_list_find(nodepath->selected, node)) {
2789 nodepath->selected = g_list_prepend(nodepath->selected, node);
2790 }
2791 sp_node_set_selected(node, TRUE);
2792 } else { // toggle
2793 if (node->selected) {
2794 g_assert(g_list_find(nodepath->selected, node));
2795 nodepath->selected = g_list_remove(nodepath->selected, node);
2796 } else {
2797 g_assert(!g_list_find(nodepath->selected, node));
2798 nodepath->selected = g_list_prepend(nodepath->selected, node);
2799 }
2800 sp_node_set_selected(node, !node->selected);
2801 }
2802 } else {
2803 sp_nodepath_deselect(nodepath);
2804 nodepath->selected = g_list_prepend(nodepath->selected, node);
2805 sp_node_set_selected(node, TRUE);
2806 }
2808 sp_nodepath_update_statusbar(nodepath);
2809 }
2812 /**
2813 \brief Deselect all nodes in the nodepath
2814 */
2815 void
2816 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2817 {
2818 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2820 while (nodepath->selected) {
2821 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2822 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2823 }
2824 sp_nodepath_update_statusbar(nodepath);
2825 }
2827 /**
2828 \brief Select or invert selection of all nodes in the nodepath
2829 */
2830 void
2831 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2832 {
2833 if (!nodepath) return;
2835 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2836 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2837 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2838 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2839 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2840 }
2841 }
2842 }
2844 /**
2845 * If nothing selected, does the same as sp_nodepath_select_all();
2846 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2847 * (i.e., similar to "select all in layer", with the "selected" subpaths
2848 * being treated as "layers" in the path).
2849 */
2850 void
2851 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2852 {
2853 if (!nodepath) return;
2855 if (g_list_length (nodepath->selected) == 0) {
2856 sp_nodepath_select_all (nodepath, invert);
2857 return;
2858 }
2860 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2861 GSList *subpaths = NULL;
2863 for (GList *l = copy; l != NULL; l = l->next) {
2864 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2865 Inkscape::NodePath::SubPath *subpath = n->subpath;
2866 if (!g_slist_find (subpaths, subpath))
2867 subpaths = g_slist_prepend (subpaths, subpath);
2868 }
2870 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2871 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2872 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2873 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2874 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2875 }
2876 }
2878 g_slist_free (subpaths);
2879 g_list_free (copy);
2880 }
2882 /**
2883 * \brief Select the node after the last selected; if none is selected,
2884 * select the first within path.
2885 */
2886 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2887 {
2888 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2890 Inkscape::NodePath::Node *last = NULL;
2891 if (nodepath->selected) {
2892 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2893 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2894 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2895 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2896 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2897 if (node->selected) {
2898 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2899 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2900 if (spl->next) { // there's a next subpath
2901 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2902 last = subpath_next->first;
2903 } else if (spl->prev) { // there's a previous subpath
2904 last = NULL; // to be set later to the first node of first subpath
2905 } else {
2906 last = node->n.other;
2907 }
2908 } else {
2909 last = node->n.other;
2910 }
2911 } else {
2912 if (node->n.other) {
2913 last = node->n.other;
2914 } else {
2915 if (spl->next) { // there's a next subpath
2916 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2917 last = subpath_next->first;
2918 } else if (spl->prev) { // there's a previous subpath
2919 last = NULL; // to be set later to the first node of first subpath
2920 } else {
2921 last = (Inkscape::NodePath::Node *) subpath->first;
2922 }
2923 }
2924 }
2925 }
2926 }
2927 }
2928 sp_nodepath_deselect(nodepath);
2929 }
2931 if (last) { // there's at least one more node after selected
2932 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2933 } else { // no more nodes, select the first one in first subpath
2934 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2935 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2936 }
2937 }
2939 /**
2940 * \brief Select the node before the first selected; if none is selected,
2941 * select the last within path
2942 */
2943 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2944 {
2945 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2947 Inkscape::NodePath::Node *last = NULL;
2948 if (nodepath->selected) {
2949 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2950 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2951 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2952 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2953 if (node->selected) {
2954 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2955 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2956 if (spl->prev) { // there's a prev subpath
2957 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2958 last = subpath_prev->last;
2959 } else if (spl->next) { // there's a next subpath
2960 last = NULL; // to be set later to the last node of last subpath
2961 } else {
2962 last = node->p.other;
2963 }
2964 } else {
2965 last = node->p.other;
2966 }
2967 } else {
2968 if (node->p.other) {
2969 last = node->p.other;
2970 } else {
2971 if (spl->prev) { // there's a prev subpath
2972 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2973 last = subpath_prev->last;
2974 } else if (spl->next) { // there's a next subpath
2975 last = NULL; // to be set later to the last node of last subpath
2976 } else {
2977 last = (Inkscape::NodePath::Node *) subpath->last;
2978 }
2979 }
2980 }
2981 }
2982 }
2983 }
2984 sp_nodepath_deselect(nodepath);
2985 }
2987 if (last) { // there's at least one more node before selected
2988 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2989 } else { // no more nodes, select the last one in last subpath
2990 GList *spl = g_list_last(nodepath->subpaths);
2991 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2992 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2993 }
2994 }
2996 /**
2997 * \brief Select all nodes that are within the rectangle.
2998 */
2999 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
3000 {
3001 if (!incremental) {
3002 sp_nodepath_deselect(nodepath);
3003 }
3005 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3006 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3007 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3008 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3010 if (b.contains(node->pos)) {
3011 sp_nodepath_node_select(node, TRUE, TRUE);
3012 }
3013 }
3014 }
3015 }
3018 void
3019 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3020 {
3021 g_assert (n);
3022 g_assert (nodepath);
3023 g_assert (n->subpath->nodepath == nodepath);
3025 if (g_list_length (nodepath->selected) == 0) {
3026 if (grow > 0) {
3027 sp_nodepath_node_select(n, TRUE, TRUE);
3028 }
3029 return;
3030 }
3032 if (g_list_length (nodepath->selected) == 1) {
3033 if (grow < 0) {
3034 sp_nodepath_deselect (nodepath);
3035 return;
3036 }
3037 }
3039 double n_sel_range = 0, p_sel_range = 0;
3040 Inkscape::NodePath::Node *farthest_n_node = n;
3041 Inkscape::NodePath::Node *farthest_p_node = n;
3043 // Calculate ranges
3044 {
3045 double n_range = 0, p_range = 0;
3046 bool n_going = true, p_going = true;
3047 Inkscape::NodePath::Node *n_node = n;
3048 Inkscape::NodePath::Node *p_node = n;
3049 do {
3050 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3051 if (n_node && n_going)
3052 n_node = n_node->n.other;
3053 if (n_node == NULL) {
3054 n_going = false;
3055 } else {
3056 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3057 if (n_node->selected) {
3058 n_sel_range = n_range;
3059 farthest_n_node = n_node;
3060 }
3061 if (n_node == p_node) {
3062 n_going = false;
3063 p_going = false;
3064 }
3065 }
3066 if (p_node && p_going)
3067 p_node = p_node->p.other;
3068 if (p_node == NULL) {
3069 p_going = false;
3070 } else {
3071 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3072 if (p_node->selected) {
3073 p_sel_range = p_range;
3074 farthest_p_node = p_node;
3075 }
3076 if (p_node == n_node) {
3077 n_going = false;
3078 p_going = false;
3079 }
3080 }
3081 } while (n_going || p_going);
3082 }
3084 if (grow > 0) {
3085 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3086 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3087 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3088 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3089 }
3090 } else {
3091 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3092 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3093 } else if (farthest_p_node && farthest_p_node->selected) {
3094 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3095 }
3096 }
3097 }
3099 void
3100 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3101 {
3102 g_assert (n);
3103 g_assert (nodepath);
3104 g_assert (n->subpath->nodepath == nodepath);
3106 if (g_list_length (nodepath->selected) == 0) {
3107 if (grow > 0) {
3108 sp_nodepath_node_select(n, TRUE, TRUE);
3109 }
3110 return;
3111 }
3113 if (g_list_length (nodepath->selected) == 1) {
3114 if (grow < 0) {
3115 sp_nodepath_deselect (nodepath);
3116 return;
3117 }
3118 }
3120 Inkscape::NodePath::Node *farthest_selected = NULL;
3121 double farthest_dist = 0;
3123 Inkscape::NodePath::Node *closest_unselected = NULL;
3124 double closest_dist = NR_HUGE;
3126 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3127 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3128 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3129 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3130 if (node == n)
3131 continue;
3132 if (node->selected) {
3133 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3134 farthest_dist = NR::L2(node->pos - n->pos);
3135 farthest_selected = node;
3136 }
3137 } else {
3138 if (NR::L2(node->pos - n->pos) < closest_dist) {
3139 closest_dist = NR::L2(node->pos - n->pos);
3140 closest_unselected = node;
3141 }
3142 }
3143 }
3144 }
3146 if (grow > 0) {
3147 if (closest_unselected) {
3148 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3149 }
3150 } else {
3151 if (farthest_selected) {
3152 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3153 }
3154 }
3155 }
3158 /**
3159 \brief Saves all nodes' and handles' current positions in their origin members
3160 */
3161 void
3162 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3163 {
3164 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3165 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3166 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3167 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3168 n->origin = n->pos;
3169 n->p.origin = n->p.pos;
3170 n->n.origin = n->n.pos;
3171 }
3172 }
3173 }
3175 /**
3176 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3177 */
3178 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3179 {
3180 GList *r = NULL;
3181 if (nodepath->selected) {
3182 guint i = 0;
3183 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3184 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3185 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3186 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3187 i++;
3188 if (node->selected) {
3189 r = g_list_append(r, GINT_TO_POINTER(i));
3190 }
3191 }
3192 }
3193 }
3194 return r;
3195 }
3197 /**
3198 \brief Restores selection by selecting nodes whose positions are in the list
3199 */
3200 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3201 {
3202 sp_nodepath_deselect(nodepath);
3204 guint i = 0;
3205 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3206 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3207 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3208 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3209 i++;
3210 if (g_list_find(r, GINT_TO_POINTER(i))) {
3211 sp_nodepath_node_select(node, TRUE, TRUE);
3212 }
3213 }
3214 }
3215 }
3218 /**
3219 \brief Adjusts handle according to node type and line code.
3220 */
3221 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3222 {
3223 g_assert(node);
3225 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3226 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3228 // nothing to do if we are an end node
3229 if (me->other == NULL) return;
3230 if (other->other == NULL) return;
3232 // nothing to do if we are a cusp node
3233 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3235 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3236 NRPathcode mecode;
3237 if (which_adjust == 1) {
3238 mecode = (NRPathcode)me->other->code;
3239 } else {
3240 mecode = (NRPathcode)node->code;
3241 }
3242 if (mecode == NR_LINETO) return;
3244 if (sp_node_side_is_line(node, other)) {
3245 // other is a line, and we are either smooth or symm
3246 Inkscape::NodePath::Node *othernode = other->other;
3247 double len = NR::L2(me->pos - node->pos);
3248 NR::Point delta = node->pos - othernode->pos;
3249 double linelen = NR::L2(delta);
3250 if (linelen < 1e-18)
3251 return;
3252 me->pos = node->pos + (len / linelen)*delta;
3253 return;
3254 }
3256 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3257 // symmetrize
3258 me->pos = 2 * node->pos - other->pos;
3259 return;
3260 } else {
3261 // smoothify
3262 double len = NR::L2(me->pos - node->pos);
3263 NR::Point delta = other->pos - node->pos;
3264 double otherlen = NR::L2(delta);
3265 if (otherlen < 1e-18) return;
3266 me->pos = node->pos - (len / otherlen) * delta;
3267 }
3268 }
3270 /**
3271 \brief Adjusts both handles according to node type and line code
3272 */
3273 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3274 {
3275 g_assert(node);
3277 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3279 /* we are either smooth or symm */
3281 if (node->p.other == NULL) return;
3282 if (node->n.other == NULL) return;
3284 if (sp_node_side_is_line(node, &node->p)) {
3285 sp_node_adjust_handle(node, 1);
3286 return;
3287 }
3289 if (sp_node_side_is_line(node, &node->n)) {
3290 sp_node_adjust_handle(node, -1);
3291 return;
3292 }
3294 /* both are curves */
3295 NR::Point const delta( node->n.pos - node->p.pos );
3297 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3298 node->p.pos = node->pos - delta / 2;
3299 node->n.pos = node->pos + delta / 2;
3300 return;
3301 }
3303 /* We are smooth */
3304 double plen = NR::L2(node->p.pos - node->pos);
3305 if (plen < 1e-18) return;
3306 double nlen = NR::L2(node->n.pos - node->pos);
3307 if (nlen < 1e-18) return;
3308 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3309 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3310 }
3312 /**
3313 * Node event callback.
3314 */
3315 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3316 {
3317 gboolean ret = FALSE;
3318 switch (event->type) {
3319 case GDK_ENTER_NOTIFY:
3320 Inkscape::NodePath::Path::active_node = n;
3321 break;
3322 case GDK_LEAVE_NOTIFY:
3323 Inkscape::NodePath::Path::active_node = NULL;
3324 break;
3325 case GDK_SCROLL:
3326 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3327 switch (event->scroll.direction) {
3328 case GDK_SCROLL_UP:
3329 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3330 break;
3331 case GDK_SCROLL_DOWN:
3332 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3333 break;
3334 default:
3335 break;
3336 }
3337 ret = TRUE;
3338 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3339 switch (event->scroll.direction) {
3340 case GDK_SCROLL_UP:
3341 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3342 break;
3343 case GDK_SCROLL_DOWN:
3344 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3345 break;
3346 default:
3347 break;
3348 }
3349 ret = TRUE;
3350 }
3351 break;
3352 case GDK_KEY_PRESS:
3353 switch (get_group0_keyval (&event->key)) {
3354 case GDK_space:
3355 if (event->key.state & GDK_BUTTON1_MASK) {
3356 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3357 stamp_repr(nodepath);
3358 ret = TRUE;
3359 }
3360 break;
3361 case GDK_Page_Up:
3362 if (event->key.state & GDK_CONTROL_MASK) {
3363 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3364 } else {
3365 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3366 }
3367 break;
3368 case GDK_Page_Down:
3369 if (event->key.state & GDK_CONTROL_MASK) {
3370 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3371 } else {
3372 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3373 }
3374 break;
3375 default:
3376 break;
3377 }
3378 break;
3379 default:
3380 break;
3381 }
3383 return ret;
3384 }
3386 /**
3387 * Handle keypress on node; directly called.
3388 */
3389 gboolean node_key(GdkEvent *event)
3390 {
3391 Inkscape::NodePath::Path *np;
3393 // there is no way to verify nodes so set active_node to nil when deleting!!
3394 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3396 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3397 gint ret = FALSE;
3398 switch (get_group0_keyval (&event->key)) {
3399 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3400 case GDK_BackSpace:
3401 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3402 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3403 sp_nodepath_update_repr(np, _("Delete node"));
3404 Inkscape::NodePath::Path::active_node = NULL;
3405 ret = TRUE;
3406 break;
3407 case GDK_c:
3408 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3409 ret = TRUE;
3410 break;
3411 case GDK_s:
3412 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3413 ret = TRUE;
3414 break;
3415 case GDK_y:
3416 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3417 ret = TRUE;
3418 break;
3419 case GDK_b:
3420 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3421 ret = TRUE;
3422 break;
3423 }
3424 return ret;
3425 }
3426 return FALSE;
3427 }
3429 /**
3430 * Mouseclick on node callback.
3431 */
3432 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3433 {
3434 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3436 if (state & GDK_CONTROL_MASK) {
3437 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3439 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3440 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3441 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3442 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3443 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3444 } else {
3445 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3446 }
3447 sp_nodepath_update_repr(nodepath, _("Change node type"));
3448 sp_nodepath_update_statusbar(nodepath);
3450 } else { //ctrl+alt+click: delete node
3451 GList *node_to_delete = NULL;
3452 node_to_delete = g_list_append(node_to_delete, n);
3453 sp_node_delete_preserve(node_to_delete);
3454 }
3456 } else {
3457 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3458 }
3459 }
3461 /**
3462 * Mouse grabbed node callback.
3463 */
3464 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3465 {
3466 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3468 if (!n->selected) {
3469 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3470 }
3472 n->is_dragging = true;
3473 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3475 sp_nodepath_remember_origins (n->subpath->nodepath);
3476 }
3478 /**
3479 * Mouse ungrabbed node callback.
3480 */
3481 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3482 {
3483 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3485 n->dragging_out = NULL;
3486 n->is_dragging = false;
3487 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3489 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3490 }
3492 /**
3493 * The point on a line, given by its angle, closest to the given point.
3494 * \param p A point.
3495 * \param a Angle of the line; it is assumed to go through coordinate origin.
3496 * \param closest Pointer to the point struct where the result is stored.
3497 * \todo FIXME: use dot product perhaps?
3498 */
3499 static void point_line_closest(NR::Point *p, double a, NR::Point *closest)
3500 {
3501 if (a == HUGE_VAL) { // vertical
3502 *closest = NR::Point(0, (*p)[NR::Y]);
3503 } else {
3504 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3505 (*closest)[NR::Y] = a * (*closest)[NR::X];
3506 }
3507 }
3509 /**
3510 * Distance from the point to a line given by its angle.
3511 * \param p A point.
3512 * \param a Angle of the line; it is assumed to go through coordinate origin.
3513 */
3514 static double point_line_distance(NR::Point *p, double a)
3515 {
3516 NR::Point c;
3517 point_line_closest(p, a, &c);
3518 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]));
3519 }
3521 /**
3522 * Callback for node "request" signal.
3523 * \todo fixme: This goes to "moved" event? (lauris)
3524 */
3525 static gboolean
3526 node_request(SPKnot */*knot*/, NR::Point *p, guint state, gpointer data)
3527 {
3528 double yn, xn, yp, xp;
3529 double an, ap, na, pa;
3530 double d_an, d_ap, d_na, d_pa;
3531 gboolean collinear = FALSE;
3532 NR::Point c;
3533 NR::Point pr;
3535 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3537 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3539 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3540 if ( (!n->subpath->nodepath->straight_path) &&
3541 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3542 || n->dragging_out ) )
3543 {
3544 NR::Point mouse = (*p);
3546 if (!n->dragging_out) {
3547 // This is the first drag-out event; find out which handle to drag out
3548 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3549 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3551 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3552 return FALSE;
3554 Inkscape::NodePath::NodeSide *opposite;
3555 if (appr_p > appr_n) { // closer to p
3556 n->dragging_out = &n->p;
3557 opposite = &n->n;
3558 n->code = NR_CURVETO;
3559 } else if (appr_p < appr_n) { // closer to n
3560 n->dragging_out = &n->n;
3561 opposite = &n->p;
3562 n->n.other->code = NR_CURVETO;
3563 } else { // p and n nodes are the same
3564 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3565 n->dragging_out = &n->p;
3566 opposite = &n->n;
3567 n->code = NR_CURVETO;
3568 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3569 n->dragging_out = &n->n;
3570 opposite = &n->p;
3571 n->n.other->code = NR_CURVETO;
3572 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3573 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);
3574 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);
3575 if (appr_other_p > appr_other_n) { // closer to other's p handle
3576 n->dragging_out = &n->n;
3577 opposite = &n->p;
3578 n->n.other->code = NR_CURVETO;
3579 } else { // closer to other's n handle
3580 n->dragging_out = &n->p;
3581 opposite = &n->n;
3582 n->code = NR_CURVETO;
3583 }
3584 }
3585 }
3587 // if there's another handle, make sure the one we drag out starts parallel to it
3588 if (opposite->pos != n->pos) {
3589 mouse = n->pos - NR::L2(mouse - n->pos) * NR::unit_vector(opposite->pos - n->pos);
3590 }
3592 // knots might not be created yet!
3593 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3594 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3595 }
3597 // pass this on to the handle-moved callback
3598 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3599 sp_node_update_handles(n);
3600 return TRUE;
3601 }
3603 if (state & GDK_CONTROL_MASK) { // constrained motion
3605 // calculate relative distances of handles
3606 // n handle:
3607 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3608 xn = n->n.pos[NR::X] - n->pos[NR::X];
3609 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3610 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3611 if (n->n.other) { // if there is the next point
3612 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3613 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3614 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3615 }
3616 }
3617 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3618 if (yn < 0) { xn = -xn; yn = -yn; }
3620 // p handle:
3621 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3622 xp = n->p.pos[NR::X] - n->pos[NR::X];
3623 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3624 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3625 if (n->p.other) {
3626 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3627 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3628 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3629 }
3630 }
3631 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3632 if (yp < 0) { xp = -xp; yp = -yp; }
3634 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3635 // sliding on handles, only if at least one of the handles is non-vertical
3636 // (otherwise it's the same as ctrl+drag anyway)
3638 // calculate angles of the handles
3639 if (xn == 0) {
3640 if (yn == 0) { // no handle, consider it the continuation of the other one
3641 an = 0;
3642 collinear = TRUE;
3643 }
3644 else an = 0; // vertical; set the angle to horizontal
3645 } else an = yn/xn;
3647 if (xp == 0) {
3648 if (yp == 0) { // no handle, consider it the continuation of the other one
3649 ap = an;
3650 }
3651 else ap = 0; // vertical; set the angle to horizontal
3652 } else ap = yp/xp;
3654 if (collinear) an = ap;
3656 // angles of the perpendiculars; HUGE_VAL means vertical
3657 if (an == 0) na = HUGE_VAL; else na = -1/an;
3658 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3660 // mouse point relative to the node's original pos
3661 pr = (*p) - n->origin;
3663 // distances to the four lines (two handles and two perpendiculars)
3664 d_an = point_line_distance(&pr, an);
3665 d_na = point_line_distance(&pr, na);
3666 d_ap = point_line_distance(&pr, ap);
3667 d_pa = point_line_distance(&pr, pa);
3669 // find out which line is the closest, save its closest point in c
3670 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3671 point_line_closest(&pr, an, &c);
3672 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3673 point_line_closest(&pr, ap, &c);
3674 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3675 point_line_closest(&pr, na, &c);
3676 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3677 point_line_closest(&pr, pa, &c);
3678 }
3680 // move the node to the closest point
3681 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3682 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3683 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3684 true);
3686 } else { // constraining to hor/vert
3688 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3689 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3690 (*p)[NR::X] - n->pos[NR::X],
3691 n->origin[NR::Y] - n->pos[NR::Y],
3692 true,
3693 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3694 } else { // snap to vert
3695 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3696 n->origin[NR::X] - n->pos[NR::X],
3697 (*p)[NR::Y] - n->pos[NR::Y],
3698 true,
3699 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3700 }
3701 }
3702 } else { // move freely
3703 if (n->is_dragging) {
3704 if (state & GDK_MOD1_MASK) { // sculpt
3705 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3706 } else {
3707 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3708 (*p)[NR::X] - n->pos[NR::X],
3709 (*p)[NR::Y] - n->pos[NR::Y],
3710 (state & GDK_SHIFT_MASK) == 0);
3711 }
3712 }
3713 }
3715 n->subpath->nodepath->desktop->scroll_to_point(p);
3717 return TRUE;
3718 }
3720 /**
3721 * Node handle clicked callback.
3722 */
3723 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3724 {
3725 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3727 if (state & GDK_CONTROL_MASK) { // "delete" handle
3728 if (n->p.knot == knot) {
3729 n->p.pos = n->pos;
3730 } else if (n->n.knot == knot) {
3731 n->n.pos = n->pos;
3732 }
3733 sp_node_update_handles(n);
3734 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3735 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3736 sp_nodepath_update_statusbar(nodepath);
3738 } else { // just select or add to selection, depending in Shift
3739 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3740 }
3741 }
3743 /**
3744 * Node handle grabbed callback.
3745 */
3746 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3747 {
3748 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3750 if (!n->selected) {
3751 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3752 }
3754 // remember the origin point of the handle
3755 if (n->p.knot == knot) {
3756 n->p.origin_radial = n->p.pos - n->pos;
3757 } else if (n->n.knot == knot) {
3758 n->n.origin_radial = n->n.pos - n->pos;
3759 } else {
3760 g_assert_not_reached();
3761 }
3763 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3764 }
3766 /**
3767 * Node handle ungrabbed callback.
3768 */
3769 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3770 {
3771 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3773 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3774 if (n->p.knot == knot) {
3775 n->p.origin_radial.a = 0;
3776 sp_knot_set_position(knot, n->p.pos, state);
3777 } else if (n->n.knot == knot) {
3778 n->n.origin_radial.a = 0;
3779 sp_knot_set_position(knot, n->n.pos, state);
3780 } else {
3781 g_assert_not_reached();
3782 }
3784 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3785 }
3787 /**
3788 * Node handle "request" signal callback.
3789 */
3790 static gboolean node_handle_request(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3791 {
3792 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3794 Inkscape::NodePath::NodeSide *me, *opposite;
3795 gint which;
3796 if (n->p.knot == knot) {
3797 me = &n->p;
3798 opposite = &n->n;
3799 which = -1;
3800 } else if (n->n.knot == knot) {
3801 me = &n->n;
3802 opposite = &n->p;
3803 which = 1;
3804 } else {
3805 me = opposite = NULL;
3806 which = 0;
3807 g_assert_not_reached();
3808 }
3810 SPDesktop *desktop = n->subpath->nodepath->desktop;
3811 SnapManager &m = desktop->namedview->snap_manager;
3812 m.setup(desktop, true, n->subpath->nodepath->item);
3813 Inkscape::SnappedPoint s;
3815 if ((state & GDK_SHIFT_MASK) != 0) {
3816 // We will not try to snap when the shift-key is pressed
3817 // so remove the old snap indicator and don't wait for it to time-out
3818 desktop->snapindicator->remove_snappoint();
3819 }
3821 Inkscape::NodePath::Node *othernode = opposite->other;
3822 if (othernode) {
3823 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3824 /* We are smooth node adjacent with line */
3825 NR::Point const delta = *p - n->pos;
3826 NR::Coord const len = NR::L2(delta);
3827 Inkscape::NodePath::Node *othernode = opposite->other;
3828 NR::Point const ndelta = n->pos - othernode->pos;
3829 NR::Coord const linelen = NR::L2(ndelta);
3830 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3831 NR::Coord const scal = dot(delta, ndelta) / linelen;
3832 (*p) = n->pos + (scal / linelen) * ndelta;
3833 }
3834 if ((state & GDK_SHIFT_MASK) == 0) {
3835 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p), Inkscape::Snapper::ConstraintLine(*p, ndelta));
3836 }
3837 } else {
3838 if ((state & GDK_SHIFT_MASK) == 0) {
3839 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3840 }
3841 }
3842 } else {
3843 if ((state & GDK_SHIFT_MASK) == 0) {
3844 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3845 }
3846 }
3848 Geom::Point pt2g = *p;
3849 s.getPoint(pt2g);
3850 *p = pt2g;
3852 sp_node_adjust_handle(n, -which);
3854 return FALSE;
3855 }
3857 /**
3858 * Node handle moved callback.
3859 */
3860 static void node_handle_moved(SPKnot *knot, NR::Point *p, guint state, gpointer data)
3861 {
3862 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3864 Inkscape::NodePath::NodeSide *me;
3865 Inkscape::NodePath::NodeSide *other;
3866 if (n->p.knot == knot) {
3867 me = &n->p;
3868 other = &n->n;
3869 } else if (n->n.knot == knot) {
3870 me = &n->n;
3871 other = &n->p;
3872 } else {
3873 me = NULL;
3874 other = NULL;
3875 g_assert_not_reached();
3876 }
3878 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3879 Radial rme(me->pos - n->pos);
3880 Radial rother(other->pos - n->pos);
3881 Radial rnew(*p - n->pos);
3883 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3884 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3885 /* 0 interpreted as "no snapping". */
3887 // 1. Snap to the closest PI/snaps angle, starting from zero.
3888 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3890 // 2. Snap to the original angle, its opposite and perpendiculars
3891 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3892 /* The closest PI/2 angle, starting from original angle */
3893 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3895 // Snap to the closest.
3896 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3897 ? a_snapped
3898 : a_ortho );
3899 }
3901 // 3. Snap to the angle of the opposite line, if any
3902 Inkscape::NodePath::Node *othernode = other->other;
3903 if (othernode) {
3904 NR::Point other_to_snap(0,0);
3905 if (sp_node_side_is_line(n, other)) {
3906 other_to_snap = othernode->pos - n->pos;
3907 } else {
3908 other_to_snap = other->pos - n->pos;
3909 }
3910 if (NR::L2(other_to_snap) > 1e-3) {
3911 Radial rother_to_snap(other_to_snap);
3912 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3913 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3915 // Snap to the closest.
3916 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3917 ? a_snapped
3918 : a_oppo );
3919 }
3920 }
3922 rnew.a = a_snapped;
3923 }
3925 if (state & GDK_MOD1_MASK) {
3926 // lock handle length
3927 rnew.r = me->origin_radial.r;
3928 }
3930 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3931 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3932 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3933 rother.a += rnew.a - rme.a;
3934 other->pos = NR::Point(rother) + n->pos;
3935 if (other->knot) {
3936 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3937 sp_knot_moveto(other->knot, other->pos);
3938 }
3939 }
3941 me->pos = NR::Point(rnew) + n->pos;
3942 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3944 // move knot, but without emitting the signal:
3945 // we cannot emit a "moved" signal because we're now processing it
3946 sp_knot_moveto(me->knot, me->pos);
3948 update_object(n->subpath->nodepath);
3950 /* status text */
3951 SPDesktop *desktop = n->subpath->nodepath->desktop;
3952 if (!desktop) return;
3953 SPEventContext *ec = desktop->event_context;
3954 if (!ec) return;
3956 Inkscape::MessageContext *mc = get_message_context(ec);
3958 if (!mc) return;
3960 double degrees = 180 / M_PI * rnew.a;
3961 if (degrees > 180) degrees -= 360;
3962 if (degrees < -180) degrees += 360;
3963 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3964 degrees = angle_to_compass (degrees);
3966 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3968 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3969 _("<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);
3971 g_string_free(length, TRUE);
3972 }
3974 /**
3975 * Node handle event callback.
3976 */
3977 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3978 {
3979 gboolean ret = FALSE;
3980 switch (event->type) {
3981 case GDK_KEY_PRESS:
3982 switch (get_group0_keyval (&event->key)) {
3983 case GDK_space:
3984 if (event->key.state & GDK_BUTTON1_MASK) {
3985 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3986 stamp_repr(nodepath);
3987 ret = TRUE;
3988 }
3989 break;
3990 default:
3991 break;
3992 }
3993 break;
3994 case GDK_ENTER_NOTIFY:
3995 // we use an experimentally determined threshold that seems to work fine
3996 if (NR::L2(n->pos - knot->pos) < 0.75)
3997 Inkscape::NodePath::Path::active_node = n;
3998 break;
3999 case GDK_LEAVE_NOTIFY:
4000 // we use an experimentally determined threshold that seems to work fine
4001 if (NR::L2(n->pos - knot->pos) < 0.75)
4002 Inkscape::NodePath::Path::active_node = NULL;
4003 break;
4004 default:
4005 break;
4006 }
4008 return ret;
4009 }
4011 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4012 Radial &rme, Radial &rother, gboolean const both)
4013 {
4014 rme.a += angle;
4015 if ( both
4016 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4017 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4018 {
4019 rother.a += angle;
4020 }
4021 }
4023 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4024 Radial &rme, Radial &rother, gboolean const both)
4025 {
4026 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4028 gdouble r;
4029 if ( both
4030 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4031 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4032 {
4033 r = MAX(rme.r, rother.r);
4034 } else {
4035 r = rme.r;
4036 }
4038 gdouble const weird_angle = atan2(norm_angle, r);
4039 /* Bulia says norm_angle is just the visible distance that the
4040 * object's end must travel on the screen. Left as 'angle' for want of
4041 * a better name.*/
4043 rme.a += weird_angle;
4044 if ( both
4045 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4046 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4047 {
4048 rother.a += weird_angle;
4049 }
4050 }
4052 /**
4053 * Rotate one node.
4054 */
4055 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4056 {
4057 Inkscape::NodePath::NodeSide *me, *other;
4058 bool both = false;
4060 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4061 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4063 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4064 me = &(n->p);
4065 other = &(n->n);
4066 } else if (!n->p.other) {
4067 me = &(n->n);
4068 other = &(n->p);
4069 } else {
4070 if (which > 0) { // right handle
4071 if (xn > xp) {
4072 me = &(n->n);
4073 other = &(n->p);
4074 } else {
4075 me = &(n->p);
4076 other = &(n->n);
4077 }
4078 } else if (which < 0){ // left handle
4079 if (xn <= xp) {
4080 me = &(n->n);
4081 other = &(n->p);
4082 } else {
4083 me = &(n->p);
4084 other = &(n->n);
4085 }
4086 } else { // both handles
4087 me = &(n->n);
4088 other = &(n->p);
4089 both = true;
4090 }
4091 }
4093 Radial rme(me->pos - n->pos);
4094 Radial rother(other->pos - n->pos);
4096 if (screen) {
4097 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4098 } else {
4099 node_rotate_one_internal (*n, angle, rme, rother, both);
4100 }
4102 me->pos = n->pos + NR::Point(rme);
4104 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4105 other->pos = n->pos + NR::Point(rother);
4106 }
4108 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4109 // so here we just move all the knots without emitting move signals, for speed
4110 sp_node_update_handles(n, false);
4111 }
4113 /**
4114 * Rotate selected nodes.
4115 */
4116 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4117 {
4118 if (!nodepath || !nodepath->selected) return;
4120 if (g_list_length(nodepath->selected) == 1) {
4121 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4122 node_rotate_one (n, angle, which, screen);
4123 } else {
4124 // rotate as an object:
4126 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4127 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4128 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4129 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4130 box.expandTo (n->pos); // contain all selected nodes
4131 }
4133 gdouble rot;
4134 if (screen) {
4135 gdouble const zoom = nodepath->desktop->current_zoom();
4136 gdouble const zmove = angle / zoom;
4137 gdouble const r = NR::L2(box.max() - box.midpoint());
4138 rot = atan2(zmove, r);
4139 } else {
4140 rot = angle;
4141 }
4143 NR::Point rot_center;
4144 if (Inkscape::NodePath::Path::active_node == NULL)
4145 rot_center = box.midpoint();
4146 else
4147 rot_center = Inkscape::NodePath::Path::active_node->pos;
4149 NR::Matrix t =
4150 NR::Matrix (NR::translate(-rot_center)) *
4151 NR::Matrix (NR::rotate(rot)) *
4152 NR::Matrix (NR::translate(rot_center));
4154 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4155 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4156 n->pos *= t;
4157 n->n.pos *= t;
4158 n->p.pos *= t;
4159 sp_node_update_handles(n, false);
4160 }
4161 }
4163 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4164 }
4166 /**
4167 * Scale one node.
4168 */
4169 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4170 {
4171 bool both = false;
4172 Inkscape::NodePath::NodeSide *me, *other;
4174 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4175 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4177 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4178 me = &(n->p);
4179 other = &(n->n);
4180 n->code = NR_CURVETO;
4181 } else if (!n->p.other) {
4182 me = &(n->n);
4183 other = &(n->p);
4184 if (n->n.other)
4185 n->n.other->code = NR_CURVETO;
4186 } else {
4187 if (which > 0) { // right handle
4188 if (xn > xp) {
4189 me = &(n->n);
4190 other = &(n->p);
4191 if (n->n.other)
4192 n->n.other->code = NR_CURVETO;
4193 } else {
4194 me = &(n->p);
4195 other = &(n->n);
4196 n->code = NR_CURVETO;
4197 }
4198 } else if (which < 0){ // left handle
4199 if (xn <= xp) {
4200 me = &(n->n);
4201 other = &(n->p);
4202 if (n->n.other)
4203 n->n.other->code = NR_CURVETO;
4204 } else {
4205 me = &(n->p);
4206 other = &(n->n);
4207 n->code = NR_CURVETO;
4208 }
4209 } else { // both handles
4210 me = &(n->n);
4211 other = &(n->p);
4212 both = true;
4213 n->code = NR_CURVETO;
4214 if (n->n.other)
4215 n->n.other->code = NR_CURVETO;
4216 }
4217 }
4219 Radial rme(me->pos - n->pos);
4220 Radial rother(other->pos - n->pos);
4222 rme.r += grow;
4223 if (rme.r < 0) rme.r = 0;
4224 if (rme.a == HUGE_VAL) {
4225 if (me->other) { // if direction is unknown, initialize it towards the next node
4226 Radial rme_next(me->other->pos - n->pos);
4227 rme.a = rme_next.a;
4228 } else { // if there's no next, initialize to 0
4229 rme.a = 0;
4230 }
4231 }
4232 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4233 rother.r += grow;
4234 if (rother.r < 0) rother.r = 0;
4235 if (rother.a == HUGE_VAL) {
4236 rother.a = rme.a + M_PI;
4237 }
4238 }
4240 me->pos = n->pos + NR::Point(rme);
4242 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4243 other->pos = n->pos + NR::Point(rother);
4244 }
4246 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4247 // so here we just move all the knots without emitting move signals, for speed
4248 sp_node_update_handles(n, false);
4249 }
4251 /**
4252 * Scale selected nodes.
4253 */
4254 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4255 {
4256 if (!nodepath || !nodepath->selected) return;
4258 if (g_list_length(nodepath->selected) == 1) {
4259 // scale handles of the single selected node
4260 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4261 node_scale_one (n, grow, which);
4262 } else {
4263 // scale nodes as an "object":
4265 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4266 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4267 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4268 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4269 box.expandTo (n->pos); // contain all selected nodes
4270 }
4272 double scale = (box.maxExtent() + grow)/box.maxExtent();
4274 NR::Point scale_center;
4275 if (Inkscape::NodePath::Path::active_node == NULL)
4276 scale_center = box.midpoint();
4277 else
4278 scale_center = Inkscape::NodePath::Path::active_node->pos;
4280 NR::Matrix t =
4281 NR::Matrix (NR::translate(-scale_center)) *
4282 NR::Matrix (NR::scale(scale, scale)) *
4283 NR::Matrix (NR::translate(scale_center));
4285 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4286 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4287 n->pos *= t;
4288 n->n.pos *= t;
4289 n->p.pos *= t;
4290 sp_node_update_handles(n, false);
4291 }
4292 }
4294 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4295 }
4297 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4298 {
4299 if (!nodepath) return;
4300 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4301 }
4303 /**
4304 * Flip selected nodes horizontally/vertically.
4305 */
4306 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, boost::optional<NR::Point> center)
4307 {
4308 if (!nodepath || !nodepath->selected) return;
4310 if (g_list_length(nodepath->selected) == 1 && !center) {
4311 // flip handles of the single selected node
4312 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4313 double temp = n->p.pos[axis];
4314 n->p.pos[axis] = n->n.pos[axis];
4315 n->n.pos[axis] = temp;
4316 sp_node_update_handles(n, false);
4317 } else {
4318 // scale nodes as an "object":
4320 Geom::Rect box = sp_node_selected_bbox (nodepath);
4321 if (!center) {
4322 center = box.midpoint();
4323 }
4324 NR::Matrix t =
4325 NR::Matrix (NR::translate(- *center)) *
4326 NR::Matrix ((axis == NR::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4327 NR::Matrix (NR::translate(*center));
4329 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4330 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4331 n->pos *= t;
4332 n->n.pos *= t;
4333 n->p.pos *= t;
4334 sp_node_update_handles(n, false);
4335 }
4336 }
4338 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4339 }
4341 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4342 {
4343 g_assert (nodepath->selected);
4345 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4346 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4347 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4348 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4349 box.expandTo (n->pos); // contain all selected nodes
4350 }
4351 return box;
4352 }
4354 //-----------------------------------------------
4355 /**
4356 * Return new subpath under given nodepath.
4357 */
4358 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4359 {
4360 g_assert(nodepath);
4361 g_assert(nodepath->desktop);
4363 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4365 s->nodepath = nodepath;
4366 s->closed = FALSE;
4367 s->nodes = NULL;
4368 s->first = NULL;
4369 s->last = NULL;
4371 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4372 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4373 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4375 return s;
4376 }
4378 /**
4379 * Destroy nodes in subpath, then subpath itself.
4380 */
4381 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4382 {
4383 g_assert(subpath);
4384 g_assert(subpath->nodepath);
4385 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4387 while (subpath->nodes) {
4388 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4389 }
4391 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4393 g_free(subpath);
4394 }
4396 /**
4397 * Link head to tail in subpath.
4398 */
4399 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4400 {
4401 g_assert(!sp->closed);
4402 g_assert(sp->last != sp->first);
4403 g_assert(sp->first->code == NR_MOVETO);
4405 sp->closed = TRUE;
4407 //Link the head to the tail
4408 sp->first->p.other = sp->last;
4409 sp->last->n.other = sp->first;
4410 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4411 sp->first = sp->last;
4413 //Remove the extra end node
4414 sp_nodepath_node_destroy(sp->last->n.other);
4415 }
4417 /**
4418 * Open closed (loopy) subpath at node.
4419 */
4420 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4421 {
4422 g_assert(sp->closed);
4423 g_assert(n->subpath == sp);
4424 g_assert(sp->first == sp->last);
4426 /* We create new startpoint, current node will become last one */
4428 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4429 &n->pos, &n->pos, &n->n.pos);
4432 sp->closed = FALSE;
4434 //Unlink to make a head and tail
4435 sp->first = new_path;
4436 sp->last = n;
4437 n->n.other = NULL;
4438 new_path->p.other = NULL;
4439 }
4441 /**
4442 * Return new node in subpath with given properties.
4443 * \param pos Position of node.
4444 * \param ppos Handle position in previous direction
4445 * \param npos Handle position in previous direction
4446 */
4447 Inkscape::NodePath::Node *
4448 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)
4449 {
4450 g_assert(sp);
4451 g_assert(sp->nodepath);
4452 g_assert(sp->nodepath->desktop);
4454 if (nodechunk == NULL)
4455 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4457 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4459 n->subpath = sp;
4461 if (type != Inkscape::NodePath::NODE_NONE) {
4462 // use the type from sodipodi:nodetypes
4463 n->type = type;
4464 } else {
4465 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4466 // points are (almost) collinear
4467 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4468 // endnode, or a node with a retracted handle
4469 n->type = Inkscape::NodePath::NODE_CUSP;
4470 } else {
4471 n->type = Inkscape::NodePath::NODE_SMOOTH;
4472 }
4473 } else {
4474 n->type = Inkscape::NodePath::NODE_CUSP;
4475 }
4476 }
4478 n->code = code;
4479 n->selected = FALSE;
4480 n->pos = *pos;
4481 n->p.pos = *ppos;
4482 n->n.pos = *npos;
4484 n->dragging_out = NULL;
4486 Inkscape::NodePath::Node *prev;
4487 if (next) {
4488 //g_assert(g_list_find(sp->nodes, next));
4489 prev = next->p.other;
4490 } else {
4491 prev = sp->last;
4492 }
4494 if (prev)
4495 prev->n.other = n;
4496 else
4497 sp->first = n;
4499 if (next)
4500 next->p.other = n;
4501 else
4502 sp->last = n;
4504 n->p.other = prev;
4505 n->n.other = next;
4507 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"));
4508 sp_knot_set_position(n->knot, *pos, 0);
4510 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4511 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4512 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4513 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4514 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4515 sp_knot_update_ctrl(n->knot);
4517 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4518 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4519 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4520 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4521 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4522 sp_knot_show(n->knot);
4524 // We only create handle knots and lines on demand
4525 n->p.knot = NULL;
4526 n->p.line = NULL;
4527 n->n.knot = NULL;
4528 n->n.line = NULL;
4530 sp->nodes = g_list_prepend(sp->nodes, n);
4532 return n;
4533 }
4535 /**
4536 * Destroy node and its knots, link neighbors in subpath.
4537 */
4538 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4539 {
4540 g_assert(node);
4541 g_assert(node->subpath);
4542 g_assert(SP_IS_KNOT(node->knot));
4544 Inkscape::NodePath::SubPath *sp = node->subpath;
4546 if (node->selected) { // first, deselect
4547 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4548 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4549 }
4551 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4553 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4554 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4555 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4556 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4557 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4558 g_object_unref(G_OBJECT(node->knot));
4560 if (node->p.knot) {
4561 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4562 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4563 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4564 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4565 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4566 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4567 g_object_unref(G_OBJECT(node->p.knot));
4568 node->p.knot = NULL;
4569 }
4571 if (node->n.knot) {
4572 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4573 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4574 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4575 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4576 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4577 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4578 g_object_unref(G_OBJECT(node->n.knot));
4579 node->n.knot = NULL;
4580 }
4582 if (node->p.line)
4583 gtk_object_destroy(GTK_OBJECT(node->p.line));
4584 if (node->n.line)
4585 gtk_object_destroy(GTK_OBJECT(node->n.line));
4587 if (sp->nodes) { // there are others nodes on the subpath
4588 if (sp->closed) {
4589 if (sp->first == node) {
4590 g_assert(sp->last == node);
4591 sp->first = node->n.other;
4592 sp->last = sp->first;
4593 }
4594 node->p.other->n.other = node->n.other;
4595 node->n.other->p.other = node->p.other;
4596 } else {
4597 if (sp->first == node) {
4598 sp->first = node->n.other;
4599 sp->first->code = NR_MOVETO;
4600 }
4601 if (sp->last == node) sp->last = node->p.other;
4602 if (node->p.other) node->p.other->n.other = node->n.other;
4603 if (node->n.other) node->n.other->p.other = node->p.other;
4604 }
4605 } else { // this was the last node on subpath
4606 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4607 }
4609 g_mem_chunk_free(nodechunk, node);
4610 }
4612 /**
4613 * Returns one of the node's two sides.
4614 * \param which Indicates which side.
4615 * \return Pointer to previous node side if which==-1, next if which==1.
4616 */
4617 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4618 {
4619 g_assert(node);
4620 Inkscape::NodePath::NodeSide * result = 0;
4621 switch (which) {
4622 case -1:
4623 result = &node->p;
4624 break;
4625 case 1:
4626 result = &node->n;
4627 break;
4628 default:
4629 g_assert_not_reached();
4630 }
4632 return result;
4633 }
4635 /**
4636 * Return the other side of the node, given one of its sides.
4637 */
4638 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4639 {
4640 g_assert(node);
4641 Inkscape::NodePath::NodeSide *result = 0;
4643 if (me == &node->p) {
4644 result = &node->n;
4645 } else if (me == &node->n) {
4646 result = &node->p;
4647 } else {
4648 g_assert_not_reached();
4649 }
4651 return result;
4652 }
4654 /**
4655 * Return NRPathcode on the given side of the node.
4656 */
4657 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4658 {
4659 g_assert(node);
4661 NRPathcode result = NR_END;
4662 if (me == &node->p) {
4663 if (node->p.other) {
4664 result = (NRPathcode)node->code;
4665 } else {
4666 result = NR_MOVETO;
4667 }
4668 } else if (me == &node->n) {
4669 if (node->n.other) {
4670 result = (NRPathcode)node->n.other->code;
4671 } else {
4672 result = NR_MOVETO;
4673 }
4674 } else {
4675 g_assert_not_reached();
4676 }
4678 return result;
4679 }
4681 /**
4682 * Return node with the given index
4683 */
4684 Inkscape::NodePath::Node *
4685 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4686 {
4687 Inkscape::NodePath::Node *e = NULL;
4689 if (!nodepath) {
4690 return e;
4691 }
4693 //find segment
4694 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4696 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4697 int n = g_list_length(sp->nodes);
4698 if (sp->closed) {
4699 n++;
4700 }
4702 //if the piece belongs to this subpath grab it
4703 //otherwise move onto the next subpath
4704 if (index < n) {
4705 e = sp->first;
4706 for (int i = 0; i < index; ++i) {
4707 e = e->n.other;
4708 }
4709 break;
4710 } else {
4711 if (sp->closed) {
4712 index -= (n+1);
4713 } else {
4714 index -= n;
4715 }
4716 }
4717 }
4719 return e;
4720 }
4722 /**
4723 * Returns plain text meaning of node type.
4724 */
4725 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4726 {
4727 unsigned retracted = 0;
4728 bool endnode = false;
4730 for (int which = -1; which <= 1; which += 2) {
4731 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4732 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4733 retracted ++;
4734 if (!side->other)
4735 endnode = true;
4736 }
4738 if (retracted == 0) {
4739 if (endnode) {
4740 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4741 return _("end node");
4742 } else {
4743 switch (node->type) {
4744 case Inkscape::NodePath::NODE_CUSP:
4745 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4746 return _("cusp");
4747 case Inkscape::NodePath::NODE_SMOOTH:
4748 // TRANSLATORS: "smooth" is an adjective here
4749 return _("smooth");
4750 case Inkscape::NodePath::NODE_SYMM:
4751 return _("symmetric");
4752 }
4753 }
4754 } else if (retracted == 1) {
4755 if (endnode) {
4756 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4757 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4758 } else {
4759 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4760 }
4761 } else {
4762 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4763 }
4765 return NULL;
4766 }
4768 /**
4769 * Handles content of statusbar as long as node tool is active.
4770 */
4771 void
4772 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4773 {
4774 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");
4775 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4777 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4778 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4779 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4780 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4782 SPDesktop *desktop = NULL;
4783 if (nodepath) {
4784 desktop = nodepath->desktop;
4785 } else {
4786 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4787 }
4789 SPEventContext *ec = desktop->event_context;
4790 if (!ec) return;
4792 Inkscape::MessageContext *mc = get_message_context(ec);
4793 if (!mc) return;
4795 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4797 if (selected_nodes == 0) {
4798 Inkscape::Selection *sel = desktop->selection;
4799 if (!sel || sel->isEmpty()) {
4800 mc->setF(Inkscape::NORMAL_MESSAGE,
4801 _("Select a single object to edit its nodes or handles."));
4802 } else {
4803 if (nodepath) {
4804 mc->setF(Inkscape::NORMAL_MESSAGE,
4805 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.",
4806 "<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.",
4807 total_nodes),
4808 total_nodes);
4809 } else {
4810 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4811 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4812 } else {
4813 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4814 }
4815 }
4816 }
4817 } else if (nodepath && selected_nodes == 1) {
4818 mc->setF(Inkscape::NORMAL_MESSAGE,
4819 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4820 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4821 total_nodes),
4822 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4823 } else {
4824 if (selected_subpaths > 1) {
4825 mc->setF(Inkscape::NORMAL_MESSAGE,
4826 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4827 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4828 total_nodes),
4829 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4830 } else {
4831 mc->setF(Inkscape::NORMAL_MESSAGE,
4832 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4833 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4834 total_nodes),
4835 selected_nodes, total_nodes, when_selected);
4836 }
4837 }
4838 }
4840 /*
4841 * returns a *copy* of the curve of that object.
4842 */
4843 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4844 if (!object)
4845 return NULL;
4847 SPCurve *curve = NULL;
4848 if (SP_IS_PATH(object)) {
4849 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4850 curve = curve_new->copy();
4851 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4852 const gchar *svgd = object->repr->attribute(key);
4853 if (svgd) {
4854 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4855 SPCurve *curve_new = new SPCurve(pv);
4856 if (curve_new) {
4857 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4858 }
4859 }
4860 }
4862 return curve;
4863 }
4865 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4866 if (!np || !np->object || !curve)
4867 return;
4869 if (SP_IS_PATH(np->object)) {
4870 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4871 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4872 } else {
4873 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4874 }
4875 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4876 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4877 if (pathparam) {
4878 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4879 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4880 }
4881 }
4882 }
4884 /**
4885 SPCanvasItem *
4886 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4887 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4888 }
4889 **/
4891 /**
4892 SPCanvasItem *
4893 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4894 SPCurve *flash_curve = curve->copy();
4895 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4896 flash_curve->transform(i2d);
4897 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4898 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4899 // unless we also flash the nodes...
4900 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4901 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4902 sp_canvas_item_show(canvasitem);
4903 flash_curve->unref();
4904 return canvasitem;
4905 }
4907 SPCanvasItem *
4908 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4909 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4910 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4911 }
4912 **/
4914 SPCanvasItem *
4915 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4916 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4917 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4918 flash_curve->transform(i2d);
4919 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4920 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4921 // unless we also flash the nodes...
4922 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4923 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4924 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4925 sp_canvas_item_show(canvasitem);
4926 flash_curve->unref();
4927 return canvasitem;
4928 }
4930 // TODO: Merge this with sp_nodepath_make_helper_item()!
4931 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4932 np->show_helperpath = show;
4934 if (show) {
4935 SPCurve *helper_curve = np->curve->copy();
4936 helper_curve->transform(np->i2d);
4937 if (!np->helper_path) {
4938 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4940 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4941 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);
4942 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4943 sp_canvas_item_move_to_z(np->helper_path, 0);
4944 sp_canvas_item_show(np->helper_path);
4945 } else {
4946 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4947 }
4948 helper_curve->unref();
4949 } else {
4950 if (np->helper_path) {
4951 GtkObject *temp = np->helper_path;
4952 np->helper_path = NULL;
4953 gtk_object_destroy(temp);
4954 }
4955 }
4956 }
4958 /* sp_nodepath_make_straight_path:
4959 * Prevents user from curving the path by dragging a segment or activating handles etc.
4960 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4961 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4962 */
4963 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4964 np->straight_path = true;
4965 np->show_handles = false;
4966 g_message("add code to make the path straight.");
4967 // do sp_nodepath_convert_node_type on all nodes?
4968 // coding tip: search for this text : "Make selected segments lines"
4969 }
4971 /*
4972 Local Variables:
4973 mode:c++
4974 c-file-style:"stroustrup"
4975 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4976 indent-tabs-mode:nil
4977 fill-column:99
4978 End:
4979 */
4980 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :