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 "preferences.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 "live_effects/lpeobject.h"
57 #include "live_effects/lpeobject-reference.h"
58 #include "live_effects/effect.h"
59 #include "live_effects/parameter/parameter.h"
60 #include "live_effects/parameter/path.h"
61 #include "util/mathfns.h"
62 #include "display/snap-indicator.h"
63 #include "snapped-point.h"
65 namespace Geom { class Matrix; }
67 /// \todo
68 /// evil evil evil. FIXME: conflict of two different Path classes!
69 /// There is a conflict in the namespace between two classes named Path.
70 /// #include "sp-flowtext.h"
71 /// #include "sp-flowregion.h"
73 #define SP_TYPE_FLOWREGION (sp_flowregion_get_type ())
74 #define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION))
75 GType sp_flowregion_get_type (void);
76 #define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ())
77 #define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT))
78 GType sp_flowtext_get_type (void);
79 // end evil workaround
81 #include "helper/stlport.h"
84 /// \todo fixme: Implement these via preferences */
86 #define NODE_FILL 0xbfbfbf00
87 #define NODE_STROKE 0x000000ff
88 #define NODE_FILL_HI 0xff000000
89 #define NODE_STROKE_HI 0x000000ff
90 #define NODE_FILL_SEL 0x0000ffff
91 #define NODE_STROKE_SEL 0x000000ff
92 #define NODE_FILL_SEL_HI 0xff000000
93 #define NODE_STROKE_SEL_HI 0x000000ff
94 #define KNOT_FILL 0xffffffff
95 #define KNOT_STROKE 0x000000ff
96 #define KNOT_FILL_HI 0xff000000
97 #define KNOT_STROKE_HI 0x000000ff
99 static GMemChunk *nodechunk = NULL;
101 /* Creation from object */
103 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t);
104 static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length);
106 /* Object updating */
108 static void stamp_repr(Inkscape::NodePath::Path *np);
109 static SPCurve *create_curve(Inkscape::NodePath::Path *np);
110 static gchar *create_typestr(Inkscape::NodePath::Path *np);
112 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true);
114 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override);
116 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected);
118 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type);
120 /* Adjust handle placement, if the node or the other handle is moved */
121 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust);
122 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node);
123 static void sp_node_adjust_handles_auto(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 const &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 const &p, guint state, gpointer data);
136 static void node_handle_moved(SPKnot *knot, Geom::Point const &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->get_lpe();
195 if (lpe) {
196 // create new canvas items from the effect's helper paths
197 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
198 for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) {
199 np->helper_path_vec[lpe].push_back(canvasitem_from_pathvec(np, *j, true));
200 }
201 }
202 }
203 }
205 void
206 sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) {
207 //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec;
208 if (!SP_IS_LPE_ITEM(np->item)) {
209 g_print ("Only LPEItems can have helperpaths!\n");
210 return;
211 }
213 SPLPEItem *lpeitem = SP_LPE_ITEM(np->item);
214 PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem);
215 for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) {
216 Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe();
217 if (lpe) {
218 /* update canvas items from the effect's helper paths; note that this code relies on the
219 * fact that getHelperPaths() will always return the same number of helperpaths in the same
220 * order as during their creation in sp_nodepath_create_helperpaths
221 */
222 std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem);
223 for (unsigned int j = 0; j < hpaths.size(); ++j) {
224 SPCurve *curve = new SPCurve(hpaths[j]);
225 curve->transform(np->i2d);
226 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH((np->helper_path_vec[lpe])[j]), curve);
227 curve = curve->unref();
228 }
229 }
230 }
231 }
233 static void
234 sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) {
235 for (HelperPathList::iterator i = np->helper_path_vec.begin(); i != np->helper_path_vec.end(); ++i) {
236 for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) {
237 GtkObject *temp = *j;
238 *j = NULL;
239 gtk_object_destroy(temp);
240 }
241 }
242 np->helper_path_vec.clear();
243 }
246 /**
247 * \brief Creates new nodepath from item
248 *
249 * \todo create proper constructor for nodepath::path, this method returns null a constructor cannot so this cannot be simply converted to constructor.
250 */
251 Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item)
252 {
253 Inkscape::XML::Node *repr = object->repr;
255 /** \todo
256 * FIXME: remove this. We don't want to edit paths inside flowtext.
257 * Instead we will build our flowtext with cloned paths, so that the
258 * real paths are outside the flowtext and thus editable as usual.
259 */
260 if (SP_IS_FLOWTEXT(object)) {
261 for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
262 if SP_IS_FLOWREGION(child) {
263 SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
264 if (grandchild && SP_IS_PATH(grandchild)) {
265 object = SP_ITEM(grandchild);
266 break;
267 }
268 }
269 }
270 }
272 SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
274 if (curve == NULL) {
275 return NULL;
276 }
278 if (curve->get_segment_count() < 1) {
279 curve->unref();
280 return NULL; // prevent crash for one-node paths
281 }
283 //Create new nodepath
284 Inkscape::NodePath::Path *np = new Inkscape::NodePath::Path();
285 if (!np) {
286 curve->unref();
287 return NULL;
288 }
290 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
292 // Set defaults
293 np->desktop = desktop;
294 np->object = object;
295 np->subpaths = NULL;
296 np->selected = NULL;
297 np->shape_editor = NULL; //Let the shapeeditor that makes this set it
298 np->local_change = 0;
299 np->show_handles = show_handles;
300 np->helper_path = NULL;
301 np->helperpath_rgba = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
302 np->helperpath_width = 1.0;
303 np->curve = curve->copy();
304 np->show_helperpath = prefs->getBool("/tools/nodes/show_helperpath");
305 if (SP_IS_LPE_ITEM(object)) {
306 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object));
307 if (lpe && lpe->isVisible() && lpe->showOrigPath()) {
308 np->show_helperpath = true;
309 }
310 }
311 np->straight_path = false;
312 if (IS_LIVEPATHEFFECT(object) && item) {
313 np->item = item;
314 } else {
315 np->item = SP_ITEM(object);
316 }
318 np->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
320 // we need to update item's transform from the repr here,
321 // because they may be out of sync when we respond
322 // to a change in repr by regenerating nodepath --bb
323 sp_object_read_attr(SP_OBJECT(np->item), "transform");
325 np->i2d = sp_item_i2d_affine(np->item);
326 np->d2i = np->i2d.inverse();
328 np->repr = repr;
329 if (repr_key_in) { // apparently the object is an LPEObject (this is a dirty check, hopefully nobody tries feeding non-lpeobjects into this method with non-null repr_key_in)
330 np->repr_key = g_strdup(repr_key_in);
331 np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
332 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(object)->get_lpe();
333 if (!lpe) {
334 g_error("sp_nodepath_new: lpeobject without real lpe passed as argument!");
335 delete np;
336 }
337 Inkscape::LivePathEffect::Parameter *lpeparam = lpe->getParameter(repr_key_in);
338 if (lpeparam) {
339 lpeparam->param_setup_nodepath(np);
340 }
341 } else {
342 np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
343 if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) {
344 np->repr_key = g_strdup("inkscape:original-d");
346 Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object));
347 if (lpe) {
348 lpe->setup_nodepath(np);
349 }
350 } else {
351 np->repr_key = g_strdup("d");
352 }
353 }
355 /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice.
356 * So for example a closed rectangle has a nodetypestring of length 5.
357 * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */
358 Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector());
359 np->curve->set_pathvector(pathv_sanitized);
360 guint length = np->curve->get_segment_count();
361 for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) {
362 length += pit->empty() ? 0 : 1;
363 }
365 gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
366 Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length);
368 // create the subpath(s) from the bpath
369 subpaths_from_pathvector(np, pathv_sanitized, typestr);
371 // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed)
372 np->subpaths = g_list_reverse(np->subpaths);
374 delete[] typestr;
375 curve->unref();
377 // Draw helper curve
378 if (np->show_helperpath) {
379 np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true);
380 }
382 sp_nodepath_create_helperpaths(np);
384 return np;
385 }
387 /**
388 * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it.
389 */
390 Inkscape::NodePath::Path::~Path() {
391 while (this->subpaths) {
392 sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) this->subpaths->data);
393 }
395 //Inform the ShapeEditor that made me, if any, that I am gone.
396 if (this->shape_editor)
397 this->shape_editor->nodepath_destroyed();
399 g_assert(!this->selected);
401 if (this->helper_path) {
402 GtkObject *temp = this->helper_path;
403 this->helper_path = NULL;
404 gtk_object_destroy(temp);
405 }
406 if (this->curve) {
407 this->curve->unref();
408 this->curve = NULL;
409 }
411 if (this->repr_key) {
412 g_free(this->repr_key);
413 this->repr_key = NULL;
414 }
415 if (this->repr_nodetypes_key) {
416 g_free(this->repr_nodetypes_key);
417 this->repr_nodetypes_key = NULL;
418 }
420 sp_nodepath_destroy_helperpaths(this);
422 this->desktop = NULL;
423 }
425 /**
426 * Return the node count of a given NodeSubPath.
427 */
428 static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath)
429 {
430 int nodeCount = 0;
432 if (subpath) {
433 nodeCount = g_list_length(subpath->nodes);
434 }
436 return nodeCount;
437 }
439 /**
440 * Return the node count of a given NodePath.
441 */
442 static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np)
443 {
444 gint nodeCount = 0;
445 if (np) {
446 for (GList *item = np->subpaths ; item ; item=item->next) {
447 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data;
448 nodeCount += g_list_length(subpath->nodes);
449 }
450 }
451 return nodeCount;
452 }
454 /**
455 * Return the subpath count of a given NodePath.
456 */
457 static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np)
458 {
459 gint nodeCount = 0;
460 if (np) {
461 nodeCount = g_list_length(np->subpaths);
462 }
463 return nodeCount;
464 }
466 /**
467 * Return the selected node count of a given NodePath.
468 */
469 static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np)
470 {
471 gint nodeCount = 0;
472 if (np) {
473 nodeCount = g_list_length(np->selected);
474 }
475 return nodeCount;
476 }
478 /**
479 * Return the number of subpaths where nodes are selected in a given NodePath.
480 */
481 static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np)
482 {
483 gint nodeCount = 0;
484 if (np && np->selected) {
485 if (!np->selected->next) {
486 nodeCount = 1;
487 } else {
488 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
489 Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data);
490 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
491 Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data);
492 if (node->selected) {
493 nodeCount++;
494 break;
495 }
496 }
497 }
498 }
499 }
500 return nodeCount;
501 }
503 /**
504 * Clean up a nodepath after editing.
505 *
506 * Currently we are deleting trivial subpaths.
507 */
508 static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath)
509 {
510 GList *badSubPaths = NULL;
512 //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes
513 for (GList *l = nodepath->subpaths; l ; l=l->next) {
514 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
515 if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed))
516 badSubPaths = g_list_append(badSubPaths, sp);
517 }
519 //Delete them. This second step is because sp_nodepath_subpath_destroy()
520 //also removes the subpath from nodepath->subpaths
521 for (GList *l = badSubPaths; l ; l=l->next) {
522 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
523 sp_nodepath_subpath_destroy(sp);
524 }
526 g_list_free(badSubPaths);
527 }
529 /**
530 * Create new nodepaths from pathvector, make it subpaths of np.
531 * \param t The node type array.
532 */
533 static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t)
534 {
535 guint i = 0; // index into node type array
536 for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
537 if (pit->empty())
538 continue; // don't add single knot paths
540 Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np);
542 Geom::Point ppos = pit->initialPoint() * np->i2d;
543 NRPathcode pcode = NR_MOVETO;
545 /* 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)*/
546 for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) {
547 if( dynamic_cast<Geom::LineSegment const*>(&*cit) ||
548 dynamic_cast<Geom::HLineSegment const*>(&*cit) ||
549 dynamic_cast<Geom::VLineSegment const*>(&*cit) )
550 {
551 Geom::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d;
552 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos);
554 ppos = cit->finalPoint() * (Geom::Matrix)np->i2d;
555 pcode = NR_LINETO;
556 }
557 else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
558 std::vector<Geom::Point> points = cubic_bezier->points();
559 Geom::Point pos = points[0] * (Geom::Matrix)np->i2d;
560 Geom::Point npos = points[1] * (Geom::Matrix)np->i2d;
561 sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos);
563 ppos = points[2] * (Geom::Matrix)np->i2d;
564 pcode = NR_CURVETO;
565 }
566 }
568 if (pit->closed()) {
569 // Add last knot (because sp_nodepath_subpath_close kills the last knot)
570 /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already
571 * If the length is zero, don't add it to the nodepath. */
572 Geom::Curve const &closing_seg = pit->back_closed();
573 // Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289)
574 if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) {
575 Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d;
576 sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos);
577 }
579 sp_nodepath_subpath_close(sp);
580 }
581 }
582 }
584 /**
585 * Convert from sodipodi:nodetypes to new style type array.
586 */
587 static
588 Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length)
589 {
590 Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1];
592 guint pos = 0;
594 if (types) {
595 for (guint i = 0; types[i] && ( i < length ); i++) {
596 while ((types[i] > '\0') && (types[i] <= ' ')) i++;
597 if (types[i] != '\0') {
598 switch (types[i]) {
599 case 's':
600 typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH;
601 break;
602 case 'a':
603 typestr[pos++] =Inkscape::NodePath::NODE_AUTO;
604 break;
605 case 'z':
606 typestr[pos++] =Inkscape::NodePath::NODE_SYMM;
607 break;
608 case 'c':
609 typestr[pos++] =Inkscape::NodePath::NODE_CUSP;
610 break;
611 default:
612 typestr[pos++] =Inkscape::NodePath::NODE_NONE;
613 break;
614 }
615 }
616 }
617 }
619 while (pos < length) {
620 typestr[pos++] = Inkscape::NodePath::NODE_NONE;
621 }
623 return typestr;
624 }
626 /**
627 * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is
628 * updated but repr is not (for speed). Used during curve and node drag.
629 */
630 static void update_object(Inkscape::NodePath::Path *np)
631 {
632 g_assert(np);
634 np->curve->unref();
635 np->curve = create_curve(np);
637 sp_nodepath_set_curve(np, np->curve);
639 if (np->show_helperpath) {
640 SPCurve * helper_curve = np->curve->copy();
641 helper_curve->transform(np->i2d);
642 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
643 helper_curve->unref();
644 }
646 // updating helperpaths of LPEItems is now done in sp_lpe_item_update();
647 //sp_nodepath_update_helperpaths(np);
649 // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too
650 // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder!
651 np->shape_editor->update_knotholder();
652 }
654 /**
655 * Update XML path node with data from path object.
656 */
657 static void update_repr_internal(Inkscape::NodePath::Path *np)
658 {
659 g_assert(np);
661 Inkscape::XML::Node *repr = np->object->repr;
663 np->curve->unref();
664 np->curve = create_curve(np);
666 gchar *typestr = create_typestr(np);
667 gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector());
669 // determine if path has an effect applied and write to correct "d" attribute.
670 if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
671 np->local_change++;
672 repr->setAttribute(np->repr_key, svgpath);
673 }
675 if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
676 np->local_change++;
677 repr->setAttribute(np->repr_nodetypes_key, typestr);
678 }
680 g_free(svgpath);
681 g_free(typestr);
683 if (np->show_helperpath) {
684 SPCurve * helper_curve = np->curve->copy();
685 helper_curve->transform(np->i2d);
686 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
687 helper_curve->unref();
688 }
690 // TODO: do we need this call here? after all, update_object() should have been called just before
691 //sp_nodepath_update_helperpaths(np);
692 }
694 /**
695 * Update XML path node with data from path object, commit changes forever.
696 */
697 void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation)
698 {
699 //fixme: np can be NULL, so check before proceeding
700 g_return_if_fail(np != NULL);
702 update_repr_internal(np);
703 sp_canvas_end_forced_full_redraws(np->desktop->canvas);
705 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
706 annotation);
707 }
709 /**
710 * Update XML path node with data from path object, commit changes with undo.
711 */
712 static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation)
713 {
714 update_repr_internal(np);
715 sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE,
716 annotation);
717 }
719 /**
720 * Make duplicate of path, replace corresponding XML node in tree, commit.
721 */
722 static void stamp_repr(Inkscape::NodePath::Path *np)
723 {
724 g_assert(np);
726 Inkscape::XML::Node *old_repr = np->object->repr;
727 Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
729 // remember the position of the item
730 gint pos = old_repr->position();
731 // remember parent
732 Inkscape::XML::Node *parent = sp_repr_parent(old_repr);
734 SPCurve *curve = create_curve(np);
735 gchar *typestr = create_typestr(np);
737 gchar *svgpath = sp_svg_write_path(curve->get_pathvector());
739 new_repr->setAttribute(np->repr_key, svgpath);
740 new_repr->setAttribute(np->repr_nodetypes_key, typestr);
742 // add the new repr to the parent
743 parent->appendChild(new_repr);
744 // move to the saved position
745 new_repr->setPosition(pos > 0 ? pos : 0);
747 sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE,
748 _("Stamp"));
750 Inkscape::GC::release(new_repr);
751 g_free(svgpath);
752 g_free(typestr);
753 curve->unref();
754 }
756 /**
757 * Create curve from path.
758 */
759 static SPCurve *create_curve(Inkscape::NodePath::Path *np)
760 {
761 SPCurve *curve = new SPCurve();
763 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
764 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
765 curve->moveto(sp->first->pos * np->d2i);
766 Inkscape::NodePath::Node *n = sp->first->n.other;
767 while (n) {
768 Geom::Point const end_pt = n->pos * np->d2i;
769 if (!IS_FINITE(n->pos[0]) || !IS_FINITE(n->pos[1])){
770 g_message("niet finite");
771 }
772 switch (n->code) {
773 case NR_LINETO:
774 curve->lineto(end_pt);
775 break;
776 case NR_CURVETO:
777 curve->curveto(n->p.other->n.pos * np->d2i,
778 n->p.pos * np->d2i,
779 end_pt);
780 break;
781 default:
782 g_assert_not_reached();
783 break;
784 }
785 if (n != sp->last) {
786 n = n->n.other;
787 } else {
788 n = NULL;
789 }
790 }
791 if (sp->closed) {
792 curve->closepath();
793 }
794 }
796 return curve;
797 }
799 /**
800 * Convert path type string to sodipodi:nodetypes style.
801 */
802 static gchar *create_typestr(Inkscape::NodePath::Path *np)
803 {
804 gchar *typestr = g_new(gchar, 32);
805 gint len = 32;
806 gint pos = 0;
808 for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) {
809 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data;
811 if (pos >= len) {
812 typestr = g_renew(gchar, typestr, len + 32);
813 len += 32;
814 }
816 typestr[pos++] = 'c';
818 Inkscape::NodePath::Node *n;
819 n = sp->first->n.other;
820 while (n) {
821 gchar code;
823 switch (n->type) {
824 case Inkscape::NodePath::NODE_CUSP:
825 code = 'c';
826 break;
827 case Inkscape::NodePath::NODE_SMOOTH:
828 code = 's';
829 break;
830 case Inkscape::NodePath::NODE_AUTO:
831 code = 'a';
832 break;
833 case Inkscape::NodePath::NODE_SYMM:
834 code = 'z';
835 break;
836 default:
837 g_assert_not_reached();
838 code = '\0';
839 break;
840 }
842 if (pos >= len) {
843 typestr = g_renew(gchar, typestr, len + 32);
844 len += 32;
845 }
847 typestr[pos++] = code;
849 if (n != sp->last) {
850 n = n->n.other;
851 } else {
852 n = NULL;
853 }
854 }
855 }
857 if (pos >= len) {
858 typestr = g_renew(gchar, typestr, len + 1);
859 len += 1;
860 }
862 typestr[pos++] = '\0';
864 return typestr;
865 }
867 // Returns different message contexts depending on the current context. This function should only
868 // be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all
869 // other cases.
870 static Inkscape::MessageContext *
871 get_message_context(SPEventContext *ec)
872 {
873 Inkscape::MessageContext *mc = 0;
875 if (SP_IS_NODE_CONTEXT(ec)) {
876 mc = SP_NODE_CONTEXT(ec)->_node_message_context;
877 } else if (SP_IS_LPETOOL_CONTEXT(ec)) {
878 mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context;
879 } else {
880 g_warning ("Nodepath should only be present in Node tool or Geometric tool.");
881 }
883 return mc;
884 }
886 /**
887 \brief Fills node and handle positions for three nodes, splitting line
888 marked by end at distance t.
889 */
890 static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t)
891 {
892 g_assert(new_path != NULL);
893 g_assert(end != NULL);
895 g_assert(end->p.other == new_path);
896 Inkscape::NodePath::Node *start = new_path->p.other;
897 g_assert(start);
899 if (end->code == NR_LINETO) {
900 new_path->type =Inkscape::NodePath::NODE_CUSP;
901 new_path->code = NR_LINETO;
902 new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos);
903 } else {
904 new_path->type =Inkscape::NodePath::NODE_SMOOTH;
905 new_path->code = NR_CURVETO;
906 gdouble s = 1 - t;
907 for (int dim = 0; dim < 2; dim++) {
908 Geom::Coord const f000 = start->pos[dim];
909 Geom::Coord const f001 = start->n.pos[dim];
910 Geom::Coord const f011 = end->p.pos[dim];
911 Geom::Coord const f111 = end->pos[dim];
912 Geom::Coord const f00t = s * f000 + t * f001;
913 Geom::Coord const f01t = s * f001 + t * f011;
914 Geom::Coord const f11t = s * f011 + t * f111;
915 Geom::Coord const f0tt = s * f00t + t * f01t;
916 Geom::Coord const f1tt = s * f01t + t * f11t;
917 Geom::Coord const fttt = s * f0tt + t * f1tt;
918 start->n.pos[dim] = f00t;
919 new_path->p.pos[dim] = f0tt;
920 new_path->pos[dim] = fttt;
921 new_path->n.pos[dim] = f1tt;
922 end->p.pos[dim] = f11t;
923 }
924 }
925 }
927 /**
928 * Adds new node on direct line between two nodes, activates handles of all
929 * three nodes.
930 */
931 static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t)
932 {
933 g_assert(end);
934 g_assert(end->subpath);
935 g_assert(g_list_find(end->subpath->nodes, end));
937 Inkscape::NodePath::Node *start = end->p.other;
938 g_assert( start->n.other == end );
939 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath,
940 end,
941 (NRPathcode)end->code == NR_LINETO?
942 Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH,
943 (NRPathcode)end->code,
944 &start->pos, &start->pos, &start->n.pos);
945 sp_nodepath_line_midpoint(newnode, end, t);
947 sp_node_adjust_handles(start);
948 sp_node_update_handles(start);
949 sp_node_update_handles(newnode);
950 sp_node_adjust_handles(end);
951 sp_node_update_handles(end);
953 return newnode;
954 }
956 /**
957 \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
958 */
959 static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node)
960 {
961 g_assert(node);
962 g_assert(node->subpath);
963 g_assert(g_list_find(node->subpath->nodes, node));
965 Inkscape::NodePath::Node* result = 0;
966 Inkscape::NodePath::SubPath *sp = node->subpath;
967 Inkscape::NodePath::Path *np = sp->nodepath;
969 if (sp->closed) {
970 sp_nodepath_subpath_open(sp, node);
971 result = sp->first;
972 } else if ( (node == sp->first) || (node == sp->last ) ){
973 // no break for end nodes
974 result = 0;
975 } else {
976 // create a new subpath
977 Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np);
979 // duplicate the break node as start of the new subpath
980 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL,
981 static_cast<Inkscape::NodePath::NodeType>(node->type),
982 NR_MOVETO, &node->pos, &node->pos, &node->n.pos);
984 // attach rest of curve to new node
985 g_assert(node->n.other);
986 newnode->n.other = node->n.other; node->n.other = NULL;
987 newnode->n.other->p.other = newnode;
988 newsubpath->last = sp->last;
989 sp->last = node;
990 node = newnode;
991 while (node->n.other) {
992 node = node->n.other;
993 node->subpath = newsubpath;
994 sp->nodes = g_list_remove(sp->nodes, node);
995 newsubpath->nodes = g_list_prepend(newsubpath->nodes, node);
996 }
999 result = newnode;
1000 }
1001 return result;
1002 }
1004 /**
1005 * Duplicate node and connect to neighbours.
1006 */
1007 static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node)
1008 {
1009 g_assert(node);
1010 g_assert(node->subpath);
1011 g_assert(g_list_find(node->subpath->nodes, node));
1013 Inkscape::NodePath::SubPath *sp = node->subpath;
1015 NRPathcode code = (NRPathcode) node->code;
1016 if (code == NR_MOVETO) { // if node is the endnode,
1017 node->code = NR_LINETO; // new one is inserted before it, so change that to line
1018 }
1020 Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos);
1022 if (!node->n.other || !node->p.other) { // if node is an endnode, select it
1023 return node;
1024 } else {
1025 return newnode; // otherwise select the newly created node
1026 }
1027 }
1029 static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node)
1030 {
1031 node->p.pos = (node->pos + (node->pos - node->n.pos));
1032 }
1034 static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node)
1035 {
1036 node->n.pos = (node->pos + (node->pos - node->p.pos));
1037 }
1039 /**
1040 * Change line type at node, with side effects on neighbours.
1041 */
1042 static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code)
1043 {
1044 g_assert(end);
1045 g_assert(end->subpath);
1046 g_assert(end->p.other);
1048 if (end->code != static_cast<guint>(code) ) {
1049 Inkscape::NodePath::Node *start = end->p.other;
1051 end->code = code;
1053 if (code == NR_LINETO) {
1054 if (start->code == NR_LINETO) {
1055 sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP);
1056 }
1057 if (end->n.other) {
1058 if (end->n.other->code == NR_LINETO) {
1059 sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP);
1060 }
1061 }
1063 if (start->type == Inkscape::NodePath::NODE_AUTO)
1064 start->type = Inkscape::NodePath::NODE_SMOOTH;
1065 if (end->type == Inkscape::NodePath::NODE_AUTO)
1066 end->type = Inkscape::NodePath::NODE_SMOOTH;
1068 sp_node_adjust_handle(start, -1);
1069 sp_node_adjust_handle(end, 1);
1071 } else {
1072 Geom::Point delta = end->pos - start->pos;
1073 start->n.pos = start->pos + delta / 3;
1074 end->p.pos = end->pos - delta / 3;
1075 sp_node_adjust_handle(start, 1);
1076 sp_node_adjust_handle(end, -1);
1077 }
1079 sp_node_update_handles(start);
1080 sp_node_update_handles(end);
1081 }
1082 }
1084 static void
1085 sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
1086 {
1087 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1088 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1089 node->knot->setSize (node->selected? 11 : 9);
1090 sp_knot_update_ctrl(node->knot);
1091 } else if (node->type == Inkscape::NodePath::NODE_AUTO) {
1092 node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1093 node->knot->setSize (node->selected? 11 : 9);
1094 sp_knot_update_ctrl(node->knot);
1095 } else {
1096 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1097 node->knot->setSize (node->selected? 9 : 7);
1098 sp_knot_update_ctrl(node->knot);
1099 }
1100 }
1103 /**
1104 * Change node type, and its handles accordingly.
1105 */
1106 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1107 {
1108 g_assert(node);
1109 g_assert(node->subpath);
1111 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1112 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1113 type =Inkscape::NodePath::NODE_CUSP;
1114 }
1115 }
1117 node->type = type;
1119 sp_nodepath_update_node_knot(node);
1121 // if one of handles is mouseovered, preserve its position
1122 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1123 sp_node_adjust_handle(node, 1);
1124 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1125 sp_node_adjust_handle(node, -1);
1126 } else {
1127 sp_node_adjust_handles(node);
1128 }
1130 sp_node_update_handles(node);
1132 sp_nodepath_update_statusbar(node->subpath->nodepath);
1134 return node;
1135 }
1137 bool
1138 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1139 {
1140 // TODO clean up multiple returns
1141 Inkscape::NodePath::Node *othernode = side->other;
1142 if (!othernode)
1143 return false;
1144 NRPathcode const code = sp_node_path_code_from_side(node, side);
1145 if (code == NR_LINETO)
1146 return true;
1147 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1148 if (&node->p == side) {
1149 other_to_me = &othernode->n;
1150 } else if (&node->n == side) {
1151 other_to_me = &othernode->p;
1152 }
1153 if (!other_to_me)
1154 return false;
1155 bool is_line =
1156 (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1157 Geom::L2(node->pos - side->pos) < 1e-6);
1158 return is_line;
1159 }
1161 /**
1162 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1163 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1164 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1165 * If already cusp and set to cusp, retracts handles.
1166 */
1167 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1168 {
1169 if (type == Inkscape::NodePath::NODE_AUTO) {
1170 if (node->p.other != NULL)
1171 node->code = NR_CURVETO;
1172 if (node->n.other != NULL)
1173 node->n.other->code = NR_CURVETO;
1174 }
1176 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1178 /*
1179 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1181 if (two_handles) {
1182 // do nothing, adjust_handles called via set_node_type will line them up
1183 } else if (one_handle) {
1184 if (opposite_to_handle_is_line) {
1185 if (lined_up) {
1186 // already half-smooth; pull opposite handle too making it fully smooth
1187 } else {
1188 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1189 }
1190 } else {
1191 // pull opposite handle in line with the existing one
1192 }
1193 } else if (no_handles) {
1194 if (both_segments_are_lines OR both_segments_are_curves) {
1195 //pull both handles
1196 } else {
1197 // pull the handle opposite to line segment, making node half-smooth
1198 }
1199 }
1200 */
1201 bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
1202 bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
1203 bool p_is_line = sp_node_side_is_line(node, &node->p);
1204 bool n_is_line = sp_node_side_is_line(node, &node->n);
1206 if (p_has_handle && n_has_handle) {
1207 // do nothing, adjust_handles will line them up
1208 } else if (p_has_handle || n_has_handle) {
1209 if (p_has_handle && n_is_line) {
1210 Radial line (node->n.other->pos - node->pos);
1211 Radial handle (node->pos - node->p.pos);
1212 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1213 // already half-smooth; pull opposite handle too making it fully smooth
1214 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1215 } else {
1216 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1217 }
1218 } else if (n_has_handle && p_is_line) {
1219 Radial line (node->p.other->pos - node->pos);
1220 Radial handle (node->pos - node->n.pos);
1221 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1222 // already half-smooth; pull opposite handle too making it fully smooth
1223 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1224 } else {
1225 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1226 }
1227 } else if (p_has_handle && node->n.other) {
1228 // pull n handle
1229 node->n.other->code = NR_CURVETO;
1230 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1231 Geom::L2(node->p.pos - node->pos) :
1232 Geom::L2(node->n.other->pos - node->pos) / 3;
1233 node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1234 } else if (n_has_handle && node->p.other) {
1235 // pull p handle
1236 node->code = NR_CURVETO;
1237 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1238 Geom::L2(node->n.pos - node->pos) :
1239 Geom::L2(node->p.other->pos - node->pos) / 3;
1240 node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1241 }
1242 } else if (!p_has_handle && !n_has_handle) {
1243 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1244 // no handles, but both segments are either lnes or curves:
1245 //pull both handles
1247 // convert both to curves:
1248 node->code = NR_CURVETO;
1249 node->n.other->code = NR_CURVETO;
1251 sp_node_adjust_handles_auto(node);
1252 } else {
1253 // pull the handle opposite to line segment, making it half-smooth
1254 if (p_is_line && node->n.other) {
1255 if (type != Inkscape::NodePath::NODE_SYMM) {
1256 // pull n handle
1257 node->n.other->code = NR_CURVETO;
1258 double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1259 node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1260 }
1261 } else if (n_is_line && node->p.other) {
1262 if (type != Inkscape::NodePath::NODE_SYMM) {
1263 // pull p handle
1264 node->code = NR_CURVETO;
1265 double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1266 node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1267 }
1268 }
1269 }
1270 }
1271 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1272 // cusping a cusp: retract nodes
1273 node->p.pos = node->pos;
1274 node->n.pos = node->pos;
1275 }
1277 sp_nodepath_set_node_type (node, type);
1278 }
1280 /**
1281 * Move node to point, and adjust its and neighbouring handles.
1282 */
1283 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1284 {
1285 if (node->type == Inkscape::NodePath::NODE_AUTO) {
1286 node->pos = p;
1287 sp_node_adjust_handles_auto(node);
1288 } else {
1289 Geom::Point delta = p - node->pos;
1290 node->pos = p;
1292 node->p.pos += delta;
1293 node->n.pos += delta;
1294 }
1296 Inkscape::NodePath::Node *node_p = NULL;
1297 Inkscape::NodePath::Node *node_n = NULL;
1299 if (node->p.other) {
1300 if (node->code == NR_LINETO) {
1301 sp_node_adjust_handle(node, 1);
1302 sp_node_adjust_handle(node->p.other, -1);
1303 node_p = node->p.other;
1304 }
1305 if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1306 sp_node_adjust_handles_auto(node->p.other);
1307 node_p = node->p.other;
1308 }
1309 }
1310 if (node->n.other) {
1311 if (node->n.other->code == NR_LINETO) {
1312 sp_node_adjust_handle(node, -1);
1313 sp_node_adjust_handle(node->n.other, 1);
1314 node_n = node->n.other;
1315 }
1316 if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1317 sp_node_adjust_handles_auto(node->n.other);
1318 node_n = node->n.other;
1319 }
1320 }
1322 // this function is only called from batch movers that will update display at the end
1323 // themselves, so here we just move all the knots without emitting move signals, for speed
1324 sp_node_update_handles(node, false);
1325 if (node_n) {
1326 sp_node_update_handles(node_n, false);
1327 }
1328 if (node_p) {
1329 sp_node_update_handles(node_p, false);
1330 }
1331 }
1333 /**
1334 * Call sp_node_moveto() for node selection and handle possible snapping.
1335 */
1336 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1337 bool const snap, bool constrained = false,
1338 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1339 {
1340 Geom::Point delta(dx, dy);
1341 Geom::Point best_pt = delta;
1342 Inkscape::SnappedPoint best;
1344 if (snap) {
1345 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1346 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1347 * must provide that information. */
1349 // Build a list of the unselected nodes to which the snapper should snap
1350 std::vector<Geom::Point> unselected_nodes;
1351 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1352 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1353 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1354 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1355 if (!node->selected) {
1356 unselected_nodes.push_back(to_2geom(node->pos));
1357 }
1358 }
1359 }
1361 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1363 // When only the node closest to the mouse pointer is to be snapped
1364 // then we will not even try to snap to other points and discard those immediately
1365 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1366 bool closest_only = prefs->getBool("/options/snapclosestonly/value", false);
1368 Inkscape::NodePath::Node *closest_node = NULL;
1369 Geom::Coord closest_dist = NR_HUGE;
1371 if (closest_only) {
1372 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1373 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1374 Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin);
1375 if (dist < closest_dist) {
1376 closest_node = n;
1377 closest_dist = dist;
1378 }
1379 }
1380 }
1382 // Iterate through all selected nodes
1383 m.setup(nodepath->desktop, false, SP_PATH(nodepath->item), &unselected_nodes);
1384 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1385 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1386 if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one
1387 Inkscape::SnappedPoint s;
1388 if (constrained) {
1389 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1390 dedicated_constraint.setPoint(n->pos);
1391 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1392 } else {
1393 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1394 }
1396 if (s.getSnapped()) {
1397 s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
1398 if (!s.isOtherSnapBetter(best, true)) {
1399 best = s;
1400 best_pt = from_2geom(s.getPoint()) - n->pos;
1401 }
1402 }
1403 }
1404 }
1406 if (best.getSnapped()) {
1407 nodepath->desktop->snapindicator->set_new_snaptarget(best);
1408 } else {
1409 nodepath->desktop->snapindicator->remove_snaptarget();
1410 }
1411 }
1413 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1414 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1415 sp_node_moveto(n, n->pos + best_pt);
1416 }
1418 // do not update repr here so that node dragging is acceptably fast
1419 update_object(nodepath);
1420 }
1422 /**
1423 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1424 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1425 near x = 0.
1426 */
1427 double
1428 sculpt_profile (double x, double alpha, guint profile)
1429 {
1430 double result = 1;
1432 if (x >= 1) {
1433 result = 0;
1434 } else if (x <= 0) {
1435 result = 1;
1436 } else {
1437 switch (profile) {
1438 case SCULPT_PROFILE_LINEAR:
1439 result = 1 - x;
1440 break;
1441 case SCULPT_PROFILE_BELL:
1442 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1443 break;
1444 case SCULPT_PROFILE_ELLIPTIC:
1445 result = sqrt(1 - x*x);
1446 break;
1447 default:
1448 g_assert_not_reached();
1449 }
1450 }
1452 return result;
1453 }
1455 double
1456 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1457 {
1458 // extremely primitive for now, don't have time to look for the real one
1459 double lower = Geom::L2(b - a);
1460 double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1461 return (lower + upper)/2;
1462 }
1464 void
1465 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1466 {
1467 n->pos = n->origin + delta;
1468 n->n.pos = n->n.origin + delta_n;
1469 n->p.pos = n->p.origin + delta_p;
1470 sp_node_adjust_handles(n);
1471 sp_node_update_handles(n, false);
1472 }
1474 /**
1475 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1476 * on how far they are from the dragged node n.
1477 */
1478 static void
1479 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1480 {
1481 g_assert (n);
1482 g_assert (nodepath);
1483 g_assert (n->subpath->nodepath == nodepath);
1485 double pressure = n->knot->pressure;
1486 if (pressure == 0)
1487 pressure = 0.5; // default
1488 pressure = CLAMP (pressure, 0.2, 0.8);
1490 // map pressure to alpha = 1/5 ... 5
1491 double alpha = 1 - 2 * fabs(pressure - 0.5);
1492 if (pressure > 0.5)
1493 alpha = 1/alpha;
1495 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1496 guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1498 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1499 // Only one subpath has selected nodes:
1500 // use linear mode, where the distance from n to node being dragged is calculated along the path
1502 double n_sel_range = 0, p_sel_range = 0;
1503 guint n_nodes = 0, p_nodes = 0;
1504 guint n_sel_nodes = 0, p_sel_nodes = 0;
1506 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1507 {
1508 double n_range = 0, p_range = 0;
1509 bool n_going = true, p_going = true;
1510 Inkscape::NodePath::Node *n_node = n;
1511 Inkscape::NodePath::Node *p_node = n;
1512 do {
1513 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1514 if (n_node && n_going)
1515 n_node = n_node->n.other;
1516 if (n_node == NULL) {
1517 n_going = false;
1518 } else {
1519 n_nodes ++;
1520 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1521 if (n_node->selected) {
1522 n_sel_nodes ++;
1523 n_sel_range = n_range;
1524 }
1525 if (n_node == p_node) {
1526 n_going = false;
1527 p_going = false;
1528 }
1529 }
1530 if (p_node && p_going)
1531 p_node = p_node->p.other;
1532 if (p_node == NULL) {
1533 p_going = false;
1534 } else {
1535 p_nodes ++;
1536 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1537 if (p_node->selected) {
1538 p_sel_nodes ++;
1539 p_sel_range = p_range;
1540 }
1541 if (p_node == n_node) {
1542 n_going = false;
1543 p_going = false;
1544 }
1545 }
1546 } while (n_going || p_going);
1547 }
1549 // Second pass: actually move nodes in this subpath
1550 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1551 {
1552 double n_range = 0, p_range = 0;
1553 bool n_going = true, p_going = true;
1554 Inkscape::NodePath::Node *n_node = n;
1555 Inkscape::NodePath::Node *p_node = n;
1556 do {
1557 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1558 if (n_node && n_going)
1559 n_node = n_node->n.other;
1560 if (n_node == NULL) {
1561 n_going = false;
1562 } else {
1563 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1564 if (n_node->selected) {
1565 sp_nodepath_move_node_and_handles (n_node,
1566 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1567 sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1568 sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1569 }
1570 if (n_node == p_node) {
1571 n_going = false;
1572 p_going = false;
1573 }
1574 }
1575 if (p_node && p_going)
1576 p_node = p_node->p.other;
1577 if (p_node == NULL) {
1578 p_going = false;
1579 } else {
1580 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1581 if (p_node->selected) {
1582 sp_nodepath_move_node_and_handles (p_node,
1583 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1584 sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1585 sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1586 }
1587 if (p_node == n_node) {
1588 n_going = false;
1589 p_going = false;
1590 }
1591 }
1592 } while (n_going || p_going);
1593 }
1595 } else {
1596 // Multiple subpaths have selected nodes:
1597 // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1598 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1599 // fix the pear-like shape when sculpting e.g. a ring
1601 // First pass: calculate range
1602 gdouble direct_range = 0;
1603 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1604 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1605 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1606 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1607 if (node->selected) {
1608 direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1609 }
1610 }
1611 }
1613 // Second pass: actually move nodes
1614 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1615 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1616 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1617 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1618 if (node->selected) {
1619 if (direct_range > 1e-6) {
1620 sp_nodepath_move_node_and_handles (node,
1621 sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1622 sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1623 sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1624 } else {
1625 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1626 }
1628 }
1629 }
1630 }
1631 }
1633 // do not update repr here so that node dragging is acceptably fast
1634 update_object(nodepath);
1635 }
1638 /**
1639 * Move node selection to point, adjust its and neighbouring handles,
1640 * handle possible snapping, and commit the change with possible undo.
1641 */
1642 void
1643 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1644 {
1645 if (!nodepath) return;
1647 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1649 if (dx == 0) {
1650 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1651 } else if (dy == 0) {
1652 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1653 } else {
1654 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1655 }
1656 }
1658 /**
1659 * Move node selection off screen and commit the change.
1660 */
1661 void
1662 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1663 {
1664 // borrowed from sp_selection_move_screen in selection-chemistry.c
1665 // we find out the current zoom factor and divide deltas by it
1667 gdouble zoom = desktop->current_zoom();
1668 gdouble zdx = dx / zoom;
1669 gdouble zdy = dy / zoom;
1671 if (!nodepath) return;
1673 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1675 if (dx == 0) {
1676 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1677 } else if (dy == 0) {
1678 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1679 } else {
1680 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1681 }
1682 }
1684 /**
1685 * Move selected nodes to the absolute position given
1686 */
1687 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1688 {
1689 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1690 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1691 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1692 sp_node_moveto(n, npos);
1693 }
1695 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1696 }
1698 /**
1699 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1700 */
1701 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1702 {
1703 boost::optional<Geom::Coord> no_coord;
1704 g_return_val_if_fail(nodepath->selected, no_coord);
1706 // determine coordinate of first selected node
1707 GList *nsel = nodepath->selected;
1708 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1709 Geom::Coord coord = n->pos[axis];
1710 bool coincide = true;
1712 // compare it to the coordinates of all the other selected nodes
1713 for (GList *l = nsel->next; l != NULL; l = l->next) {
1714 n = (Inkscape::NodePath::Node *) l->data;
1715 if (n->pos[axis] != coord) {
1716 coincide = false;
1717 }
1718 }
1719 if (coincide) {
1720 return coord;
1721 } else {
1722 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1723 // currently we return the coordinate of the bounding box midpoint because I don't know how
1724 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1725 return bbox.midpoint()[axis];
1726 }
1727 }
1729 /** If they don't yet exist, creates knot and line for the given side of the node */
1730 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1731 {
1732 if (!side->knot) {
1733 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"));
1735 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1736 side->knot->setSize (7);
1737 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1738 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1739 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1740 sp_knot_update_ctrl(side->knot);
1742 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1743 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1744 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1745 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1746 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1747 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1748 }
1750 if (!side->line) {
1751 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1752 SP_TYPE_CTRLLINE, NULL);
1753 }
1754 }
1756 /**
1757 * Ensure the given handle of the node is visible/invisible, update its screen position
1758 */
1759 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1760 {
1761 g_assert(node != NULL);
1763 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1764 NRPathcode code = sp_node_path_code_from_side(node, side);
1766 show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1768 if (show_handle) {
1769 if (!side->knot) { // No handle knot at all
1770 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1771 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1772 side->knot->pos = side->pos;
1773 if (side->knot->item)
1774 SP_CTRL(side->knot->item)->moveto(side->pos);
1775 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1776 sp_knot_show(side->knot);
1777 } else {
1778 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1779 if (fire_move_signals) {
1780 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1781 } else {
1782 sp_knot_moveto(side->knot, side->pos);
1783 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1784 }
1785 }
1786 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1787 sp_knot_show(side->knot);
1788 }
1789 }
1790 sp_canvas_item_show(side->line);
1791 } else {
1792 if (side->knot) {
1793 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1794 sp_knot_hide(side->knot);
1795 }
1796 }
1797 if (side->line) {
1798 sp_canvas_item_hide(side->line);
1799 }
1800 }
1801 }
1803 /**
1804 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1805 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1806 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1807 * updated; otherwise, just move the knots silently (used in batch moves).
1808 */
1809 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1810 {
1811 g_assert(node != NULL);
1813 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1814 sp_knot_show(node->knot);
1815 }
1817 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1818 if (fire_move_signals)
1819 sp_knot_set_position(node->knot, node->pos, 0);
1820 else
1821 sp_knot_moveto(node->knot, node->pos);
1822 }
1824 gboolean show_handles = node->selected;
1825 if (node->p.other != NULL) {
1826 if (node->p.other->selected) show_handles = TRUE;
1827 }
1828 if (node->n.other != NULL) {
1829 if (node->n.other->selected) show_handles = TRUE;
1830 }
1832 if (node->subpath->nodepath->show_handles == false)
1833 show_handles = FALSE;
1835 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1836 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1837 }
1839 /**
1840 * Call sp_node_update_handles() for all nodes on subpath.
1841 */
1842 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1843 {
1844 g_assert(subpath != NULL);
1846 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1847 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1848 }
1849 }
1851 /**
1852 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1853 */
1854 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1855 {
1856 g_assert(nodepath != NULL);
1858 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1859 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1860 }
1861 }
1863 void
1864 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1865 {
1866 if (nodepath) {
1867 nodepath->show_handles = show;
1868 sp_nodepath_update_handles(nodepath);
1869 }
1870 }
1872 /**
1873 * Adds all selected nodes in nodepath to list.
1874 */
1875 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1876 {
1877 StlConv<Node *>::list(l, selected);
1878 /// \todo this adds a copying, rework when the selection becomes a stl list
1879 }
1881 /**
1882 * Align selected nodes on the specified axis.
1883 */
1884 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1885 {
1886 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1887 return;
1888 }
1890 if ( !nodepath->selected->next ) { // only one node selected
1891 return;
1892 }
1893 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1894 Geom::Point dest(pNode->pos);
1895 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1896 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1897 if (pNode) {
1898 dest[axis] = pNode->pos[axis];
1899 sp_node_moveto(pNode, dest);
1900 }
1901 }
1903 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1904 }
1906 /// Helper struct.
1907 struct NodeSort
1908 {
1909 Inkscape::NodePath::Node *_node;
1910 Geom::Coord _coord;
1911 /// \todo use vectorof pointers instead of calling copy ctor
1912 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1913 _node(node), _coord(node->pos[axis])
1914 {}
1916 };
1918 static bool operator<(NodeSort const &a, NodeSort const &b)
1919 {
1920 return (a._coord < b._coord);
1921 }
1923 /**
1924 * Distribute selected nodes on the specified axis.
1925 */
1926 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1927 {
1928 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1929 return;
1930 }
1932 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1933 return;
1934 }
1936 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1937 std::vector<NodeSort> sorted;
1938 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1939 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1940 if (pNode) {
1941 NodeSort n(pNode, axis);
1942 sorted.push_back(n);
1943 //dest[axis] = pNode->pos[axis];
1944 //sp_node_moveto(pNode, dest);
1945 }
1946 }
1947 std::sort(sorted.begin(), sorted.end());
1948 unsigned int len = sorted.size();
1949 //overall bboxes span
1950 float dist = (sorted.back()._coord -
1951 sorted.front()._coord);
1952 //new distance between each bbox
1953 float step = (dist) / (len - 1);
1954 float pos = sorted.front()._coord;
1955 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1956 it < sorted.end();
1957 it ++ )
1958 {
1959 Geom::Point dest((*it)._node->pos);
1960 dest[axis] = pos;
1961 sp_node_moveto((*it)._node, dest);
1962 pos += step;
1963 }
1965 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1966 }
1969 /**
1970 * Call sp_nodepath_line_add_node() for all selected segments.
1971 */
1972 void
1973 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1974 {
1975 if (!nodepath) {
1976 return;
1977 }
1979 GList *nl = NULL;
1981 int n_added = 0;
1983 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1984 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1985 g_assert(t->selected);
1986 if (t->p.other && t->p.other->selected) {
1987 nl = g_list_prepend(nl, t);
1988 }
1989 }
1991 while (nl) {
1992 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1993 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1994 sp_nodepath_node_select(n, TRUE, FALSE);
1995 n_added ++;
1996 nl = g_list_remove(nl, t);
1997 }
1999 /** \todo fixme: adjust ? */
2000 sp_nodepath_update_handles(nodepath);
2002 if (n_added > 1) {
2003 sp_nodepath_update_repr(nodepath, _("Add nodes"));
2004 } else if (n_added > 0) {
2005 sp_nodepath_update_repr(nodepath, _("Add node"));
2006 }
2008 sp_nodepath_update_statusbar(nodepath);
2009 }
2011 /**
2012 * Select segment nearest to point
2013 */
2014 void
2015 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
2016 {
2017 if (!nodepath) {
2018 return;
2019 }
2021 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2022 Geom::PathVector const &pathv = curve->get_pathvector();
2023 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2024 if (!pvpos) {
2025 g_print ("Possible error?\n");
2026 return;
2027 }
2029 // calculate index for nodepath's representation.
2030 unsigned int segment_index = floor(pvpos->t) + 1;
2031 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2032 segment_index += pathv[i].size() + 1;
2033 if (pathv[i].closed()) {
2034 segment_index += 1;
2035 }
2036 }
2038 curve->unref();
2040 //find segment to segment
2041 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2043 //fixme: this can return NULL, so check before proceeding.
2044 g_return_if_fail(e != NULL);
2046 gboolean force = FALSE;
2047 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2048 force = TRUE;
2049 }
2050 sp_nodepath_node_select(e, (gboolean) toggle, force);
2051 if (e->p.other)
2052 sp_nodepath_node_select(e->p.other, TRUE, force);
2054 sp_nodepath_update_handles(nodepath);
2056 sp_nodepath_update_statusbar(nodepath);
2057 }
2059 /**
2060 * Add a node nearest to point
2061 */
2062 void
2063 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2064 {
2065 if (!nodepath) {
2066 return;
2067 }
2069 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2070 Geom::PathVector const &pathv = curve->get_pathvector();
2071 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2072 if (!pvpos) {
2073 g_print ("Possible error?\n");
2074 return;
2075 }
2077 // calculate index for nodepath's representation.
2078 double int_part;
2079 double t = std::modf(pvpos->t, &int_part);
2080 unsigned int segment_index = (unsigned int)int_part + 1;
2081 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2082 segment_index += pathv[i].size() + 1;
2083 if (pathv[i].closed()) {
2084 segment_index += 1;
2085 }
2086 }
2088 curve->unref();
2090 //find segment to split
2091 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2092 if (!e) {
2093 return;
2094 }
2096 //don't know why but t seems to flip for lines
2097 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2098 t = 1.0 - t;
2099 }
2101 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2102 sp_nodepath_node_select(n, FALSE, TRUE);
2104 /* fixme: adjust ? */
2105 sp_nodepath_update_handles(nodepath);
2107 sp_nodepath_update_repr(nodepath, _("Add node"));
2109 sp_nodepath_update_statusbar(nodepath);
2110 }
2112 /*
2113 * Adjusts a segment so that t moves by a certain delta for dragging
2114 * converts lines to curves
2115 *
2116 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2117 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2118 */
2119 void
2120 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2121 {
2122 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2124 //fixme: e and e->p can be NULL, so check for those before proceeding
2125 g_return_if_fail(e != NULL);
2126 g_return_if_fail(&e->p != NULL);
2128 if (e->type == Inkscape::NodePath::NODE_AUTO) {
2129 e->type = Inkscape::NodePath::NODE_SMOOTH;
2130 sp_nodepath_update_node_knot (e);
2131 }
2132 if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2133 e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2134 sp_nodepath_update_node_knot (e->p.other);
2135 }
2137 /* feel good is an arbitrary parameter that distributes the delta between handles
2138 * if t of the drag point is less than 1/6 distance form the endpoint only
2139 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2140 */
2141 double feel_good;
2142 if (t <= 1.0 / 6.0)
2143 feel_good = 0;
2144 else if (t <= 0.5)
2145 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2146 else if (t <= 5.0 / 6.0)
2147 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2148 else
2149 feel_good = 1;
2151 //if we're dragging a line convert it to a curve
2152 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2153 sp_nodepath_set_line_type(e, NR_CURVETO);
2154 }
2156 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2157 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2158 e->p.other->n.pos += offsetcoord0;
2159 e->p.pos += offsetcoord1;
2161 // adjust handles of adjacent nodes where necessary
2162 sp_node_adjust_handle(e,1);
2163 sp_node_adjust_handle(e->p.other,-1);
2165 sp_nodepath_update_handles(e->subpath->nodepath);
2167 update_object(e->subpath->nodepath);
2169 sp_nodepath_update_statusbar(e->subpath->nodepath);
2170 }
2173 /**
2174 * Call sp_nodepath_break() for all selected segments.
2175 */
2176 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2177 {
2178 if (!nodepath) return;
2180 GList *tempin = g_list_copy(nodepath->selected);
2181 GList *temp = NULL;
2182 for (GList *l = tempin; l != NULL; l = l->next) {
2183 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2184 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2185 if (nn == NULL) continue; // no break, no new node
2186 temp = g_list_prepend(temp, nn);
2187 }
2188 g_list_free(tempin);
2190 if (temp) {
2191 sp_nodepath_deselect(nodepath);
2192 }
2193 for (GList *l = temp; l != NULL; l = l->next) {
2194 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2195 }
2197 sp_nodepath_update_handles(nodepath);
2199 sp_nodepath_update_repr(nodepath, _("Break path"));
2200 }
2202 /**
2203 * Duplicate the selected node(s).
2204 */
2205 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2206 {
2207 if (!nodepath) {
2208 return;
2209 }
2211 GList *temp = NULL;
2212 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2213 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2214 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2215 if (nn == NULL) continue; // could not duplicate
2216 temp = g_list_prepend(temp, nn);
2217 }
2219 if (temp) {
2220 sp_nodepath_deselect(nodepath);
2221 }
2222 for (GList *l = temp; l != NULL; l = l->next) {
2223 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2224 }
2226 sp_nodepath_update_handles(nodepath);
2228 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2229 }
2231 /**
2232 * Internal function to join two nodes by merging them into one.
2233 */
2234 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2235 {
2236 /* a and b are endpoints */
2238 // if one of the two nodes is mouseovered, fix its position
2239 Geom::Point c;
2240 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2241 c = a->pos;
2242 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2243 c = b->pos;
2244 } else {
2245 // otherwise, move joined node to the midpoint
2246 c = (a->pos + b->pos) / 2;
2247 }
2249 if (a->subpath == b->subpath) {
2250 Inkscape::NodePath::SubPath *sp = a->subpath;
2251 sp_nodepath_subpath_close(sp);
2252 sp_node_moveto (sp->first, c);
2254 sp_nodepath_update_handles(sp->nodepath);
2255 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2256 return;
2257 }
2259 /* a and b are separate subpaths */
2260 Inkscape::NodePath::SubPath *sa = a->subpath;
2261 Inkscape::NodePath::SubPath *sb = b->subpath;
2262 Geom::Point p;
2263 Inkscape::NodePath::Node *n;
2264 NRPathcode code;
2265 if (a == sa->first) {
2266 // we will now reverse sa, so that a is its last node, not first, and drop that node
2267 p = sa->first->n.pos;
2268 code = (NRPathcode)sa->first->n.other->code;
2269 // create new subpath
2270 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2271 // create a first moveto node on it
2272 n = sa->last;
2273 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2274 n = n->p.other;
2275 if (n == sa->first) n = NULL;
2276 while (n) {
2277 // copy the rest of the nodes from sa to t, going backwards
2278 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2279 n = n->p.other;
2280 if (n == sa->first) n = NULL;
2281 }
2282 // replace sa with t
2283 sp_nodepath_subpath_destroy(sa);
2284 sa = t;
2285 } else if (a == sa->last) {
2286 // a is already last, just drop it
2287 p = sa->last->p.pos;
2288 code = (NRPathcode)sa->last->code;
2289 sp_nodepath_node_destroy(sa->last);
2290 } else {
2291 code = NR_END;
2292 g_assert_not_reached();
2293 }
2295 if (b == sb->first) {
2296 // copy all nodes from b to a, forward
2297 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2298 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2299 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2300 }
2301 } else if (b == sb->last) {
2302 // copy all nodes from b to a, backward
2303 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2304 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2305 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2306 }
2307 } else {
2308 g_assert_not_reached();
2309 }
2310 /* and now destroy sb */
2312 sp_nodepath_subpath_destroy(sb);
2314 sp_nodepath_update_handles(sa->nodepath);
2316 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2318 sp_nodepath_update_statusbar(nodepath);
2319 }
2321 /**
2322 * Internal function to join two nodes by adding a segment between them.
2323 */
2324 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2325 {
2326 if (a->subpath == b->subpath) {
2327 Inkscape::NodePath::SubPath *sp = a->subpath;
2329 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2330 sp->closed = TRUE;
2332 sp->first->p.other = sp->last;
2333 sp->last->n.other = sp->first;
2335 sp_node_handle_mirror_p_to_n(sp->last);
2336 sp_node_handle_mirror_n_to_p(sp->first);
2338 sp->first->code = sp->last->code;
2339 sp->first = sp->last;
2341 sp_nodepath_update_handles(sp->nodepath);
2343 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2345 return;
2346 }
2348 /* a and b are separate subpaths */
2349 Inkscape::NodePath::SubPath *sa = a->subpath;
2350 Inkscape::NodePath::SubPath *sb = b->subpath;
2352 Inkscape::NodePath::Node *n;
2353 Geom::Point p;
2354 NRPathcode code;
2355 if (a == sa->first) {
2356 code = (NRPathcode) sa->first->n.other->code;
2357 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2358 n = sa->last;
2359 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2360 for (n = n->p.other; n != NULL; n = n->p.other) {
2361 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2362 }
2363 sp_nodepath_subpath_destroy(sa);
2364 sa = t;
2365 } else if (a == sa->last) {
2366 code = (NRPathcode)sa->last->code;
2367 } else {
2368 code = NR_END;
2369 g_assert_not_reached();
2370 }
2372 if (b == sb->first) {
2373 n = sb->first;
2374 sp_node_handle_mirror_p_to_n(sa->last);
2375 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2376 sp_node_handle_mirror_n_to_p(sa->last);
2377 for (n = n->n.other; n != NULL; n = n->n.other) {
2378 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2379 }
2380 } else if (b == sb->last) {
2381 n = sb->last;
2382 sp_node_handle_mirror_p_to_n(sa->last);
2383 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2384 sp_node_handle_mirror_n_to_p(sa->last);
2385 for (n = n->p.other; n != NULL; n = n->p.other) {
2386 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2387 }
2388 } else {
2389 g_assert_not_reached();
2390 }
2391 /* and now destroy sb */
2393 sp_nodepath_subpath_destroy(sb);
2395 sp_nodepath_update_handles(sa->nodepath);
2397 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2398 }
2400 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2402 /**
2403 * Internal function to handle joining two nodes.
2404 */
2405 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2406 {
2407 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2409 if (g_list_length(nodepath->selected) != 2) {
2410 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2411 return;
2412 }
2414 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2415 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2417 g_assert(a != b);
2418 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2419 // someone tried to join an orphan node (i.e. a single-node subpath).
2420 // this is not worth an error message, just fail silently.
2421 return;
2422 }
2424 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2425 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2426 return;
2427 }
2429 switch(mode) {
2430 case NODE_JOIN_ENDPOINTS:
2431 do_node_selected_join(nodepath, a, b);
2432 break;
2433 case NODE_JOIN_SEGMENT:
2434 do_node_selected_join_segment(nodepath, a, b);
2435 break;
2436 }
2437 }
2439 /**
2440 * Join two nodes by merging them into one.
2441 */
2442 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2443 {
2444 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2445 }
2447 /**
2448 * Join two nodes by adding a segment between them.
2449 */
2450 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2451 {
2452 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2453 }
2455 /**
2456 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2457 */
2458 void sp_node_delete_preserve(GList *nodes_to_delete)
2459 {
2460 GSList *nodepaths = NULL;
2462 while (nodes_to_delete) {
2463 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2464 Inkscape::NodePath::SubPath *sp = node->subpath;
2465 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2466 Inkscape::NodePath::Node *sample_cursor = NULL;
2467 Inkscape::NodePath::Node *sample_end = NULL;
2468 Inkscape::NodePath::Node *delete_cursor = node;
2469 bool just_delete = false;
2471 //find the start of this contiguous selection
2472 //move left to the first node that is not selected
2473 //or the start of the non-closed path
2474 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2475 delete_cursor = curr;
2476 }
2478 //just delete at the beginning of an open path
2479 if (!delete_cursor->p.other) {
2480 sample_cursor = delete_cursor;
2481 just_delete = true;
2482 } else {
2483 sample_cursor = delete_cursor->p.other;
2484 }
2486 //calculate points for each segment
2487 int rate = 5;
2488 float period = 1.0 / rate;
2489 std::vector<Geom::Point> data;
2490 if (!just_delete) {
2491 data.push_back(sample_cursor->pos);
2492 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2493 //just delete at the end of an open path
2494 if (!sp->closed && curr == sp->last) {
2495 just_delete = true;
2496 break;
2497 }
2499 //sample points on the contiguous selected segment
2500 Geom::Point *bez;
2501 bez = new Geom::Point [4];
2502 bez[0] = curr->pos;
2503 bez[1] = curr->n.pos;
2504 bez[2] = curr->n.other->p.pos;
2505 bez[3] = curr->n.other->pos;
2506 for (int i=1; i<rate; i++) {
2507 gdouble t = i * period;
2508 Geom::Point p = bezier_pt(3, bez, t);
2509 data.push_back(p);
2510 }
2511 data.push_back(curr->n.other->pos);
2513 sample_end = curr->n.other;
2514 //break if we've come full circle or hit the end of the selection
2515 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2516 break;
2517 }
2518 }
2519 }
2521 if (!just_delete) {
2522 //calculate the best fitting single segment and adjust the endpoints
2523 Geom::Point *adata;
2524 adata = new Geom::Point [data.size()];
2525 copy(data.begin(), data.end(), adata);
2527 Geom::Point *bez;
2528 bez = new Geom::Point [4];
2529 //would decreasing error create a better fitting approximation?
2530 gdouble error = 1.0;
2531 gint ret;
2532 ret = sp_bezier_fit_cubic (bez, adata, data.size(), error);
2534 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2535 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2536 //the resulting nodes behave as expected.
2537 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2538 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2539 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2540 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2542 //adjust endpoints
2543 sample_cursor->n.pos = bez[1];
2544 sample_end->p.pos = bez[2];
2545 }
2547 //destroy this contiguous selection
2548 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2549 Inkscape::NodePath::Node *temp = delete_cursor;
2550 if (delete_cursor->n.other == delete_cursor) {
2551 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2552 delete_cursor = NULL;
2553 } else {
2554 delete_cursor = delete_cursor->n.other;
2555 }
2556 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2557 sp_nodepath_node_destroy(temp);
2558 }
2560 sp_nodepath_update_handles(nodepath);
2562 if (!g_slist_find(nodepaths, nodepath))
2563 nodepaths = g_slist_prepend (nodepaths, nodepath);
2564 }
2566 for (GSList *i = nodepaths; i; i = i->next) {
2567 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2568 // different nodepaths will give us one undo event per nodepath
2569 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2571 // if the entire nodepath is removed, delete the selected object.
2572 if (nodepath->subpaths == NULL ||
2573 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2574 //at least 2
2575 sp_nodepath_get_node_count(nodepath) < 2) {
2576 SPDocument *document = sp_desktop_document (nodepath->desktop);
2577 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2578 //delete this nodepath's object, not the entire selection! (though at this time, this
2579 //does not matter)
2580 sp_selection_delete(nodepath->desktop);
2581 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2582 _("Delete nodes"));
2583 } else {
2584 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2585 sp_nodepath_update_statusbar(nodepath);
2586 }
2587 }
2589 g_slist_free (nodepaths);
2590 }
2592 /**
2593 * Delete one or more selected nodes.
2594 */
2595 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2596 {
2597 if (!nodepath) return;
2598 if (!nodepath->selected) return;
2600 /** \todo fixme: do it the right way */
2601 while (nodepath->selected) {
2602 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2603 sp_nodepath_node_destroy(node);
2604 }
2607 //clean up the nodepath (such as for trivial subpaths)
2608 sp_nodepath_cleanup(nodepath);
2610 sp_nodepath_update_handles(nodepath);
2612 // if the entire nodepath is removed, delete the selected object.
2613 if (nodepath->subpaths == NULL ||
2614 sp_nodepath_get_node_count(nodepath) < 2) {
2615 SPDocument *document = sp_desktop_document (nodepath->desktop);
2616 sp_selection_delete(nodepath->desktop);
2617 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2618 _("Delete nodes"));
2619 return;
2620 }
2622 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2624 sp_nodepath_update_statusbar(nodepath);
2625 }
2627 /**
2628 * Delete one or more segments between two selected nodes.
2629 * This is the code for 'split'.
2630 */
2631 void
2632 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2633 {
2634 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2635 Inkscape::NodePath::Node *curr, *next; //Iterators
2637 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2639 if (g_list_length(nodepath->selected) != 2) {
2640 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2641 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2642 return;
2643 }
2645 //Selected nodes, not inclusive
2646 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2647 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2649 if ( ( a==b) || //same node
2650 (a->subpath != b->subpath ) || //not the same path
2651 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2652 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2653 {
2654 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2655 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2656 return;
2657 }
2659 //###########################################
2660 //# BEGIN EDITS
2661 //###########################################
2662 //##################################
2663 //# CLOSED PATH
2664 //##################################
2665 if (a->subpath->closed) {
2668 gboolean reversed = FALSE;
2670 //Since we can go in a circle, we need to find the shorter distance.
2671 // a->b or b->a
2672 start = end = NULL;
2673 int distance = 0;
2674 int minDistance = 0;
2675 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2676 if (curr==b) {
2677 //printf("a to b:%d\n", distance);
2678 start = a;//go from a to b
2679 end = b;
2680 minDistance = distance;
2681 //printf("A to B :\n");
2682 break;
2683 }
2684 distance++;
2685 }
2687 //try again, the other direction
2688 distance = 0;
2689 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2690 if (curr==a) {
2691 //printf("b to a:%d\n", distance);
2692 if (distance < minDistance) {
2693 start = b; //we go from b to a
2694 end = a;
2695 reversed = TRUE;
2696 //printf("B to A\n");
2697 }
2698 break;
2699 }
2700 distance++;
2701 }
2704 //Copy everything from 'end' to 'start' to a new subpath
2705 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2706 for (curr=end ; curr ; curr=curr->n.other) {
2707 NRPathcode code = (NRPathcode) curr->code;
2708 if (curr == end)
2709 code = NR_MOVETO;
2710 sp_nodepath_node_new(t, NULL,
2711 (Inkscape::NodePath::NodeType)curr->type, code,
2712 &curr->p.pos, &curr->pos, &curr->n.pos);
2713 if (curr == start)
2714 break;
2715 }
2716 sp_nodepath_subpath_destroy(a->subpath);
2719 }
2723 //##################################
2724 //# OPEN PATH
2725 //##################################
2726 else {
2728 //We need to get the direction of the list between A and B
2729 //Can we walk from a to b?
2730 start = end = NULL;
2731 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2732 if (curr==b) {
2733 start = a; //did it! we go from a to b
2734 end = b;
2735 //printf("A to B\n");
2736 break;
2737 }
2738 }
2739 if (!start) {//didn't work? let's try the other direction
2740 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2741 if (curr==a) {
2742 start = b; //did it! we go from b to a
2743 end = a;
2744 //printf("B to A\n");
2745 break;
2746 }
2747 }
2748 }
2749 if (!start) {
2750 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2751 _("Cannot find path between nodes."));
2752 return;
2753 }
2757 //Copy everything after 'end' to a new subpath
2758 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2759 for (curr=end ; curr ; curr=curr->n.other) {
2760 NRPathcode code = (NRPathcode) curr->code;
2761 if (curr == end)
2762 code = NR_MOVETO;
2763 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2764 &curr->p.pos, &curr->pos, &curr->n.pos);
2765 }
2767 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2768 for (curr = start->n.other ; curr ; curr=next) {
2769 next = curr->n.other;
2770 sp_nodepath_node_destroy(curr);
2771 }
2773 }
2774 //###########################################
2775 //# END EDITS
2776 //###########################################
2778 //clean up the nodepath (such as for trivial subpaths)
2779 sp_nodepath_cleanup(nodepath);
2781 sp_nodepath_update_handles(nodepath);
2783 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2785 sp_nodepath_update_statusbar(nodepath);
2786 }
2788 /**
2789 * Call sp_nodepath_set_line() for all selected segments.
2790 */
2791 void
2792 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2793 {
2794 if (nodepath == NULL) return;
2796 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2797 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2798 g_assert(n->selected);
2799 if (n->p.other && n->p.other->selected) {
2800 sp_nodepath_set_line_type(n, code);
2801 }
2802 }
2804 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2805 }
2807 /**
2808 * Call sp_nodepath_convert_node_type() for all selected nodes.
2809 */
2810 void
2811 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2812 {
2813 if (nodepath == NULL) return;
2815 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2817 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2818 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2819 }
2821 sp_nodepath_update_repr(nodepath, _("Change node type"));
2822 }
2824 /**
2825 * Change select status of node, update its own and neighbour handles.
2826 */
2827 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2828 {
2829 node->selected = selected;
2831 if (selected) {
2832 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2833 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2834 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2835 sp_knot_update_ctrl(node->knot);
2836 } else {
2837 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2838 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2839 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2840 sp_knot_update_ctrl(node->knot);
2841 }
2843 sp_node_update_handles(node);
2844 if (node->n.other) sp_node_update_handles(node->n.other);
2845 if (node->p.other) sp_node_update_handles(node->p.other);
2846 }
2848 /**
2849 \brief Select a node
2850 \param node The node to select
2851 \param incremental If true, add to selection, otherwise deselect others
2852 \param override If true, always select this node, otherwise toggle selected status
2853 */
2854 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2855 {
2856 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2858 if (incremental) {
2859 if (override) {
2860 if (!g_list_find(nodepath->selected, node)) {
2861 nodepath->selected = g_list_prepend(nodepath->selected, node);
2862 }
2863 sp_node_set_selected(node, TRUE);
2864 } else { // toggle
2865 if (node->selected) {
2866 g_assert(g_list_find(nodepath->selected, node));
2867 nodepath->selected = g_list_remove(nodepath->selected, node);
2868 } else {
2869 g_assert(!g_list_find(nodepath->selected, node));
2870 nodepath->selected = g_list_prepend(nodepath->selected, node);
2871 }
2872 sp_node_set_selected(node, !node->selected);
2873 }
2874 } else {
2875 sp_nodepath_deselect(nodepath);
2876 nodepath->selected = g_list_prepend(nodepath->selected, node);
2877 sp_node_set_selected(node, TRUE);
2878 }
2880 sp_nodepath_update_statusbar(nodepath);
2881 }
2884 /**
2885 \brief Deselect all nodes in the nodepath
2886 */
2887 void
2888 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2889 {
2890 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2892 while (nodepath->selected) {
2893 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2894 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2895 }
2896 sp_nodepath_update_statusbar(nodepath);
2897 }
2899 /**
2900 \brief Select or invert selection of all nodes in the nodepath
2901 */
2902 void
2903 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2904 {
2905 if (!nodepath) return;
2907 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2908 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2909 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2910 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2911 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2912 }
2913 }
2914 }
2916 /**
2917 * If nothing selected, does the same as sp_nodepath_select_all();
2918 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2919 * (i.e., similar to "select all in layer", with the "selected" subpaths
2920 * being treated as "layers" in the path).
2921 */
2922 void
2923 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2924 {
2925 if (!nodepath) return;
2927 if (g_list_length (nodepath->selected) == 0) {
2928 sp_nodepath_select_all (nodepath, invert);
2929 return;
2930 }
2932 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2933 GSList *subpaths = NULL;
2935 for (GList *l = copy; l != NULL; l = l->next) {
2936 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2937 Inkscape::NodePath::SubPath *subpath = n->subpath;
2938 if (!g_slist_find (subpaths, subpath))
2939 subpaths = g_slist_prepend (subpaths, subpath);
2940 }
2942 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2943 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2944 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2945 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2946 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2947 }
2948 }
2950 g_slist_free (subpaths);
2951 g_list_free (copy);
2952 }
2954 /**
2955 * \brief Select the node after the last selected; if none is selected,
2956 * select the first within path.
2957 */
2958 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2959 {
2960 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2962 Inkscape::NodePath::Node *last = NULL;
2963 if (nodepath->selected) {
2964 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2965 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2966 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2967 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2968 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2969 if (node->selected) {
2970 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2971 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2972 if (spl->next) { // there's a next subpath
2973 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2974 last = subpath_next->first;
2975 } else if (spl->prev) { // there's a previous subpath
2976 last = NULL; // to be set later to the first node of first subpath
2977 } else {
2978 last = node->n.other;
2979 }
2980 } else {
2981 last = node->n.other;
2982 }
2983 } else {
2984 if (node->n.other) {
2985 last = node->n.other;
2986 } else {
2987 if (spl->next) { // there's a next subpath
2988 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2989 last = subpath_next->first;
2990 } else if (spl->prev) { // there's a previous subpath
2991 last = NULL; // to be set later to the first node of first subpath
2992 } else {
2993 last = (Inkscape::NodePath::Node *) subpath->first;
2994 }
2995 }
2996 }
2997 }
2998 }
2999 }
3000 sp_nodepath_deselect(nodepath);
3001 }
3003 if (last) { // there's at least one more node after selected
3004 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3005 } else { // no more nodes, select the first one in first subpath
3006 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
3007 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
3008 }
3009 }
3011 /**
3012 * \brief Select the node before the first selected; if none is selected,
3013 * select the last within path
3014 */
3015 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
3016 {
3017 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
3019 Inkscape::NodePath::Node *last = NULL;
3020 if (nodepath->selected) {
3021 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
3022 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3023 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
3024 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3025 if (node->selected) {
3026 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3027 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3028 if (spl->prev) { // there's a prev subpath
3029 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3030 last = subpath_prev->last;
3031 } else if (spl->next) { // there's a next subpath
3032 last = NULL; // to be set later to the last node of last subpath
3033 } else {
3034 last = node->p.other;
3035 }
3036 } else {
3037 last = node->p.other;
3038 }
3039 } else {
3040 if (node->p.other) {
3041 last = node->p.other;
3042 } else {
3043 if (spl->prev) { // there's a prev subpath
3044 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3045 last = subpath_prev->last;
3046 } else if (spl->next) { // there's a next subpath
3047 last = NULL; // to be set later to the last node of last subpath
3048 } else {
3049 last = (Inkscape::NodePath::Node *) subpath->last;
3050 }
3051 }
3052 }
3053 }
3054 }
3055 }
3056 sp_nodepath_deselect(nodepath);
3057 }
3059 if (last) { // there's at least one more node before selected
3060 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3061 } else { // no more nodes, select the last one in last subpath
3062 GList *spl = g_list_last(nodepath->subpaths);
3063 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3064 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3065 }
3066 }
3068 /**
3069 * \brief Select all nodes that are within the rectangle.
3070 */
3071 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3072 {
3073 if (!incremental) {
3074 sp_nodepath_deselect(nodepath);
3075 }
3077 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3078 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3079 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3080 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3082 if (b.contains(node->pos)) {
3083 sp_nodepath_node_select(node, TRUE, TRUE);
3084 }
3085 }
3086 }
3087 }
3090 void
3091 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3092 {
3093 g_assert (n);
3094 g_assert (nodepath);
3095 g_assert (n->subpath->nodepath == nodepath);
3097 if (g_list_length (nodepath->selected) == 0) {
3098 if (grow > 0) {
3099 sp_nodepath_node_select(n, TRUE, TRUE);
3100 }
3101 return;
3102 }
3104 if (g_list_length (nodepath->selected) == 1) {
3105 if (grow < 0) {
3106 sp_nodepath_deselect (nodepath);
3107 return;
3108 }
3109 }
3111 double n_sel_range = 0, p_sel_range = 0;
3112 Inkscape::NodePath::Node *farthest_n_node = n;
3113 Inkscape::NodePath::Node *farthest_p_node = n;
3115 // Calculate ranges
3116 {
3117 double n_range = 0, p_range = 0;
3118 bool n_going = true, p_going = true;
3119 Inkscape::NodePath::Node *n_node = n;
3120 Inkscape::NodePath::Node *p_node = n;
3121 do {
3122 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3123 if (n_node && n_going)
3124 n_node = n_node->n.other;
3125 if (n_node == NULL) {
3126 n_going = false;
3127 } else {
3128 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3129 if (n_node->selected) {
3130 n_sel_range = n_range;
3131 farthest_n_node = n_node;
3132 }
3133 if (n_node == p_node) {
3134 n_going = false;
3135 p_going = false;
3136 }
3137 }
3138 if (p_node && p_going)
3139 p_node = p_node->p.other;
3140 if (p_node == NULL) {
3141 p_going = false;
3142 } else {
3143 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3144 if (p_node->selected) {
3145 p_sel_range = p_range;
3146 farthest_p_node = p_node;
3147 }
3148 if (p_node == n_node) {
3149 n_going = false;
3150 p_going = false;
3151 }
3152 }
3153 } while (n_going || p_going);
3154 }
3156 if (grow > 0) {
3157 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3158 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3159 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3160 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3161 }
3162 } else {
3163 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3164 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3165 } else if (farthest_p_node && farthest_p_node->selected) {
3166 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3167 }
3168 }
3169 }
3171 void
3172 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3173 {
3174 g_assert (n);
3175 g_assert (nodepath);
3176 g_assert (n->subpath->nodepath == nodepath);
3178 if (g_list_length (nodepath->selected) == 0) {
3179 if (grow > 0) {
3180 sp_nodepath_node_select(n, TRUE, TRUE);
3181 }
3182 return;
3183 }
3185 if (g_list_length (nodepath->selected) == 1) {
3186 if (grow < 0) {
3187 sp_nodepath_deselect (nodepath);
3188 return;
3189 }
3190 }
3192 Inkscape::NodePath::Node *farthest_selected = NULL;
3193 double farthest_dist = 0;
3195 Inkscape::NodePath::Node *closest_unselected = NULL;
3196 double closest_dist = NR_HUGE;
3198 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3199 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3200 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3201 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3202 if (node == n)
3203 continue;
3204 if (node->selected) {
3205 if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3206 farthest_dist = Geom::L2(node->pos - n->pos);
3207 farthest_selected = node;
3208 }
3209 } else {
3210 if (Geom::L2(node->pos - n->pos) < closest_dist) {
3211 closest_dist = Geom::L2(node->pos - n->pos);
3212 closest_unselected = node;
3213 }
3214 }
3215 }
3216 }
3218 if (grow > 0) {
3219 if (closest_unselected) {
3220 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3221 }
3222 } else {
3223 if (farthest_selected) {
3224 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3225 }
3226 }
3227 }
3230 /**
3231 \brief Saves all nodes' and handles' current positions in their origin members
3232 */
3233 void
3234 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3235 {
3236 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3237 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3238 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3239 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3240 n->origin = n->pos;
3241 n->p.origin = n->p.pos;
3242 n->n.origin = n->n.pos;
3243 }
3244 }
3245 }
3247 /**
3248 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3249 */
3250 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3251 {
3252 GList *r = NULL;
3253 if (nodepath->selected) {
3254 guint i = 0;
3255 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3256 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3257 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3258 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3259 i++;
3260 if (node->selected) {
3261 r = g_list_append(r, GINT_TO_POINTER(i));
3262 }
3263 }
3264 }
3265 }
3266 return r;
3267 }
3269 /**
3270 \brief Restores selection by selecting nodes whose positions are in the list
3271 */
3272 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3273 {
3274 sp_nodepath_deselect(nodepath);
3276 guint i = 0;
3277 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3278 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3279 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3280 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3281 i++;
3282 if (g_list_find(r, GINT_TO_POINTER(i))) {
3283 sp_nodepath_node_select(node, TRUE, TRUE);
3284 }
3285 }
3286 }
3287 }
3290 /**
3291 \brief Adjusts handle according to node type and line code.
3292 */
3293 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3294 {
3295 g_assert(node);
3297 // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3298 if (node->type == Inkscape::NodePath::NODE_AUTO)
3299 return;
3301 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3302 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3304 // nothing to do if we are an end node
3305 if (me->other == NULL) return;
3306 if (other->other == NULL) return;
3308 // nothing to do if we are a cusp node
3309 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3311 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3312 NRPathcode mecode;
3313 if (which_adjust == 1) {
3314 mecode = (NRPathcode)me->other->code;
3315 } else {
3316 mecode = (NRPathcode)node->code;
3317 }
3318 if (mecode == NR_LINETO) return;
3320 if (sp_node_side_is_line(node, other)) {
3321 // other is a line, and we are either smooth or symm
3322 Inkscape::NodePath::Node *othernode = other->other;
3323 double len = Geom::L2(me->pos - node->pos);
3324 Geom::Point delta = node->pos - othernode->pos;
3325 double linelen = Geom::L2(delta);
3326 if (linelen < 1e-18)
3327 return;
3328 me->pos = node->pos + (len / linelen)*delta;
3329 return;
3330 }
3332 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3333 // symmetrize
3334 me->pos = 2 * node->pos - other->pos;
3335 return;
3336 } else {
3337 // smoothify
3338 double len = Geom::L2(me->pos - node->pos);
3339 Geom::Point delta = other->pos - node->pos;
3340 double otherlen = Geom::L2(delta);
3341 if (otherlen < 1e-18) return;
3342 me->pos = node->pos - (len / otherlen) * delta;
3343 }
3344 }
3346 /**
3347 \brief Adjusts both handles according to node type and line code
3348 */
3349 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3350 {
3351 g_assert(node);
3353 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3355 /* we are either smooth or symm */
3357 if (node->p.other == NULL) return;
3358 if (node->n.other == NULL) return;
3360 if (node->type == Inkscape::NodePath::NODE_AUTO) {
3361 sp_node_adjust_handles_auto(node);
3362 return;
3363 }
3365 if (sp_node_side_is_line(node, &node->p)) {
3366 sp_node_adjust_handle(node, 1);
3367 return;
3368 }
3370 if (sp_node_side_is_line(node, &node->n)) {
3371 sp_node_adjust_handle(node, -1);
3372 return;
3373 }
3375 /* both are curves */
3376 Geom::Point const delta( node->n.pos - node->p.pos );
3378 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3379 node->p.pos = node->pos - delta / 2;
3380 node->n.pos = node->pos + delta / 2;
3381 return;
3382 }
3384 /* We are smooth */
3385 double plen = Geom::L2(node->p.pos - node->pos);
3386 if (plen < 1e-18) return;
3387 double nlen = Geom::L2(node->n.pos - node->pos);
3388 if (nlen < 1e-18) return;
3389 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3390 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3391 }
3393 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3394 {
3395 if (node->p.other == NULL || node->n.other == NULL) {
3396 node->p.pos = node->pos;
3397 node->n.pos = node->pos;
3398 return;
3399 }
3401 Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3402 Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3404 double norm_leg_prev = Geom::L2(leg_prev);
3405 double norm_leg_next = Geom::L2(leg_next);
3407 Geom::Point delta;
3408 if (norm_leg_next > 0.0) {
3409 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3410 delta.normalize();
3411 }
3413 node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3414 node->n.pos = node->pos + norm_leg_next / 3 * delta;
3415 }
3417 /**
3418 * Node event callback.
3419 */
3420 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3421 {
3422 gboolean ret = FALSE;
3423 switch (event->type) {
3424 case GDK_ENTER_NOTIFY:
3425 Inkscape::NodePath::Path::active_node = n;
3426 break;
3427 case GDK_LEAVE_NOTIFY:
3428 Inkscape::NodePath::Path::active_node = NULL;
3429 break;
3430 case GDK_SCROLL:
3431 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3432 switch (event->scroll.direction) {
3433 case GDK_SCROLL_UP:
3434 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3435 break;
3436 case GDK_SCROLL_DOWN:
3437 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3438 break;
3439 default:
3440 break;
3441 }
3442 ret = TRUE;
3443 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3444 switch (event->scroll.direction) {
3445 case GDK_SCROLL_UP:
3446 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3447 break;
3448 case GDK_SCROLL_DOWN:
3449 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3450 break;
3451 default:
3452 break;
3453 }
3454 ret = TRUE;
3455 }
3456 break;
3457 case GDK_KEY_PRESS:
3458 switch (get_group0_keyval (&event->key)) {
3459 case GDK_space:
3460 if (event->key.state & GDK_BUTTON1_MASK) {
3461 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3462 stamp_repr(nodepath);
3463 ret = TRUE;
3464 }
3465 break;
3466 case GDK_Page_Up:
3467 if (event->key.state & GDK_CONTROL_MASK) {
3468 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3469 } else {
3470 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3471 }
3472 break;
3473 case GDK_Page_Down:
3474 if (event->key.state & GDK_CONTROL_MASK) {
3475 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3476 } else {
3477 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3478 }
3479 break;
3480 default:
3481 break;
3482 }
3483 break;
3484 default:
3485 break;
3486 }
3488 return ret;
3489 }
3491 /**
3492 * Handle keypress on node; directly called.
3493 */
3494 gboolean node_key(GdkEvent *event)
3495 {
3496 Inkscape::NodePath::Path *np;
3498 // there is no way to verify nodes so set active_node to nil when deleting!!
3499 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3501 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3502 gint ret = FALSE;
3503 switch (get_group0_keyval (&event->key)) {
3504 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3505 case GDK_BackSpace:
3506 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3507 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3508 sp_nodepath_update_repr(np, _("Delete node"));
3509 Inkscape::NodePath::Path::active_node = NULL;
3510 ret = TRUE;
3511 break;
3512 case GDK_c:
3513 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3514 ret = TRUE;
3515 break;
3516 case GDK_s:
3517 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3518 ret = TRUE;
3519 break;
3520 case GDK_a:
3521 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3522 ret = TRUE;
3523 break;
3524 case GDK_y:
3525 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3526 ret = TRUE;
3527 break;
3528 case GDK_b:
3529 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3530 ret = TRUE;
3531 break;
3532 }
3533 return ret;
3534 }
3535 return FALSE;
3536 }
3538 /**
3539 * Mouseclick on node callback.
3540 */
3541 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3542 {
3543 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3545 if (state & GDK_CONTROL_MASK) {
3546 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3548 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3549 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3550 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3551 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3552 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3553 } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3554 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3555 } else {
3556 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3557 }
3558 sp_nodepath_update_repr(nodepath, _("Change node type"));
3559 sp_nodepath_update_statusbar(nodepath);
3561 } else { //ctrl+alt+click: delete node
3562 GList *node_to_delete = NULL;
3563 node_to_delete = g_list_append(node_to_delete, n);
3564 sp_node_delete_preserve(node_to_delete);
3565 }
3567 } else {
3568 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3569 }
3570 }
3572 /**
3573 * Mouse grabbed node callback.
3574 */
3575 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3576 {
3577 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3579 if (!n->selected) {
3580 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3581 }
3583 n->is_dragging = true;
3584 // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
3585 n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
3587 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3589 sp_nodepath_remember_origins (n->subpath->nodepath);
3590 }
3592 /**
3593 * Mouse ungrabbed node callback.
3594 */
3595 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3596 {
3597 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3599 n->dragging_out = NULL;
3600 n->is_dragging = false;
3601 n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
3602 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3604 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3605 }
3607 /**
3608 * The point on a line, given by its angle, closest to the given point.
3609 * \param p A point.
3610 * \param a Angle of the line; it is assumed to go through coordinate origin.
3611 * \param closest Pointer to the point struct where the result is stored.
3612 * \todo FIXME: use dot product perhaps?
3613 */
3614 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3615 {
3616 if (a == HUGE_VAL) { // vertical
3617 *closest = Geom::Point(0, (*p)[Geom::Y]);
3618 } else {
3619 (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3620 (*closest)[Geom::Y] = a * (*closest)[Geom::X];
3621 }
3622 }
3624 /**
3625 * Distance from the point to a line given by its angle.
3626 * \param p A point.
3627 * \param a Angle of the line; it is assumed to go through coordinate origin.
3628 */
3629 static double point_line_distance(Geom::Point *p, double a)
3630 {
3631 Geom::Point c;
3632 point_line_closest(p, a, &c);
3633 return sqrt(((*p)[Geom::X] - c[Geom::X])*((*p)[Geom::X] - c[Geom::X]) + ((*p)[Geom::Y] - c[Geom::Y])*((*p)[Geom::Y] - c[Geom::Y]));
3634 }
3636 /**
3637 * Callback for node "request" signal.
3638 * \todo fixme: This goes to "moved" event? (lauris)
3639 */
3640 static gboolean
3641 node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
3642 {
3643 double yn, xn, yp, xp;
3644 double an, ap, na, pa;
3645 double d_an, d_ap, d_na, d_pa;
3646 gboolean collinear = FALSE;
3647 Geom::Point c;
3648 Geom::Point pr;
3650 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3652 n->subpath->nodepath->desktop->snapindicator->remove_snaptarget();
3654 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3655 if ( (!n->subpath->nodepath->straight_path) &&
3656 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3657 || n->dragging_out ) )
3658 {
3659 Geom::Point mouse = p;
3661 if (!n->dragging_out) {
3662 // This is the first drag-out event; find out which handle to drag out
3663 double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
3664 double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
3666 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3667 return FALSE;
3669 Inkscape::NodePath::NodeSide *opposite;
3670 if (appr_p > appr_n) { // closer to p
3671 n->dragging_out = &n->p;
3672 opposite = &n->n;
3673 n->code = NR_CURVETO;
3674 } else if (appr_p < appr_n) { // closer to n
3675 n->dragging_out = &n->n;
3676 opposite = &n->p;
3677 n->n.other->code = NR_CURVETO;
3678 } else { // p and n nodes are the same
3679 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3680 n->dragging_out = &n->p;
3681 opposite = &n->n;
3682 n->code = NR_CURVETO;
3683 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3684 n->dragging_out = &n->n;
3685 opposite = &n->p;
3686 n->n.other->code = NR_CURVETO;
3687 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3688 double appr_other_n = (n->n.other ? Geom::L2(n->n.other->n.pos - n->pos) - Geom::L2(n->n.other->n.pos - p) : -HUGE_VAL);
3689 double appr_other_p = (n->n.other ? Geom::L2(n->n.other->p.pos - n->pos) - Geom::L2(n->n.other->p.pos - p) : -HUGE_VAL);
3690 if (appr_other_p > appr_other_n) { // closer to other's p handle
3691 n->dragging_out = &n->n;
3692 opposite = &n->p;
3693 n->n.other->code = NR_CURVETO;
3694 } else { // closer to other's n handle
3695 n->dragging_out = &n->p;
3696 opposite = &n->n;
3697 n->code = NR_CURVETO;
3698 }
3699 }
3700 }
3702 // if there's another handle, make sure the one we drag out starts parallel to it
3703 if (opposite->pos != n->pos) {
3704 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3705 }
3707 // knots might not be created yet!
3708 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3709 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3710 }
3712 // pass this on to the handle-moved callback
3713 node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
3714 sp_node_update_handles(n);
3715 return TRUE;
3716 }
3718 if (state & GDK_CONTROL_MASK) { // constrained motion
3720 // calculate relative distances of handles
3721 // n handle:
3722 yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3723 xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3724 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3725 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3726 if (n->n.other) { // if there is the next point
3727 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3728 yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3729 xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3730 }
3731 }
3732 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3733 if (yn < 0) { xn = -xn; yn = -yn; }
3735 // p handle:
3736 yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3737 xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3738 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3739 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3740 if (n->p.other) {
3741 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3742 yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3743 xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3744 }
3745 }
3746 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3747 if (yp < 0) { xp = -xp; yp = -yp; }
3749 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3750 // sliding on handles, only if at least one of the handles is non-vertical
3751 // (otherwise it's the same as ctrl+drag anyway)
3753 // calculate angles of the handles
3754 if (xn == 0) {
3755 if (yn == 0) { // no handle, consider it the continuation of the other one
3756 an = 0;
3757 collinear = TRUE;
3758 }
3759 else an = 0; // vertical; set the angle to horizontal
3760 } else an = yn/xn;
3762 if (xp == 0) {
3763 if (yp == 0) { // no handle, consider it the continuation of the other one
3764 ap = an;
3765 }
3766 else ap = 0; // vertical; set the angle to horizontal
3767 } else ap = yp/xp;
3769 if (collinear) an = ap;
3771 // angles of the perpendiculars; HUGE_VAL means vertical
3772 if (an == 0) na = HUGE_VAL; else na = -1/an;
3773 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3775 // mouse point relative to the node's original pos
3776 pr = p - n->origin;
3778 // distances to the four lines (two handles and two perpendiculars)
3779 d_an = point_line_distance(&pr, an);
3780 d_na = point_line_distance(&pr, na);
3781 d_ap = point_line_distance(&pr, ap);
3782 d_pa = point_line_distance(&pr, pa);
3784 // find out which line is the closest, save its closest point in c
3785 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3786 point_line_closest(&pr, an, &c);
3787 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3788 point_line_closest(&pr, ap, &c);
3789 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3790 point_line_closest(&pr, na, &c);
3791 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3792 point_line_closest(&pr, pa, &c);
3793 }
3795 // move the node to the closest point
3796 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3797 n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3798 n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3799 true);
3801 } else { // constraining to hor/vert
3803 if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3804 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3805 p[Geom::X] - n->pos[Geom::X],
3806 n->origin[Geom::Y] - n->pos[Geom::Y],
3807 true,
3808 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3809 } else { // snap to vert
3810 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3811 n->origin[Geom::X] - n->pos[Geom::X],
3812 p[Geom::Y] - n->pos[Geom::Y],
3813 true,
3814 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3815 }
3816 }
3817 } else { // move freely
3818 if (n->is_dragging) {
3819 if (state & GDK_MOD1_MASK) { // sculpt
3820 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
3821 } else {
3822 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3823 p[Geom::X] - n->pos[Geom::X],
3824 p[Geom::Y] - n->pos[Geom::Y],
3825 (state & GDK_SHIFT_MASK) == 0);
3826 }
3827 }
3828 }
3830 n->subpath->nodepath->desktop->scroll_to_point(p);
3832 return TRUE;
3833 }
3835 /**
3836 * Node handle clicked callback.
3837 */
3838 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3839 {
3840 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3842 if (state & GDK_CONTROL_MASK) { // "delete" handle
3843 if (n->p.knot == knot) {
3844 n->p.pos = n->pos;
3845 } else if (n->n.knot == knot) {
3846 n->n.pos = n->pos;
3847 }
3848 sp_node_update_handles(n);
3849 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3850 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3851 sp_nodepath_update_statusbar(nodepath);
3853 } else { // just select or add to selection, depending in Shift
3854 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3855 }
3856 }
3858 /**
3859 * Node handle grabbed callback.
3860 */
3861 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3862 {
3863 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3865 // convert auto -> smooth when dragging handle
3866 if (n->type == Inkscape::NodePath::NODE_AUTO) {
3867 n->type = Inkscape::NodePath::NODE_SMOOTH;
3868 sp_nodepath_update_node_knot (n);
3869 }
3871 if (!n->selected) {
3872 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3873 }
3875 // remember the origin point of the handle
3876 if (n->p.knot == knot) {
3877 n->p.origin_radial = n->p.pos - n->pos;
3878 } else if (n->n.knot == knot) {
3879 n->n.origin_radial = n->n.pos - n->pos;
3880 } else {
3881 g_assert_not_reached();
3882 }
3884 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3885 }
3887 /**
3888 * Node handle ungrabbed callback.
3889 */
3890 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3891 {
3892 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3894 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3895 if (n->p.knot == knot) {
3896 n->p.origin_radial.a = 0;
3897 sp_knot_set_position(knot, n->p.pos, state);
3898 } else if (n->n.knot == knot) {
3899 n->n.origin_radial.a = 0;
3900 sp_knot_set_position(knot, n->n.pos, state);
3901 } else {
3902 g_assert_not_reached();
3903 }
3905 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3906 }
3908 /**
3909 * Node handle "request" signal callback.
3910 */
3911 static gboolean node_handle_request(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3912 {
3913 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3915 Inkscape::NodePath::NodeSide *me, *opposite;
3916 gint which;
3917 if (n->p.knot == knot) {
3918 me = &n->p;
3919 opposite = &n->n;
3920 which = -1;
3921 } else if (n->n.knot == knot) {
3922 me = &n->n;
3923 opposite = &n->p;
3924 which = 1;
3925 } else {
3926 me = opposite = NULL;
3927 which = 0;
3928 g_assert_not_reached();
3929 }
3931 SPDesktop *desktop = n->subpath->nodepath->desktop;
3932 SnapManager &m = desktop->namedview->snap_manager;
3933 m.setup(desktop, true, n->subpath->nodepath->item);
3934 Inkscape::SnappedPoint s;
3936 if ((state & GDK_SHIFT_MASK) != 0) {
3937 // We will not try to snap when the shift-key is pressed
3938 // so remove the old snap indicator and don't wait for it to time-out
3939 desktop->snapindicator->remove_snaptarget();
3940 }
3942 Inkscape::NodePath::Node *othernode = opposite->other;
3943 if (othernode) {
3944 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3945 /* We are smooth node adjacent with line */
3946 Geom::Point const delta = p - n->pos;
3947 Geom::Coord const len = Geom::L2(delta);
3948 Inkscape::NodePath::Node *othernode = opposite->other;
3949 Geom::Point const ndelta = n->pos - othernode->pos;
3950 Geom::Coord const linelen = Geom::L2(ndelta);
3951 Geom::Point ptemp = p;
3952 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3953 Geom::Coord const scal = dot(delta, ndelta) / linelen;
3954 ptemp = n->pos + (scal / linelen) * ndelta;
3955 }
3956 if ((state & GDK_SHIFT_MASK) == 0) {
3957 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, ptemp, Inkscape::Snapper::ConstraintLine(ptemp, ndelta));
3958 }
3959 } else {
3960 if ((state & GDK_SHIFT_MASK) == 0) {
3961 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3962 }
3963 }
3964 } else {
3965 if ((state & GDK_SHIFT_MASK) == 0) {
3966 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3967 }
3968 }
3970 sp_node_adjust_handle(n, -which);
3972 return FALSE;
3973 }
3975 /**
3976 * Node handle moved callback.
3977 */
3978 static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3979 {
3980 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3981 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3983 Inkscape::NodePath::NodeSide *me;
3984 Inkscape::NodePath::NodeSide *other;
3985 if (n->p.knot == knot) {
3986 me = &n->p;
3987 other = &n->n;
3988 } else if (n->n.knot == knot) {
3989 me = &n->n;
3990 other = &n->p;
3991 } else {
3992 me = NULL;
3993 other = NULL;
3994 g_assert_not_reached();
3995 }
3997 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
3998 Radial rme(me->pos - n->pos);
3999 Radial rother(other->pos - n->pos);
4000 Radial rnew(p - n->pos);
4002 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
4003 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
4004 /* 0 interpreted as "no snapping". */
4006 // 1. Snap to the closest PI/snaps angle, starting from zero.
4007 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
4009 // 2. Snap to the original angle, its opposite and perpendiculars
4010 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
4011 /* The closest PI/2 angle, starting from original angle */
4012 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
4014 // Snap to the closest.
4015 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
4016 ? a_snapped
4017 : a_ortho );
4018 }
4020 // 3. Snap to the angle of the opposite line, if any
4021 Inkscape::NodePath::Node *othernode = other->other;
4022 if (othernode) {
4023 Geom::Point other_to_snap(0,0);
4024 if (sp_node_side_is_line(n, other)) {
4025 other_to_snap = othernode->pos - n->pos;
4026 } else {
4027 other_to_snap = other->pos - n->pos;
4028 }
4029 if (Geom::L2(other_to_snap) > 1e-3) {
4030 Radial rother_to_snap(other_to_snap);
4031 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
4032 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4034 // Snap to the closest.
4035 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4036 ? a_snapped
4037 : a_oppo );
4038 }
4039 }
4041 rnew.a = a_snapped;
4042 }
4044 if (state & GDK_MOD1_MASK) {
4045 // lock handle length
4046 rnew.r = me->origin_radial.r;
4047 }
4049 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
4050 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
4051 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
4052 rother.a += rnew.a - rme.a;
4053 other->pos = Geom::Point(rother) + n->pos;
4054 if (other->knot) {
4055 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4056 sp_knot_moveto(other->knot, other->pos);
4057 }
4058 }
4060 me->pos = Geom::Point(rnew) + n->pos;
4061 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4063 // move knot, but without emitting the signal:
4064 // we cannot emit a "moved" signal because we're now processing it
4065 sp_knot_moveto(me->knot, me->pos);
4067 update_object(n->subpath->nodepath);
4069 /* status text */
4070 SPDesktop *desktop = n->subpath->nodepath->desktop;
4071 if (!desktop) return;
4072 SPEventContext *ec = desktop->event_context;
4073 if (!ec) return;
4075 Inkscape::MessageContext *mc = get_message_context(ec);
4077 if (!mc) return;
4079 double degrees = 180 / M_PI * rnew.a;
4080 if (degrees > 180) degrees -= 360;
4081 if (degrees < -180) degrees += 360;
4082 if (prefs->getBool("/options/compassangledisplay/value"))
4083 degrees = angle_to_compass (degrees);
4085 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4087 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4088 _("<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);
4090 g_string_free(length, TRUE);
4091 }
4093 /**
4094 * Node handle event callback.
4095 */
4096 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4097 {
4098 gboolean ret = FALSE;
4099 switch (event->type) {
4100 case GDK_KEY_PRESS:
4101 switch (get_group0_keyval (&event->key)) {
4102 case GDK_space:
4103 if (event->key.state & GDK_BUTTON1_MASK) {
4104 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4105 stamp_repr(nodepath);
4106 ret = TRUE;
4107 }
4108 break;
4109 default:
4110 break;
4111 }
4112 break;
4113 case GDK_ENTER_NOTIFY:
4114 // we use an experimentally determined threshold that seems to work fine
4115 if (Geom::L2(n->pos - knot->pos) < 0.75)
4116 Inkscape::NodePath::Path::active_node = n;
4117 break;
4118 case GDK_LEAVE_NOTIFY:
4119 // we use an experimentally determined threshold that seems to work fine
4120 if (Geom::L2(n->pos - knot->pos) < 0.75)
4121 Inkscape::NodePath::Path::active_node = NULL;
4122 break;
4123 default:
4124 break;
4125 }
4127 return ret;
4128 }
4130 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4131 Radial &rme, Radial &rother, gboolean const both)
4132 {
4133 rme.a += angle;
4134 if ( both
4135 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4136 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4137 {
4138 rother.a += angle;
4139 }
4140 }
4142 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4143 Radial &rme, Radial &rother, gboolean const both)
4144 {
4145 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4147 gdouble r;
4148 if ( both
4149 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4150 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4151 {
4152 r = MAX(rme.r, rother.r);
4153 } else {
4154 r = rme.r;
4155 }
4157 gdouble const weird_angle = atan2(norm_angle, r);
4158 /* Bulia says norm_angle is just the visible distance that the
4159 * object's end must travel on the screen. Left as 'angle' for want of
4160 * a better name.*/
4162 rme.a += weird_angle;
4163 if ( both
4164 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4165 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4166 {
4167 rother.a += weird_angle;
4168 }
4169 }
4171 /**
4172 * Rotate one node.
4173 */
4174 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4175 {
4176 Inkscape::NodePath::NodeSide *me, *other;
4177 bool both = false;
4179 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4180 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4182 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4183 me = &(n->p);
4184 other = &(n->n);
4185 } else if (!n->p.other) {
4186 me = &(n->n);
4187 other = &(n->p);
4188 } else {
4189 if (which > 0) { // right handle
4190 if (xn > xp) {
4191 me = &(n->n);
4192 other = &(n->p);
4193 } else {
4194 me = &(n->p);
4195 other = &(n->n);
4196 }
4197 } else if (which < 0){ // left handle
4198 if (xn <= xp) {
4199 me = &(n->n);
4200 other = &(n->p);
4201 } else {
4202 me = &(n->p);
4203 other = &(n->n);
4204 }
4205 } else { // both handles
4206 me = &(n->n);
4207 other = &(n->p);
4208 both = true;
4209 }
4210 }
4212 Radial rme(me->pos - n->pos);
4213 Radial rother(other->pos - n->pos);
4215 if (screen) {
4216 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4217 } else {
4218 node_rotate_one_internal (*n, angle, rme, rother, both);
4219 }
4221 me->pos = n->pos + Geom::Point(rme);
4223 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4224 other->pos = n->pos + Geom::Point(rother);
4225 }
4227 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4228 // so here we just move all the knots without emitting move signals, for speed
4229 sp_node_update_handles(n, false);
4230 }
4232 /**
4233 * Rotate selected nodes.
4234 */
4235 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4236 {
4237 if (!nodepath || !nodepath->selected) return;
4239 if (g_list_length(nodepath->selected) == 1) {
4240 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4241 node_rotate_one (n, angle, which, screen);
4242 } else {
4243 // rotate as an object:
4245 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4246 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4247 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4248 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4249 box.expandTo (n->pos); // contain all selected nodes
4250 }
4252 gdouble rot;
4253 if (screen) {
4254 gdouble const zoom = nodepath->desktop->current_zoom();
4255 gdouble const zmove = angle / zoom;
4256 gdouble const r = Geom::L2(box.max() - box.midpoint());
4257 rot = atan2(zmove, r);
4258 } else {
4259 rot = angle;
4260 }
4262 Geom::Point rot_center;
4263 if (Inkscape::NodePath::Path::active_node == NULL)
4264 rot_center = box.midpoint();
4265 else
4266 rot_center = Inkscape::NodePath::Path::active_node->pos;
4268 Geom::Matrix t =
4269 Geom::Matrix (Geom::Translate(-rot_center)) *
4270 Geom::Matrix (Geom::Rotate(rot)) *
4271 Geom::Matrix (Geom::Translate(rot_center));
4273 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4274 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4275 n->pos *= t;
4276 n->n.pos *= t;
4277 n->p.pos *= t;
4278 sp_node_update_handles(n, false);
4279 }
4280 }
4282 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4283 }
4285 /**
4286 * Scale one node.
4287 */
4288 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4289 {
4290 bool both = false;
4291 Inkscape::NodePath::NodeSide *me, *other;
4293 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4294 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4296 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4297 me = &(n->p);
4298 other = &(n->n);
4299 n->code = NR_CURVETO;
4300 } else if (!n->p.other) {
4301 me = &(n->n);
4302 other = &(n->p);
4303 if (n->n.other)
4304 n->n.other->code = NR_CURVETO;
4305 } else {
4306 if (which > 0) { // right handle
4307 if (xn > xp) {
4308 me = &(n->n);
4309 other = &(n->p);
4310 if (n->n.other)
4311 n->n.other->code = NR_CURVETO;
4312 } else {
4313 me = &(n->p);
4314 other = &(n->n);
4315 n->code = NR_CURVETO;
4316 }
4317 } else if (which < 0){ // left handle
4318 if (xn <= xp) {
4319 me = &(n->n);
4320 other = &(n->p);
4321 if (n->n.other)
4322 n->n.other->code = NR_CURVETO;
4323 } else {
4324 me = &(n->p);
4325 other = &(n->n);
4326 n->code = NR_CURVETO;
4327 }
4328 } else { // both handles
4329 me = &(n->n);
4330 other = &(n->p);
4331 both = true;
4332 n->code = NR_CURVETO;
4333 if (n->n.other)
4334 n->n.other->code = NR_CURVETO;
4335 }
4336 }
4338 Radial rme(me->pos - n->pos);
4339 Radial rother(other->pos - n->pos);
4341 rme.r += grow;
4342 if (rme.r < 0) rme.r = 0;
4343 if (rme.a == HUGE_VAL) {
4344 if (me->other) { // if direction is unknown, initialize it towards the next node
4345 Radial rme_next(me->other->pos - n->pos);
4346 rme.a = rme_next.a;
4347 } else { // if there's no next, initialize to 0
4348 rme.a = 0;
4349 }
4350 }
4351 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4352 rother.r += grow;
4353 if (rother.r < 0) rother.r = 0;
4354 if (rother.a == HUGE_VAL) {
4355 rother.a = rme.a + M_PI;
4356 }
4357 }
4359 me->pos = n->pos + Geom::Point(rme);
4361 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4362 other->pos = n->pos + Geom::Point(rother);
4363 }
4365 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4366 // so here we just move all the knots without emitting move signals, for speed
4367 sp_node_update_handles(n, false);
4368 }
4370 /**
4371 * Scale selected nodes.
4372 */
4373 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4374 {
4375 if (!nodepath || !nodepath->selected) return;
4377 if (g_list_length(nodepath->selected) == 1) {
4378 // scale handles of the single selected node
4379 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4380 node_scale_one (n, grow, which);
4381 } else {
4382 // scale nodes as an "object":
4384 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4385 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4386 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4387 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4388 box.expandTo (n->pos); // contain all selected nodes
4389 }
4391 if ( Geom::are_near(box.maxExtent(), 0) ) {
4392 SPEventContext *ec = nodepath->desktop->event_context;
4393 if (!ec) return;
4394 Inkscape::MessageContext *mc = get_message_context(ec);
4395 if (!mc) return;
4396 mc->setF(Inkscape::WARNING_MESSAGE,
4397 _("Cannot scale nodes when all are at the same location."));
4398 return;
4399 }
4400 double scale = (box.maxExtent() + grow)/box.maxExtent();
4403 Geom::Point scale_center;
4404 if (Inkscape::NodePath::Path::active_node == NULL)
4405 scale_center = box.midpoint();
4406 else
4407 scale_center = Inkscape::NodePath::Path::active_node->pos;
4409 Geom::Matrix t =
4410 Geom::Translate(-scale_center) *
4411 Geom::Scale(scale, scale) *
4412 Geom::Translate(scale_center);
4414 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4415 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4416 n->pos *= t;
4417 n->n.pos *= t;
4418 n->p.pos *= t;
4419 sp_node_update_handles(n, false);
4420 }
4421 }
4423 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4424 }
4426 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4427 {
4428 if (!nodepath) return;
4429 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4430 }
4432 /**
4433 * Flip selected nodes horizontally/vertically.
4434 */
4435 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4436 {
4437 if (!nodepath || !nodepath->selected) return;
4439 if (g_list_length(nodepath->selected) == 1 && !center) {
4440 // flip handles of the single selected node
4441 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4442 double temp = n->p.pos[axis];
4443 n->p.pos[axis] = n->n.pos[axis];
4444 n->n.pos[axis] = temp;
4445 sp_node_update_handles(n, false);
4446 } else {
4447 // scale nodes as an "object":
4449 Geom::Rect box = sp_node_selected_bbox (nodepath);
4450 if (!center) {
4451 center = box.midpoint();
4452 }
4453 Geom::Matrix t =
4454 Geom::Matrix (Geom::Translate(- *center)) *
4455 Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4456 Geom::Matrix (Geom::Translate(*center));
4458 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4459 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4460 n->pos *= t;
4461 n->n.pos *= t;
4462 n->p.pos *= t;
4463 sp_node_update_handles(n, false);
4464 }
4465 }
4467 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4468 }
4470 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4471 {
4472 g_assert (nodepath->selected);
4474 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4475 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4476 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4477 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4478 box.expandTo (n->pos); // contain all selected nodes
4479 }
4480 return box;
4481 }
4483 //-----------------------------------------------
4484 /**
4485 * Return new subpath under given nodepath.
4486 */
4487 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4488 {
4489 g_assert(nodepath);
4490 g_assert(nodepath->desktop);
4492 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4494 s->nodepath = nodepath;
4495 s->closed = FALSE;
4496 s->nodes = NULL;
4497 s->first = NULL;
4498 s->last = NULL;
4500 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4501 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4502 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4504 return s;
4505 }
4507 /**
4508 * Destroy nodes in subpath, then subpath itself.
4509 */
4510 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4511 {
4512 g_assert(subpath);
4513 g_assert(subpath->nodepath);
4514 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4516 while (subpath->nodes) {
4517 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4518 }
4520 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4522 g_free(subpath);
4523 }
4525 /**
4526 * Link head to tail in subpath.
4527 */
4528 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4529 {
4530 g_assert(!sp->closed);
4531 g_assert(sp->last != sp->first);
4532 g_assert(sp->first->code == NR_MOVETO);
4534 sp->closed = TRUE;
4536 //Link the head to the tail
4537 sp->first->p.other = sp->last;
4538 sp->last->n.other = sp->first;
4539 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4540 sp->first = sp->last;
4542 //Remove the extra end node
4543 sp_nodepath_node_destroy(sp->last->n.other);
4544 }
4546 /**
4547 * Open closed (loopy) subpath at node.
4548 */
4549 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4550 {
4551 g_assert(sp->closed);
4552 g_assert(n->subpath == sp);
4553 g_assert(sp->first == sp->last);
4555 /* We create new startpoint, current node will become last one */
4557 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4558 &n->pos, &n->pos, &n->n.pos);
4561 sp->closed = FALSE;
4563 //Unlink to make a head and tail
4564 sp->first = new_path;
4565 sp->last = n;
4566 n->n.other = NULL;
4567 new_path->p.other = NULL;
4568 }
4570 /**
4571 * Return new node in subpath with given properties.
4572 * \param pos Position of node.
4573 * \param ppos Handle position in previous direction
4574 * \param npos Handle position in previous direction
4575 */
4576 Inkscape::NodePath::Node *
4577 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)
4578 {
4579 g_assert(sp);
4580 g_assert(sp->nodepath);
4581 g_assert(sp->nodepath->desktop);
4583 if (nodechunk == NULL)
4584 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4586 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4588 n->subpath = sp;
4590 if (type != Inkscape::NodePath::NODE_NONE) {
4591 // use the type from sodipodi:nodetypes
4592 n->type = type;
4593 } else {
4594 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4595 // points are (almost) collinear
4596 if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4597 // endnode, or a node with a retracted handle
4598 n->type = Inkscape::NodePath::NODE_CUSP;
4599 } else {
4600 n->type = Inkscape::NodePath::NODE_SMOOTH;
4601 }
4602 } else {
4603 n->type = Inkscape::NodePath::NODE_CUSP;
4604 }
4605 }
4607 n->code = code;
4608 n->selected = FALSE;
4609 n->pos = *pos;
4610 n->p.pos = *ppos;
4611 n->n.pos = *npos;
4613 n->dragging_out = NULL;
4615 Inkscape::NodePath::Node *prev;
4616 if (next) {
4617 //g_assert(g_list_find(sp->nodes, next));
4618 prev = next->p.other;
4619 } else {
4620 prev = sp->last;
4621 }
4623 if (prev)
4624 prev->n.other = n;
4625 else
4626 sp->first = n;
4628 if (next)
4629 next->p.other = n;
4630 else
4631 sp->last = n;
4633 n->p.other = prev;
4634 n->n.other = next;
4636 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"));
4637 sp_knot_set_position(n->knot, *pos, 0);
4639 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4640 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4641 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4643 sp_nodepath_update_node_knot(n);
4645 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4646 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4647 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4648 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4649 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4650 sp_knot_show(n->knot);
4652 // We only create handle knots and lines on demand
4653 n->p.knot = NULL;
4654 n->p.line = NULL;
4655 n->n.knot = NULL;
4656 n->n.line = NULL;
4658 sp->nodes = g_list_prepend(sp->nodes, n);
4660 return n;
4661 }
4663 /**
4664 * Destroy node and its knots, link neighbors in subpath.
4665 */
4666 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4667 {
4668 g_assert(node);
4669 g_assert(node->subpath);
4670 g_assert(SP_IS_KNOT(node->knot));
4672 Inkscape::NodePath::SubPath *sp = node->subpath;
4674 if (node->selected) { // first, deselect
4675 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4676 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4677 }
4679 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4681 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4682 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4683 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4684 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4685 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4686 g_object_unref(G_OBJECT(node->knot));
4688 if (node->p.knot) {
4689 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4690 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4691 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4692 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4693 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4694 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4695 g_object_unref(G_OBJECT(node->p.knot));
4696 node->p.knot = NULL;
4697 }
4699 if (node->n.knot) {
4700 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4701 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4702 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4703 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4704 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4705 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4706 g_object_unref(G_OBJECT(node->n.knot));
4707 node->n.knot = NULL;
4708 }
4710 if (node->p.line)
4711 gtk_object_destroy(GTK_OBJECT(node->p.line));
4712 if (node->n.line)
4713 gtk_object_destroy(GTK_OBJECT(node->n.line));
4715 if (sp->nodes) { // there are others nodes on the subpath
4716 if (sp->closed) {
4717 if (sp->first == node) {
4718 g_assert(sp->last == node);
4719 sp->first = node->n.other;
4720 sp->last = sp->first;
4721 }
4722 node->p.other->n.other = node->n.other;
4723 node->n.other->p.other = node->p.other;
4724 } else {
4725 if (sp->first == node) {
4726 sp->first = node->n.other;
4727 sp->first->code = NR_MOVETO;
4728 }
4729 if (sp->last == node) sp->last = node->p.other;
4730 if (node->p.other) node->p.other->n.other = node->n.other;
4731 if (node->n.other) node->n.other->p.other = node->p.other;
4732 }
4733 } else { // this was the last node on subpath
4734 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4735 }
4737 g_mem_chunk_free(nodechunk, node);
4738 }
4740 /**
4741 * Returns one of the node's two sides.
4742 * \param which Indicates which side.
4743 * \return Pointer to previous node side if which==-1, next if which==1.
4744 */
4745 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4746 {
4747 g_assert(node);
4748 Inkscape::NodePath::NodeSide * result = 0;
4749 switch (which) {
4750 case -1:
4751 result = &node->p;
4752 break;
4753 case 1:
4754 result = &node->n;
4755 break;
4756 default:
4757 g_assert_not_reached();
4758 }
4760 return result;
4761 }
4763 /**
4764 * Return the other side of the node, given one of its sides.
4765 */
4766 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4767 {
4768 g_assert(node);
4769 Inkscape::NodePath::NodeSide *result = 0;
4771 if (me == &node->p) {
4772 result = &node->n;
4773 } else if (me == &node->n) {
4774 result = &node->p;
4775 } else {
4776 g_assert_not_reached();
4777 }
4779 return result;
4780 }
4782 /**
4783 * Return NRPathcode on the given side of the node.
4784 */
4785 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4786 {
4787 g_assert(node);
4789 NRPathcode result = NR_END;
4790 if (me == &node->p) {
4791 if (node->p.other) {
4792 result = (NRPathcode)node->code;
4793 } else {
4794 result = NR_MOVETO;
4795 }
4796 } else if (me == &node->n) {
4797 if (node->n.other) {
4798 result = (NRPathcode)node->n.other->code;
4799 } else {
4800 result = NR_MOVETO;
4801 }
4802 } else {
4803 g_assert_not_reached();
4804 }
4806 return result;
4807 }
4809 /**
4810 * Return node with the given index
4811 */
4812 Inkscape::NodePath::Node *
4813 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4814 {
4815 Inkscape::NodePath::Node *e = NULL;
4817 if (!nodepath) {
4818 return e;
4819 }
4821 //find segment
4822 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4824 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4825 int n = g_list_length(sp->nodes);
4826 if (sp->closed) {
4827 n++;
4828 }
4830 //if the piece belongs to this subpath grab it
4831 //otherwise move onto the next subpath
4832 if (index < n) {
4833 e = sp->first;
4834 for (int i = 0; i < index; ++i) {
4835 e = e->n.other;
4836 }
4837 break;
4838 } else {
4839 if (sp->closed) {
4840 index -= (n+1);
4841 } else {
4842 index -= n;
4843 }
4844 }
4845 }
4847 return e;
4848 }
4850 /**
4851 * Returns plain text meaning of node type.
4852 */
4853 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4854 {
4855 unsigned retracted = 0;
4856 bool endnode = false;
4858 for (int which = -1; which <= 1; which += 2) {
4859 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4860 if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4861 retracted ++;
4862 if (!side->other)
4863 endnode = true;
4864 }
4866 if (retracted == 0) {
4867 if (endnode) {
4868 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4869 return _("end node");
4870 } else {
4871 switch (node->type) {
4872 case Inkscape::NodePath::NODE_CUSP:
4873 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4874 return _("cusp");
4875 case Inkscape::NodePath::NODE_SMOOTH:
4876 // TRANSLATORS: "smooth" is an adjective here
4877 return _("smooth");
4878 case Inkscape::NodePath::NODE_AUTO:
4879 return _("auto");
4880 case Inkscape::NodePath::NODE_SYMM:
4881 return _("symmetric");
4882 }
4883 }
4884 } else if (retracted == 1) {
4885 if (endnode) {
4886 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4887 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4888 } else {
4889 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4890 }
4891 } else {
4892 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4893 }
4895 return NULL;
4896 }
4898 /**
4899 * Handles content of statusbar as long as node tool is active.
4900 */
4901 void
4902 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4903 {
4904 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");
4905 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4907 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4908 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4909 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4910 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4912 SPDesktop *desktop = NULL;
4913 if (nodepath) {
4914 desktop = nodepath->desktop;
4915 } else {
4916 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4917 }
4919 SPEventContext *ec = desktop->event_context;
4920 if (!ec) return;
4922 Inkscape::MessageContext *mc = get_message_context(ec);
4923 if (!mc) return;
4925 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4927 if (selected_nodes == 0) {
4928 Inkscape::Selection *sel = desktop->selection;
4929 if (!sel || sel->isEmpty()) {
4930 mc->setF(Inkscape::NORMAL_MESSAGE,
4931 _("Select a single object to edit its nodes or handles."));
4932 } else {
4933 if (nodepath) {
4934 mc->setF(Inkscape::NORMAL_MESSAGE,
4935 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.",
4936 "<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.",
4937 total_nodes),
4938 total_nodes);
4939 } else {
4940 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4941 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4942 } else {
4943 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4944 }
4945 }
4946 }
4947 } else if (nodepath && selected_nodes == 1) {
4948 mc->setF(Inkscape::NORMAL_MESSAGE,
4949 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4950 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4951 total_nodes),
4952 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4953 } else {
4954 if (selected_subpaths > 1) {
4955 mc->setF(Inkscape::NORMAL_MESSAGE,
4956 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4957 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4958 total_nodes),
4959 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4960 } else {
4961 mc->setF(Inkscape::NORMAL_MESSAGE,
4962 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4963 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4964 total_nodes),
4965 selected_nodes, total_nodes, when_selected);
4966 }
4967 }
4968 }
4970 /*
4971 * returns a *copy* of the curve of that object.
4972 */
4973 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4974 if (!object)
4975 return NULL;
4977 SPCurve *curve = NULL;
4978 if (SP_IS_PATH(object)) {
4979 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4980 curve = curve_new->copy();
4981 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4982 const gchar *svgd = object->repr->attribute(key);
4983 if (svgd) {
4984 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4985 SPCurve *curve_new = new SPCurve(pv);
4986 if (curve_new) {
4987 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4988 }
4989 }
4990 }
4992 return curve;
4993 }
4995 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
4996 if (!np || !np->object || !curve)
4997 return;
4999 if (SP_IS_PATH(np->object)) {
5000 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
5001 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
5002 } else {
5003 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
5004 }
5005 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
5006 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
5007 if (lpe) {
5008 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
5009 if (pathparam) {
5010 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
5011 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
5012 }
5013 }
5014 }
5015 }
5017 /*
5018 SPCanvasItem *
5019 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
5020 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
5021 }
5022 */
5024 /*
5025 SPCanvasItem *
5026 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
5027 SPCurve *flash_curve = curve->copy();
5028 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
5029 flash_curve->transform(i2d);
5030 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5031 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5032 // unless we also flash the nodes...
5033 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5034 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5035 sp_canvas_item_show(canvasitem);
5036 flash_curve->unref();
5037 return canvasitem;
5038 }
5040 SPCanvasItem *
5041 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
5042 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5043 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
5044 prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff));
5045 }
5046 */
5048 SPCanvasItem *
5049 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
5050 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
5051 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
5052 flash_curve->transform(i2d);
5053 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5054 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5055 // unless we also flash the nodes...
5056 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5057 guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5058 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5059 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5060 sp_canvas_item_show(canvasitem);
5061 flash_curve->unref();
5062 return canvasitem;
5063 }
5065 // TODO: Merge this with sp_nodepath_make_helper_item()!
5066 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5067 np->show_helperpath = show;
5069 if (show) {
5070 SPCurve *helper_curve = np->curve->copy();
5071 helper_curve->transform(np->i2d);
5072 if (!np->helper_path) {
5073 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5075 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5076 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);
5077 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5078 sp_canvas_item_move_to_z(np->helper_path, 0);
5079 sp_canvas_item_show(np->helper_path);
5080 } else {
5081 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5082 }
5083 helper_curve->unref();
5084 } else {
5085 if (np->helper_path) {
5086 GtkObject *temp = np->helper_path;
5087 np->helper_path = NULL;
5088 gtk_object_destroy(temp);
5089 }
5090 }
5091 }
5093 /* sp_nodepath_make_straight_path:
5094 * Prevents user from curving the path by dragging a segment or activating handles etc.
5095 * The resulting path is a linear interpolation between nodal points, with only straight segments.
5096 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5097 */
5098 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5099 np->straight_path = true;
5100 np->show_handles = false;
5101 g_message("add code to make the path straight.");
5102 // do sp_nodepath_convert_node_type on all nodes?
5103 // coding tip: search for this text : "Make selected segments lines"
5104 }
5106 /*
5107 Local Variables:
5108 mode:c++
5109 c-file-style:"stroustrup"
5110 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5111 indent-tabs-mode:nil
5112 fill-column:99
5113 End:
5114 */
5115 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :