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, Geom::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, Geom::Point *p, guint state, gpointer data);
136 static void node_handle_moved(SPKnot *knot, Geom::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 Geom::Point *ppos, Geom::Point *pos, Geom::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 Geom::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 Geom::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 Geom::Point pos = points[0] * (Geom::Matrix)np->i2d;
554 Geom::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 // Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289)
568 if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) {
569 Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
570 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
571 }
573 sp_nodepath_subpath_close(sp);
574 }
575 }
576 }
578 /**
579 * Convert from sodipodi:nodetypes to new style type array.
580 */
581 static
582 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
583 {
584 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
586 guint pos = 0;
588 if (types) {
589 for (guint i = 0; types[i] && ( i < length ); i++) {
590 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
591 if (types[i] != '\0') {
592 switch (types[i]) {
593 case 's':
594 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
595 break;
596 case 'z':
597 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
598 break;
599 case 'c':
600 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
601 break;
602 default:
603 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
604 break;
605 }
606 }
607 }
608 }
610 while (pos < length) {
611 typestr[pos++] = Inkscape::NodePath::NODE_NONE;
612 }
614 return typestr;
615 }
617 /**
618 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
619 * updated but repr is not (for speed). Used during curve and node drag.
620 */
621 static void update_object(Inkscape::NodePath::Path *np)
622 {
623 g_assert(np);
625 np->curve->unref();
626 np->curve = create_curve(np);
628 sp_nodepath_set_curve(np, np->curve);
630 if (np->show_helperpath) {
631 SPCurve * helper_curve = np->curve->copy();
632 helper_curve->transform(np->i2d);
633 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
634 helper_curve->unref();
635 }
637 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
638 //sp_nodepath_update_helperpaths(np);
640 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
641 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
642 np->shape_editor->update_knotholder();
643 }
645 /**
646 * Update XML path node with data from path object.
647 */
648 static void update_repr_internal(Inkscape::NodePath::Path *np)
649 {
650 g_assert(np);
652 Inkscape::XML::Node *repr = np->object->repr;
654 np->curve->unref();
655 np->curve = create_curve(np);
657 gchar *typestr = create_typestr(np);
658 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
660 // determine if path has an effect applied and write to correct "d" attribute.
661 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
662 np->local_change++;
663 repr->setAttribute(np->repr_key, svgpath);
664 }
666 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
667 np->local_change++;
668 repr->setAttribute(np->repr_nodetypes_key, typestr);
669 }
671 g_free(svgpath);
672 g_free(typestr);
674 if (np->show_helperpath) {
675 SPCurve * helper_curve = np->curve->copy();
676 helper_curve->transform(np->i2d);
677 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
678 helper_curve->unref();
679 }
681 // TODO: do we need this call here? after all, update_object() should have been called just before
682 //sp_nodepath_update_helperpaths(np);
683 }
685 /**
686 * Update XML path node with data from path object, commit changes forever.
687 */
688 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
689 {
690 //fixme: np can be NULL, so check before proceeding
691 g_return_if_fail(np != NULL);
693 update_repr_internal(np);
694 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
696 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
697 annotation);
698 }
700 /**
701 * Update XML path node with data from path object, commit changes with undo.
702 */
703 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
704 {
705 update_repr_internal(np);
706 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
707 annotation);
708 }
710 /**
711 * Make duplicate of path, replace corresponding XML node in tree, commit.
712 */
713 static void stamp_repr(Inkscape::NodePath::Path *np)
714 {
715 g_assert(np);
717 Inkscape::XML::Node *old_repr = np->object->repr;
718 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
720 // remember the position of the item
721 gint pos = old_repr->position();
722 // remember parent
723 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
725 SPCurve *curve = create_curve(np);
726 gchar *typestr = create_typestr(np);
728 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
730 new_repr->setAttribute(np->repr_key, svgpath);
731 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
733 // add the new repr to the parent
734 parent->appendChild(new_repr);
735 // move to the saved position
736 new_repr->setPosition(pos > 0 ? pos : 0);
738 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
739 _("Stamp"));
741 Inkscape::GC::release(new_repr);
742 g_free(svgpath);
743 g_free(typestr);
744 curve->unref();
745 }
747 /**
748 * Create curve from path.
749 */
750 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
751 {
752 SPCurve *curve = new SPCurve();
754 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
755 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
756 curve->moveto(sp->first->pos * np->d2i);
757 Inkscape::NodePath::Node *n = sp->first->n.other;
758 while (n) {
759 Geom::Point const end_pt = n->pos * np->d2i;
760 switch (n->code) {
761 case NR_LINETO:
762 curve->lineto(end_pt);
763 break;
764 case NR_CURVETO:
765 curve->curveto(n->p.other->n.pos * np->d2i,
766 n->p.pos * np->d2i,
767 end_pt);
768 break;
769 default:
770 g_assert_not_reached();
771 break;
772 }
773 if (n != sp->last) {
774 n = n->n.other;
775 } else {
776 n = NULL;
777 }
778 }
779 if (sp->closed) {
780 curve->closepath();
781 }
782 }
784 return curve;
785 }
787 /**
788 * Convert path type string to sodipodi:nodetypes style.
789 */
790 static gchar *create_typestr(Inkscape::NodePath::Path *np)
791 {
792 gchar *typestr = g_new(gchar, 32);
793 gint len = 32;
794 gint pos = 0;
796 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
797 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
799 if (pos >= len) {
800 typestr = g_renew(gchar, typestr, len + 32);
801 len += 32;
802 }
804 typestr[pos++] = 'c';
806 Inkscape::NodePath::Node *n;
807 n = sp->first->n.other;
808 while (n) {
809 gchar code;
811 switch (n->type) {
812 case Inkscape::NodePath::NODE_CUSP:
813 code = 'c';
814 break;
815 case Inkscape::NodePath::NODE_SMOOTH:
816 code = 's';
817 break;
818 case Inkscape::NodePath::NODE_SYMM:
819 code = 'z';
820 break;
821 default:
822 g_assert_not_reached();
823 code = '\0';
824 break;
825 }
827 if (pos >= len) {
828 typestr = g_renew(gchar, typestr, len + 32);
829 len += 32;
830 }
832 typestr[pos++] = code;
834 if (n != sp->last) {
835 n = n->n.other;
836 } else {
837 n = NULL;
838 }
839 }
840 }
842 if (pos >= len) {
843 typestr = g_renew(gchar, typestr, len + 1);
844 len += 1;
845 }
847 typestr[pos++] = '\0';
849 return typestr;
850 }
852 // Returns different message contexts depending on the current context. This function should only
853 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
854 // other cases.
855 static Inkscape::MessageContext *
856 get_message_context(SPEventContext *ec)
857 {
858 Inkscape::MessageContext *mc = 0;
860 if (SP_IS_NODE_CONTEXT(ec)) {
861 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
862 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
863 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
864 } else {
865 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
866 }
868 return mc;
869 }
871 /**
872 \brief Fills node and handle positions for three nodes, splitting line
873 marked by end at distance t.
874 */
875 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
876 {
877 g_assert(new_path != NULL);
878 g_assert(end != NULL);
880 g_assert(end->p.other == new_path);
881 Inkscape::NodePath::Node *start = new_path->p.other;
882 g_assert(start);
884 if (end->code == NR_LINETO) {
885 new_path->type =Inkscape::NodePath::NODE_CUSP;
886 new_path->code = NR_LINETO;
887 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
888 } else {
889 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
890 new_path->code = NR_CURVETO;
891 gdouble s = 1 - t;
892 for (int dim = 0; dim < 2; dim++) {
893 NR::Coord const f000 = start->pos[dim];
894 NR::Coord const f001 = start->n.pos[dim];
895 NR::Coord const f011 = end->p.pos[dim];
896 NR::Coord const f111 = end->pos[dim];
897 NR::Coord const f00t = s * f000 + t * f001;
898 NR::Coord const f01t = s * f001 + t * f011;
899 NR::Coord const f11t = s * f011 + t * f111;
900 NR::Coord const f0tt = s * f00t + t * f01t;
901 NR::Coord const f1tt = s * f01t + t * f11t;
902 NR::Coord const fttt = s * f0tt + t * f1tt;
903 start->n.pos[dim] = f00t;
904 new_path->p.pos[dim] = f0tt;
905 new_path->pos[dim] = fttt;
906 new_path->n.pos[dim] = f1tt;
907 end->p.pos[dim] = f11t;
908 }
909 }
910 }
912 /**
913 * Adds new node on direct line between two nodes, activates handles of all
914 * three nodes.
915 */
916 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
917 {
918 g_assert(end);
919 g_assert(end->subpath);
920 g_assert(g_list_find(end->subpath->nodes, end));
922 Inkscape::NodePath::Node *start = end->p.other;
923 g_assert( start->n.other == end );
924 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
925 end,
926 (NRPathcode)end->code == NR_LINETO?
927 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
928 (NRPathcode)end->code,
929 &start->pos, &start->pos, &start->n.pos);
930 sp_nodepath_line_midpoint(newnode, end, t);
932 sp_node_adjust_handles(start);
933 sp_node_update_handles(start);
934 sp_node_update_handles(newnode);
935 sp_node_adjust_handles(end);
936 sp_node_update_handles(end);
938 return newnode;
939 }
941 /**
942 \brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it
943 */
944 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
945 {
946 g_assert(node);
947 g_assert(node->subpath);
948 g_assert(g_list_find(node->subpath->nodes, node));
950 Inkscape::NodePath::Node* result = 0;
951 Inkscape::NodePath::SubPath *sp = node->subpath;
952 Inkscape::NodePath::Path *np = sp->nodepath;
954 if (sp->closed) {
955 sp_nodepath_subpath_open(sp, node);
956 result = sp->first;
957 } else if ( (node == sp->first) || (node == sp->last ) ){
958 // no break for end nodes
959 result = 0;
960 } else {
961 // create a new subpath
962 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
964 // duplicate the break node as start of the new subpath
965 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
966 static_cast<Inkscape::NodePath::NodeType>(node->type),
967 NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
969 // attach rest of curve to new node
970 g_assert(node->n.other);
971 newnode->n.other = node->n.other; node->n.other = NULL;
972 newnode->n.other->p.other = newnode;
973 newsubpath->last = sp->last;
974 sp->last = node;
975 node = newnode;
976 while (node->n.other) {
977 node = node->n.other;
978 node->subpath = newsubpath;
979 sp->nodes = g_list_remove(sp->nodes, node);
980 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
981 }
984 result = newnode;
985 }
986 return result;
987 }
989 /**
990 * Duplicate node and connect to neighbours.
991 */
992 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
993 {
994 g_assert(node);
995 g_assert(node->subpath);
996 g_assert(g_list_find(node->subpath->nodes, node));
998 Inkscape::NodePath::SubPath *sp = node->subpath;
1000 NRPathcode code = (NRPathcode) node->code;
1001 if (code == NR_MOVETO) { // if node is the endnode,
1002 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1003 }
1005 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1007 if (!node->n.other || !node->p.other) { // if node is an endnode, select it
1008 return node;
1009 } else {
1010 return newnode; // otherwise select the newly created node
1011 }
1012 }
1014 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1015 {
1016 node->p.pos = (node->pos + (node->pos - node->n.pos));
1017 }
1019 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1020 {
1021 node->n.pos = (node->pos + (node->pos - node->p.pos));
1022 }
1024 /**
1025 * Change line type at node, with side effects on neighbours.
1026 */
1027 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1028 {
1029 g_assert(end);
1030 g_assert(end->subpath);
1031 g_assert(end->p.other);
1033 if (end->code != static_cast<guint>(code) ) {
1034 Inkscape::NodePath::Node *start = end->p.other;
1036 end->code = code;
1038 if (code == NR_LINETO) {
1039 if (start->code == NR_LINETO) {
1040 sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1041 }
1042 if (end->n.other) {
1043 if (end->n.other->code == NR_LINETO) {
1044 sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1045 }
1046 }
1047 } else {
1048 Geom::Point delta = end->pos - start->pos;
1049 start->n.pos = start->pos + delta / 3;
1050 end->p.pos = end->pos - delta / 3;
1051 sp_node_adjust_handle(start, 1);
1052 sp_node_adjust_handle(end, -1);
1053 }
1055 sp_node_update_handles(start);
1056 sp_node_update_handles(end);
1057 }
1058 }
1060 /**
1061 * Change node type, and its handles accordingly.
1062 */
1063 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1064 {
1065 g_assert(node);
1066 g_assert(node->subpath);
1068 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1069 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1070 type =Inkscape::NodePath::NODE_CUSP;
1071 }
1072 }
1074 node->type = type;
1076 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1077 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1078 node->knot->setSize (node->selected? 11 : 9);
1079 sp_knot_update_ctrl(node->knot);
1080 } else {
1081 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1082 node->knot->setSize (node->selected? 9 : 7);
1083 sp_knot_update_ctrl(node->knot);
1084 }
1086 // if one of handles is mouseovered, preserve its position
1087 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1088 sp_node_adjust_handle(node, 1);
1089 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1090 sp_node_adjust_handle(node, -1);
1091 } else {
1092 sp_node_adjust_handles(node);
1093 }
1095 sp_node_update_handles(node);
1097 sp_nodepath_update_statusbar(node->subpath->nodepath);
1099 return node;
1100 }
1102 bool
1103 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1104 {
1105 // TODO clean up multiple returns
1106 Inkscape::NodePath::Node *othernode = side->other;
1107 if (!othernode)
1108 return false;
1109 NRPathcode const code = sp_node_path_code_from_side(node, side);
1110 if (code == NR_LINETO)
1111 return true;
1112 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1113 if (&node->p == side) {
1114 other_to_me = &othernode->n;
1115 } else if (&node->n == side) {
1116 other_to_me = &othernode->p;
1117 }
1118 if (!other_to_me)
1119 return false;
1120 bool is_line =
1121 (NR::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1122 NR::L2(node->pos - side->pos) < 1e-6);
1123 return is_line;
1124 }
1126 /**
1127 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1128 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1129 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1130 * If already cusp and set to cusp, retracts handles.
1131 */
1132 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1133 {
1134 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1136 /*
1137 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1139 if (two_handles) {
1140 // do nothing, adjust_handles called via set_node_type will line them up
1141 } else if (one_handle) {
1142 if (opposite_to_handle_is_line) {
1143 if (lined_up) {
1144 // already half-smooth; pull opposite handle too making it fully smooth
1145 } else {
1146 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1147 }
1148 } else {
1149 // pull opposite handle in line with the existing one
1150 }
1151 } else if (no_handles) {
1152 if (both_segments_are_lines OR both_segments_are_curves) {
1153 //pull both handles
1154 } else {
1155 // pull the handle opposite to line segment, making node half-smooth
1156 }
1157 }
1158 */
1159 bool p_has_handle = (NR::L2(node->pos - node->p.pos) > 1e-6);
1160 bool n_has_handle = (NR::L2(node->pos - node->n.pos) > 1e-6);
1161 bool p_is_line = sp_node_side_is_line(node, &node->p);
1162 bool n_is_line = sp_node_side_is_line(node, &node->n);
1164 if (p_has_handle && n_has_handle) {
1165 // do nothing, adjust_handles will line them up
1166 } else if (p_has_handle || n_has_handle) {
1167 if (p_has_handle && n_is_line) {
1168 Radial line (node->n.other->pos - node->pos);
1169 Radial handle (node->pos - node->p.pos);
1170 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1171 // already half-smooth; pull opposite handle too making it fully smooth
1172 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1173 } else {
1174 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1175 }
1176 } else if (n_has_handle && p_is_line) {
1177 Radial line (node->p.other->pos - node->pos);
1178 Radial handle (node->pos - node->n.pos);
1179 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1180 // already half-smooth; pull opposite handle too making it fully smooth
1181 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1182 } else {
1183 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1184 }
1185 } else if (p_has_handle && node->n.other) {
1186 // pull n handle
1187 node->n.other->code = NR_CURVETO;
1188 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1189 NR::L2(node->p.pos - node->pos) :
1190 NR::L2(node->n.other->pos - node->pos) / 3;
1191 node->n.pos = node->pos - (len / NR::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1192 } else if (n_has_handle && node->p.other) {
1193 // pull p handle
1194 node->code = NR_CURVETO;
1195 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1196 NR::L2(node->n.pos - node->pos) :
1197 NR::L2(node->p.other->pos - node->pos) / 3;
1198 node->p.pos = node->pos - (len / NR::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1199 }
1200 } else if (!p_has_handle && !n_has_handle) {
1201 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1202 // no handles, but both segments are either lnes or curves:
1203 //pull both handles
1205 // convert both to curves:
1206 node->code = NR_CURVETO;
1207 node->n.other->code = NR_CURVETO;
1209 Geom::Point leg_prev = node->pos - node->p.other->pos;
1210 Geom::Point leg_next = node->pos - node->n.other->pos;
1212 double norm_leg_prev = L2(leg_prev);
1213 double norm_leg_next = L2(leg_next);
1215 Geom::Point delta;
1216 if (norm_leg_next > 0.0) {
1217 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
1218 (&delta)->normalize();
1219 }
1221 if (type == Inkscape::NodePath::NODE_SYMM) {
1222 double norm_leg_avg = (norm_leg_prev + norm_leg_next) / 2;
1223 node->p.pos = node->pos + 0.3 * norm_leg_avg * delta;
1224 node->n.pos = node->pos - 0.3 * norm_leg_avg * delta;
1225 } else {
1226 // length of handle is proportional to distance to adjacent node
1227 node->p.pos = node->pos + 0.3 * norm_leg_prev * delta;
1228 node->n.pos = node->pos - 0.3 * norm_leg_next * delta;
1229 }
1231 } else {
1232 // pull the handle opposite to line segment, making it half-smooth
1233 if (p_is_line && node->n.other) {
1234 if (type != Inkscape::NodePath::NODE_SYMM) {
1235 // pull n handle
1236 node->n.other->code = NR_CURVETO;
1237 double len = NR::L2(node->n.other->pos - node->pos) / 3;
1238 node->n.pos = node->pos + (len / NR::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1239 }
1240 } else if (n_is_line && node->p.other) {
1241 if (type != Inkscape::NodePath::NODE_SYMM) {
1242 // pull p handle
1243 node->code = NR_CURVETO;
1244 double len = NR::L2(node->p.other->pos - node->pos) / 3;
1245 node->p.pos = node->pos + (len / NR::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1246 }
1247 }
1248 }
1249 }
1250 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1251 // cusping a cusp: retract nodes
1252 node->p.pos = node->pos;
1253 node->n.pos = node->pos;
1254 }
1256 sp_nodepath_set_node_type (node, type);
1257 }
1259 /**
1260 * Move node to point, and adjust its and neighbouring handles.
1261 */
1262 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1263 {
1264 Geom::Point delta = p - node->pos;
1265 node->pos = p;
1267 node->p.pos += delta;
1268 node->n.pos += delta;
1270 Inkscape::NodePath::Node *node_p = NULL;
1271 Inkscape::NodePath::Node *node_n = NULL;
1273 if (node->p.other) {
1274 if (node->code == NR_LINETO) {
1275 sp_node_adjust_handle(node, 1);
1276 sp_node_adjust_handle(node->p.other, -1);
1277 node_p = node->p.other;
1278 }
1279 }
1280 if (node->n.other) {
1281 if (node->n.other->code == NR_LINETO) {
1282 sp_node_adjust_handle(node, -1);
1283 sp_node_adjust_handle(node->n.other, 1);
1284 node_n = node->n.other;
1285 }
1286 }
1288 // this function is only called from batch movers that will update display at the end
1289 // themselves, so here we just move all the knots without emitting move signals, for speed
1290 sp_node_update_handles(node, false);
1291 if (node_n) {
1292 sp_node_update_handles(node_n, false);
1293 }
1294 if (node_p) {
1295 sp_node_update_handles(node_p, false);
1296 }
1297 }
1299 /**
1300 * Call sp_node_moveto() for node selection and handle possible snapping.
1301 */
1302 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, NR::Coord dx, NR::Coord dy,
1303 bool const snap, bool constrained = false,
1304 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1305 {
1306 NR::Coord best = NR_HUGE;
1307 Geom::Point delta(dx, dy);
1308 Geom::Point best_pt = delta;
1309 Inkscape::SnappedPoint best_abs;
1311 if (snap) {
1312 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1313 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1314 * must provide that information. */
1316 // Build a list of the unselected nodes to which the snapper should snap
1317 std::vector<Geom::Point> unselected_nodes;
1318 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1319 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1320 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1321 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1322 if (!node->selected) {
1323 unselected_nodes.push_back(to_2geom(node->pos));
1324 }
1325 }
1326 }
1328 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1330 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1331 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1332 m.setup(nodepath->desktop, false, SP_PATH(n->subpath->nodepath->item), &unselected_nodes);
1333 Inkscape::SnappedPoint s;
1334 if (constrained) {
1335 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1336 dedicated_constraint.setPoint(n->pos);
1337 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1338 } else {
1339 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1340 }
1341 if (s.getSnapped() && (s.getDistance() < best)) {
1342 best = s.getDistance();
1343 best_abs = s;
1344 best_pt = from_2geom(s.getPoint()) - n->pos;
1345 }
1346 }
1348 if (best_abs.getSnapped()) {
1349 nodepath->desktop->snapindicator->set_new_snappoint(best_abs);
1350 } else {
1351 nodepath->desktop->snapindicator->remove_snappoint();
1352 }
1353 }
1355 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1356 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1357 sp_node_moveto(n, n->pos + best_pt);
1358 }
1360 // do not update repr here so that node dragging is acceptably fast
1361 update_object(nodepath);
1362 }
1364 /**
1365 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1366 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1367 near x = 0.
1368 */
1369 double
1370 sculpt_profile (double x, double alpha, guint profile)
1371 {
1372 double result = 1;
1374 if (x >= 1) {
1375 result = 0;
1376 } else if (x <= 0) {
1377 result = 1;
1378 } else {
1379 switch (profile) {
1380 case SCULPT_PROFILE_LINEAR:
1381 result = 1 - x;
1382 break;
1383 case SCULPT_PROFILE_BELL:
1384 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1385 break;
1386 case SCULPT_PROFILE_ELLIPTIC:
1387 result = sqrt(1 - x*x);
1388 break;
1389 default:
1390 g_assert_not_reached();
1391 }
1392 }
1394 return result;
1395 }
1397 double
1398 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1399 {
1400 // extremely primitive for now, don't have time to look for the real one
1401 double lower = NR::L2(b - a);
1402 double upper = NR::L2(ah - a) + NR::L2(bh - ah) + NR::L2(bh - b);
1403 return (lower + upper)/2;
1404 }
1406 void
1407 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1408 {
1409 n->pos = n->origin + delta;
1410 n->n.pos = n->n.origin + delta_n;
1411 n->p.pos = n->p.origin + delta_p;
1412 sp_node_adjust_handles(n);
1413 sp_node_update_handles(n, false);
1414 }
1416 /**
1417 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1418 * on how far they are from the dragged node n.
1419 */
1420 static void
1421 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1422 {
1423 g_assert (n);
1424 g_assert (nodepath);
1425 g_assert (n->subpath->nodepath == nodepath);
1427 double pressure = n->knot->pressure;
1428 if (pressure == 0)
1429 pressure = 0.5; // default
1430 pressure = CLAMP (pressure, 0.2, 0.8);
1432 // map pressure to alpha = 1/5 ... 5
1433 double alpha = 1 - 2 * fabs(pressure - 0.5);
1434 if (pressure > 0.5)
1435 alpha = 1/alpha;
1437 guint profile = prefs_get_int_attribute("tools.nodes", "sculpting_profile", SCULPT_PROFILE_BELL);
1439 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1440 // Only one subpath has selected nodes:
1441 // use linear mode, where the distance from n to node being dragged is calculated along the path
1443 double n_sel_range = 0, p_sel_range = 0;
1444 guint n_nodes = 0, p_nodes = 0;
1445 guint n_sel_nodes = 0, p_sel_nodes = 0;
1447 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1448 {
1449 double n_range = 0, p_range = 0;
1450 bool n_going = true, p_going = true;
1451 Inkscape::NodePath::Node *n_node = n;
1452 Inkscape::NodePath::Node *p_node = n;
1453 do {
1454 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1455 if (n_node && n_going)
1456 n_node = n_node->n.other;
1457 if (n_node == NULL) {
1458 n_going = false;
1459 } else {
1460 n_nodes ++;
1461 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1462 if (n_node->selected) {
1463 n_sel_nodes ++;
1464 n_sel_range = n_range;
1465 }
1466 if (n_node == p_node) {
1467 n_going = false;
1468 p_going = false;
1469 }
1470 }
1471 if (p_node && p_going)
1472 p_node = p_node->p.other;
1473 if (p_node == NULL) {
1474 p_going = false;
1475 } else {
1476 p_nodes ++;
1477 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1478 if (p_node->selected) {
1479 p_sel_nodes ++;
1480 p_sel_range = p_range;
1481 }
1482 if (p_node == n_node) {
1483 n_going = false;
1484 p_going = false;
1485 }
1486 }
1487 } while (n_going || p_going);
1488 }
1490 // Second pass: actually move nodes in this subpath
1491 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1492 {
1493 double n_range = 0, p_range = 0;
1494 bool n_going = true, p_going = true;
1495 Inkscape::NodePath::Node *n_node = n;
1496 Inkscape::NodePath::Node *p_node = n;
1497 do {
1498 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1499 if (n_node && n_going)
1500 n_node = n_node->n.other;
1501 if (n_node == NULL) {
1502 n_going = false;
1503 } else {
1504 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1505 if (n_node->selected) {
1506 sp_nodepath_move_node_and_handles (n_node,
1507 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1508 sculpt_profile ((n_range + NR::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1509 sculpt_profile ((n_range - NR::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1510 }
1511 if (n_node == p_node) {
1512 n_going = false;
1513 p_going = false;
1514 }
1515 }
1516 if (p_node && p_going)
1517 p_node = p_node->p.other;
1518 if (p_node == NULL) {
1519 p_going = false;
1520 } else {
1521 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1522 if (p_node->selected) {
1523 sp_nodepath_move_node_and_handles (p_node,
1524 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1525 sculpt_profile ((p_range - NR::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1526 sculpt_profile ((p_range + NR::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1527 }
1528 if (p_node == n_node) {
1529 n_going = false;
1530 p_going = false;
1531 }
1532 }
1533 } while (n_going || p_going);
1534 }
1536 } else {
1537 // Multiple subpaths have selected nodes:
1538 // use spatial mode, where the distance from n to node being dragged is measured directly as NR::L2.
1539 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1540 // fix the pear-like shape when sculpting e.g. a ring
1542 // First pass: calculate range
1543 gdouble direct_range = 0;
1544 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1545 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1546 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1547 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1548 if (node->selected) {
1549 direct_range = MAX(direct_range, NR::L2(node->origin - n->origin));
1550 }
1551 }
1552 }
1554 // Second pass: actually move nodes
1555 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1556 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1557 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1558 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1559 if (node->selected) {
1560 if (direct_range > 1e-6) {
1561 sp_nodepath_move_node_and_handles (node,
1562 sculpt_profile (NR::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1563 sculpt_profile (NR::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1564 sculpt_profile (NR::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1565 } else {
1566 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1567 }
1569 }
1570 }
1571 }
1572 }
1574 // do not update repr here so that node dragging is acceptably fast
1575 update_object(nodepath);
1576 }
1579 /**
1580 * Move node selection to point, adjust its and neighbouring handles,
1581 * handle possible snapping, and commit the change with possible undo.
1582 */
1583 void
1584 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1585 {
1586 if (!nodepath) return;
1588 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1590 if (dx == 0) {
1591 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1592 } else if (dy == 0) {
1593 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1594 } else {
1595 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1596 }
1597 }
1599 /**
1600 * Move node selection off screen and commit the change.
1601 */
1602 void
1603 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1604 {
1605 // borrowed from sp_selection_move_screen in selection-chemistry.c
1606 // we find out the current zoom factor and divide deltas by it
1608 gdouble zoom = desktop->current_zoom();
1609 gdouble zdx = dx / zoom;
1610 gdouble zdy = dy / zoom;
1612 if (!nodepath) return;
1614 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1616 if (dx == 0) {
1617 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1618 } else if (dy == 0) {
1619 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1620 } else {
1621 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1622 }
1623 }
1625 /**
1626 * Move selected nodes to the absolute position given
1627 */
1628 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1629 {
1630 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1631 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1632 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1633 sp_node_moveto(n, npos);
1634 }
1636 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1637 }
1639 /**
1640 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
1641 */
1642 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1643 {
1644 boost::optional<Geom::Coord> no_coord;
1645 g_return_val_if_fail(nodepath->selected, no_coord);
1647 // determine coordinate of first selected node
1648 GList *nsel = nodepath->selected;
1649 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1650 NR::Coord coord = n->pos[axis];
1651 bool coincide = true;
1653 // compare it to the coordinates of all the other selected nodes
1654 for (GList *l = nsel->next; l != NULL; l = l->next) {
1655 n = (Inkscape::NodePath::Node *) l->data;
1656 if (n->pos[axis] != coord) {
1657 coincide = false;
1658 }
1659 }
1660 if (coincide) {
1661 return coord;
1662 } else {
1663 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1664 // currently we return the coordinate of the bounding box midpoint because I don't know how
1665 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1666 return bbox.midpoint()[axis];
1667 }
1668 }
1670 /** If they don't yet exist, creates knot and line for the given side of the node */
1671 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1672 {
1673 if (!side->knot) {
1674 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"));
1676 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1677 side->knot->setSize (7);
1678 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1679 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1680 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1681 sp_knot_update_ctrl(side->knot);
1683 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1684 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1685 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1686 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1687 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1688 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1689 }
1691 if (!side->line) {
1692 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1693 SP_TYPE_CTRLLINE, NULL);
1694 }
1695 }
1697 /**
1698 * Ensure the given handle of the node is visible/invisible, update its screen position
1699 */
1700 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1701 {
1702 g_assert(node != NULL);
1704 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1705 NRPathcode code = sp_node_path_code_from_side(node, side);
1707 show_handle = show_handle && (code == NR_CURVETO) && (NR::L2(side->pos - node->pos) > 1e-6);
1709 if (show_handle) {
1710 if (!side->knot) { // No handle knot at all
1711 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1712 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1713 side->knot->pos = side->pos;
1714 if (side->knot->item)
1715 SP_CTRL(side->knot->item)->moveto(side->pos);
1716 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1717 sp_knot_show(side->knot);
1718 } else {
1719 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1720 if (fire_move_signals) {
1721 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1722 } else {
1723 sp_knot_moveto(side->knot, side->pos);
1724 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1725 }
1726 }
1727 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1728 sp_knot_show(side->knot);
1729 }
1730 }
1731 sp_canvas_item_show(side->line);
1732 } else {
1733 if (side->knot) {
1734 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1735 sp_knot_hide(side->knot);
1736 }
1737 }
1738 if (side->line) {
1739 sp_canvas_item_hide(side->line);
1740 }
1741 }
1742 }
1744 /**
1745 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1746 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1747 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1748 * updated; otherwise, just move the knots silently (used in batch moves).
1749 */
1750 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1751 {
1752 g_assert(node != NULL);
1754 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1755 sp_knot_show(node->knot);
1756 }
1758 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1759 if (fire_move_signals)
1760 sp_knot_set_position(node->knot, node->pos, 0);
1761 else
1762 sp_knot_moveto(node->knot, node->pos);
1763 }
1765 gboolean show_handles = node->selected;
1766 if (node->p.other != NULL) {
1767 if (node->p.other->selected) show_handles = TRUE;
1768 }
1769 if (node->n.other != NULL) {
1770 if (node->n.other->selected) show_handles = TRUE;
1771 }
1773 if (node->subpath->nodepath->show_handles == false)
1774 show_handles = FALSE;
1776 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1777 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1778 }
1780 /**
1781 * Call sp_node_update_handles() for all nodes on subpath.
1782 */
1783 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1784 {
1785 g_assert(subpath != NULL);
1787 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1788 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1789 }
1790 }
1792 /**
1793 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1794 */
1795 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1796 {
1797 g_assert(nodepath != NULL);
1799 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1800 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1801 }
1802 }
1804 void
1805 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1806 {
1807 if (nodepath) {
1808 nodepath->show_handles = show;
1809 sp_nodepath_update_handles(nodepath);
1810 }
1811 }
1813 /**
1814 * Adds all selected nodes in nodepath to list.
1815 */
1816 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1817 {
1818 StlConv<Node *>::list(l, selected);
1819 /// \todo this adds a copying, rework when the selection becomes a stl list
1820 }
1822 /**
1823 * Align selected nodes on the specified axis.
1824 */
1825 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1826 {
1827 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1828 return;
1829 }
1831 if ( !nodepath->selected->next ) { // only one node selected
1832 return;
1833 }
1834 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1835 Geom::Point dest(pNode->pos);
1836 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1837 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1838 if (pNode) {
1839 dest[axis] = pNode->pos[axis];
1840 sp_node_moveto(pNode, dest);
1841 }
1842 }
1844 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1845 }
1847 /// Helper struct.
1848 struct NodeSort
1849 {
1850 Inkscape::NodePath::Node *_node;
1851 NR::Coord _coord;
1852 /// \todo use vectorof pointers instead of calling copy ctor
1853 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1854 _node(node), _coord(node->pos[axis])
1855 {}
1857 };
1859 static bool operator<(NodeSort const &a, NodeSort const &b)
1860 {
1861 return (a._coord < b._coord);
1862 }
1864 /**
1865 * Distribute selected nodes on the specified axis.
1866 */
1867 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1868 {
1869 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1870 return;
1871 }
1873 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1874 return;
1875 }
1877 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1878 std::vector<NodeSort> sorted;
1879 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1880 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1881 if (pNode) {
1882 NodeSort n(pNode, axis);
1883 sorted.push_back(n);
1884 //dest[axis] = pNode->pos[axis];
1885 //sp_node_moveto(pNode, dest);
1886 }
1887 }
1888 std::sort(sorted.begin(), sorted.end());
1889 unsigned int len = sorted.size();
1890 //overall bboxes span
1891 float dist = (sorted.back()._coord -
1892 sorted.front()._coord);
1893 //new distance between each bbox
1894 float step = (dist) / (len - 1);
1895 float pos = sorted.front()._coord;
1896 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1897 it < sorted.end();
1898 it ++ )
1899 {
1900 Geom::Point dest((*it)._node->pos);
1901 dest[axis] = pos;
1902 sp_node_moveto((*it)._node, dest);
1903 pos += step;
1904 }
1906 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1907 }
1910 /**
1911 * Call sp_nodepath_line_add_node() for all selected segments.
1912 */
1913 void
1914 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1915 {
1916 if (!nodepath) {
1917 return;
1918 }
1920 GList *nl = NULL;
1922 int n_added = 0;
1924 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1925 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1926 g_assert(t->selected);
1927 if (t->p.other && t->p.other->selected) {
1928 nl = g_list_prepend(nl, t);
1929 }
1930 }
1932 while (nl) {
1933 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1934 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1935 sp_nodepath_node_select(n, TRUE, FALSE);
1936 n_added ++;
1937 nl = g_list_remove(nl, t);
1938 }
1940 /** \todo fixme: adjust ? */
1941 sp_nodepath_update_handles(nodepath);
1943 if (n_added > 1) {
1944 sp_nodepath_update_repr(nodepath, _("Add nodes"));
1945 } else if (n_added > 0) {
1946 sp_nodepath_update_repr(nodepath, _("Add node"));
1947 }
1949 sp_nodepath_update_statusbar(nodepath);
1950 }
1952 /**
1953 * Select segment nearest to point
1954 */
1955 void
1956 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
1957 {
1958 if (!nodepath) {
1959 return;
1960 }
1962 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
1963 Geom::PathVector const &pathv = curve->get_pathvector();
1964 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
1965 if (!pvpos) {
1966 g_print ("Possible error?\n");
1967 return;
1968 }
1970 // calculate index for nodepath's representation.
1971 unsigned int segment_index = floor(pvpos->t) + 1;
1972 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
1973 segment_index += pathv[i].size() + 1;
1974 if (pathv[i].closed()) {
1975 segment_index += 1;
1976 }
1977 }
1979 curve->unref();
1981 //find segment to segment
1982 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
1984 //fixme: this can return NULL, so check before proceeding.
1985 g_return_if_fail(e != NULL);
1987 gboolean force = FALSE;
1988 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
1989 force = TRUE;
1990 }
1991 sp_nodepath_node_select(e, (gboolean) toggle, force);
1992 if (e->p.other)
1993 sp_nodepath_node_select(e->p.other, TRUE, force);
1995 sp_nodepath_update_handles(nodepath);
1997 sp_nodepath_update_statusbar(nodepath);
1998 }
2000 /**
2001 * Add a node nearest to point
2002 */
2003 void
2004 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2005 {
2006 if (!nodepath) {
2007 return;
2008 }
2010 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2011 Geom::PathVector const &pathv = curve->get_pathvector();
2012 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2013 if (!pvpos) {
2014 g_print ("Possible error?\n");
2015 return;
2016 }
2018 // calculate index for nodepath's representation.
2019 double int_part;
2020 double t = std::modf(pvpos->t, &int_part);
2021 unsigned int segment_index = (unsigned int)int_part + 1;
2022 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2023 segment_index += pathv[i].size() + 1;
2024 if (pathv[i].closed()) {
2025 segment_index += 1;
2026 }
2027 }
2029 curve->unref();
2031 //find segment to split
2032 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2034 //don't know why but t seems to flip for lines
2035 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2036 t = 1.0 - t;
2037 }
2039 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2040 sp_nodepath_node_select(n, FALSE, TRUE);
2042 /* fixme: adjust ? */
2043 sp_nodepath_update_handles(nodepath);
2045 sp_nodepath_update_repr(nodepath, _("Add node"));
2047 sp_nodepath_update_statusbar(nodepath);
2048 }
2050 /*
2051 * Adjusts a segment so that t moves by a certain delta for dragging
2052 * converts lines to curves
2053 *
2054 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2055 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2056 */
2057 void
2058 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2059 {
2060 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2062 //fixme: e and e->p can be NULL, so check for those before proceeding
2063 g_return_if_fail(e != NULL);
2064 g_return_if_fail(&e->p != NULL);
2066 /* feel good is an arbitrary parameter that distributes the delta between handles
2067 * if t of the drag point is less than 1/6 distance form the endpoint only
2068 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2069 */
2070 double feel_good;
2071 if (t <= 1.0 / 6.0)
2072 feel_good = 0;
2073 else if (t <= 0.5)
2074 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2075 else if (t <= 5.0 / 6.0)
2076 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2077 else
2078 feel_good = 1;
2080 //if we're dragging a line convert it to a curve
2081 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2082 sp_nodepath_set_line_type(e, NR_CURVETO);
2083 }
2085 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2086 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2087 e->p.other->n.pos += offsetcoord0;
2088 e->p.pos += offsetcoord1;
2090 // adjust handles of adjacent nodes where necessary
2091 sp_node_adjust_handle(e,1);
2092 sp_node_adjust_handle(e->p.other,-1);
2094 sp_nodepath_update_handles(e->subpath->nodepath);
2096 update_object(e->subpath->nodepath);
2098 sp_nodepath_update_statusbar(e->subpath->nodepath);
2099 }
2102 /**
2103 * Call sp_nodepath_break() for all selected segments.
2104 */
2105 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2106 {
2107 if (!nodepath) return;
2109 GList *tempin = g_list_copy(nodepath->selected);
2110 GList *temp = NULL;
2111 for (GList *l = tempin; l != NULL; l = l->next) {
2112 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2113 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2114 if (nn == NULL) continue; // no break, no new node
2115 temp = g_list_prepend(temp, nn);
2116 }
2117 g_list_free(tempin);
2119 if (temp) {
2120 sp_nodepath_deselect(nodepath);
2121 }
2122 for (GList *l = temp; l != NULL; l = l->next) {
2123 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2124 }
2126 sp_nodepath_update_handles(nodepath);
2128 sp_nodepath_update_repr(nodepath, _("Break path"));
2129 }
2131 /**
2132 * Duplicate the selected node(s).
2133 */
2134 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2135 {
2136 if (!nodepath) {
2137 return;
2138 }
2140 GList *temp = NULL;
2141 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2142 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2143 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2144 if (nn == NULL) continue; // could not duplicate
2145 temp = g_list_prepend(temp, nn);
2146 }
2148 if (temp) {
2149 sp_nodepath_deselect(nodepath);
2150 }
2151 for (GList *l = temp; l != NULL; l = l->next) {
2152 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2153 }
2155 sp_nodepath_update_handles(nodepath);
2157 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2158 }
2160 /**
2161 * Internal function to join two nodes by merging them into one.
2162 */
2163 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2164 {
2165 /* a and b are endpoints */
2167 // if one of the two nodes is mouseovered, fix its position
2168 Geom::Point c;
2169 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2170 c = a->pos;
2171 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2172 c = b->pos;
2173 } else {
2174 // otherwise, move joined node to the midpoint
2175 c = (a->pos + b->pos) / 2;
2176 }
2178 if (a->subpath == b->subpath) {
2179 Inkscape::NodePath::SubPath *sp = a->subpath;
2180 sp_nodepath_subpath_close(sp);
2181 sp_node_moveto (sp->first, c);
2183 sp_nodepath_update_handles(sp->nodepath);
2184 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2185 return;
2186 }
2188 /* a and b are separate subpaths */
2189 Inkscape::NodePath::SubPath *sa = a->subpath;
2190 Inkscape::NodePath::SubPath *sb = b->subpath;
2191 Geom::Point p;
2192 Inkscape::NodePath::Node *n;
2193 NRPathcode code;
2194 if (a == sa->first) {
2195 // we will now reverse sa, so that a is its last node, not first, and drop that node
2196 p = sa->first->n.pos;
2197 code = (NRPathcode)sa->first->n.other->code;
2198 // create new subpath
2199 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2200 // create a first moveto node on it
2201 n = sa->last;
2202 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2203 n = n->p.other;
2204 if (n == sa->first) n = NULL;
2205 while (n) {
2206 // copy the rest of the nodes from sa to t, going backwards
2207 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2208 n = n->p.other;
2209 if (n == sa->first) n = NULL;
2210 }
2211 // replace sa with t
2212 sp_nodepath_subpath_destroy(sa);
2213 sa = t;
2214 } else if (a == sa->last) {
2215 // a is already last, just drop it
2216 p = sa->last->p.pos;
2217 code = (NRPathcode)sa->last->code;
2218 sp_nodepath_node_destroy(sa->last);
2219 } else {
2220 code = NR_END;
2221 g_assert_not_reached();
2222 }
2224 if (b == sb->first) {
2225 // copy all nodes from b to a, forward
2226 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2227 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2228 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2229 }
2230 } else if (b == sb->last) {
2231 // copy all nodes from b to a, backward
2232 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2233 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2234 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2235 }
2236 } else {
2237 g_assert_not_reached();
2238 }
2239 /* and now destroy sb */
2241 sp_nodepath_subpath_destroy(sb);
2243 sp_nodepath_update_handles(sa->nodepath);
2245 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2247 sp_nodepath_update_statusbar(nodepath);
2248 }
2250 /**
2251 * Internal function to join two nodes by adding a segment between them.
2252 */
2253 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2254 {
2255 if (a->subpath == b->subpath) {
2256 Inkscape::NodePath::SubPath *sp = a->subpath;
2258 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2259 sp->closed = TRUE;
2261 sp->first->p.other = sp->last;
2262 sp->last->n.other = sp->first;
2264 sp_node_handle_mirror_p_to_n(sp->last);
2265 sp_node_handle_mirror_n_to_p(sp->first);
2267 sp->first->code = sp->last->code;
2268 sp->first = sp->last;
2270 sp_nodepath_update_handles(sp->nodepath);
2272 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2274 return;
2275 }
2277 /* a and b are separate subpaths */
2278 Inkscape::NodePath::SubPath *sa = a->subpath;
2279 Inkscape::NodePath::SubPath *sb = b->subpath;
2281 Inkscape::NodePath::Node *n;
2282 Geom::Point p;
2283 NRPathcode code;
2284 if (a == sa->first) {
2285 code = (NRPathcode) sa->first->n.other->code;
2286 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2287 n = sa->last;
2288 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2289 for (n = n->p.other; n != NULL; n = n->p.other) {
2290 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2291 }
2292 sp_nodepath_subpath_destroy(sa);
2293 sa = t;
2294 } else if (a == sa->last) {
2295 code = (NRPathcode)sa->last->code;
2296 } else {
2297 code = NR_END;
2298 g_assert_not_reached();
2299 }
2301 if (b == sb->first) {
2302 n = sb->first;
2303 sp_node_handle_mirror_p_to_n(sa->last);
2304 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2305 sp_node_handle_mirror_n_to_p(sa->last);
2306 for (n = n->n.other; n != NULL; n = n->n.other) {
2307 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2308 }
2309 } else if (b == sb->last) {
2310 n = sb->last;
2311 sp_node_handle_mirror_p_to_n(sa->last);
2312 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2313 sp_node_handle_mirror_n_to_p(sa->last);
2314 for (n = n->p.other; n != NULL; n = n->p.other) {
2315 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2316 }
2317 } else {
2318 g_assert_not_reached();
2319 }
2320 /* and now destroy sb */
2322 sp_nodepath_subpath_destroy(sb);
2324 sp_nodepath_update_handles(sa->nodepath);
2326 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2327 }
2329 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2331 /**
2332 * Internal function to handle joining two nodes.
2333 */
2334 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2335 {
2336 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2338 if (g_list_length(nodepath->selected) != 2) {
2339 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2340 return;
2341 }
2343 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2344 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2346 g_assert(a != b);
2347 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2348 // someone tried to join an orphan node (i.e. a single-node subpath).
2349 // this is not worth an error message, just fail silently.
2350 return;
2351 }
2353 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2354 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2355 return;
2356 }
2358 switch(mode) {
2359 case NODE_JOIN_ENDPOINTS:
2360 do_node_selected_join(nodepath, a, b);
2361 break;
2362 case NODE_JOIN_SEGMENT:
2363 do_node_selected_join_segment(nodepath, a, b);
2364 break;
2365 }
2366 }
2368 /**
2369 * Join two nodes by merging them into one.
2370 */
2371 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2372 {
2373 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2374 }
2376 /**
2377 * Join two nodes by adding a segment between them.
2378 */
2379 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2380 {
2381 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2382 }
2384 /**
2385 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2386 */
2387 void sp_node_delete_preserve(GList *nodes_to_delete)
2388 {
2389 GSList *nodepaths = NULL;
2391 while (nodes_to_delete) {
2392 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2393 Inkscape::NodePath::SubPath *sp = node->subpath;
2394 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2395 Inkscape::NodePath::Node *sample_cursor = NULL;
2396 Inkscape::NodePath::Node *sample_end = NULL;
2397 Inkscape::NodePath::Node *delete_cursor = node;
2398 bool just_delete = false;
2400 //find the start of this contiguous selection
2401 //move left to the first node that is not selected
2402 //or the start of the non-closed path
2403 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2404 delete_cursor = curr;
2405 }
2407 //just delete at the beginning of an open path
2408 if (!delete_cursor->p.other) {
2409 sample_cursor = delete_cursor;
2410 just_delete = true;
2411 } else {
2412 sample_cursor = delete_cursor->p.other;
2413 }
2415 //calculate points for each segment
2416 int rate = 5;
2417 float period = 1.0 / rate;
2418 std::vector<Geom::Point> data;
2419 if (!just_delete) {
2420 data.push_back(sample_cursor->pos);
2421 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2422 //just delete at the end of an open path
2423 if (!sp->closed && curr == sp->last) {
2424 just_delete = true;
2425 break;
2426 }
2428 //sample points on the contiguous selected segment
2429 Geom::Point *bez;
2430 bez = new Geom::Point [4];
2431 bez[0] = curr->pos;
2432 bez[1] = curr->n.pos;
2433 bez[2] = curr->n.other->p.pos;
2434 bez[3] = curr->n.other->pos;
2435 for (int i=1; i<rate; i++) {
2436 gdouble t = i * period;
2437 Geom::Point p = bezier_pt(3, bez, t);
2438 data.push_back(p);
2439 }
2440 data.push_back(curr->n.other->pos);
2442 sample_end = curr->n.other;
2443 //break if we've come full circle or hit the end of the selection
2444 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2445 break;
2446 }
2447 }
2448 }
2450 if (!just_delete) {
2451 //calculate the best fitting single segment and adjust the endpoints
2452 Geom::Point *adata;
2453 adata = new Geom::Point [data.size()];
2454 copy(data.begin(), data.end(), adata);
2456 Geom::Point *bez;
2457 bez = new Geom::Point [4];
2458 //would decreasing error create a better fitting approximation?
2459 gdouble error = 1.0;
2460 gint ret;
2461 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2463 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2464 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2465 //the resulting nodes behave as expected.
2466 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2467 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2468 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2469 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2471 //adjust endpoints
2472 sample_cursor->n.pos = bez[1];
2473 sample_end->p.pos = bez[2];
2474 }
2476 //destroy this contiguous selection
2477 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2478 Inkscape::NodePath::Node *temp = delete_cursor;
2479 if (delete_cursor->n.other == delete_cursor) {
2480 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2481 delete_cursor = NULL;
2482 } else {
2483 delete_cursor = delete_cursor->n.other;
2484 }
2485 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2486 sp_nodepath_node_destroy(temp);
2487 }
2489 sp_nodepath_update_handles(nodepath);
2491 if (!g_slist_find(nodepaths, nodepath))
2492 nodepaths = g_slist_prepend (nodepaths, nodepath);
2493 }
2495 for (GSList *i = nodepaths; i; i = i->next) {
2496 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2497 // different nodepaths will give us one undo event per nodepath
2498 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2500 // if the entire nodepath is removed, delete the selected object.
2501 if (nodepath->subpaths == NULL ||
2502 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2503 //at least 2
2504 sp_nodepath_get_node_count(nodepath) < 2) {
2505 SPDocument *document = sp_desktop_document (nodepath->desktop);
2506 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2507 //delete this nodepath's object, not the entire selection! (though at this time, this
2508 //does not matter)
2509 sp_selection_delete(nodepath->desktop);
2510 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2511 _("Delete nodes"));
2512 } else {
2513 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2514 sp_nodepath_update_statusbar(nodepath);
2515 }
2516 }
2518 g_slist_free (nodepaths);
2519 }
2521 /**
2522 * Delete one or more selected nodes.
2523 */
2524 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2525 {
2526 if (!nodepath) return;
2527 if (!nodepath->selected) return;
2529 /** \todo fixme: do it the right way */
2530 while (nodepath->selected) {
2531 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2532 sp_nodepath_node_destroy(node);
2533 }
2536 //clean up the nodepath (such as for trivial subpaths)
2537 sp_nodepath_cleanup(nodepath);
2539 sp_nodepath_update_handles(nodepath);
2541 // if the entire nodepath is removed, delete the selected object.
2542 if (nodepath->subpaths == NULL ||
2543 sp_nodepath_get_node_count(nodepath) < 2) {
2544 SPDocument *document = sp_desktop_document (nodepath->desktop);
2545 sp_selection_delete(nodepath->desktop);
2546 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2547 _("Delete nodes"));
2548 return;
2549 }
2551 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2553 sp_nodepath_update_statusbar(nodepath);
2554 }
2556 /**
2557 * Delete one or more segments between two selected nodes.
2558 * This is the code for 'split'.
2559 */
2560 void
2561 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2562 {
2563 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2564 Inkscape::NodePath::Node *curr, *next; //Iterators
2566 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2568 if (g_list_length(nodepath->selected) != 2) {
2569 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2570 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2571 return;
2572 }
2574 //Selected nodes, not inclusive
2575 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2576 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2578 if ( ( a==b) || //same node
2579 (a->subpath != b->subpath ) || //not the same path
2580 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2581 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2582 {
2583 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2584 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2585 return;
2586 }
2588 //###########################################
2589 //# BEGIN EDITS
2590 //###########################################
2591 //##################################
2592 //# CLOSED PATH
2593 //##################################
2594 if (a->subpath->closed) {
2597 gboolean reversed = FALSE;
2599 //Since we can go in a circle, we need to find the shorter distance.
2600 // a->b or b->a
2601 start = end = NULL;
2602 int distance = 0;
2603 int minDistance = 0;
2604 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2605 if (curr==b) {
2606 //printf("a to b:%d\n", distance);
2607 start = a;//go from a to b
2608 end = b;
2609 minDistance = distance;
2610 //printf("A to B :\n");
2611 break;
2612 }
2613 distance++;
2614 }
2616 //try again, the other direction
2617 distance = 0;
2618 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2619 if (curr==a) {
2620 //printf("b to a:%d\n", distance);
2621 if (distance < minDistance) {
2622 start = b; //we go from b to a
2623 end = a;
2624 reversed = TRUE;
2625 //printf("B to A\n");
2626 }
2627 break;
2628 }
2629 distance++;
2630 }
2633 //Copy everything from 'end' to 'start' to a new subpath
2634 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2635 for (curr=end ; curr ; curr=curr->n.other) {
2636 NRPathcode code = (NRPathcode) curr->code;
2637 if (curr == end)
2638 code = NR_MOVETO;
2639 sp_nodepath_node_new(t, NULL,
2640 (Inkscape::NodePath::NodeType)curr->type, code,
2641 &curr->p.pos, &curr->pos, &curr->n.pos);
2642 if (curr == start)
2643 break;
2644 }
2645 sp_nodepath_subpath_destroy(a->subpath);
2648 }
2652 //##################################
2653 //# OPEN PATH
2654 //##################################
2655 else {
2657 //We need to get the direction of the list between A and B
2658 //Can we walk from a to b?
2659 start = end = NULL;
2660 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2661 if (curr==b) {
2662 start = a; //did it! we go from a to b
2663 end = b;
2664 //printf("A to B\n");
2665 break;
2666 }
2667 }
2668 if (!start) {//didn't work? let's try the other direction
2669 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2670 if (curr==a) {
2671 start = b; //did it! we go from b to a
2672 end = a;
2673 //printf("B to A\n");
2674 break;
2675 }
2676 }
2677 }
2678 if (!start) {
2679 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2680 _("Cannot find path between nodes."));
2681 return;
2682 }
2686 //Copy everything after 'end' to a new subpath
2687 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2688 for (curr=end ; curr ; curr=curr->n.other) {
2689 NRPathcode code = (NRPathcode) curr->code;
2690 if (curr == end)
2691 code = NR_MOVETO;
2692 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2693 &curr->p.pos, &curr->pos, &curr->n.pos);
2694 }
2696 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2697 for (curr = start->n.other ; curr ; curr=next) {
2698 next = curr->n.other;
2699 sp_nodepath_node_destroy(curr);
2700 }
2702 }
2703 //###########################################
2704 //# END EDITS
2705 //###########################################
2707 //clean up the nodepath (such as for trivial subpaths)
2708 sp_nodepath_cleanup(nodepath);
2710 sp_nodepath_update_handles(nodepath);
2712 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2714 sp_nodepath_update_statusbar(nodepath);
2715 }
2717 /**
2718 * Call sp_nodepath_set_line() for all selected segments.
2719 */
2720 void
2721 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2722 {
2723 if (nodepath == NULL) return;
2725 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2726 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2727 g_assert(n->selected);
2728 if (n->p.other && n->p.other->selected) {
2729 sp_nodepath_set_line_type(n, code);
2730 }
2731 }
2733 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2734 }
2736 /**
2737 * Call sp_nodepath_convert_node_type() for all selected nodes.
2738 */
2739 void
2740 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2741 {
2742 if (nodepath == NULL) return;
2744 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2746 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2747 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2748 }
2750 sp_nodepath_update_repr(nodepath, _("Change node type"));
2751 }
2753 /**
2754 * Change select status of node, update its own and neighbour handles.
2755 */
2756 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2757 {
2758 node->selected = selected;
2760 if (selected) {
2761 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 11 : 9);
2762 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2763 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2764 sp_knot_update_ctrl(node->knot);
2765 } else {
2766 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP) ? 9 : 7);
2767 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2768 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2769 sp_knot_update_ctrl(node->knot);
2770 }
2772 sp_node_update_handles(node);
2773 if (node->n.other) sp_node_update_handles(node->n.other);
2774 if (node->p.other) sp_node_update_handles(node->p.other);
2775 }
2777 /**
2778 \brief Select a node
2779 \param node The node to select
2780 \param incremental If true, add to selection, otherwise deselect others
2781 \param override If true, always select this node, otherwise toggle selected status
2782 */
2783 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2784 {
2785 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2787 if (incremental) {
2788 if (override) {
2789 if (!g_list_find(nodepath->selected, node)) {
2790 nodepath->selected = g_list_prepend(nodepath->selected, node);
2791 }
2792 sp_node_set_selected(node, TRUE);
2793 } else { // toggle
2794 if (node->selected) {
2795 g_assert(g_list_find(nodepath->selected, node));
2796 nodepath->selected = g_list_remove(nodepath->selected, node);
2797 } else {
2798 g_assert(!g_list_find(nodepath->selected, node));
2799 nodepath->selected = g_list_prepend(nodepath->selected, node);
2800 }
2801 sp_node_set_selected(node, !node->selected);
2802 }
2803 } else {
2804 sp_nodepath_deselect(nodepath);
2805 nodepath->selected = g_list_prepend(nodepath->selected, node);
2806 sp_node_set_selected(node, TRUE);
2807 }
2809 sp_nodepath_update_statusbar(nodepath);
2810 }
2813 /**
2814 \brief Deselect all nodes in the nodepath
2815 */
2816 void
2817 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2818 {
2819 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2821 while (nodepath->selected) {
2822 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2823 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2824 }
2825 sp_nodepath_update_statusbar(nodepath);
2826 }
2828 /**
2829 \brief Select or invert selection of all nodes in the nodepath
2830 */
2831 void
2832 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2833 {
2834 if (!nodepath) return;
2836 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2837 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2838 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2839 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2840 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2841 }
2842 }
2843 }
2845 /**
2846 * If nothing selected, does the same as sp_nodepath_select_all();
2847 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2848 * (i.e., similar to "select all in layer", with the "selected" subpaths
2849 * being treated as "layers" in the path).
2850 */
2851 void
2852 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2853 {
2854 if (!nodepath) return;
2856 if (g_list_length (nodepath->selected) == 0) {
2857 sp_nodepath_select_all (nodepath, invert);
2858 return;
2859 }
2861 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2862 GSList *subpaths = NULL;
2864 for (GList *l = copy; l != NULL; l = l->next) {
2865 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2866 Inkscape::NodePath::SubPath *subpath = n->subpath;
2867 if (!g_slist_find (subpaths, subpath))
2868 subpaths = g_slist_prepend (subpaths, subpath);
2869 }
2871 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2872 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2873 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2874 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2875 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2876 }
2877 }
2879 g_slist_free (subpaths);
2880 g_list_free (copy);
2881 }
2883 /**
2884 * \brief Select the node after the last selected; if none is selected,
2885 * select the first within path.
2886 */
2887 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2888 {
2889 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2891 Inkscape::NodePath::Node *last = NULL;
2892 if (nodepath->selected) {
2893 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2894 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2895 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2896 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2897 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2898 if (node->selected) {
2899 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2900 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2901 if (spl->next) { // there's a next subpath
2902 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2903 last = subpath_next->first;
2904 } else if (spl->prev) { // there's a previous subpath
2905 last = NULL; // to be set later to the first node of first subpath
2906 } else {
2907 last = node->n.other;
2908 }
2909 } else {
2910 last = node->n.other;
2911 }
2912 } else {
2913 if (node->n.other) {
2914 last = node->n.other;
2915 } else {
2916 if (spl->next) { // there's a next subpath
2917 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2918 last = subpath_next->first;
2919 } else if (spl->prev) { // there's a previous subpath
2920 last = NULL; // to be set later to the first node of first subpath
2921 } else {
2922 last = (Inkscape::NodePath::Node *) subpath->first;
2923 }
2924 }
2925 }
2926 }
2927 }
2928 }
2929 sp_nodepath_deselect(nodepath);
2930 }
2932 if (last) { // there's at least one more node after selected
2933 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2934 } else { // no more nodes, select the first one in first subpath
2935 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
2936 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
2937 }
2938 }
2940 /**
2941 * \brief Select the node before the first selected; if none is selected,
2942 * select the last within path
2943 */
2944 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
2945 {
2946 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2948 Inkscape::NodePath::Node *last = NULL;
2949 if (nodepath->selected) {
2950 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
2951 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2952 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
2953 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2954 if (node->selected) {
2955 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
2956 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
2957 if (spl->prev) { // there's a prev subpath
2958 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2959 last = subpath_prev->last;
2960 } else if (spl->next) { // there's a next subpath
2961 last = NULL; // to be set later to the last node of last subpath
2962 } else {
2963 last = node->p.other;
2964 }
2965 } else {
2966 last = node->p.other;
2967 }
2968 } else {
2969 if (node->p.other) {
2970 last = node->p.other;
2971 } else {
2972 if (spl->prev) { // there's a prev subpath
2973 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
2974 last = subpath_prev->last;
2975 } else if (spl->next) { // there's a next subpath
2976 last = NULL; // to be set later to the last node of last subpath
2977 } else {
2978 last = (Inkscape::NodePath::Node *) subpath->last;
2979 }
2980 }
2981 }
2982 }
2983 }
2984 }
2985 sp_nodepath_deselect(nodepath);
2986 }
2988 if (last) { // there's at least one more node before selected
2989 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
2990 } else { // no more nodes, select the last one in last subpath
2991 GList *spl = g_list_last(nodepath->subpaths);
2992 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2993 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
2994 }
2995 }
2997 /**
2998 * \brief Select all nodes that are within the rectangle.
2999 */
3000 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, NR::Rect const &b, gboolean incremental)
3001 {
3002 if (!incremental) {
3003 sp_nodepath_deselect(nodepath);
3004 }
3006 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3007 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3008 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3009 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3011 if (b.contains(node->pos)) {
3012 sp_nodepath_node_select(node, TRUE, TRUE);
3013 }
3014 }
3015 }
3016 }
3019 void
3020 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3021 {
3022 g_assert (n);
3023 g_assert (nodepath);
3024 g_assert (n->subpath->nodepath == nodepath);
3026 if (g_list_length (nodepath->selected) == 0) {
3027 if (grow > 0) {
3028 sp_nodepath_node_select(n, TRUE, TRUE);
3029 }
3030 return;
3031 }
3033 if (g_list_length (nodepath->selected) == 1) {
3034 if (grow < 0) {
3035 sp_nodepath_deselect (nodepath);
3036 return;
3037 }
3038 }
3040 double n_sel_range = 0, p_sel_range = 0;
3041 Inkscape::NodePath::Node *farthest_n_node = n;
3042 Inkscape::NodePath::Node *farthest_p_node = n;
3044 // Calculate ranges
3045 {
3046 double n_range = 0, p_range = 0;
3047 bool n_going = true, p_going = true;
3048 Inkscape::NodePath::Node *n_node = n;
3049 Inkscape::NodePath::Node *p_node = n;
3050 do {
3051 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3052 if (n_node && n_going)
3053 n_node = n_node->n.other;
3054 if (n_node == NULL) {
3055 n_going = false;
3056 } else {
3057 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3058 if (n_node->selected) {
3059 n_sel_range = n_range;
3060 farthest_n_node = n_node;
3061 }
3062 if (n_node == p_node) {
3063 n_going = false;
3064 p_going = false;
3065 }
3066 }
3067 if (p_node && p_going)
3068 p_node = p_node->p.other;
3069 if (p_node == NULL) {
3070 p_going = false;
3071 } else {
3072 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3073 if (p_node->selected) {
3074 p_sel_range = p_range;
3075 farthest_p_node = p_node;
3076 }
3077 if (p_node == n_node) {
3078 n_going = false;
3079 p_going = false;
3080 }
3081 }
3082 } while (n_going || p_going);
3083 }
3085 if (grow > 0) {
3086 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3087 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3088 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3089 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3090 }
3091 } else {
3092 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3093 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3094 } else if (farthest_p_node && farthest_p_node->selected) {
3095 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3096 }
3097 }
3098 }
3100 void
3101 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3102 {
3103 g_assert (n);
3104 g_assert (nodepath);
3105 g_assert (n->subpath->nodepath == nodepath);
3107 if (g_list_length (nodepath->selected) == 0) {
3108 if (grow > 0) {
3109 sp_nodepath_node_select(n, TRUE, TRUE);
3110 }
3111 return;
3112 }
3114 if (g_list_length (nodepath->selected) == 1) {
3115 if (grow < 0) {
3116 sp_nodepath_deselect (nodepath);
3117 return;
3118 }
3119 }
3121 Inkscape::NodePath::Node *farthest_selected = NULL;
3122 double farthest_dist = 0;
3124 Inkscape::NodePath::Node *closest_unselected = NULL;
3125 double closest_dist = NR_HUGE;
3127 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3128 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3129 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3130 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3131 if (node == n)
3132 continue;
3133 if (node->selected) {
3134 if (NR::L2(node->pos - n->pos) > farthest_dist) {
3135 farthest_dist = NR::L2(node->pos - n->pos);
3136 farthest_selected = node;
3137 }
3138 } else {
3139 if (NR::L2(node->pos - n->pos) < closest_dist) {
3140 closest_dist = NR::L2(node->pos - n->pos);
3141 closest_unselected = node;
3142 }
3143 }
3144 }
3145 }
3147 if (grow > 0) {
3148 if (closest_unselected) {
3149 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3150 }
3151 } else {
3152 if (farthest_selected) {
3153 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3154 }
3155 }
3156 }
3159 /**
3160 \brief Saves all nodes' and handles' current positions in their origin members
3161 */
3162 void
3163 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3164 {
3165 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3166 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3167 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3168 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3169 n->origin = n->pos;
3170 n->p.origin = n->p.pos;
3171 n->n.origin = n->n.pos;
3172 }
3173 }
3174 }
3176 /**
3177 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3178 */
3179 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3180 {
3181 GList *r = NULL;
3182 if (nodepath->selected) {
3183 guint i = 0;
3184 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3185 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3186 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3187 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3188 i++;
3189 if (node->selected) {
3190 r = g_list_append(r, GINT_TO_POINTER(i));
3191 }
3192 }
3193 }
3194 }
3195 return r;
3196 }
3198 /**
3199 \brief Restores selection by selecting nodes whose positions are in the list
3200 */
3201 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3202 {
3203 sp_nodepath_deselect(nodepath);
3205 guint i = 0;
3206 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3207 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3208 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3209 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3210 i++;
3211 if (g_list_find(r, GINT_TO_POINTER(i))) {
3212 sp_nodepath_node_select(node, TRUE, TRUE);
3213 }
3214 }
3215 }
3216 }
3219 /**
3220 \brief Adjusts handle according to node type and line code.
3221 */
3222 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3223 {
3224 g_assert(node);
3226 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3227 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3229 // nothing to do if we are an end node
3230 if (me->other == NULL) return;
3231 if (other->other == NULL) return;
3233 // nothing to do if we are a cusp node
3234 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3236 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3237 NRPathcode mecode;
3238 if (which_adjust == 1) {
3239 mecode = (NRPathcode)me->other->code;
3240 } else {
3241 mecode = (NRPathcode)node->code;
3242 }
3243 if (mecode == NR_LINETO) return;
3245 if (sp_node_side_is_line(node, other)) {
3246 // other is a line, and we are either smooth or symm
3247 Inkscape::NodePath::Node *othernode = other->other;
3248 double len = NR::L2(me->pos - node->pos);
3249 Geom::Point delta = node->pos - othernode->pos;
3250 double linelen = NR::L2(delta);
3251 if (linelen < 1e-18)
3252 return;
3253 me->pos = node->pos + (len / linelen)*delta;
3254 return;
3255 }
3257 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3258 // symmetrize
3259 me->pos = 2 * node->pos - other->pos;
3260 return;
3261 } else {
3262 // smoothify
3263 double len = NR::L2(me->pos - node->pos);
3264 Geom::Point delta = other->pos - node->pos;
3265 double otherlen = NR::L2(delta);
3266 if (otherlen < 1e-18) return;
3267 me->pos = node->pos - (len / otherlen) * delta;
3268 }
3269 }
3271 /**
3272 \brief Adjusts both handles according to node type and line code
3273 */
3274 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3275 {
3276 g_assert(node);
3278 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3280 /* we are either smooth or symm */
3282 if (node->p.other == NULL) return;
3283 if (node->n.other == NULL) return;
3285 if (sp_node_side_is_line(node, &node->p)) {
3286 sp_node_adjust_handle(node, 1);
3287 return;
3288 }
3290 if (sp_node_side_is_line(node, &node->n)) {
3291 sp_node_adjust_handle(node, -1);
3292 return;
3293 }
3295 /* both are curves */
3296 Geom::Point const delta( node->n.pos - node->p.pos );
3298 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3299 node->p.pos = node->pos - delta / 2;
3300 node->n.pos = node->pos + delta / 2;
3301 return;
3302 }
3304 /* We are smooth */
3305 double plen = NR::L2(node->p.pos - node->pos);
3306 if (plen < 1e-18) return;
3307 double nlen = NR::L2(node->n.pos - node->pos);
3308 if (nlen < 1e-18) return;
3309 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3310 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3311 }
3313 /**
3314 * Node event callback.
3315 */
3316 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3317 {
3318 gboolean ret = FALSE;
3319 switch (event->type) {
3320 case GDK_ENTER_NOTIFY:
3321 Inkscape::NodePath::Path::active_node = n;
3322 break;
3323 case GDK_LEAVE_NOTIFY:
3324 Inkscape::NodePath::Path::active_node = NULL;
3325 break;
3326 case GDK_SCROLL:
3327 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3328 switch (event->scroll.direction) {
3329 case GDK_SCROLL_UP:
3330 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3331 break;
3332 case GDK_SCROLL_DOWN:
3333 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3334 break;
3335 default:
3336 break;
3337 }
3338 ret = TRUE;
3339 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3340 switch (event->scroll.direction) {
3341 case GDK_SCROLL_UP:
3342 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3343 break;
3344 case GDK_SCROLL_DOWN:
3345 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3346 break;
3347 default:
3348 break;
3349 }
3350 ret = TRUE;
3351 }
3352 break;
3353 case GDK_KEY_PRESS:
3354 switch (get_group0_keyval (&event->key)) {
3355 case GDK_space:
3356 if (event->key.state & GDK_BUTTON1_MASK) {
3357 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3358 stamp_repr(nodepath);
3359 ret = TRUE;
3360 }
3361 break;
3362 case GDK_Page_Up:
3363 if (event->key.state & GDK_CONTROL_MASK) {
3364 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3365 } else {
3366 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3367 }
3368 break;
3369 case GDK_Page_Down:
3370 if (event->key.state & GDK_CONTROL_MASK) {
3371 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3372 } else {
3373 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3374 }
3375 break;
3376 default:
3377 break;
3378 }
3379 break;
3380 default:
3381 break;
3382 }
3384 return ret;
3385 }
3387 /**
3388 * Handle keypress on node; directly called.
3389 */
3390 gboolean node_key(GdkEvent *event)
3391 {
3392 Inkscape::NodePath::Path *np;
3394 // there is no way to verify nodes so set active_node to nil when deleting!!
3395 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3397 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3398 gint ret = FALSE;
3399 switch (get_group0_keyval (&event->key)) {
3400 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3401 case GDK_BackSpace:
3402 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3403 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3404 sp_nodepath_update_repr(np, _("Delete node"));
3405 Inkscape::NodePath::Path::active_node = NULL;
3406 ret = TRUE;
3407 break;
3408 case GDK_c:
3409 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3410 ret = TRUE;
3411 break;
3412 case GDK_s:
3413 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3414 ret = TRUE;
3415 break;
3416 case GDK_y:
3417 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3418 ret = TRUE;
3419 break;
3420 case GDK_b:
3421 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3422 ret = TRUE;
3423 break;
3424 }
3425 return ret;
3426 }
3427 return FALSE;
3428 }
3430 /**
3431 * Mouseclick on node callback.
3432 */
3433 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3434 {
3435 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3437 if (state & GDK_CONTROL_MASK) {
3438 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3440 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3441 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3442 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3443 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3444 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3445 } else {
3446 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3447 }
3448 sp_nodepath_update_repr(nodepath, _("Change node type"));
3449 sp_nodepath_update_statusbar(nodepath);
3451 } else { //ctrl+alt+click: delete node
3452 GList *node_to_delete = NULL;
3453 node_to_delete = g_list_append(node_to_delete, n);
3454 sp_node_delete_preserve(node_to_delete);
3455 }
3457 } else {
3458 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3459 }
3460 }
3462 /**
3463 * Mouse grabbed node callback.
3464 */
3465 static void node_grabbed(SPKnot */*knot*/, guint state, gpointer data)
3466 {
3467 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3469 if (!n->selected) {
3470 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3471 }
3473 n->is_dragging = true;
3474 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3476 sp_nodepath_remember_origins (n->subpath->nodepath);
3477 }
3479 /**
3480 * Mouse ungrabbed node callback.
3481 */
3482 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3483 {
3484 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3486 n->dragging_out = NULL;
3487 n->is_dragging = false;
3488 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3490 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3491 }
3493 /**
3494 * The point on a line, given by its angle, closest to the given point.
3495 * \param p A point.
3496 * \param a Angle of the line; it is assumed to go through coordinate origin.
3497 * \param closest Pointer to the point struct where the result is stored.
3498 * \todo FIXME: use dot product perhaps?
3499 */
3500 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3501 {
3502 if (a == HUGE_VAL) { // vertical
3503 *closest = Geom::Point(0, (*p)[NR::Y]);
3504 } else {
3505 (*closest)[NR::X] = ( a * (*p)[NR::Y] + (*p)[NR::X]) / (a*a + 1);
3506 (*closest)[NR::Y] = a * (*closest)[NR::X];
3507 }
3508 }
3510 /**
3511 * Distance from the point to a line given by its angle.
3512 * \param p A point.
3513 * \param a Angle of the line; it is assumed to go through coordinate origin.
3514 */
3515 static double point_line_distance(Geom::Point *p, double a)
3516 {
3517 Geom::Point c;
3518 point_line_closest(p, a, &c);
3519 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]));
3520 }
3522 /**
3523 * Callback for node "request" signal.
3524 * \todo fixme: This goes to "moved" event? (lauris)
3525 */
3526 static gboolean
3527 node_request(SPKnot */*knot*/, Geom::Point *p, guint state, gpointer data)
3528 {
3529 double yn, xn, yp, xp;
3530 double an, ap, na, pa;
3531 double d_an, d_ap, d_na, d_pa;
3532 gboolean collinear = FALSE;
3533 Geom::Point c;
3534 Geom::Point pr;
3536 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3538 n->subpath->nodepath->desktop->snapindicator->remove_snappoint();
3540 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3541 if ( (!n->subpath->nodepath->straight_path) &&
3542 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3543 || n->dragging_out ) )
3544 {
3545 Geom::Point mouse = (*p);
3547 if (!n->dragging_out) {
3548 // This is the first drag-out event; find out which handle to drag out
3549 double appr_n = (n->n.other ? NR::L2(n->n.other->pos - n->pos) - NR::L2(n->n.other->pos - (*p)) : -HUGE_VAL);
3550 double appr_p = (n->p.other ? NR::L2(n->p.other->pos - n->pos) - NR::L2(n->p.other->pos - (*p)) : -HUGE_VAL);
3552 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3553 return FALSE;
3555 Inkscape::NodePath::NodeSide *opposite;
3556 if (appr_p > appr_n) { // closer to p
3557 n->dragging_out = &n->p;
3558 opposite = &n->n;
3559 n->code = NR_CURVETO;
3560 } else if (appr_p < appr_n) { // closer to n
3561 n->dragging_out = &n->n;
3562 opposite = &n->p;
3563 n->n.other->code = NR_CURVETO;
3564 } else { // p and n nodes are the same
3565 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3566 n->dragging_out = &n->p;
3567 opposite = &n->n;
3568 n->code = NR_CURVETO;
3569 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3570 n->dragging_out = &n->n;
3571 opposite = &n->p;
3572 n->n.other->code = NR_CURVETO;
3573 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3574 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);
3575 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);
3576 if (appr_other_p > appr_other_n) { // closer to other's p handle
3577 n->dragging_out = &n->n;
3578 opposite = &n->p;
3579 n->n.other->code = NR_CURVETO;
3580 } else { // closer to other's n handle
3581 n->dragging_out = &n->p;
3582 opposite = &n->n;
3583 n->code = NR_CURVETO;
3584 }
3585 }
3586 }
3588 // if there's another handle, make sure the one we drag out starts parallel to it
3589 if (opposite->pos != n->pos) {
3590 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3591 }
3593 // knots might not be created yet!
3594 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3595 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3596 }
3598 // pass this on to the handle-moved callback
3599 node_handle_moved(n->dragging_out->knot, &mouse, state, (gpointer) n);
3600 sp_node_update_handles(n);
3601 return TRUE;
3602 }
3604 if (state & GDK_CONTROL_MASK) { // constrained motion
3606 // calculate relative distances of handles
3607 // n handle:
3608 yn = n->n.pos[NR::Y] - n->pos[NR::Y];
3609 xn = n->n.pos[NR::X] - n->pos[NR::X];
3610 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3611 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3612 if (n->n.other) { // if there is the next point
3613 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3614 yn = n->n.other->origin[NR::Y] - n->origin[NR::Y]; // use origin because otherwise the direction will change as you drag
3615 xn = n->n.other->origin[NR::X] - n->origin[NR::X];
3616 }
3617 }
3618 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3619 if (yn < 0) { xn = -xn; yn = -yn; }
3621 // p handle:
3622 yp = n->p.pos[NR::Y] - n->pos[NR::Y];
3623 xp = n->p.pos[NR::X] - n->pos[NR::X];
3624 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3625 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3626 if (n->p.other) {
3627 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3628 yp = n->p.other->origin[NR::Y] - n->origin[NR::Y];
3629 xp = n->p.other->origin[NR::X] - n->origin[NR::X];
3630 }
3631 }
3632 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3633 if (yp < 0) { xp = -xp; yp = -yp; }
3635 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3636 // sliding on handles, only if at least one of the handles is non-vertical
3637 // (otherwise it's the same as ctrl+drag anyway)
3639 // calculate angles of the handles
3640 if (xn == 0) {
3641 if (yn == 0) { // no handle, consider it the continuation of the other one
3642 an = 0;
3643 collinear = TRUE;
3644 }
3645 else an = 0; // vertical; set the angle to horizontal
3646 } else an = yn/xn;
3648 if (xp == 0) {
3649 if (yp == 0) { // no handle, consider it the continuation of the other one
3650 ap = an;
3651 }
3652 else ap = 0; // vertical; set the angle to horizontal
3653 } else ap = yp/xp;
3655 if (collinear) an = ap;
3657 // angles of the perpendiculars; HUGE_VAL means vertical
3658 if (an == 0) na = HUGE_VAL; else na = -1/an;
3659 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3661 // mouse point relative to the node's original pos
3662 pr = (*p) - n->origin;
3664 // distances to the four lines (two handles and two perpendiculars)
3665 d_an = point_line_distance(&pr, an);
3666 d_na = point_line_distance(&pr, na);
3667 d_ap = point_line_distance(&pr, ap);
3668 d_pa = point_line_distance(&pr, pa);
3670 // find out which line is the closest, save its closest point in c
3671 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3672 point_line_closest(&pr, an, &c);
3673 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3674 point_line_closest(&pr, ap, &c);
3675 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3676 point_line_closest(&pr, na, &c);
3677 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3678 point_line_closest(&pr, pa, &c);
3679 }
3681 // move the node to the closest point
3682 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3683 n->origin[NR::X] + c[NR::X] - n->pos[NR::X],
3684 n->origin[NR::Y] + c[NR::Y] - n->pos[NR::Y],
3685 true);
3687 } else { // constraining to hor/vert
3689 if (fabs((*p)[NR::X] - n->origin[NR::X]) > fabs((*p)[NR::Y] - n->origin[NR::Y])) { // snap to hor
3690 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3691 (*p)[NR::X] - n->pos[NR::X],
3692 n->origin[NR::Y] - n->pos[NR::Y],
3693 true,
3694 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::X]));
3695 } else { // snap to vert
3696 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3697 n->origin[NR::X] - n->pos[NR::X],
3698 (*p)[NR::Y] - n->pos[NR::Y],
3699 true,
3700 true, Inkscape::Snapper::ConstraintLine(component_vectors[NR::Y]));
3701 }
3702 }
3703 } else { // move freely
3704 if (n->is_dragging) {
3705 if (state & GDK_MOD1_MASK) { // sculpt
3706 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, (*p) - n->origin);
3707 } else {
3708 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3709 (*p)[NR::X] - n->pos[NR::X],
3710 (*p)[NR::Y] - n->pos[NR::Y],
3711 (state & GDK_SHIFT_MASK) == 0);
3712 }
3713 }
3714 }
3716 n->subpath->nodepath->desktop->scroll_to_point(*p);
3718 return TRUE;
3719 }
3721 /**
3722 * Node handle clicked callback.
3723 */
3724 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3725 {
3726 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3728 if (state & GDK_CONTROL_MASK) { // "delete" handle
3729 if (n->p.knot == knot) {
3730 n->p.pos = n->pos;
3731 } else if (n->n.knot == knot) {
3732 n->n.pos = n->pos;
3733 }
3734 sp_node_update_handles(n);
3735 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3736 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3737 sp_nodepath_update_statusbar(nodepath);
3739 } else { // just select or add to selection, depending in Shift
3740 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3741 }
3742 }
3744 /**
3745 * Node handle grabbed callback.
3746 */
3747 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3748 {
3749 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3751 if (!n->selected) {
3752 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3753 }
3755 // remember the origin point of the handle
3756 if (n->p.knot == knot) {
3757 n->p.origin_radial = n->p.pos - n->pos;
3758 } else if (n->n.knot == knot) {
3759 n->n.origin_radial = n->n.pos - n->pos;
3760 } else {
3761 g_assert_not_reached();
3762 }
3764 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3765 }
3767 /**
3768 * Node handle ungrabbed callback.
3769 */
3770 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3771 {
3772 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3774 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3775 if (n->p.knot == knot) {
3776 n->p.origin_radial.a = 0;
3777 sp_knot_set_position(knot, n->p.pos, state);
3778 } else if (n->n.knot == knot) {
3779 n->n.origin_radial.a = 0;
3780 sp_knot_set_position(knot, n->n.pos, state);
3781 } else {
3782 g_assert_not_reached();
3783 }
3785 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3786 }
3788 /**
3789 * Node handle "request" signal callback.
3790 */
3791 static gboolean node_handle_request(SPKnot *knot, Geom::Point *p, guint state, gpointer data)
3792 {
3793 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3795 Inkscape::NodePath::NodeSide *me, *opposite;
3796 gint which;
3797 if (n->p.knot == knot) {
3798 me = &n->p;
3799 opposite = &n->n;
3800 which = -1;
3801 } else if (n->n.knot == knot) {
3802 me = &n->n;
3803 opposite = &n->p;
3804 which = 1;
3805 } else {
3806 me = opposite = NULL;
3807 which = 0;
3808 g_assert_not_reached();
3809 }
3811 SPDesktop *desktop = n->subpath->nodepath->desktop;
3812 SnapManager &m = desktop->namedview->snap_manager;
3813 m.setup(desktop, true, n->subpath->nodepath->item);
3814 Inkscape::SnappedPoint s;
3816 if ((state & GDK_SHIFT_MASK) != 0) {
3817 // We will not try to snap when the shift-key is pressed
3818 // so remove the old snap indicator and don't wait for it to time-out
3819 desktop->snapindicator->remove_snappoint();
3820 }
3822 Inkscape::NodePath::Node *othernode = opposite->other;
3823 if (othernode) {
3824 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3825 /* We are smooth node adjacent with line */
3826 Geom::Point const delta = *p - n->pos;
3827 NR::Coord const len = NR::L2(delta);
3828 Inkscape::NodePath::Node *othernode = opposite->other;
3829 Geom::Point const ndelta = n->pos - othernode->pos;
3830 NR::Coord const linelen = NR::L2(ndelta);
3831 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3832 NR::Coord const scal = dot(delta, ndelta) / linelen;
3833 (*p) = n->pos + (scal / linelen) * ndelta;
3834 }
3835 if ((state & GDK_SHIFT_MASK) == 0) {
3836 s = m.constrainedSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p), Inkscape::Snapper::ConstraintLine(*p, ndelta));
3837 }
3838 } else {
3839 if ((state & GDK_SHIFT_MASK) == 0) {
3840 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3841 }
3842 }
3843 } else {
3844 if ((state & GDK_SHIFT_MASK) == 0) {
3845 s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, to_2geom(*p));
3846 }
3847 }
3849 Geom::Point pt2g = *p;
3850 s.getPoint(pt2g);
3851 *p = pt2g;
3853 sp_node_adjust_handle(n, -which);
3855 return FALSE;
3856 }
3858 /**
3859 * Node handle moved callback.
3860 */
3861 static void node_handle_moved(SPKnot *knot, Geom::Point *p, guint state, gpointer data)
3862 {
3863 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3865 Inkscape::NodePath::NodeSide *me;
3866 Inkscape::NodePath::NodeSide *other;
3867 if (n->p.knot == knot) {
3868 me = &n->p;
3869 other = &n->n;
3870 } else if (n->n.knot == knot) {
3871 me = &n->n;
3872 other = &n->p;
3873 } else {
3874 me = NULL;
3875 other = NULL;
3876 g_assert_not_reached();
3877 }
3879 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3880 Radial rme(me->pos - n->pos);
3881 Radial rother(other->pos - n->pos);
3882 Radial rnew(*p - n->pos);
3884 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
3885 int const snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
3886 /* 0 interpreted as "no snapping". */
3888 // 1. Snap to the closest PI/snaps angle, starting from zero.
3889 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
3891 // 2. Snap to the original angle, its opposite and perpendiculars
3892 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
3893 /* The closest PI/2 angle, starting from original angle */
3894 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
3896 // Snap to the closest.
3897 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
3898 ? a_snapped
3899 : a_ortho );
3900 }
3902 // 3. Snap to the angle of the opposite line, if any
3903 Inkscape::NodePath::Node *othernode = other->other;
3904 if (othernode) {
3905 Geom::Point other_to_snap(0,0);
3906 if (sp_node_side_is_line(n, other)) {
3907 other_to_snap = othernode->pos - n->pos;
3908 } else {
3909 other_to_snap = other->pos - n->pos;
3910 }
3911 if (NR::L2(other_to_snap) > 1e-3) {
3912 Radial rother_to_snap(other_to_snap);
3913 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
3914 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
3916 // Snap to the closest.
3917 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
3918 ? a_snapped
3919 : a_oppo );
3920 }
3921 }
3923 rnew.a = a_snapped;
3924 }
3926 if (state & GDK_MOD1_MASK) {
3927 // lock handle length
3928 rnew.r = me->origin_radial.r;
3929 }
3931 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
3932 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
3933 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
3934 rother.a += rnew.a - rme.a;
3935 other->pos = Geom::Point(rother) + n->pos;
3936 if (other->knot) {
3937 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
3938 sp_knot_moveto(other->knot, other->pos);
3939 }
3940 }
3942 me->pos = Geom::Point(rnew) + n->pos;
3943 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
3945 // move knot, but without emitting the signal:
3946 // we cannot emit a "moved" signal because we're now processing it
3947 sp_knot_moveto(me->knot, me->pos);
3949 update_object(n->subpath->nodepath);
3951 /* status text */
3952 SPDesktop *desktop = n->subpath->nodepath->desktop;
3953 if (!desktop) return;
3954 SPEventContext *ec = desktop->event_context;
3955 if (!ec) return;
3957 Inkscape::MessageContext *mc = get_message_context(ec);
3959 if (!mc) return;
3961 double degrees = 180 / M_PI * rnew.a;
3962 if (degrees > 180) degrees -= 360;
3963 if (degrees < -180) degrees += 360;
3964 if (prefs_get_int_attribute("options.compassangledisplay", "value", 0) != 0)
3965 degrees = angle_to_compass (degrees);
3967 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
3969 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
3970 _("<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);
3972 g_string_free(length, TRUE);
3973 }
3975 /**
3976 * Node handle event callback.
3977 */
3978 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
3979 {
3980 gboolean ret = FALSE;
3981 switch (event->type) {
3982 case GDK_KEY_PRESS:
3983 switch (get_group0_keyval (&event->key)) {
3984 case GDK_space:
3985 if (event->key.state & GDK_BUTTON1_MASK) {
3986 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3987 stamp_repr(nodepath);
3988 ret = TRUE;
3989 }
3990 break;
3991 default:
3992 break;
3993 }
3994 break;
3995 case GDK_ENTER_NOTIFY:
3996 // we use an experimentally determined threshold that seems to work fine
3997 if (NR::L2(n->pos - knot->pos) < 0.75)
3998 Inkscape::NodePath::Path::active_node = n;
3999 break;
4000 case GDK_LEAVE_NOTIFY:
4001 // we use an experimentally determined threshold that seems to work fine
4002 if (NR::L2(n->pos - knot->pos) < 0.75)
4003 Inkscape::NodePath::Path::active_node = NULL;
4004 break;
4005 default:
4006 break;
4007 }
4009 return ret;
4010 }
4012 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4013 Radial &rme, Radial &rother, gboolean const both)
4014 {
4015 rme.a += angle;
4016 if ( both
4017 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4018 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4019 {
4020 rother.a += angle;
4021 }
4022 }
4024 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4025 Radial &rme, Radial &rother, gboolean const both)
4026 {
4027 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4029 gdouble r;
4030 if ( both
4031 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4032 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4033 {
4034 r = MAX(rme.r, rother.r);
4035 } else {
4036 r = rme.r;
4037 }
4039 gdouble const weird_angle = atan2(norm_angle, r);
4040 /* Bulia says norm_angle is just the visible distance that the
4041 * object's end must travel on the screen. Left as 'angle' for want of
4042 * a better name.*/
4044 rme.a += weird_angle;
4045 if ( both
4046 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4047 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4048 {
4049 rother.a += weird_angle;
4050 }
4051 }
4053 /**
4054 * Rotate one node.
4055 */
4056 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4057 {
4058 Inkscape::NodePath::NodeSide *me, *other;
4059 bool both = false;
4061 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4062 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4064 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4065 me = &(n->p);
4066 other = &(n->n);
4067 } else if (!n->p.other) {
4068 me = &(n->n);
4069 other = &(n->p);
4070 } else {
4071 if (which > 0) { // right handle
4072 if (xn > xp) {
4073 me = &(n->n);
4074 other = &(n->p);
4075 } else {
4076 me = &(n->p);
4077 other = &(n->n);
4078 }
4079 } else if (which < 0){ // left handle
4080 if (xn <= xp) {
4081 me = &(n->n);
4082 other = &(n->p);
4083 } else {
4084 me = &(n->p);
4085 other = &(n->n);
4086 }
4087 } else { // both handles
4088 me = &(n->n);
4089 other = &(n->p);
4090 both = true;
4091 }
4092 }
4094 Radial rme(me->pos - n->pos);
4095 Radial rother(other->pos - n->pos);
4097 if (screen) {
4098 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4099 } else {
4100 node_rotate_one_internal (*n, angle, rme, rother, both);
4101 }
4103 me->pos = n->pos + Geom::Point(rme);
4105 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4106 other->pos = n->pos + Geom::Point(rother);
4107 }
4109 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4110 // so here we just move all the knots without emitting move signals, for speed
4111 sp_node_update_handles(n, false);
4112 }
4114 /**
4115 * Rotate selected nodes.
4116 */
4117 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4118 {
4119 if (!nodepath || !nodepath->selected) return;
4121 if (g_list_length(nodepath->selected) == 1) {
4122 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4123 node_rotate_one (n, angle, which, screen);
4124 } else {
4125 // rotate as an object:
4127 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4128 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4129 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4130 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4131 box.expandTo (n->pos); // contain all selected nodes
4132 }
4134 gdouble rot;
4135 if (screen) {
4136 gdouble const zoom = nodepath->desktop->current_zoom();
4137 gdouble const zmove = angle / zoom;
4138 gdouble const r = NR::L2(box.max() - box.midpoint());
4139 rot = atan2(zmove, r);
4140 } else {
4141 rot = angle;
4142 }
4144 Geom::Point rot_center;
4145 if (Inkscape::NodePath::Path::active_node == NULL)
4146 rot_center = box.midpoint();
4147 else
4148 rot_center = Inkscape::NodePath::Path::active_node->pos;
4150 NR::Matrix t =
4151 NR::Matrix (NR::translate(-rot_center)) *
4152 NR::Matrix (NR::rotate(rot)) *
4153 NR::Matrix (NR::translate(rot_center));
4155 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4156 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4157 n->pos *= t;
4158 n->n.pos *= t;
4159 n->p.pos *= t;
4160 sp_node_update_handles(n, false);
4161 }
4162 }
4164 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4165 }
4167 /**
4168 * Scale one node.
4169 */
4170 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4171 {
4172 bool both = false;
4173 Inkscape::NodePath::NodeSide *me, *other;
4175 double xn = n->n.other? n->n.other->pos[NR::X] : n->pos[NR::X];
4176 double xp = n->p.other? n->p.other->pos[NR::X] : n->pos[NR::X];
4178 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4179 me = &(n->p);
4180 other = &(n->n);
4181 n->code = NR_CURVETO;
4182 } else if (!n->p.other) {
4183 me = &(n->n);
4184 other = &(n->p);
4185 if (n->n.other)
4186 n->n.other->code = NR_CURVETO;
4187 } else {
4188 if (which > 0) { // right handle
4189 if (xn > xp) {
4190 me = &(n->n);
4191 other = &(n->p);
4192 if (n->n.other)
4193 n->n.other->code = NR_CURVETO;
4194 } else {
4195 me = &(n->p);
4196 other = &(n->n);
4197 n->code = NR_CURVETO;
4198 }
4199 } else if (which < 0){ // left handle
4200 if (xn <= xp) {
4201 me = &(n->n);
4202 other = &(n->p);
4203 if (n->n.other)
4204 n->n.other->code = NR_CURVETO;
4205 } else {
4206 me = &(n->p);
4207 other = &(n->n);
4208 n->code = NR_CURVETO;
4209 }
4210 } else { // both handles
4211 me = &(n->n);
4212 other = &(n->p);
4213 both = true;
4214 n->code = NR_CURVETO;
4215 if (n->n.other)
4216 n->n.other->code = NR_CURVETO;
4217 }
4218 }
4220 Radial rme(me->pos - n->pos);
4221 Radial rother(other->pos - n->pos);
4223 rme.r += grow;
4224 if (rme.r < 0) rme.r = 0;
4225 if (rme.a == HUGE_VAL) {
4226 if (me->other) { // if direction is unknown, initialize it towards the next node
4227 Radial rme_next(me->other->pos - n->pos);
4228 rme.a = rme_next.a;
4229 } else { // if there's no next, initialize to 0
4230 rme.a = 0;
4231 }
4232 }
4233 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4234 rother.r += grow;
4235 if (rother.r < 0) rother.r = 0;
4236 if (rother.a == HUGE_VAL) {
4237 rother.a = rme.a + M_PI;
4238 }
4239 }
4241 me->pos = n->pos + Geom::Point(rme);
4243 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4244 other->pos = n->pos + Geom::Point(rother);
4245 }
4247 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4248 // so here we just move all the knots without emitting move signals, for speed
4249 sp_node_update_handles(n, false);
4250 }
4252 /**
4253 * Scale selected nodes.
4254 */
4255 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4256 {
4257 if (!nodepath || !nodepath->selected) return;
4259 if (g_list_length(nodepath->selected) == 1) {
4260 // scale handles of the single selected node
4261 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4262 node_scale_one (n, grow, which);
4263 } else {
4264 // scale nodes as an "object":
4266 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4267 NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4268 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4269 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4270 box.expandTo (n->pos); // contain all selected nodes
4271 }
4273 double scale = (box.maxExtent() + grow)/box.maxExtent();
4275 Geom::Point scale_center;
4276 if (Inkscape::NodePath::Path::active_node == NULL)
4277 scale_center = box.midpoint();
4278 else
4279 scale_center = Inkscape::NodePath::Path::active_node->pos;
4281 NR::Matrix t =
4282 NR::Matrix (NR::translate(-scale_center)) *
4283 NR::Matrix (NR::scale(scale, scale)) *
4284 NR::Matrix (NR::translate(scale_center));
4286 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4287 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4288 n->pos *= t;
4289 n->n.pos *= t;
4290 n->p.pos *= t;
4291 sp_node_update_handles(n, false);
4292 }
4293 }
4295 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4296 }
4298 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4299 {
4300 if (!nodepath) return;
4301 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4302 }
4304 /**
4305 * Flip selected nodes horizontally/vertically.
4306 */
4307 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4308 {
4309 if (!nodepath || !nodepath->selected) return;
4311 if (g_list_length(nodepath->selected) == 1 && !center) {
4312 // flip handles of the single selected node
4313 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4314 double temp = n->p.pos[axis];
4315 n->p.pos[axis] = n->n.pos[axis];
4316 n->n.pos[axis] = temp;
4317 sp_node_update_handles(n, false);
4318 } else {
4319 // scale nodes as an "object":
4321 Geom::Rect box = sp_node_selected_bbox (nodepath);
4322 if (!center) {
4323 center = box.midpoint();
4324 }
4325 NR::Matrix t =
4326 NR::Matrix (NR::translate(- *center)) *
4327 NR::Matrix ((axis == Geom::X)? NR::scale(-1, 1) : NR::scale(1, -1)) *
4328 NR::Matrix (NR::translate(*center));
4330 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4331 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4332 n->pos *= t;
4333 n->n.pos *= t;
4334 n->p.pos *= t;
4335 sp_node_update_handles(n, false);
4336 }
4337 }
4339 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4340 }
4342 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4343 {
4344 g_assert (nodepath->selected);
4346 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4347 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4348 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4349 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4350 box.expandTo (n->pos); // contain all selected nodes
4351 }
4352 return box;
4353 }
4355 //-----------------------------------------------
4356 /**
4357 * Return new subpath under given nodepath.
4358 */
4359 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4360 {
4361 g_assert(nodepath);
4362 g_assert(nodepath->desktop);
4364 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4366 s->nodepath = nodepath;
4367 s->closed = FALSE;
4368 s->nodes = NULL;
4369 s->first = NULL;
4370 s->last = NULL;
4372 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4373 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4374 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4376 return s;
4377 }
4379 /**
4380 * Destroy nodes in subpath, then subpath itself.
4381 */
4382 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4383 {
4384 g_assert(subpath);
4385 g_assert(subpath->nodepath);
4386 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4388 while (subpath->nodes) {
4389 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4390 }
4392 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4394 g_free(subpath);
4395 }
4397 /**
4398 * Link head to tail in subpath.
4399 */
4400 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4401 {
4402 g_assert(!sp->closed);
4403 g_assert(sp->last != sp->first);
4404 g_assert(sp->first->code == NR_MOVETO);
4406 sp->closed = TRUE;
4408 //Link the head to the tail
4409 sp->first->p.other = sp->last;
4410 sp->last->n.other = sp->first;
4411 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4412 sp->first = sp->last;
4414 //Remove the extra end node
4415 sp_nodepath_node_destroy(sp->last->n.other);
4416 }
4418 /**
4419 * Open closed (loopy) subpath at node.
4420 */
4421 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4422 {
4423 g_assert(sp->closed);
4424 g_assert(n->subpath == sp);
4425 g_assert(sp->first == sp->last);
4427 /* We create new startpoint, current node will become last one */
4429 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4430 &n->pos, &n->pos, &n->n.pos);
4433 sp->closed = FALSE;
4435 //Unlink to make a head and tail
4436 sp->first = new_path;
4437 sp->last = n;
4438 n->n.other = NULL;
4439 new_path->p.other = NULL;
4440 }
4442 /**
4443 * Return new node in subpath with given properties.
4444 * \param pos Position of node.
4445 * \param ppos Handle position in previous direction
4446 * \param npos Handle position in previous direction
4447 */
4448 Inkscape::NodePath::Node *
4449 sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos)
4450 {
4451 g_assert(sp);
4452 g_assert(sp->nodepath);
4453 g_assert(sp->nodepath->desktop);
4455 if (nodechunk == NULL)
4456 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4458 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4460 n->subpath = sp;
4462 if (type != Inkscape::NodePath::NODE_NONE) {
4463 // use the type from sodipodi:nodetypes
4464 n->type = type;
4465 } else {
4466 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4467 // points are (almost) collinear
4468 if (NR::L2(*pos - *ppos) < 1e-6 || NR::L2(*pos - *npos) < 1e-6) {
4469 // endnode, or a node with a retracted handle
4470 n->type = Inkscape::NodePath::NODE_CUSP;
4471 } else {
4472 n->type = Inkscape::NodePath::NODE_SMOOTH;
4473 }
4474 } else {
4475 n->type = Inkscape::NodePath::NODE_CUSP;
4476 }
4477 }
4479 n->code = code;
4480 n->selected = FALSE;
4481 n->pos = *pos;
4482 n->p.pos = *ppos;
4483 n->n.pos = *npos;
4485 n->dragging_out = NULL;
4487 Inkscape::NodePath::Node *prev;
4488 if (next) {
4489 //g_assert(g_list_find(sp->nodes, next));
4490 prev = next->p.other;
4491 } else {
4492 prev = sp->last;
4493 }
4495 if (prev)
4496 prev->n.other = n;
4497 else
4498 sp->first = n;
4500 if (next)
4501 next->p.other = n;
4502 else
4503 sp->last = n;
4505 n->p.other = prev;
4506 n->n.other = next;
4508 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"));
4509 sp_knot_set_position(n->knot, *pos, 0);
4511 n->knot->setShape ((n->type == Inkscape::NodePath::NODE_CUSP)? SP_KNOT_SHAPE_DIAMOND : SP_KNOT_SHAPE_SQUARE);
4512 n->knot->setSize ((n->type == Inkscape::NodePath::NODE_CUSP)? 9 : 7);
4513 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4514 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4515 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4516 sp_knot_update_ctrl(n->knot);
4518 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4519 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4520 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4521 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4522 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4523 sp_knot_show(n->knot);
4525 // We only create handle knots and lines on demand
4526 n->p.knot = NULL;
4527 n->p.line = NULL;
4528 n->n.knot = NULL;
4529 n->n.line = NULL;
4531 sp->nodes = g_list_prepend(sp->nodes, n);
4533 return n;
4534 }
4536 /**
4537 * Destroy node and its knots, link neighbors in subpath.
4538 */
4539 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4540 {
4541 g_assert(node);
4542 g_assert(node->subpath);
4543 g_assert(SP_IS_KNOT(node->knot));
4545 Inkscape::NodePath::SubPath *sp = node->subpath;
4547 if (node->selected) { // first, deselect
4548 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4549 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4550 }
4552 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4554 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4555 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4556 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4557 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4558 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4559 g_object_unref(G_OBJECT(node->knot));
4561 if (node->p.knot) {
4562 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4563 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4564 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4565 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4566 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4567 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4568 g_object_unref(G_OBJECT(node->p.knot));
4569 node->p.knot = NULL;
4570 }
4572 if (node->n.knot) {
4573 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4574 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4575 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4576 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4577 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4578 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4579 g_object_unref(G_OBJECT(node->n.knot));
4580 node->n.knot = NULL;
4581 }
4583 if (node->p.line)
4584 gtk_object_destroy(GTK_OBJECT(node->p.line));
4585 if (node->n.line)
4586 gtk_object_destroy(GTK_OBJECT(node->n.line));
4588 if (sp->nodes) { // there are others nodes on the subpath
4589 if (sp->closed) {
4590 if (sp->first == node) {
4591 g_assert(sp->last == node);
4592 sp->first = node->n.other;
4593 sp->last = sp->first;
4594 }
4595 node->p.other->n.other = node->n.other;
4596 node->n.other->p.other = node->p.other;
4597 } else {
4598 if (sp->first == node) {
4599 sp->first = node->n.other;
4600 sp->first->code = NR_MOVETO;
4601 }
4602 if (sp->last == node) sp->last = node->p.other;
4603 if (node->p.other) node->p.other->n.other = node->n.other;
4604 if (node->n.other) node->n.other->p.other = node->p.other;
4605 }
4606 } else { // this was the last node on subpath
4607 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4608 }
4610 g_mem_chunk_free(nodechunk, node);
4611 }
4613 /**
4614 * Returns one of the node's two sides.
4615 * \param which Indicates which side.
4616 * \return Pointer to previous node side if which==-1, next if which==1.
4617 */
4618 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4619 {
4620 g_assert(node);
4621 Inkscape::NodePath::NodeSide * result = 0;
4622 switch (which) {
4623 case -1:
4624 result = &node->p;
4625 break;
4626 case 1:
4627 result = &node->n;
4628 break;
4629 default:
4630 g_assert_not_reached();
4631 }
4633 return result;
4634 }
4636 /**
4637 * Return the other side of the node, given one of its sides.
4638 */
4639 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4640 {
4641 g_assert(node);
4642 Inkscape::NodePath::NodeSide *result = 0;
4644 if (me == &node->p) {
4645 result = &node->n;
4646 } else if (me == &node->n) {
4647 result = &node->p;
4648 } else {
4649 g_assert_not_reached();
4650 }
4652 return result;
4653 }
4655 /**
4656 * Return NRPathcode on the given side of the node.
4657 */
4658 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4659 {
4660 g_assert(node);
4662 NRPathcode result = NR_END;
4663 if (me == &node->p) {
4664 if (node->p.other) {
4665 result = (NRPathcode)node->code;
4666 } else {
4667 result = NR_MOVETO;
4668 }
4669 } else if (me == &node->n) {
4670 if (node->n.other) {
4671 result = (NRPathcode)node->n.other->code;
4672 } else {
4673 result = NR_MOVETO;
4674 }
4675 } else {
4676 g_assert_not_reached();
4677 }
4679 return result;
4680 }
4682 /**
4683 * Return node with the given index
4684 */
4685 Inkscape::NodePath::Node *
4686 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4687 {
4688 Inkscape::NodePath::Node *e = NULL;
4690 if (!nodepath) {
4691 return e;
4692 }
4694 //find segment
4695 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4697 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4698 int n = g_list_length(sp->nodes);
4699 if (sp->closed) {
4700 n++;
4701 }
4703 //if the piece belongs to this subpath grab it
4704 //otherwise move onto the next subpath
4705 if (index < n) {
4706 e = sp->first;
4707 for (int i = 0; i < index; ++i) {
4708 e = e->n.other;
4709 }
4710 break;
4711 } else {
4712 if (sp->closed) {
4713 index -= (n+1);
4714 } else {
4715 index -= n;
4716 }
4717 }
4718 }
4720 return e;
4721 }
4723 /**
4724 * Returns plain text meaning of node type.
4725 */
4726 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4727 {
4728 unsigned retracted = 0;
4729 bool endnode = false;
4731 for (int which = -1; which <= 1; which += 2) {
4732 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4733 if (side->other && NR::L2(side->pos - node->pos) < 1e-6)
4734 retracted ++;
4735 if (!side->other)
4736 endnode = true;
4737 }
4739 if (retracted == 0) {
4740 if (endnode) {
4741 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4742 return _("end node");
4743 } else {
4744 switch (node->type) {
4745 case Inkscape::NodePath::NODE_CUSP:
4746 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4747 return _("cusp");
4748 case Inkscape::NodePath::NODE_SMOOTH:
4749 // TRANSLATORS: "smooth" is an adjective here
4750 return _("smooth");
4751 case Inkscape::NodePath::NODE_SYMM:
4752 return _("symmetric");
4753 }
4754 }
4755 } else if (retracted == 1) {
4756 if (endnode) {
4757 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4758 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4759 } else {
4760 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4761 }
4762 } else {
4763 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4764 }
4766 return NULL;
4767 }
4769 /**
4770 * Handles content of statusbar as long as node tool is active.
4771 */
4772 void
4773 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4774 {
4775 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");
4776 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4778 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4779 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4780 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4781 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4783 SPDesktop *desktop = NULL;
4784 if (nodepath) {
4785 desktop = nodepath->desktop;
4786 } else {
4787 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4788 }
4790 SPEventContext *ec = desktop->event_context;
4791 if (!ec) return;
4793 Inkscape::MessageContext *mc = get_message_context(ec);
4794 if (!mc) return;
4796 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4798 if (selected_nodes == 0) {
4799 Inkscape::Selection *sel = desktop->selection;
4800 if (!sel || sel->isEmpty()) {
4801 mc->setF(Inkscape::NORMAL_MESSAGE,
4802 _("Select a single object to edit its nodes or handles."));
4803 } else {
4804 if (nodepath) {
4805 mc->setF(Inkscape::NORMAL_MESSAGE,
4806 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.",
4807 "<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.",
4808 total_nodes),
4809 total_nodes);
4810 } else {
4811 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4812 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4813 } else {
4814 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4815 }
4816 }
4817 }
4818 } else if (nodepath && selected_nodes == 1) {
4819 mc->setF(Inkscape::NORMAL_MESSAGE,
4820 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4821 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4822 total_nodes),
4823 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4824 } else {
4825 if (selected_subpaths > 1) {
4826 mc->setF(Inkscape::NORMAL_MESSAGE,
4827 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4828 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4829 total_nodes),
4830 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4831 } else {
4832 mc->setF(Inkscape::NORMAL_MESSAGE,
4833 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4834 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4835 total_nodes),
4836 selected_nodes, total_nodes, when_selected);
4837 }
4838 }
4839 }
4841 /*
4842 * returns a *copy* of the curve of that object.
4843 */
4844 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4845 if (!object)
4846 return NULL;
4848 SPCurve *curve = NULL;
4849 if (SP_IS_PATH(object)) {
4850 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4851 curve = curve_new->copy();
4852 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4853 const gchar *svgd = object->repr->attribute(key);
4854 if (svgd) {
4855 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4856 SPCurve *curve_new = new SPCurve(pv);
4857 if (curve_new) {
4858 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4859 }
4860 }
4861 }
4863 return curve;
4864 }
4866 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4867 if (!np || !np->object || !curve)
4868 return;
4870 if (SP_IS_PATH(np->object)) {
4871 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
4872 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
4873 } else {
4874 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
4875 }
4876 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
4877 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( LIVEPATHEFFECT(np->object)->lpe->getParameter(np->repr_key) );
4878 if (pathparam) {
4879 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
4880 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
4881 }
4882 }
4883 }
4885 /**
4886 SPCanvasItem *
4887 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
4888 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
4889 }
4890 **/
4892 /**
4893 SPCanvasItem *
4894 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
4895 SPCurve *flash_curve = curve->copy();
4896 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
4897 flash_curve->transform(i2d);
4898 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4899 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4900 // unless we also flash the nodes...
4901 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4902 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4903 sp_canvas_item_show(canvasitem);
4904 flash_curve->unref();
4905 return canvasitem;
4906 }
4908 SPCanvasItem *
4909 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
4910 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
4911 prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff));
4912 }
4913 **/
4915 SPCanvasItem *
4916 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
4917 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
4918 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
4919 flash_curve->transform(i2d);
4920 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
4921 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
4922 // unless we also flash the nodes...
4923 guint32 color = prefs_get_int_attribute("tools.nodes", "highlight_color", 0xff0000ff);
4924 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
4925 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
4926 sp_canvas_item_show(canvasitem);
4927 flash_curve->unref();
4928 return canvasitem;
4929 }
4931 // TODO: Merge this with sp_nodepath_make_helper_item()!
4932 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
4933 np->show_helperpath = show;
4935 if (show) {
4936 SPCurve *helper_curve = np->curve->copy();
4937 helper_curve->transform(np->i2d);
4938 if (!np->helper_path) {
4939 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
4941 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
4942 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);
4943 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
4944 sp_canvas_item_move_to_z(np->helper_path, 0);
4945 sp_canvas_item_show(np->helper_path);
4946 } else {
4947 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
4948 }
4949 helper_curve->unref();
4950 } else {
4951 if (np->helper_path) {
4952 GtkObject *temp = np->helper_path;
4953 np->helper_path = NULL;
4954 gtk_object_destroy(temp);
4955 }
4956 }
4957 }
4959 /* sp_nodepath_make_straight_path:
4960 * Prevents user from curving the path by dragging a segment or activating handles etc.
4961 * The resulting path is a linear interpolation between nodal points, with only straight segments.
4962 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
4963 */
4964 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
4965 np->straight_path = true;
4966 np->show_handles = false;
4967 g_message("add code to make the path straight.");
4968 // do sp_nodepath_convert_node_type on all nodes?
4969 // coding tip: search for this text : "Make selected segments lines"
4970 }
4972 /*
4973 Local Variables:
4974 mode:c++
4975 c-file-style:"stroustrup"
4976 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
4977 indent-tabs-mode:nil
4978 fill-column:99
4979 End:
4980 */
4981 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :