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 <2geom/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 &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 start->n.pos = start->pos;
1069 end->p.pos = end->pos;
1071 sp_node_adjust_handle(start, -1);
1072 sp_node_adjust_handle(end, 1);
1074 } else {
1075 Geom::Point delta = end->pos - start->pos;
1076 start->n.pos = start->pos + delta / 3;
1077 end->p.pos = end->pos - delta / 3;
1078 sp_node_adjust_handle(start, 1);
1079 sp_node_adjust_handle(end, -1);
1080 }
1082 sp_node_update_handles(start);
1083 sp_node_update_handles(end);
1084 }
1085 }
1087 static void
1088 sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node)
1089 {
1090 if (node->type == Inkscape::NodePath::NODE_CUSP) {
1091 node->knot->setShape (SP_KNOT_SHAPE_DIAMOND);
1092 node->knot->setSize (node->selected? 11 : 9);
1093 sp_knot_update_ctrl(node->knot);
1094 } else if (node->type == Inkscape::NodePath::NODE_AUTO) {
1095 node->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1096 node->knot->setSize (node->selected? 11 : 9);
1097 sp_knot_update_ctrl(node->knot);
1098 } else {
1099 node->knot->setShape (SP_KNOT_SHAPE_SQUARE);
1100 node->knot->setSize (node->selected? 9 : 7);
1101 sp_knot_update_ctrl(node->knot);
1102 }
1103 }
1106 /**
1107 * Change node type, and its handles accordingly.
1108 */
1109 static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1110 {
1111 g_assert(node);
1112 g_assert(node->subpath);
1114 if ((node->p.other != NULL) && (node->n.other != NULL)) {
1115 if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) {
1116 type =Inkscape::NodePath::NODE_CUSP;
1117 }
1118 }
1120 node->type = type;
1122 sp_nodepath_update_node_knot(node);
1124 // if one of handles is mouseovered, preserve its position
1125 if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) {
1126 sp_node_adjust_handle(node, 1);
1127 } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) {
1128 sp_node_adjust_handle(node, -1);
1129 } else {
1130 sp_node_adjust_handles(node);
1131 }
1133 sp_node_update_handles(node);
1135 sp_nodepath_update_statusbar(node->subpath->nodepath);
1137 return node;
1138 }
1140 bool
1141 sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1142 {
1143 // TODO clean up multiple returns
1144 Inkscape::NodePath::Node *othernode = side->other;
1145 if (!othernode)
1146 return false;
1147 NRPathcode const code = sp_node_path_code_from_side(node, side);
1148 if (code == NR_LINETO)
1149 return true;
1150 Inkscape::NodePath::NodeSide *other_to_me = NULL;
1151 if (&node->p == side) {
1152 other_to_me = &othernode->n;
1153 } else if (&node->n == side) {
1154 other_to_me = &othernode->p;
1155 }
1156 if (!other_to_me)
1157 return false;
1158 bool is_line =
1159 (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 &&
1160 Geom::L2(node->pos - side->pos) < 1e-6);
1161 return is_line;
1162 }
1164 /**
1165 * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from
1166 * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align
1167 * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too.
1168 * If already cusp and set to cusp, retracts handles.
1169 */
1170 void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type)
1171 {
1172 if (type == Inkscape::NodePath::NODE_AUTO) {
1173 if (node->p.other != NULL)
1174 node->code = NR_CURVETO;
1175 if (node->n.other != NULL)
1176 node->n.other->code = NR_CURVETO;
1177 }
1179 if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) {
1181 /*
1182 Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode:
1184 if (two_handles) {
1185 // do nothing, adjust_handles called via set_node_type will line them up
1186 } else if (one_handle) {
1187 if (opposite_to_handle_is_line) {
1188 if (lined_up) {
1189 // already half-smooth; pull opposite handle too making it fully smooth
1190 } else {
1191 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1192 }
1193 } else {
1194 // pull opposite handle in line with the existing one
1195 }
1196 } else if (no_handles) {
1197 if (both_segments_are_lines OR both_segments_are_curves) {
1198 //pull both handles
1199 } else {
1200 // pull the handle opposite to line segment, making node half-smooth
1201 }
1202 }
1203 */
1204 bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6);
1205 bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6);
1206 bool p_is_line = sp_node_side_is_line(node, &node->p);
1207 bool n_is_line = sp_node_side_is_line(node, &node->n);
1209 if (p_has_handle && n_has_handle) {
1210 // do nothing, adjust_handles will line them up
1211 } else if (p_has_handle || n_has_handle) {
1212 if (p_has_handle && n_is_line) {
1213 Radial line (node->n.other->pos - node->pos);
1214 Radial handle (node->pos - node->p.pos);
1215 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1216 // already half-smooth; pull opposite handle too making it fully smooth
1217 node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3;
1218 } else {
1219 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1220 }
1221 } else if (n_has_handle && p_is_line) {
1222 Radial line (node->p.other->pos - node->pos);
1223 Radial handle (node->pos - node->n.pos);
1224 if (fabs(line.a - handle.a) < 1e-3) { // lined up
1225 // already half-smooth; pull opposite handle too making it fully smooth
1226 node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3;
1227 } else {
1228 // do nothing, adjust_handles will line the handle up, producing a half-smooth node
1229 }
1230 } else if (p_has_handle && node->n.other) {
1231 // pull n handle
1232 node->n.other->code = NR_CURVETO;
1233 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1234 Geom::L2(node->p.pos - node->pos) :
1235 Geom::L2(node->n.other->pos - node->pos) / 3;
1236 node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos);
1237 } else if (n_has_handle && node->p.other) {
1238 // pull p handle
1239 node->code = NR_CURVETO;
1240 double len = (type == Inkscape::NodePath::NODE_SYMM)?
1241 Geom::L2(node->n.pos - node->pos) :
1242 Geom::L2(node->p.other->pos - node->pos) / 3;
1243 node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos);
1244 }
1245 } else if (!p_has_handle && !n_has_handle) {
1246 if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other)) {
1247 // no handles, but both segments are either lnes or curves:
1248 //pull both handles
1250 // convert both to curves:
1251 node->code = NR_CURVETO;
1252 node->n.other->code = NR_CURVETO;
1254 sp_node_adjust_handles_auto(node);
1255 } else {
1256 // pull the handle opposite to line segment, making it half-smooth
1257 if (p_is_line && node->n.other) {
1258 if (type != Inkscape::NodePath::NODE_SYMM) {
1259 // pull n handle
1260 node->n.other->code = NR_CURVETO;
1261 double len = Geom::L2(node->n.other->pos - node->pos) / 3;
1262 node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos);
1263 }
1264 } else if (n_is_line && node->p.other) {
1265 if (type != Inkscape::NodePath::NODE_SYMM) {
1266 // pull p handle
1267 node->code = NR_CURVETO;
1268 double len = Geom::L2(node->p.other->pos - node->pos) / 3;
1269 node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos);
1270 }
1271 }
1272 }
1273 }
1274 } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) {
1275 // cusping a cusp: retract nodes
1276 node->p.pos = node->pos;
1277 node->n.pos = node->pos;
1278 }
1280 sp_nodepath_set_node_type (node, type);
1281 }
1283 /**
1284 * Move node to point, and adjust its and neighbouring handles.
1285 */
1286 void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p)
1287 {
1288 if (node->type == Inkscape::NodePath::NODE_AUTO) {
1289 node->pos = p;
1290 sp_node_adjust_handles_auto(node);
1291 } else {
1292 Geom::Point delta = p - node->pos;
1293 node->pos = p;
1295 node->p.pos += delta;
1296 node->n.pos += delta;
1297 }
1299 Inkscape::NodePath::Node *node_p = NULL;
1300 Inkscape::NodePath::Node *node_n = NULL;
1302 if (node->p.other) {
1303 if (node->code == NR_LINETO) {
1304 sp_node_adjust_handle(node, 1);
1305 sp_node_adjust_handle(node->p.other, -1);
1306 node_p = node->p.other;
1307 }
1308 if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) {
1309 sp_node_adjust_handles_auto(node->p.other);
1310 node_p = node->p.other;
1311 }
1312 }
1313 if (node->n.other) {
1314 if (node->n.other->code == NR_LINETO) {
1315 sp_node_adjust_handle(node, -1);
1316 sp_node_adjust_handle(node->n.other, 1);
1317 node_n = node->n.other;
1318 }
1319 if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) {
1320 sp_node_adjust_handles_auto(node->n.other);
1321 node_n = node->n.other;
1322 }
1323 }
1325 // this function is only called from batch movers that will update display at the end
1326 // themselves, so here we just move all the knots without emitting move signals, for speed
1327 sp_node_update_handles(node, false);
1328 if (node_n) {
1329 sp_node_update_handles(node_n, false);
1330 }
1331 if (node_p) {
1332 sp_node_update_handles(node_p, false);
1333 }
1334 }
1336 /**
1337 * Call sp_node_moveto() for node selection and handle possible snapping.
1338 */
1339 static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy,
1340 bool const snap, bool constrained = false,
1341 Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point())
1342 {
1343 Geom::Point delta(dx, dy);
1344 Geom::Point best_pt = delta;
1345 Inkscape::SnappedPoint best;
1347 if (snap) {
1348 /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and
1349 * not to itself. The snapper however can not tell which nodes are selected and which are not, so we
1350 * must provide that information. */
1352 // Build a list of the unselected nodes to which the snapper should snap
1353 std::vector<Geom::Point> unselected_nodes;
1354 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1355 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1356 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1357 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1358 if (!node->selected) {
1359 unselected_nodes.push_back(to_2geom(node->pos));
1360 }
1361 }
1362 }
1364 SnapManager &m = nodepath->desktop->namedview->snap_manager;
1366 // When only the node closest to the mouse pointer is to be snapped
1367 // then we will not even try to snap to other points and discard those immediately
1368 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1369 bool closest_only = prefs->getBool("/options/snapclosestonly/value", false);
1371 Inkscape::NodePath::Node *closest_node = NULL;
1372 Geom::Coord closest_dist = NR_HUGE;
1374 if (closest_only) {
1375 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1376 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1377 Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin);
1378 if (dist < closest_dist) {
1379 closest_node = n;
1380 closest_dist = dist;
1381 }
1382 }
1383 }
1385 // Iterate through all selected nodes
1386 m.setup(nodepath->desktop, false, SP_PATH(nodepath->item), &unselected_nodes);
1387 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1388 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1389 if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one
1390 Inkscape::SnappedPoint s;
1391 if (constrained) {
1392 Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint;
1393 dedicated_constraint.setPoint(n->pos);
1394 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta), dedicated_constraint);
1395 } else {
1396 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, to_2geom(n->pos + delta));
1397 }
1399 if (s.getSnapped()) {
1400 s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin));
1401 if (!s.isOtherSnapBetter(best, true)) {
1402 best = s;
1403 best_pt = from_2geom(s.getPoint()) - n->pos;
1404 }
1405 }
1406 }
1407 }
1409 if (best.getSnapped()) {
1410 nodepath->desktop->snapindicator->set_new_snaptarget(best);
1411 } else {
1412 nodepath->desktop->snapindicator->remove_snaptarget();
1413 }
1414 }
1416 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1417 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1418 sp_node_moveto(n, n->pos + best_pt);
1419 }
1421 // do not update repr here so that node dragging is acceptably fast
1422 update_object(nodepath);
1423 }
1425 /**
1426 Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like
1427 curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve
1428 near x = 0.
1429 */
1430 double
1431 sculpt_profile (double x, double alpha, guint profile)
1432 {
1433 double result = 1;
1435 if (x >= 1) {
1436 result = 0;
1437 } else if (x <= 0) {
1438 result = 1;
1439 } else {
1440 switch (profile) {
1441 case SCULPT_PROFILE_LINEAR:
1442 result = 1 - x;
1443 break;
1444 case SCULPT_PROFILE_BELL:
1445 result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5);
1446 break;
1447 case SCULPT_PROFILE_ELLIPTIC:
1448 result = sqrt(1 - x*x);
1449 break;
1450 default:
1451 g_assert_not_reached();
1452 }
1453 }
1455 return result;
1456 }
1458 double
1459 bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b)
1460 {
1461 // extremely primitive for now, don't have time to look for the real one
1462 double lower = Geom::L2(b - a);
1463 double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b);
1464 return (lower + upper)/2;
1465 }
1467 void
1468 sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p)
1469 {
1470 n->pos = n->origin + delta;
1471 n->n.pos = n->n.origin + delta_n;
1472 n->p.pos = n->p.origin + delta_p;
1473 sp_node_adjust_handles(n);
1474 sp_node_update_handles(n, false);
1475 }
1477 /**
1478 * Displace selected nodes and their handles by fractions of delta (from their origins), depending
1479 * on how far they are from the dragged node n.
1480 */
1481 static void
1482 sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta)
1483 {
1484 g_assert (n);
1485 g_assert (nodepath);
1486 g_assert (n->subpath->nodepath == nodepath);
1488 double pressure = n->knot->pressure;
1489 if (pressure == 0)
1490 pressure = 0.5; // default
1491 pressure = CLAMP (pressure, 0.2, 0.8);
1493 // map pressure to alpha = 1/5 ... 5
1494 double alpha = 1 - 2 * fabs(pressure - 0.5);
1495 if (pressure > 0.5)
1496 alpha = 1/alpha;
1498 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1499 guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL);
1501 if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) {
1502 // Only one subpath has selected nodes:
1503 // use linear mode, where the distance from n to node being dragged is calculated along the path
1505 double n_sel_range = 0, p_sel_range = 0;
1506 guint n_nodes = 0, p_nodes = 0;
1507 guint n_sel_nodes = 0, p_sel_nodes = 0;
1509 // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging)
1510 {
1511 double n_range = 0, p_range = 0;
1512 bool n_going = true, p_going = true;
1513 Inkscape::NodePath::Node *n_node = n;
1514 Inkscape::NodePath::Node *p_node = n;
1515 do {
1516 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1517 if (n_node && n_going)
1518 n_node = n_node->n.other;
1519 if (n_node == NULL) {
1520 n_going = false;
1521 } else {
1522 n_nodes ++;
1523 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1524 if (n_node->selected) {
1525 n_sel_nodes ++;
1526 n_sel_range = n_range;
1527 }
1528 if (n_node == p_node) {
1529 n_going = false;
1530 p_going = false;
1531 }
1532 }
1533 if (p_node && p_going)
1534 p_node = p_node->p.other;
1535 if (p_node == NULL) {
1536 p_going = false;
1537 } else {
1538 p_nodes ++;
1539 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1540 if (p_node->selected) {
1541 p_sel_nodes ++;
1542 p_sel_range = p_range;
1543 }
1544 if (p_node == n_node) {
1545 n_going = false;
1546 p_going = false;
1547 }
1548 }
1549 } while (n_going || p_going);
1550 }
1552 // Second pass: actually move nodes in this subpath
1553 sp_nodepath_move_node_and_handles (n, delta, delta, delta);
1554 {
1555 double n_range = 0, p_range = 0;
1556 bool n_going = true, p_going = true;
1557 Inkscape::NodePath::Node *n_node = n;
1558 Inkscape::NodePath::Node *p_node = n;
1559 do {
1560 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
1561 if (n_node && n_going)
1562 n_node = n_node->n.other;
1563 if (n_node == NULL) {
1564 n_going = false;
1565 } else {
1566 n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin);
1567 if (n_node->selected) {
1568 sp_nodepath_move_node_and_handles (n_node,
1569 sculpt_profile (n_range / n_sel_range, alpha, profile) * delta,
1570 sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta,
1571 sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta);
1572 }
1573 if (n_node == p_node) {
1574 n_going = false;
1575 p_going = false;
1576 }
1577 }
1578 if (p_node && p_going)
1579 p_node = p_node->p.other;
1580 if (p_node == NULL) {
1581 p_going = false;
1582 } else {
1583 p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin);
1584 if (p_node->selected) {
1585 sp_nodepath_move_node_and_handles (p_node,
1586 sculpt_profile (p_range / p_sel_range, alpha, profile) * delta,
1587 sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta,
1588 sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta);
1589 }
1590 if (p_node == n_node) {
1591 n_going = false;
1592 p_going = false;
1593 }
1594 }
1595 } while (n_going || p_going);
1596 }
1598 } else {
1599 // Multiple subpaths have selected nodes:
1600 // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2.
1601 // TODO: correct these distances taking into account their angle relative to the bisector, so as to
1602 // fix the pear-like shape when sculpting e.g. a ring
1604 // First pass: calculate range
1605 gdouble direct_range = 0;
1606 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1607 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1608 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1609 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1610 if (node->selected) {
1611 direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin));
1612 }
1613 }
1614 }
1616 // Second pass: actually move nodes
1617 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
1618 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
1619 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
1620 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
1621 if (node->selected) {
1622 if (direct_range > 1e-6) {
1623 sp_nodepath_move_node_and_handles (node,
1624 sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta,
1625 sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta,
1626 sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta);
1627 } else {
1628 sp_nodepath_move_node_and_handles (node, delta, delta, delta);
1629 }
1631 }
1632 }
1633 }
1634 }
1636 // do not update repr here so that node dragging is acceptably fast
1637 update_object(nodepath);
1638 }
1641 /**
1642 * Move node selection to point, adjust its and neighbouring handles,
1643 * handle possible snapping, and commit the change with possible undo.
1644 */
1645 void
1646 sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1647 {
1648 if (!nodepath) return;
1650 sp_nodepath_selected_nodes_move(nodepath, dx, dy, false);
1652 if (dx == 0) {
1653 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1654 } else if (dy == 0) {
1655 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1656 } else {
1657 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1658 }
1659 }
1661 /**
1662 * Move node selection off screen and commit the change.
1663 */
1664 void
1665 sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy)
1666 {
1667 // borrowed from sp_selection_move_screen in selection-chemistry.c
1668 // we find out the current zoom factor and divide deltas by it
1670 gdouble zoom = desktop->current_zoom();
1671 gdouble zdx = dx / zoom;
1672 gdouble zdy = dy / zoom;
1674 if (!nodepath) return;
1676 sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false);
1678 if (dx == 0) {
1679 sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically"));
1680 } else if (dy == 0) {
1681 sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally"));
1682 } else {
1683 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1684 }
1685 }
1687 /**
1688 * Move selected nodes to the absolute position given
1689 */
1690 void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis)
1691 {
1692 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1693 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
1694 Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]);
1695 sp_node_moveto(n, npos);
1696 }
1698 sp_nodepath_update_repr(nodepath, _("Move nodes"));
1699 }
1701 /**
1702 * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing
1703 */
1704 boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1705 {
1706 boost::optional<Geom::Coord> no_coord;
1707 g_return_val_if_fail(nodepath->selected, no_coord);
1709 // determine coordinate of first selected node
1710 GList *nsel = nodepath->selected;
1711 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
1712 Geom::Coord coord = n->pos[axis];
1713 bool coincide = true;
1715 // compare it to the coordinates of all the other selected nodes
1716 for (GList *l = nsel->next; l != NULL; l = l->next) {
1717 n = (Inkscape::NodePath::Node *) l->data;
1718 if (n->pos[axis] != coord) {
1719 coincide = false;
1720 }
1721 }
1722 if (coincide) {
1723 return coord;
1724 } else {
1725 Geom::Rect bbox = sp_node_selected_bbox(nodepath);
1726 // currently we return the coordinate of the bounding box midpoint because I don't know how
1727 // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
1728 return bbox.midpoint()[axis];
1729 }
1730 }
1732 /** If they don't yet exist, creates knot and line for the given side of the node */
1733 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
1734 {
1735 if (!side->knot) {
1736 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"));
1738 side->knot->setShape (SP_KNOT_SHAPE_CIRCLE);
1739 side->knot->setSize (7);
1740 side->knot->setAnchor (GTK_ANCHOR_CENTER);
1741 side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI);
1742 side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI);
1743 sp_knot_update_ctrl(side->knot);
1745 g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node);
1746 g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node);
1747 g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node);
1748 g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node);
1749 g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node);
1750 g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node);
1751 }
1753 if (!side->line) {
1754 side->line = sp_canvas_item_new(sp_desktop_controls(desktop),
1755 SP_TYPE_CTRLLINE, NULL);
1756 }
1757 }
1759 /**
1760 * Ensure the given handle of the node is visible/invisible, update its screen position
1761 */
1762 static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals)
1763 {
1764 g_assert(node != NULL);
1766 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
1767 NRPathcode code = sp_node_path_code_from_side(node, side);
1769 show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6);
1771 if (show_handle) {
1772 if (!side->knot) { // No handle knot at all
1773 sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side);
1774 // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly
1775 side->knot->pos = side->pos;
1776 if (side->knot->item)
1777 SP_CTRL(side->knot->item)->moveto(side->pos);
1778 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1779 sp_knot_show(side->knot);
1780 } else {
1781 if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved
1782 if (fire_move_signals) {
1783 sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well
1784 } else {
1785 sp_knot_moveto(side->knot, side->pos);
1786 sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos);
1787 }
1788 }
1789 if (!SP_KNOT_IS_VISIBLE(side->knot)) {
1790 sp_knot_show(side->knot);
1791 }
1792 }
1793 sp_canvas_item_show(side->line);
1794 } else {
1795 if (side->knot) {
1796 if (SP_KNOT_IS_VISIBLE(side->knot)) {
1797 sp_knot_hide(side->knot);
1798 }
1799 }
1800 if (side->line) {
1801 sp_canvas_item_hide(side->line);
1802 }
1803 }
1804 }
1806 /**
1807 * Ensure the node itself is visible, its handles and those of the neighbours of the node are
1808 * visible if selected, update their screen positions. If fire_move_signals, move the node and its
1809 * handles so that the corresponding signals are fired, callbacks are activated, and curve is
1810 * updated; otherwise, just move the knots silently (used in batch moves).
1811 */
1812 static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals)
1813 {
1814 g_assert(node != NULL);
1816 if (!SP_KNOT_IS_VISIBLE(node->knot)) {
1817 sp_knot_show(node->knot);
1818 }
1820 if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update
1821 if (fire_move_signals)
1822 sp_knot_set_position(node->knot, node->pos, 0);
1823 else
1824 sp_knot_moveto(node->knot, node->pos);
1825 }
1827 gboolean show_handles = node->selected;
1828 if (node->p.other != NULL) {
1829 if (node->p.other->selected) show_handles = TRUE;
1830 }
1831 if (node->n.other != NULL) {
1832 if (node->n.other->selected) show_handles = TRUE;
1833 }
1835 if (node->subpath->nodepath->show_handles == false)
1836 show_handles = FALSE;
1838 sp_node_update_handle(node, -1, show_handles, fire_move_signals);
1839 sp_node_update_handle(node, 1, show_handles, fire_move_signals);
1840 }
1842 /**
1843 * Call sp_node_update_handles() for all nodes on subpath.
1844 */
1845 static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath)
1846 {
1847 g_assert(subpath != NULL);
1849 for (GList *l = subpath->nodes; l != NULL; l = l->next) {
1850 sp_node_update_handles((Inkscape::NodePath::Node *) l->data);
1851 }
1852 }
1854 /**
1855 * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath.
1856 */
1857 static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath)
1858 {
1859 g_assert(nodepath != NULL);
1861 for (GList *l = nodepath->subpaths; l != NULL; l = l->next) {
1862 sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data);
1863 }
1864 }
1866 void
1867 sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show)
1868 {
1869 if (nodepath) {
1870 nodepath->show_handles = show;
1871 sp_nodepath_update_handles(nodepath);
1872 }
1873 }
1875 /**
1876 * Adds all selected nodes in nodepath to list.
1877 */
1878 void Inkscape::NodePath::Path::selection(std::list<Node *> &l)
1879 {
1880 StlConv<Node *>::list(l, selected);
1881 /// \todo this adds a copying, rework when the selection becomes a stl list
1882 }
1884 /**
1885 * Align selected nodes on the specified axis.
1886 */
1887 void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1888 {
1889 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1890 return;
1891 }
1893 if ( !nodepath->selected->next ) { // only one node selected
1894 return;
1895 }
1896 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1897 Geom::Point dest(pNode->pos);
1898 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1899 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1900 if (pNode) {
1901 dest[axis] = pNode->pos[axis];
1902 sp_node_moveto(pNode, dest);
1903 }
1904 }
1906 sp_nodepath_update_repr(nodepath, _("Align nodes"));
1907 }
1909 /// Helper struct.
1910 struct NodeSort
1911 {
1912 Inkscape::NodePath::Node *_node;
1913 Geom::Coord _coord;
1914 /// \todo use vectorof pointers instead of calling copy ctor
1915 NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) :
1916 _node(node), _coord(node->pos[axis])
1917 {}
1919 };
1921 static bool operator<(NodeSort const &a, NodeSort const &b)
1922 {
1923 return (a._coord < b._coord);
1924 }
1926 /**
1927 * Distribute selected nodes on the specified axis.
1928 */
1929 void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis)
1930 {
1931 if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected
1932 return;
1933 }
1935 if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected
1936 return;
1937 }
1939 Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data);
1940 std::vector<NodeSort> sorted;
1941 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1942 pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data);
1943 if (pNode) {
1944 NodeSort n(pNode, axis);
1945 sorted.push_back(n);
1946 //dest[axis] = pNode->pos[axis];
1947 //sp_node_moveto(pNode, dest);
1948 }
1949 }
1950 std::sort(sorted.begin(), sorted.end());
1951 unsigned int len = sorted.size();
1952 //overall bboxes span
1953 float dist = (sorted.back()._coord -
1954 sorted.front()._coord);
1955 //new distance between each bbox
1956 float step = (dist) / (len - 1);
1957 float pos = sorted.front()._coord;
1958 for ( std::vector<NodeSort> ::iterator it(sorted.begin());
1959 it < sorted.end();
1960 it ++ )
1961 {
1962 Geom::Point dest((*it)._node->pos);
1963 dest[axis] = pos;
1964 sp_node_moveto((*it)._node, dest);
1965 pos += step;
1966 }
1968 sp_nodepath_update_repr(nodepath, _("Distribute nodes"));
1969 }
1972 /**
1973 * Call sp_nodepath_line_add_node() for all selected segments.
1974 */
1975 void
1976 sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath)
1977 {
1978 if (!nodepath) {
1979 return;
1980 }
1982 GList *nl = NULL;
1984 int n_added = 0;
1986 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
1987 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data;
1988 g_assert(t->selected);
1989 if (t->p.other && t->p.other->selected) {
1990 nl = g_list_prepend(nl, t);
1991 }
1992 }
1994 while (nl) {
1995 Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data;
1996 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5);
1997 sp_nodepath_node_select(n, TRUE, FALSE);
1998 n_added ++;
1999 nl = g_list_remove(nl, t);
2000 }
2002 /** \todo fixme: adjust ? */
2003 sp_nodepath_update_handles(nodepath);
2005 if (n_added > 1) {
2006 sp_nodepath_update_repr(nodepath, _("Add nodes"));
2007 } else if (n_added > 0) {
2008 sp_nodepath_update_repr(nodepath, _("Add node"));
2009 }
2011 sp_nodepath_update_statusbar(nodepath);
2012 }
2014 /**
2015 * Select segment nearest to point
2016 */
2017 void
2018 sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle)
2019 {
2020 if (!nodepath) {
2021 return;
2022 }
2024 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2025 Geom::PathVector const &pathv = curve->get_pathvector();
2026 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2027 if (!pvpos) {
2028 g_print ("Possible error?\n");
2029 return;
2030 }
2032 // calculate index for nodepath's representation.
2033 unsigned int segment_index = floor(pvpos->t) + 1;
2034 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2035 segment_index += pathv[i].size() + 1;
2036 if (pathv[i].closed()) {
2037 segment_index += 1;
2038 }
2039 }
2041 curve->unref();
2043 //find segment to segment
2044 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2046 //fixme: this can return NULL, so check before proceeding.
2047 g_return_if_fail(e != NULL);
2049 gboolean force = FALSE;
2050 if (!(e->selected && (!e->p.other || e->p.other->selected))) {
2051 force = TRUE;
2052 }
2053 sp_nodepath_node_select(e, (gboolean) toggle, force);
2054 if (e->p.other)
2055 sp_nodepath_node_select(e->p.other, TRUE, force);
2057 sp_nodepath_update_handles(nodepath);
2059 sp_nodepath_update_statusbar(nodepath);
2060 }
2062 /**
2063 * Add a node nearest to point
2064 */
2065 void
2066 sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p)
2067 {
2068 if (!nodepath) {
2069 return;
2070 }
2072 SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead?
2073 Geom::PathVector const &pathv = curve->get_pathvector();
2074 boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p);
2075 if (!pvpos) {
2076 g_print ("Possible error?\n");
2077 return;
2078 }
2080 // calculate index for nodepath's representation.
2081 double int_part;
2082 double t = std::modf(pvpos->t, &int_part);
2083 unsigned int segment_index = (unsigned int)int_part + 1;
2084 for (unsigned int i = 0; i < pvpos->path_nr; ++i) {
2085 segment_index += pathv[i].size() + 1;
2086 if (pathv[i].closed()) {
2087 segment_index += 1;
2088 }
2089 }
2091 curve->unref();
2093 //find segment to split
2094 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index);
2095 if (!e) {
2096 return;
2097 }
2099 //don't know why but t seems to flip for lines
2100 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) {
2101 t = 1.0 - t;
2102 }
2104 Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t);
2105 sp_nodepath_node_select(n, FALSE, TRUE);
2107 /* fixme: adjust ? */
2108 sp_nodepath_update_handles(nodepath);
2110 sp_nodepath_update_repr(nodepath, _("Add node"));
2112 sp_nodepath_update_statusbar(nodepath);
2113 }
2115 /*
2116 * Adjusts a segment so that t moves by a certain delta for dragging
2117 * converts lines to curves
2118 *
2119 * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP
2120 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
2121 */
2122 void
2123 sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta)
2124 {
2125 Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node);
2127 //fixme: e and e->p can be NULL, so check for those before proceeding
2128 g_return_if_fail(e != NULL);
2129 g_return_if_fail(&e->p != NULL);
2131 if (e->type == Inkscape::NodePath::NODE_AUTO) {
2132 e->type = Inkscape::NodePath::NODE_SMOOTH;
2133 sp_nodepath_update_node_knot (e);
2134 }
2135 if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) {
2136 e->p.other->type = Inkscape::NodePath::NODE_SMOOTH;
2137 sp_nodepath_update_node_knot (e->p.other);
2138 }
2140 /* feel good is an arbitrary parameter that distributes the delta between handles
2141 * if t of the drag point is less than 1/6 distance form the endpoint only
2142 * the corresponding hadle is adjusted. This matches the behavior in GIMP
2143 */
2144 double feel_good;
2145 if (t <= 1.0 / 6.0)
2146 feel_good = 0;
2147 else if (t <= 0.5)
2148 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
2149 else if (t <= 5.0 / 6.0)
2150 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
2151 else
2152 feel_good = 1;
2154 //if we're dragging a line convert it to a curve
2155 if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) {
2156 sp_nodepath_set_line_type(e, NR_CURVETO);
2157 }
2159 Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
2160 Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta;
2161 e->p.other->n.pos += offsetcoord0;
2162 e->p.pos += offsetcoord1;
2164 // adjust handles of adjacent nodes where necessary
2165 sp_node_adjust_handle(e,1);
2166 sp_node_adjust_handle(e->p.other,-1);
2168 sp_nodepath_update_handles(e->subpath->nodepath);
2170 update_object(e->subpath->nodepath);
2172 sp_nodepath_update_statusbar(e->subpath->nodepath);
2173 }
2176 /**
2177 * Call sp_nodepath_break() for all selected segments.
2178 */
2179 void sp_node_selected_break(Inkscape::NodePath::Path *nodepath)
2180 {
2181 if (!nodepath) return;
2183 GList *tempin = g_list_copy(nodepath->selected);
2184 GList *temp = NULL;
2185 for (GList *l = tempin; l != NULL; l = l->next) {
2186 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2187 Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n);
2188 if (nn == NULL) continue; // no break, no new node
2189 temp = g_list_prepend(temp, nn);
2190 }
2191 g_list_free(tempin);
2193 if (temp) {
2194 sp_nodepath_deselect(nodepath);
2195 }
2196 for (GList *l = temp; l != NULL; l = l->next) {
2197 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2198 }
2200 sp_nodepath_update_handles(nodepath);
2202 sp_nodepath_update_repr(nodepath, _("Break path"));
2203 }
2205 /**
2206 * Duplicate the selected node(s).
2207 */
2208 void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath)
2209 {
2210 if (!nodepath) {
2211 return;
2212 }
2214 GList *temp = NULL;
2215 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2216 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2217 Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n);
2218 if (nn == NULL) continue; // could not duplicate
2219 temp = g_list_prepend(temp, nn);
2220 }
2222 if (temp) {
2223 sp_nodepath_deselect(nodepath);
2224 }
2225 for (GList *l = temp; l != NULL; l = l->next) {
2226 sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE);
2227 }
2229 sp_nodepath_update_handles(nodepath);
2231 sp_nodepath_update_repr(nodepath, _("Duplicate node"));
2232 }
2234 /**
2235 * Internal function to join two nodes by merging them into one.
2236 */
2237 static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2238 {
2239 /* a and b are endpoints */
2241 // if one of the two nodes is mouseovered, fix its position
2242 Geom::Point c;
2243 if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) {
2244 c = a->pos;
2245 } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) {
2246 c = b->pos;
2247 } else {
2248 // otherwise, move joined node to the midpoint
2249 c = (a->pos + b->pos) / 2;
2250 }
2252 if (a->subpath == b->subpath) {
2253 Inkscape::NodePath::SubPath *sp = a->subpath;
2254 sp_nodepath_subpath_close(sp);
2255 sp_node_moveto (sp->first, c);
2257 sp_nodepath_update_handles(sp->nodepath);
2258 sp_nodepath_update_repr(nodepath, _("Close subpath"));
2259 return;
2260 }
2262 /* a and b are separate subpaths */
2263 Inkscape::NodePath::SubPath *sa = a->subpath;
2264 Inkscape::NodePath::SubPath *sb = b->subpath;
2265 Geom::Point p;
2266 Inkscape::NodePath::Node *n;
2267 NRPathcode code;
2268 if (a == sa->first) {
2269 // we will now reverse sa, so that a is its last node, not first, and drop that node
2270 p = sa->first->n.pos;
2271 code = (NRPathcode)sa->first->n.other->code;
2272 // create new subpath
2273 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2274 // create a first moveto node on it
2275 n = sa->last;
2276 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2277 n = n->p.other;
2278 if (n == sa->first) n = NULL;
2279 while (n) {
2280 // copy the rest of the nodes from sa to t, going backwards
2281 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2282 n = n->p.other;
2283 if (n == sa->first) n = NULL;
2284 }
2285 // replace sa with t
2286 sp_nodepath_subpath_destroy(sa);
2287 sa = t;
2288 } else if (a == sa->last) {
2289 // a is already last, just drop it
2290 p = sa->last->p.pos;
2291 code = (NRPathcode)sa->last->code;
2292 sp_nodepath_node_destroy(sa->last);
2293 } else {
2294 code = NR_END;
2295 g_assert_not_reached();
2296 }
2298 if (b == sb->first) {
2299 // copy all nodes from b to a, forward
2300 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos);
2301 for (n = sb->first->n.other; n != NULL; n = n->n.other) {
2302 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2303 }
2304 } else if (b == sb->last) {
2305 // copy all nodes from b to a, backward
2306 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos);
2307 for (n = sb->last->p.other; n != NULL; n = n->p.other) {
2308 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2309 }
2310 } else {
2311 g_assert_not_reached();
2312 }
2313 /* and now destroy sb */
2315 sp_nodepath_subpath_destroy(sb);
2317 sp_nodepath_update_handles(sa->nodepath);
2319 sp_nodepath_update_repr(nodepath, _("Join nodes"));
2321 sp_nodepath_update_statusbar(nodepath);
2322 }
2324 /**
2325 * Internal function to join two nodes by adding a segment between them.
2326 */
2327 static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b)
2328 {
2329 if (a->subpath == b->subpath) {
2330 Inkscape::NodePath::SubPath *sp = a->subpath;
2332 /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/
2333 sp->closed = TRUE;
2335 sp->first->p.other = sp->last;
2336 sp->last->n.other = sp->first;
2338 sp_node_handle_mirror_p_to_n(sp->last);
2339 sp_node_handle_mirror_n_to_p(sp->first);
2341 sp->first->code = sp->last->code;
2342 sp->first = sp->last;
2344 sp_nodepath_update_handles(sp->nodepath);
2346 sp_nodepath_update_repr(nodepath, _("Close subpath by segment"));
2348 return;
2349 }
2351 /* a and b are separate subpaths */
2352 Inkscape::NodePath::SubPath *sa = a->subpath;
2353 Inkscape::NodePath::SubPath *sb = b->subpath;
2355 Inkscape::NodePath::Node *n;
2356 Geom::Point p;
2357 NRPathcode code;
2358 if (a == sa->first) {
2359 code = (NRPathcode) sa->first->n.other->code;
2360 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath);
2361 n = sa->last;
2362 sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos);
2363 for (n = n->p.other; n != NULL; n = n->p.other) {
2364 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2365 }
2366 sp_nodepath_subpath_destroy(sa);
2367 sa = t;
2368 } else if (a == sa->last) {
2369 code = (NRPathcode)sa->last->code;
2370 } else {
2371 code = NR_END;
2372 g_assert_not_reached();
2373 }
2375 if (b == sb->first) {
2376 n = sb->first;
2377 sp_node_handle_mirror_p_to_n(sa->last);
2378 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos);
2379 sp_node_handle_mirror_n_to_p(sa->last);
2380 for (n = n->n.other; n != NULL; n = n->n.other) {
2381 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos);
2382 }
2383 } else if (b == sb->last) {
2384 n = sb->last;
2385 sp_node_handle_mirror_p_to_n(sa->last);
2386 sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos);
2387 sp_node_handle_mirror_n_to_p(sa->last);
2388 for (n = n->p.other; n != NULL; n = n->p.other) {
2389 sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos);
2390 }
2391 } else {
2392 g_assert_not_reached();
2393 }
2394 /* and now destroy sb */
2396 sp_nodepath_subpath_destroy(sb);
2398 sp_nodepath_update_handles(sa->nodepath);
2400 sp_nodepath_update_repr(nodepath, _("Join nodes by segment"));
2401 }
2403 enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT };
2405 /**
2406 * Internal function to handle joining two nodes.
2407 */
2408 static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode)
2409 {
2410 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2412 if (g_list_length(nodepath->selected) != 2) {
2413 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2414 return;
2415 }
2417 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2418 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2420 g_assert(a != b);
2421 if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) {
2422 // someone tried to join an orphan node (i.e. a single-node subpath).
2423 // this is not worth an error message, just fail silently.
2424 return;
2425 }
2427 if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) {
2428 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected."));
2429 return;
2430 }
2432 switch(mode) {
2433 case NODE_JOIN_ENDPOINTS:
2434 do_node_selected_join(nodepath, a, b);
2435 break;
2436 case NODE_JOIN_SEGMENT:
2437 do_node_selected_join_segment(nodepath, a, b);
2438 break;
2439 }
2440 }
2442 /**
2443 * Join two nodes by merging them into one.
2444 */
2445 void sp_node_selected_join(Inkscape::NodePath::Path *nodepath)
2446 {
2447 node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS);
2448 }
2450 /**
2451 * Join two nodes by adding a segment between them.
2452 */
2453 void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath)
2454 {
2455 node_do_selected_join(nodepath, NODE_JOIN_SEGMENT);
2456 }
2458 /**
2459 * Delete one or more selected nodes and preserve the shape of the path as much as possible.
2460 */
2461 void sp_node_delete_preserve(GList *nodes_to_delete)
2462 {
2463 GSList *nodepaths = NULL;
2465 while (nodes_to_delete) {
2466 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data;
2467 Inkscape::NodePath::SubPath *sp = node->subpath;
2468 Inkscape::NodePath::Path *nodepath = sp->nodepath;
2469 Inkscape::NodePath::Node *sample_cursor = NULL;
2470 Inkscape::NodePath::Node *sample_end = NULL;
2471 Inkscape::NodePath::Node *delete_cursor = node;
2472 bool just_delete = false;
2474 //find the start of this contiguous selection
2475 //move left to the first node that is not selected
2476 //or the start of the non-closed path
2477 for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) {
2478 delete_cursor = curr;
2479 }
2481 //just delete at the beginning of an open path
2482 if (!delete_cursor->p.other) {
2483 sample_cursor = delete_cursor;
2484 just_delete = true;
2485 } else {
2486 sample_cursor = delete_cursor->p.other;
2487 }
2489 //calculate points for each segment
2490 int rate = 5;
2491 float period = 1.0 / rate;
2492 std::vector<Geom::Point> data;
2493 if (!just_delete) {
2494 data.push_back(sample_cursor->pos);
2495 for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) {
2496 //just delete at the end of an open path
2497 if (!sp->closed && curr == sp->last) {
2498 just_delete = true;
2499 break;
2500 }
2502 //sample points on the contiguous selected segment
2503 Geom::Point *bez;
2504 bez = new Geom::Point [4];
2505 bez[0] = curr->pos;
2506 bez[1] = curr->n.pos;
2507 bez[2] = curr->n.other->p.pos;
2508 bez[3] = curr->n.other->pos;
2509 for (int i=1; i<rate; i++) {
2510 gdouble t = i * period;
2511 Geom::Point p = bezier_pt(3, bez, t);
2512 data.push_back(p);
2513 }
2514 data.push_back(curr->n.other->pos);
2516 sample_end = curr->n.other;
2517 //break if we've come full circle or hit the end of the selection
2518 if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) {
2519 break;
2520 }
2521 }
2522 }
2524 if (!just_delete) {
2525 //calculate the best fitting single segment and adjust the endpoints
2526 Geom::Point *adata;
2527 adata = new Geom::Point [data.size()];
2528 copy(data.begin(), data.end(), adata);
2530 Geom::Point *bez;
2531 bez = new Geom::Point [4];
2532 //would decreasing error create a better fitting approximation?
2533 gdouble error = 1.0;
2534 gint ret;
2535 ret = Geom::bezier_fit_cubic (bez, adata, data.size(), error);
2537 //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync.
2538 //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved,
2539 //the resulting nodes behave as expected.
2540 if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP)
2541 sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP);
2542 if (sample_end->type != Inkscape::NodePath::NODE_CUSP)
2543 sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP);
2545 //adjust endpoints
2546 sample_cursor->n.pos = bez[1];
2547 sample_end->p.pos = bez[2];
2548 }
2550 //destroy this contiguous selection
2551 while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) {
2552 Inkscape::NodePath::Node *temp = delete_cursor;
2553 if (delete_cursor->n.other == delete_cursor) {
2554 // delete_cursor->n points to itself, which means this is the last node on a closed subpath
2555 delete_cursor = NULL;
2556 } else {
2557 delete_cursor = delete_cursor->n.other;
2558 }
2559 nodes_to_delete = g_list_remove(nodes_to_delete, temp);
2560 sp_nodepath_node_destroy(temp);
2561 }
2563 sp_nodepath_update_handles(nodepath);
2565 if (!g_slist_find(nodepaths, nodepath))
2566 nodepaths = g_slist_prepend (nodepaths, nodepath);
2567 }
2569 for (GSList *i = nodepaths; i; i = i->next) {
2570 // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from
2571 // different nodepaths will give us one undo event per nodepath
2572 Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data;
2574 // if the entire nodepath is removed, delete the selected object.
2575 if (nodepath->subpaths == NULL ||
2576 //FIXME: a closed path CAN legally have one node, it's only an open one which must be
2577 //at least 2
2578 sp_nodepath_get_node_count(nodepath) < 2) {
2579 SPDocument *document = sp_desktop_document (nodepath->desktop);
2580 //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to
2581 //delete this nodepath's object, not the entire selection! (though at this time, this
2582 //does not matter)
2583 sp_selection_delete(nodepath->desktop);
2584 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2585 _("Delete nodes"));
2586 } else {
2587 sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape"));
2588 sp_nodepath_update_statusbar(nodepath);
2589 }
2590 }
2592 g_slist_free (nodepaths);
2593 }
2595 /**
2596 * Delete one or more selected nodes.
2597 */
2598 void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath)
2599 {
2600 if (!nodepath) return;
2601 if (!nodepath->selected) return;
2603 /** \todo fixme: do it the right way */
2604 while (nodepath->selected) {
2605 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data;
2606 sp_nodepath_node_destroy(node);
2607 }
2610 //clean up the nodepath (such as for trivial subpaths)
2611 sp_nodepath_cleanup(nodepath);
2613 sp_nodepath_update_handles(nodepath);
2615 // if the entire nodepath is removed, delete the selected object.
2616 if (nodepath->subpaths == NULL ||
2617 sp_nodepath_get_node_count(nodepath) < 2) {
2618 SPDocument *document = sp_desktop_document (nodepath->desktop);
2619 sp_selection_delete(nodepath->desktop);
2620 sp_document_done (document, SP_VERB_CONTEXT_NODE,
2621 _("Delete nodes"));
2622 return;
2623 }
2625 sp_nodepath_update_repr(nodepath, _("Delete nodes"));
2627 sp_nodepath_update_statusbar(nodepath);
2628 }
2630 /**
2631 * Delete one or more segments between two selected nodes.
2632 * This is the code for 'split'.
2633 */
2634 void
2635 sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath)
2636 {
2637 Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive
2638 Inkscape::NodePath::Node *curr, *next; //Iterators
2640 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2642 if (g_list_length(nodepath->selected) != 2) {
2643 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2644 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2645 return;
2646 }
2648 //Selected nodes, not inclusive
2649 Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data;
2650 Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data;
2652 if ( ( a==b) || //same node
2653 (a->subpath != b->subpath ) || //not the same path
2654 (!a->p.other || !a->n.other) || //one of a's sides does not have a segment
2655 (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment
2656 {
2657 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2658 _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments."));
2659 return;
2660 }
2662 //###########################################
2663 //# BEGIN EDITS
2664 //###########################################
2665 //##################################
2666 //# CLOSED PATH
2667 //##################################
2668 if (a->subpath->closed) {
2671 gboolean reversed = FALSE;
2673 //Since we can go in a circle, we need to find the shorter distance.
2674 // a->b or b->a
2675 start = end = NULL;
2676 int distance = 0;
2677 int minDistance = 0;
2678 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2679 if (curr==b) {
2680 //printf("a to b:%d\n", distance);
2681 start = a;//go from a to b
2682 end = b;
2683 minDistance = distance;
2684 //printf("A to B :\n");
2685 break;
2686 }
2687 distance++;
2688 }
2690 //try again, the other direction
2691 distance = 0;
2692 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2693 if (curr==a) {
2694 //printf("b to a:%d\n", distance);
2695 if (distance < minDistance) {
2696 start = b; //we go from b to a
2697 end = a;
2698 reversed = TRUE;
2699 //printf("B to A\n");
2700 }
2701 break;
2702 }
2703 distance++;
2704 }
2707 //Copy everything from 'end' to 'start' to a new subpath
2708 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2709 for (curr=end ; curr ; curr=curr->n.other) {
2710 NRPathcode code = (NRPathcode) curr->code;
2711 if (curr == end)
2712 code = NR_MOVETO;
2713 sp_nodepath_node_new(t, NULL,
2714 (Inkscape::NodePath::NodeType)curr->type, code,
2715 &curr->p.pos, &curr->pos, &curr->n.pos);
2716 if (curr == start)
2717 break;
2718 }
2719 sp_nodepath_subpath_destroy(a->subpath);
2722 }
2726 //##################################
2727 //# OPEN PATH
2728 //##################################
2729 else {
2731 //We need to get the direction of the list between A and B
2732 //Can we walk from a to b?
2733 start = end = NULL;
2734 for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) {
2735 if (curr==b) {
2736 start = a; //did it! we go from a to b
2737 end = b;
2738 //printf("A to B\n");
2739 break;
2740 }
2741 }
2742 if (!start) {//didn't work? let's try the other direction
2743 for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) {
2744 if (curr==a) {
2745 start = b; //did it! we go from b to a
2746 end = a;
2747 //printf("B to A\n");
2748 break;
2749 }
2750 }
2751 }
2752 if (!start) {
2753 nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE,
2754 _("Cannot find path between nodes."));
2755 return;
2756 }
2760 //Copy everything after 'end' to a new subpath
2761 Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath);
2762 for (curr=end ; curr ; curr=curr->n.other) {
2763 NRPathcode code = (NRPathcode) curr->code;
2764 if (curr == end)
2765 code = NR_MOVETO;
2766 sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code,
2767 &curr->p.pos, &curr->pos, &curr->n.pos);
2768 }
2770 //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list
2771 for (curr = start->n.other ; curr ; curr=next) {
2772 next = curr->n.other;
2773 sp_nodepath_node_destroy(curr);
2774 }
2776 }
2777 //###########################################
2778 //# END EDITS
2779 //###########################################
2781 //clean up the nodepath (such as for trivial subpaths)
2782 sp_nodepath_cleanup(nodepath);
2784 sp_nodepath_update_handles(nodepath);
2786 sp_nodepath_update_repr(nodepath, _("Delete segment"));
2788 sp_nodepath_update_statusbar(nodepath);
2789 }
2791 /**
2792 * Call sp_nodepath_set_line() for all selected segments.
2793 */
2794 void
2795 sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code)
2796 {
2797 if (nodepath == NULL) return;
2799 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2800 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2801 g_assert(n->selected);
2802 if (n->p.other && n->p.other->selected) {
2803 sp_nodepath_set_line_type(n, code);
2804 }
2805 }
2807 sp_nodepath_update_repr(nodepath, _("Change segment type"));
2808 }
2810 /**
2811 * Call sp_nodepath_convert_node_type() for all selected nodes.
2812 */
2813 void
2814 sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type)
2815 {
2816 if (nodepath == NULL) return;
2818 if (nodepath->straight_path) return; // don't change type when it is a straight path!
2820 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
2821 sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type);
2822 }
2824 sp_nodepath_update_repr(nodepath, _("Change node type"));
2825 }
2827 /**
2828 * Change select status of node, update its own and neighbour handles.
2829 */
2830 static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected)
2831 {
2832 node->selected = selected;
2834 if (selected) {
2835 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9);
2836 node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI);
2837 node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI);
2838 sp_knot_update_ctrl(node->knot);
2839 } else {
2840 node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7);
2841 node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
2842 node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
2843 sp_knot_update_ctrl(node->knot);
2844 }
2846 sp_node_update_handles(node);
2847 if (node->n.other) sp_node_update_handles(node->n.other);
2848 if (node->p.other) sp_node_update_handles(node->p.other);
2849 }
2851 /**
2852 \brief Select a node
2853 \param node The node to select
2854 \param incremental If true, add to selection, otherwise deselect others
2855 \param override If true, always select this node, otherwise toggle selected status
2856 */
2857 static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override)
2858 {
2859 Inkscape::NodePath::Path *nodepath = node->subpath->nodepath;
2861 if (incremental) {
2862 if (override) {
2863 if (!g_list_find(nodepath->selected, node)) {
2864 nodepath->selected = g_list_prepend(nodepath->selected, node);
2865 }
2866 sp_node_set_selected(node, TRUE);
2867 } else { // toggle
2868 if (node->selected) {
2869 g_assert(g_list_find(nodepath->selected, node));
2870 nodepath->selected = g_list_remove(nodepath->selected, node);
2871 } else {
2872 g_assert(!g_list_find(nodepath->selected, node));
2873 nodepath->selected = g_list_prepend(nodepath->selected, node);
2874 }
2875 sp_node_set_selected(node, !node->selected);
2876 }
2877 } else {
2878 sp_nodepath_deselect(nodepath);
2879 nodepath->selected = g_list_prepend(nodepath->selected, node);
2880 sp_node_set_selected(node, TRUE);
2881 }
2883 sp_nodepath_update_statusbar(nodepath);
2884 }
2887 /**
2888 \brief Deselect all nodes in the nodepath
2889 */
2890 void
2891 sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath)
2892 {
2893 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2895 while (nodepath->selected) {
2896 sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE);
2897 nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data);
2898 }
2899 sp_nodepath_update_statusbar(nodepath);
2900 }
2902 /**
2903 \brief Select or invert selection of all nodes in the nodepath
2904 */
2905 void
2906 sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert)
2907 {
2908 if (!nodepath) return;
2910 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2911 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
2912 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2913 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2914 sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE);
2915 }
2916 }
2917 }
2919 /**
2920 * If nothing selected, does the same as sp_nodepath_select_all();
2921 * otherwise selects/inverts all nodes in all subpaths that have selected nodes
2922 * (i.e., similar to "select all in layer", with the "selected" subpaths
2923 * being treated as "layers" in the path).
2924 */
2925 void
2926 sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert)
2927 {
2928 if (!nodepath) return;
2930 if (g_list_length (nodepath->selected) == 0) {
2931 sp_nodepath_select_all (nodepath, invert);
2932 return;
2933 }
2935 GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us
2936 GSList *subpaths = NULL;
2938 for (GList *l = copy; l != NULL; l = l->next) {
2939 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
2940 Inkscape::NodePath::SubPath *subpath = n->subpath;
2941 if (!g_slist_find (subpaths, subpath))
2942 subpaths = g_slist_prepend (subpaths, subpath);
2943 }
2945 for (GSList *sp = subpaths; sp != NULL; sp = sp->next) {
2946 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data;
2947 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2948 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2949 sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE);
2950 }
2951 }
2953 g_slist_free (subpaths);
2954 g_list_free (copy);
2955 }
2957 /**
2958 * \brief Select the node after the last selected; if none is selected,
2959 * select the first within path.
2960 */
2961 void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath)
2962 {
2963 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
2965 Inkscape::NodePath::Node *last = NULL;
2966 if (nodepath->selected) {
2967 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
2968 Inkscape::NodePath::SubPath *subpath, *subpath_next;
2969 subpath = (Inkscape::NodePath::SubPath *) spl->data;
2970 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
2971 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
2972 if (node->selected) {
2973 if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) {
2974 if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath
2975 if (spl->next) { // there's a next subpath
2976 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2977 last = subpath_next->first;
2978 } else if (spl->prev) { // there's a previous subpath
2979 last = NULL; // to be set later to the first node of first subpath
2980 } else {
2981 last = node->n.other;
2982 }
2983 } else {
2984 last = node->n.other;
2985 }
2986 } else {
2987 if (node->n.other) {
2988 last = node->n.other;
2989 } else {
2990 if (spl->next) { // there's a next subpath
2991 subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data;
2992 last = subpath_next->first;
2993 } else if (spl->prev) { // there's a previous subpath
2994 last = NULL; // to be set later to the first node of first subpath
2995 } else {
2996 last = (Inkscape::NodePath::Node *) subpath->first;
2997 }
2998 }
2999 }
3000 }
3001 }
3002 }
3003 sp_nodepath_deselect(nodepath);
3004 }
3006 if (last) { // there's at least one more node after selected
3007 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3008 } else { // no more nodes, select the first one in first subpath
3009 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data;
3010 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE);
3011 }
3012 }
3014 /**
3015 * \brief Select the node before the first selected; if none is selected,
3016 * select the last within path
3017 */
3018 void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath)
3019 {
3020 if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses
3022 Inkscape::NodePath::Node *last = NULL;
3023 if (nodepath->selected) {
3024 for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) {
3025 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3026 for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) {
3027 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3028 if (node->selected) {
3029 if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) {
3030 if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath
3031 if (spl->prev) { // there's a prev subpath
3032 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3033 last = subpath_prev->last;
3034 } else if (spl->next) { // there's a next subpath
3035 last = NULL; // to be set later to the last node of last subpath
3036 } else {
3037 last = node->p.other;
3038 }
3039 } else {
3040 last = node->p.other;
3041 }
3042 } else {
3043 if (node->p.other) {
3044 last = node->p.other;
3045 } else {
3046 if (spl->prev) { // there's a prev subpath
3047 Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data;
3048 last = subpath_prev->last;
3049 } else if (spl->next) { // there's a next subpath
3050 last = NULL; // to be set later to the last node of last subpath
3051 } else {
3052 last = (Inkscape::NodePath::Node *) subpath->last;
3053 }
3054 }
3055 }
3056 }
3057 }
3058 }
3059 sp_nodepath_deselect(nodepath);
3060 }
3062 if (last) { // there's at least one more node before selected
3063 sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE);
3064 } else { // no more nodes, select the last one in last subpath
3065 GList *spl = g_list_last(nodepath->subpaths);
3066 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3067 sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE);
3068 }
3069 }
3071 /**
3072 * \brief Select all nodes that are within the rectangle.
3073 */
3074 void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental)
3075 {
3076 if (!incremental) {
3077 sp_nodepath_deselect(nodepath);
3078 }
3080 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3081 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3082 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3083 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3085 if (b.contains(node->pos)) {
3086 sp_nodepath_node_select(node, TRUE, TRUE);
3087 }
3088 }
3089 }
3090 }
3093 void
3094 nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3095 {
3096 g_assert (n);
3097 g_assert (nodepath);
3098 g_assert (n->subpath->nodepath == nodepath);
3100 if (g_list_length (nodepath->selected) == 0) {
3101 if (grow > 0) {
3102 sp_nodepath_node_select(n, TRUE, TRUE);
3103 }
3104 return;
3105 }
3107 if (g_list_length (nodepath->selected) == 1) {
3108 if (grow < 0) {
3109 sp_nodepath_deselect (nodepath);
3110 return;
3111 }
3112 }
3114 double n_sel_range = 0, p_sel_range = 0;
3115 Inkscape::NodePath::Node *farthest_n_node = n;
3116 Inkscape::NodePath::Node *farthest_p_node = n;
3118 // Calculate ranges
3119 {
3120 double n_range = 0, p_range = 0;
3121 bool n_going = true, p_going = true;
3122 Inkscape::NodePath::Node *n_node = n;
3123 Inkscape::NodePath::Node *p_node = n;
3124 do {
3125 // Do one step in both directions from n, until reaching the end of subpath or bumping into each other
3126 if (n_node && n_going)
3127 n_node = n_node->n.other;
3128 if (n_node == NULL) {
3129 n_going = false;
3130 } else {
3131 n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos);
3132 if (n_node->selected) {
3133 n_sel_range = n_range;
3134 farthest_n_node = n_node;
3135 }
3136 if (n_node == p_node) {
3137 n_going = false;
3138 p_going = false;
3139 }
3140 }
3141 if (p_node && p_going)
3142 p_node = p_node->p.other;
3143 if (p_node == NULL) {
3144 p_going = false;
3145 } else {
3146 p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos);
3147 if (p_node->selected) {
3148 p_sel_range = p_range;
3149 farthest_p_node = p_node;
3150 }
3151 if (p_node == n_node) {
3152 n_going = false;
3153 p_going = false;
3154 }
3155 }
3156 } while (n_going || p_going);
3157 }
3159 if (grow > 0) {
3160 if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) {
3161 sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE);
3162 } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) {
3163 sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE);
3164 }
3165 } else {
3166 if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) {
3167 sp_nodepath_node_select(farthest_n_node, TRUE, FALSE);
3168 } else if (farthest_p_node && farthest_p_node->selected) {
3169 sp_nodepath_node_select(farthest_p_node, TRUE, FALSE);
3170 }
3171 }
3172 }
3174 void
3175 nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow)
3176 {
3177 g_assert (n);
3178 g_assert (nodepath);
3179 g_assert (n->subpath->nodepath == nodepath);
3181 if (g_list_length (nodepath->selected) == 0) {
3182 if (grow > 0) {
3183 sp_nodepath_node_select(n, TRUE, TRUE);
3184 }
3185 return;
3186 }
3188 if (g_list_length (nodepath->selected) == 1) {
3189 if (grow < 0) {
3190 sp_nodepath_deselect (nodepath);
3191 return;
3192 }
3193 }
3195 Inkscape::NodePath::Node *farthest_selected = NULL;
3196 double farthest_dist = 0;
3198 Inkscape::NodePath::Node *closest_unselected = NULL;
3199 double closest_dist = NR_HUGE;
3201 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3202 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3203 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3204 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3205 if (node == n)
3206 continue;
3207 if (node->selected) {
3208 if (Geom::L2(node->pos - n->pos) > farthest_dist) {
3209 farthest_dist = Geom::L2(node->pos - n->pos);
3210 farthest_selected = node;
3211 }
3212 } else {
3213 if (Geom::L2(node->pos - n->pos) < closest_dist) {
3214 closest_dist = Geom::L2(node->pos - n->pos);
3215 closest_unselected = node;
3216 }
3217 }
3218 }
3219 }
3221 if (grow > 0) {
3222 if (closest_unselected) {
3223 sp_nodepath_node_select(closest_unselected, TRUE, TRUE);
3224 }
3225 } else {
3226 if (farthest_selected) {
3227 sp_nodepath_node_select(farthest_selected, TRUE, FALSE);
3228 }
3229 }
3230 }
3233 /**
3234 \brief Saves all nodes' and handles' current positions in their origin members
3235 */
3236 void
3237 sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath)
3238 {
3239 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3240 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3241 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3242 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data;
3243 n->origin = n->pos;
3244 n->p.origin = n->p.pos;
3245 n->n.origin = n->n.pos;
3246 }
3247 }
3248 }
3250 /**
3251 \brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes
3252 */
3253 GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath)
3254 {
3255 GList *r = NULL;
3256 if (nodepath->selected) {
3257 guint i = 0;
3258 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3259 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3260 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3261 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3262 i++;
3263 if (node->selected) {
3264 r = g_list_append(r, GINT_TO_POINTER(i));
3265 }
3266 }
3267 }
3268 }
3269 return r;
3270 }
3272 /**
3273 \brief Restores selection by selecting nodes whose positions are in the list
3274 */
3275 void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r)
3276 {
3277 sp_nodepath_deselect(nodepath);
3279 guint i = 0;
3280 for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) {
3281 Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data;
3282 for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) {
3283 Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data;
3284 i++;
3285 if (g_list_find(r, GINT_TO_POINTER(i))) {
3286 sp_nodepath_node_select(node, TRUE, TRUE);
3287 }
3288 }
3289 }
3290 }
3293 /**
3294 \brief Adjusts handle according to node type and line code.
3295 */
3296 static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust)
3297 {
3298 g_assert(node);
3300 // nothing to do for auto nodes (sp_node_adjust_handles() does the job)
3301 if (node->type == Inkscape::NodePath::NODE_AUTO)
3302 return;
3304 Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust);
3305 Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me);
3307 // nothing to do if we are an end node
3308 if (me->other == NULL) return;
3309 if (other->other == NULL) return;
3311 // nothing to do if we are a cusp node
3312 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3314 // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust)
3315 NRPathcode mecode;
3316 if (which_adjust == 1) {
3317 mecode = (NRPathcode)me->other->code;
3318 } else {
3319 mecode = (NRPathcode)node->code;
3320 }
3321 if (mecode == NR_LINETO) return;
3323 if (sp_node_side_is_line(node, other)) {
3324 // other is a line, and we are either smooth or symm
3325 Inkscape::NodePath::Node *othernode = other->other;
3326 double len = Geom::L2(me->pos - node->pos);
3327 Geom::Point delta = node->pos - othernode->pos;
3328 double linelen = Geom::L2(delta);
3329 if (linelen < 1e-18)
3330 return;
3331 me->pos = node->pos + (len / linelen)*delta;
3332 return;
3333 }
3335 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3336 // symmetrize
3337 me->pos = 2 * node->pos - other->pos;
3338 return;
3339 } else {
3340 // smoothify
3341 double len = Geom::L2(me->pos - node->pos);
3342 Geom::Point delta = other->pos - node->pos;
3343 double otherlen = Geom::L2(delta);
3344 if (otherlen < 1e-18) return;
3345 me->pos = node->pos - (len / otherlen) * delta;
3346 }
3347 }
3349 /**
3350 \brief Adjusts both handles according to node type and line code
3351 */
3352 static void sp_node_adjust_handles(Inkscape::NodePath::Node *node)
3353 {
3354 g_assert(node);
3356 if (node->type == Inkscape::NodePath::NODE_CUSP) return;
3358 /* we are either smooth or symm */
3360 if (node->p.other == NULL) return;
3361 if (node->n.other == NULL) return;
3363 if (node->type == Inkscape::NodePath::NODE_AUTO) {
3364 sp_node_adjust_handles_auto(node);
3365 return;
3366 }
3368 if (sp_node_side_is_line(node, &node->p)) {
3369 sp_node_adjust_handle(node, 1);
3370 return;
3371 }
3373 if (sp_node_side_is_line(node, &node->n)) {
3374 sp_node_adjust_handle(node, -1);
3375 return;
3376 }
3378 /* both are curves */
3379 Geom::Point const delta( node->n.pos - node->p.pos );
3381 if (node->type == Inkscape::NodePath::NODE_SYMM) {
3382 node->p.pos = node->pos - delta / 2;
3383 node->n.pos = node->pos + delta / 2;
3384 return;
3385 }
3387 /* We are smooth */
3388 double plen = Geom::L2(node->p.pos - node->pos);
3389 if (plen < 1e-18) return;
3390 double nlen = Geom::L2(node->n.pos - node->pos);
3391 if (nlen < 1e-18) return;
3392 node->p.pos = node->pos - (plen / (plen + nlen)) * delta;
3393 node->n.pos = node->pos + (nlen / (plen + nlen)) * delta;
3394 }
3396 static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node)
3397 {
3398 if (node->p.other == NULL || node->n.other == NULL) {
3399 node->p.pos = node->pos;
3400 node->n.pos = node->pos;
3401 return;
3402 }
3404 Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos);
3405 Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos);
3407 double norm_leg_prev = Geom::L2(leg_prev);
3408 double norm_leg_next = Geom::L2(leg_next);
3410 Geom::Point delta;
3411 if (norm_leg_next > 0.0) {
3412 delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev;
3413 delta.normalize();
3414 }
3416 node->p.pos = node->pos - norm_leg_prev / 3 * delta;
3417 node->n.pos = node->pos + norm_leg_next / 3 * delta;
3418 }
3420 /**
3421 * Node event callback.
3422 */
3423 static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n)
3424 {
3425 gboolean ret = FALSE;
3426 switch (event->type) {
3427 case GDK_ENTER_NOTIFY:
3428 Inkscape::NodePath::Path::active_node = n;
3429 break;
3430 case GDK_LEAVE_NOTIFY:
3431 Inkscape::NodePath::Path::active_node = NULL;
3432 break;
3433 case GDK_SCROLL:
3434 if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly
3435 switch (event->scroll.direction) {
3436 case GDK_SCROLL_UP:
3437 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3438 break;
3439 case GDK_SCROLL_DOWN:
3440 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3441 break;
3442 default:
3443 break;
3444 }
3445 ret = TRUE;
3446 } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially
3447 switch (event->scroll.direction) {
3448 case GDK_SCROLL_UP:
3449 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3450 break;
3451 case GDK_SCROLL_DOWN:
3452 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3453 break;
3454 default:
3455 break;
3456 }
3457 ret = TRUE;
3458 }
3459 break;
3460 case GDK_KEY_PRESS:
3461 switch (get_group0_keyval (&event->key)) {
3462 case GDK_space:
3463 if (event->key.state & GDK_BUTTON1_MASK) {
3464 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3465 stamp_repr(nodepath);
3466 ret = TRUE;
3467 }
3468 break;
3469 case GDK_Page_Up:
3470 if (event->key.state & GDK_CONTROL_MASK) {
3471 nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1);
3472 } else {
3473 nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1);
3474 }
3475 break;
3476 case GDK_Page_Down:
3477 if (event->key.state & GDK_CONTROL_MASK) {
3478 nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1);
3479 } else {
3480 nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1);
3481 }
3482 break;
3483 default:
3484 break;
3485 }
3486 break;
3487 default:
3488 break;
3489 }
3491 return ret;
3492 }
3494 /**
3495 * Handle keypress on node; directly called.
3496 */
3497 gboolean node_key(GdkEvent *event)
3498 {
3499 Inkscape::NodePath::Path *np;
3501 // there is no way to verify nodes so set active_node to nil when deleting!!
3502 if (Inkscape::NodePath::Path::active_node == NULL) return FALSE;
3504 if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
3505 gint ret = FALSE;
3506 switch (get_group0_keyval (&event->key)) {
3507 /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts!
3508 case GDK_BackSpace:
3509 np = Inkscape::NodePath::Path::active_node->subpath->nodepath;
3510 sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node);
3511 sp_nodepath_update_repr(np, _("Delete node"));
3512 Inkscape::NodePath::Path::active_node = NULL;
3513 ret = TRUE;
3514 break;
3515 case GDK_c:
3516 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP);
3517 ret = TRUE;
3518 break;
3519 case GDK_s:
3520 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH);
3521 ret = TRUE;
3522 break;
3523 case GDK_a:
3524 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO);
3525 ret = TRUE;
3526 break;
3527 case GDK_y:
3528 sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM);
3529 ret = TRUE;
3530 break;
3531 case GDK_b:
3532 sp_nodepath_node_break(Inkscape::NodePath::Path::active_node);
3533 ret = TRUE;
3534 break;
3535 }
3536 return ret;
3537 }
3538 return FALSE;
3539 }
3541 /**
3542 * Mouseclick on node callback.
3543 */
3544 static void node_clicked(SPKnot */*knot*/, guint state, gpointer data)
3545 {
3546 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3548 if (state & GDK_CONTROL_MASK) {
3549 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3551 if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type
3552 if (n->type == Inkscape::NodePath::NODE_CUSP) {
3553 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH);
3554 } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) {
3555 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM);
3556 } else if (n->type == Inkscape::NodePath::NODE_SYMM) {
3557 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO);
3558 } else {
3559 sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP);
3560 }
3561 sp_nodepath_update_repr(nodepath, _("Change node type"));
3562 sp_nodepath_update_statusbar(nodepath);
3564 } else { //ctrl+alt+click: delete node
3565 GList *node_to_delete = NULL;
3566 node_to_delete = g_list_append(node_to_delete, n);
3567 sp_node_delete_preserve(node_to_delete);
3568 }
3570 } else {
3571 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3572 }
3573 }
3575 /**
3576 * Mouse grabbed node callback.
3577 */
3578 static void node_grabbed(SPKnot *knot, guint state, gpointer data)
3579 {
3580 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3582 if (!n->selected) {
3583 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3584 }
3586 n->is_dragging = true;
3587 // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping)
3588 n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin;
3590 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3592 sp_nodepath_remember_origins (n->subpath->nodepath);
3593 }
3595 /**
3596 * Mouse ungrabbed node callback.
3597 */
3598 static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data)
3599 {
3600 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3602 n->dragging_out = NULL;
3603 n->is_dragging = false;
3604 n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE);
3605 sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas);
3607 sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes"));
3608 }
3610 /**
3611 * The point on a line, given by its angle, closest to the given point.
3612 * \param p A point.
3613 * \param a Angle of the line; it is assumed to go through coordinate origin.
3614 * \param closest Pointer to the point struct where the result is stored.
3615 * \todo FIXME: use dot product perhaps?
3616 */
3617 static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest)
3618 {
3619 if (a == HUGE_VAL) { // vertical
3620 *closest = Geom::Point(0, (*p)[Geom::Y]);
3621 } else {
3622 (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1);
3623 (*closest)[Geom::Y] = a * (*closest)[Geom::X];
3624 }
3625 }
3627 /**
3628 * Distance from the point to a line given by its angle.
3629 * \param p A point.
3630 * \param a Angle of the line; it is assumed to go through coordinate origin.
3631 */
3632 static double point_line_distance(Geom::Point *p, double a)
3633 {
3634 Geom::Point c;
3635 point_line_closest(p, a, &c);
3636 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]));
3637 }
3639 /**
3640 * Callback for node "request" signal.
3641 * \todo fixme: This goes to "moved" event? (lauris)
3642 */
3643 static gboolean
3644 node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data)
3645 {
3646 double yn, xn, yp, xp;
3647 double an, ap, na, pa;
3648 double d_an, d_ap, d_na, d_pa;
3649 gboolean collinear = FALSE;
3650 Geom::Point c;
3651 Geom::Point pr;
3653 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3655 n->subpath->nodepath->desktop->snapindicator->remove_snaptarget();
3657 // If either (Shift and some handle retracted), or (we're already dragging out a handle)
3658 if ( (!n->subpath->nodepath->straight_path) &&
3659 ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos)))
3660 || n->dragging_out ) )
3661 {
3662 Geom::Point mouse = p;
3664 if (!n->dragging_out) {
3665 // This is the first drag-out event; find out which handle to drag out
3666 double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL);
3667 double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL);
3669 if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node?
3670 return FALSE;
3672 Inkscape::NodePath::NodeSide *opposite;
3673 if (appr_p > appr_n) { // closer to p
3674 n->dragging_out = &n->p;
3675 opposite = &n->n;
3676 n->code = NR_CURVETO;
3677 } else if (appr_p < appr_n) { // closer to n
3678 n->dragging_out = &n->n;
3679 opposite = &n->p;
3680 n->n.other->code = NR_CURVETO;
3681 } else { // p and n nodes are the same
3682 if (n->n.pos != n->pos) { // n handle already dragged, drag p
3683 n->dragging_out = &n->p;
3684 opposite = &n->n;
3685 n->code = NR_CURVETO;
3686 } else if (n->p.pos != n->pos) { // p handle already dragged, drag n
3687 n->dragging_out = &n->n;
3688 opposite = &n->p;
3689 n->n.other->code = NR_CURVETO;
3690 } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other
3691 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);
3692 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);
3693 if (appr_other_p > appr_other_n) { // closer to other's p handle
3694 n->dragging_out = &n->n;
3695 opposite = &n->p;
3696 n->n.other->code = NR_CURVETO;
3697 } else { // closer to other's n handle
3698 n->dragging_out = &n->p;
3699 opposite = &n->n;
3700 n->code = NR_CURVETO;
3701 }
3702 }
3703 }
3705 // if there's another handle, make sure the one we drag out starts parallel to it
3706 if (opposite->pos != n->pos) {
3707 mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos);
3708 }
3710 // knots might not be created yet!
3711 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out);
3712 sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite);
3713 }
3715 // pass this on to the handle-moved callback
3716 node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n);
3717 sp_node_update_handles(n);
3718 return TRUE;
3719 }
3721 if (state & GDK_CONTROL_MASK) { // constrained motion
3723 // calculate relative distances of handles
3724 // n handle:
3725 yn = n->n.pos[Geom::Y] - n->pos[Geom::Y];
3726 xn = n->n.pos[Geom::X] - n->pos[Geom::X];
3727 // if there's no n handle (straight line), see if we can use the direction to the next point on path
3728 if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) {
3729 if (n->n.other) { // if there is the next point
3730 if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either
3731 yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag
3732 xn = n->n.other->origin[Geom::X] - n->origin[Geom::X];
3733 }
3734 }
3735 if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi
3736 if (yn < 0) { xn = -xn; yn = -yn; }
3738 // p handle:
3739 yp = n->p.pos[Geom::Y] - n->pos[Geom::Y];
3740 xp = n->p.pos[Geom::X] - n->pos[Geom::X];
3741 // if there's no p handle (straight line), see if we can use the direction to the prev point on path
3742 if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) {
3743 if (n->p.other) {
3744 if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6)
3745 yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y];
3746 xp = n->p.other->origin[Geom::X] - n->origin[Geom::X];
3747 }
3748 }
3749 if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi
3750 if (yp < 0) { xp = -xp; yp = -yp; }
3752 if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) {
3753 // sliding on handles, only if at least one of the handles is non-vertical
3754 // (otherwise it's the same as ctrl+drag anyway)
3756 // calculate angles of the handles
3757 if (xn == 0) {
3758 if (yn == 0) { // no handle, consider it the continuation of the other one
3759 an = 0;
3760 collinear = TRUE;
3761 }
3762 else an = 0; // vertical; set the angle to horizontal
3763 } else an = yn/xn;
3765 if (xp == 0) {
3766 if (yp == 0) { // no handle, consider it the continuation of the other one
3767 ap = an;
3768 }
3769 else ap = 0; // vertical; set the angle to horizontal
3770 } else ap = yp/xp;
3772 if (collinear) an = ap;
3774 // angles of the perpendiculars; HUGE_VAL means vertical
3775 if (an == 0) na = HUGE_VAL; else na = -1/an;
3776 if (ap == 0) pa = HUGE_VAL; else pa = -1/ap;
3778 // mouse point relative to the node's original pos
3779 pr = p - n->origin;
3781 // distances to the four lines (two handles and two perpendiculars)
3782 d_an = point_line_distance(&pr, an);
3783 d_na = point_line_distance(&pr, na);
3784 d_ap = point_line_distance(&pr, ap);
3785 d_pa = point_line_distance(&pr, pa);
3787 // find out which line is the closest, save its closest point in c
3788 if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) {
3789 point_line_closest(&pr, an, &c);
3790 } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) {
3791 point_line_closest(&pr, ap, &c);
3792 } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) {
3793 point_line_closest(&pr, na, &c);
3794 } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) {
3795 point_line_closest(&pr, pa, &c);
3796 }
3798 // move the node to the closest point
3799 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3800 n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X],
3801 n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y],
3802 true);
3804 } else { // constraining to hor/vert
3806 if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor
3807 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3808 p[Geom::X] - n->pos[Geom::X],
3809 n->origin[Geom::Y] - n->pos[Geom::Y],
3810 true,
3811 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X]));
3812 } else { // snap to vert
3813 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3814 n->origin[Geom::X] - n->pos[Geom::X],
3815 p[Geom::Y] - n->pos[Geom::Y],
3816 true,
3817 true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y]));
3818 }
3819 }
3820 } else { // move freely
3821 if (n->is_dragging) {
3822 if (state & GDK_MOD1_MASK) { // sculpt
3823 sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin);
3824 } else {
3825 sp_nodepath_selected_nodes_move(n->subpath->nodepath,
3826 p[Geom::X] - n->pos[Geom::X],
3827 p[Geom::Y] - n->pos[Geom::Y],
3828 (state & GDK_SHIFT_MASK) == 0);
3829 }
3830 }
3831 }
3833 n->subpath->nodepath->desktop->scroll_to_point(p);
3835 return TRUE;
3836 }
3838 /**
3839 * Node handle clicked callback.
3840 */
3841 static void node_handle_clicked(SPKnot *knot, guint state, gpointer data)
3842 {
3843 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3845 if (state & GDK_CONTROL_MASK) { // "delete" handle
3846 if (n->p.knot == knot) {
3847 n->p.pos = n->pos;
3848 } else if (n->n.knot == knot) {
3849 n->n.pos = n->pos;
3850 }
3851 sp_node_update_handles(n);
3852 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
3853 sp_nodepath_update_repr(nodepath, _("Retract handle"));
3854 sp_nodepath_update_statusbar(nodepath);
3856 } else { // just select or add to selection, depending in Shift
3857 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3858 }
3859 }
3861 /**
3862 * Node handle grabbed callback.
3863 */
3864 static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data)
3865 {
3866 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3868 // convert auto -> smooth when dragging handle
3869 if (n->type == Inkscape::NodePath::NODE_AUTO) {
3870 n->type = Inkscape::NodePath::NODE_SMOOTH;
3871 sp_nodepath_update_node_knot (n);
3872 }
3874 if (!n->selected) {
3875 sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE);
3876 }
3878 // remember the origin point of the handle
3879 if (n->p.knot == knot) {
3880 n->p.origin_radial = n->p.pos - n->pos;
3881 } else if (n->n.knot == knot) {
3882 n->n.origin_radial = n->n.pos - n->pos;
3883 } else {
3884 g_assert_not_reached();
3885 }
3887 sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5);
3888 }
3890 /**
3891 * Node handle ungrabbed callback.
3892 */
3893 static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data)
3894 {
3895 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3897 // forget origin and set knot position once more (because it can be wrong now due to restrictions)
3898 if (n->p.knot == knot) {
3899 n->p.origin_radial.a = 0;
3900 sp_knot_set_position(knot, n->p.pos, state);
3901 } else if (n->n.knot == knot) {
3902 n->n.origin_radial.a = 0;
3903 sp_knot_set_position(knot, n->n.pos, state);
3904 } else {
3905 g_assert_not_reached();
3906 }
3908 sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle"));
3909 }
3911 /**
3912 * Node handle "request" signal callback.
3913 */
3914 static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data)
3915 {
3916 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3918 Inkscape::NodePath::NodeSide *me, *opposite;
3919 gint which;
3920 if (n->p.knot == knot) {
3921 me = &n->p;
3922 opposite = &n->n;
3923 which = -1;
3924 } else if (n->n.knot == knot) {
3925 me = &n->n;
3926 opposite = &n->p;
3927 which = 1;
3928 } else {
3929 me = opposite = NULL;
3930 which = 0;
3931 g_assert_not_reached();
3932 }
3934 SPDesktop *desktop = n->subpath->nodepath->desktop;
3935 SnapManager &m = desktop->namedview->snap_manager;
3936 m.setup(desktop, true, n->subpath->nodepath->item);
3937 Inkscape::SnappedPoint s;
3939 if ((state & GDK_SHIFT_MASK) != 0) {
3940 // We will not try to snap when the shift-key is pressed
3941 // so remove the old snap indicator and don't wait for it to time-out
3942 desktop->snapindicator->remove_snaptarget();
3943 }
3945 Inkscape::NodePath::Node *othernode = opposite->other;
3946 if (othernode) {
3947 if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) {
3948 /* We are smooth node adjacent with line */
3949 Geom::Point const delta = p - n->pos;
3950 Geom::Coord const len = Geom::L2(delta);
3951 Inkscape::NodePath::Node *othernode = opposite->other;
3952 Geom::Point const ndelta = n->pos - othernode->pos;
3953 Geom::Coord const linelen = Geom::L2(ndelta);
3954 if (len > NR_EPSILON && linelen > NR_EPSILON) {
3955 Geom::Coord const scal = dot(delta, ndelta) / linelen;
3956 p = n->pos + (scal / linelen) * ndelta;
3957 }
3958 if ((state & GDK_SHIFT_MASK) == 0) {
3959 s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p, Inkscape::Snapper::ConstraintLine(p, ndelta));
3960 }
3961 } else {
3962 if ((state & GDK_SHIFT_MASK) == 0) {
3963 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3964 }
3965 }
3966 } else {
3967 if ((state & GDK_SHIFT_MASK) == 0) {
3968 s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, p);
3969 }
3970 }
3972 s.getPoint(p);
3974 sp_node_adjust_handle(n, -which);
3976 return FALSE;
3977 }
3979 /**
3980 * Node handle moved callback.
3981 */
3982 static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data)
3983 {
3984 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data;
3985 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
3987 Inkscape::NodePath::NodeSide *me;
3988 Inkscape::NodePath::NodeSide *other;
3989 if (n->p.knot == knot) {
3990 me = &n->p;
3991 other = &n->n;
3992 } else if (n->n.knot == knot) {
3993 me = &n->n;
3994 other = &n->p;
3995 } else {
3996 me = NULL;
3997 other = NULL;
3998 g_assert_not_reached();
3999 }
4001 // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point
4002 Radial rme(me->pos - n->pos);
4003 Radial rother(other->pos - n->pos);
4004 Radial rnew(p - n->pos);
4006 if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) {
4007 int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
4008 /* 0 interpreted as "no snapping". */
4010 // 1. Snap to the closest PI/snaps angle, starting from zero.
4011 double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps);
4013 // 2. Snap to the original angle, its opposite and perpendiculars
4014 if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length
4015 /* The closest PI/2 angle, starting from original angle */
4016 double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2);
4018 // Snap to the closest.
4019 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a)
4020 ? a_snapped
4021 : a_ortho );
4022 }
4024 // 3. Snap to the angle of the opposite line, if any
4025 Inkscape::NodePath::Node *othernode = other->other;
4026 if (othernode) {
4027 Geom::Point other_to_snap(0,0);
4028 if (sp_node_side_is_line(n, other)) {
4029 other_to_snap = othernode->pos - n->pos;
4030 } else {
4031 other_to_snap = other->pos - n->pos;
4032 }
4033 if (Geom::L2(other_to_snap) > 1e-3) {
4034 Radial rother_to_snap(other_to_snap);
4035 /* The closest PI/2 angle, starting from the angle of the opposite line segment */
4036 double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2);
4038 // Snap to the closest.
4039 a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a)
4040 ? a_snapped
4041 : a_oppo );
4042 }
4043 }
4045 rnew.a = a_snapped;
4046 }
4048 if (state & GDK_MOD1_MASK) {
4049 // lock handle length
4050 rnew.r = me->origin_radial.r;
4051 }
4053 if (( n->type !=Inkscape::NodePath::NODE_CUSP || (state & GDK_SHIFT_MASK))
4054 && rme.a != HUGE_VAL && rnew.a != HUGE_VAL && (fabs(rme.a - rnew.a) > 0.001 || n->type ==Inkscape::NodePath::NODE_SYMM)) {
4055 // rotate the other handle correspondingly, if both old and new angles exist and are not the same
4056 rother.a += rnew.a - rme.a;
4057 other->pos = Geom::Point(rother) + n->pos;
4058 if (other->knot) {
4059 sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos);
4060 sp_knot_moveto(other->knot, other->pos);
4061 }
4062 }
4064 me->pos = Geom::Point(rnew) + n->pos;
4065 sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos);
4067 // move knot, but without emitting the signal:
4068 // we cannot emit a "moved" signal because we're now processing it
4069 sp_knot_moveto(me->knot, me->pos);
4071 update_object(n->subpath->nodepath);
4073 /* status text */
4074 SPDesktop *desktop = n->subpath->nodepath->desktop;
4075 if (!desktop) return;
4076 SPEventContext *ec = desktop->event_context;
4077 if (!ec) return;
4079 Inkscape::MessageContext *mc = get_message_context(ec);
4081 if (!mc) return;
4083 double degrees = 180 / M_PI * rnew.a;
4084 if (degrees > 180) degrees -= 360;
4085 if (degrees < -180) degrees += 360;
4086 if (prefs->getBool("/options/compassangledisplay/value"))
4087 degrees = angle_to_compass (degrees);
4089 GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric());
4091 mc->setF(Inkscape::IMMEDIATE_MESSAGE,
4092 _("<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);
4094 g_string_free(length, TRUE);
4095 }
4097 /**
4098 * Node handle event callback.
4099 */
4100 static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n)
4101 {
4102 gboolean ret = FALSE;
4103 switch (event->type) {
4104 case GDK_KEY_PRESS:
4105 switch (get_group0_keyval (&event->key)) {
4106 case GDK_space:
4107 if (event->key.state & GDK_BUTTON1_MASK) {
4108 Inkscape::NodePath::Path *nodepath = n->subpath->nodepath;
4109 stamp_repr(nodepath);
4110 ret = TRUE;
4111 }
4112 break;
4113 default:
4114 break;
4115 }
4116 break;
4117 case GDK_ENTER_NOTIFY:
4118 // we use an experimentally determined threshold that seems to work fine
4119 if (Geom::L2(n->pos - knot->pos) < 0.75)
4120 Inkscape::NodePath::Path::active_node = n;
4121 break;
4122 case GDK_LEAVE_NOTIFY:
4123 // we use an experimentally determined threshold that seems to work fine
4124 if (Geom::L2(n->pos - knot->pos) < 0.75)
4125 Inkscape::NodePath::Path::active_node = NULL;
4126 break;
4127 default:
4128 break;
4129 }
4131 return ret;
4132 }
4134 static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle,
4135 Radial &rme, Radial &rother, gboolean const both)
4136 {
4137 rme.a += angle;
4138 if ( both
4139 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4140 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4141 {
4142 rother.a += angle;
4143 }
4144 }
4146 static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle,
4147 Radial &rme, Radial &rother, gboolean const both)
4148 {
4149 gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom();
4151 gdouble r;
4152 if ( both
4153 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4154 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4155 {
4156 r = MAX(rme.r, rother.r);
4157 } else {
4158 r = rme.r;
4159 }
4161 gdouble const weird_angle = atan2(norm_angle, r);
4162 /* Bulia says norm_angle is just the visible distance that the
4163 * object's end must travel on the screen. Left as 'angle' for want of
4164 * a better name.*/
4166 rme.a += weird_angle;
4167 if ( both
4168 || ( n.type == Inkscape::NodePath::NODE_SMOOTH )
4169 || ( n.type == Inkscape::NodePath::NODE_SYMM ) )
4170 {
4171 rother.a += weird_angle;
4172 }
4173 }
4175 /**
4176 * Rotate one node.
4177 */
4178 static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen)
4179 {
4180 Inkscape::NodePath::NodeSide *me, *other;
4181 bool both = false;
4183 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4184 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4186 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4187 me = &(n->p);
4188 other = &(n->n);
4189 } else if (!n->p.other) {
4190 me = &(n->n);
4191 other = &(n->p);
4192 } else {
4193 if (which > 0) { // right handle
4194 if (xn > xp) {
4195 me = &(n->n);
4196 other = &(n->p);
4197 } else {
4198 me = &(n->p);
4199 other = &(n->n);
4200 }
4201 } else if (which < 0){ // left handle
4202 if (xn <= xp) {
4203 me = &(n->n);
4204 other = &(n->p);
4205 } else {
4206 me = &(n->p);
4207 other = &(n->n);
4208 }
4209 } else { // both handles
4210 me = &(n->n);
4211 other = &(n->p);
4212 both = true;
4213 }
4214 }
4216 Radial rme(me->pos - n->pos);
4217 Radial rother(other->pos - n->pos);
4219 if (screen) {
4220 node_rotate_one_internal_screen (*n, angle, rme, rother, both);
4221 } else {
4222 node_rotate_one_internal (*n, angle, rme, rother, both);
4223 }
4225 me->pos = n->pos + Geom::Point(rme);
4227 if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) {
4228 other->pos = n->pos + Geom::Point(rother);
4229 }
4231 // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end,
4232 // so here we just move all the knots without emitting move signals, for speed
4233 sp_node_update_handles(n, false);
4234 }
4236 /**
4237 * Rotate selected nodes.
4238 */
4239 void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen)
4240 {
4241 if (!nodepath || !nodepath->selected) return;
4243 if (g_list_length(nodepath->selected) == 1) {
4244 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4245 node_rotate_one (n, angle, which, screen);
4246 } else {
4247 // rotate as an object:
4249 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4250 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4251 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4252 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4253 box.expandTo (n->pos); // contain all selected nodes
4254 }
4256 gdouble rot;
4257 if (screen) {
4258 gdouble const zoom = nodepath->desktop->current_zoom();
4259 gdouble const zmove = angle / zoom;
4260 gdouble const r = Geom::L2(box.max() - box.midpoint());
4261 rot = atan2(zmove, r);
4262 } else {
4263 rot = angle;
4264 }
4266 Geom::Point rot_center;
4267 if (Inkscape::NodePath::Path::active_node == NULL)
4268 rot_center = box.midpoint();
4269 else
4270 rot_center = Inkscape::NodePath::Path::active_node->pos;
4272 Geom::Matrix t =
4273 Geom::Matrix (Geom::Translate(-rot_center)) *
4274 Geom::Matrix (Geom::Rotate(rot)) *
4275 Geom::Matrix (Geom::Translate(rot_center));
4277 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4278 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4279 n->pos *= t;
4280 n->n.pos *= t;
4281 n->p.pos *= t;
4282 sp_node_update_handles(n, false);
4283 }
4284 }
4286 sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes"));
4287 }
4289 /**
4290 * Scale one node.
4291 */
4292 static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which)
4293 {
4294 bool both = false;
4295 Inkscape::NodePath::NodeSide *me, *other;
4297 double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X];
4298 double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X];
4300 if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which"
4301 me = &(n->p);
4302 other = &(n->n);
4303 n->code = NR_CURVETO;
4304 } else if (!n->p.other) {
4305 me = &(n->n);
4306 other = &(n->p);
4307 if (n->n.other)
4308 n->n.other->code = NR_CURVETO;
4309 } else {
4310 if (which > 0) { // right handle
4311 if (xn > xp) {
4312 me = &(n->n);
4313 other = &(n->p);
4314 if (n->n.other)
4315 n->n.other->code = NR_CURVETO;
4316 } else {
4317 me = &(n->p);
4318 other = &(n->n);
4319 n->code = NR_CURVETO;
4320 }
4321 } else if (which < 0){ // left handle
4322 if (xn <= xp) {
4323 me = &(n->n);
4324 other = &(n->p);
4325 if (n->n.other)
4326 n->n.other->code = NR_CURVETO;
4327 } else {
4328 me = &(n->p);
4329 other = &(n->n);
4330 n->code = NR_CURVETO;
4331 }
4332 } else { // both handles
4333 me = &(n->n);
4334 other = &(n->p);
4335 both = true;
4336 n->code = NR_CURVETO;
4337 if (n->n.other)
4338 n->n.other->code = NR_CURVETO;
4339 }
4340 }
4342 Radial rme(me->pos - n->pos);
4343 Radial rother(other->pos - n->pos);
4345 rme.r += grow;
4346 if (rme.r < 0) rme.r = 0;
4347 if (rme.a == HUGE_VAL) {
4348 if (me->other) { // if direction is unknown, initialize it towards the next node
4349 Radial rme_next(me->other->pos - n->pos);
4350 rme.a = rme_next.a;
4351 } else { // if there's no next, initialize to 0
4352 rme.a = 0;
4353 }
4354 }
4355 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4356 rother.r += grow;
4357 if (rother.r < 0) rother.r = 0;
4358 if (rother.a == HUGE_VAL) {
4359 rother.a = rme.a + M_PI;
4360 }
4361 }
4363 me->pos = n->pos + Geom::Point(rme);
4365 if (both || n->type == Inkscape::NodePath::NODE_SYMM) {
4366 other->pos = n->pos + Geom::Point(rother);
4367 }
4369 // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end,
4370 // so here we just move all the knots without emitting move signals, for speed
4371 sp_node_update_handles(n, false);
4372 }
4374 /**
4375 * Scale selected nodes.
4376 */
4377 void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4378 {
4379 if (!nodepath || !nodepath->selected) return;
4381 if (g_list_length(nodepath->selected) == 1) {
4382 // scale handles of the single selected node
4383 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4384 node_scale_one (n, grow, which);
4385 } else {
4386 // scale nodes as an "object":
4388 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4389 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4390 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4391 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4392 box.expandTo (n->pos); // contain all selected nodes
4393 }
4395 if ( Geom::are_near(box.maxExtent(), 0) ) {
4396 SPEventContext *ec = nodepath->desktop->event_context;
4397 if (!ec) return;
4398 Inkscape::MessageContext *mc = get_message_context(ec);
4399 if (!mc) return;
4400 mc->setF(Inkscape::WARNING_MESSAGE,
4401 _("Cannot scale nodes when all are at the same location."));
4402 return;
4403 }
4404 double scale = (box.maxExtent() + grow)/box.maxExtent();
4407 Geom::Point scale_center;
4408 if (Inkscape::NodePath::Path::active_node == NULL)
4409 scale_center = box.midpoint();
4410 else
4411 scale_center = Inkscape::NodePath::Path::active_node->pos;
4413 Geom::Matrix t =
4414 Geom::Translate(-scale_center) *
4415 Geom::Scale(scale, scale) *
4416 Geom::Translate(scale_center);
4418 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4419 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4420 n->pos *= t;
4421 n->n.pos *= t;
4422 n->p.pos *= t;
4423 sp_node_update_handles(n, false);
4424 }
4425 }
4427 sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes"));
4428 }
4430 void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which)
4431 {
4432 if (!nodepath) return;
4433 sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which);
4434 }
4436 /**
4437 * Flip selected nodes horizontally/vertically.
4438 */
4439 void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center)
4440 {
4441 if (!nodepath || !nodepath->selected) return;
4443 if (g_list_length(nodepath->selected) == 1 && !center) {
4444 // flip handles of the single selected node
4445 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data;
4446 double temp = n->p.pos[axis];
4447 n->p.pos[axis] = n->n.pos[axis];
4448 n->n.pos[axis] = temp;
4449 sp_node_update_handles(n, false);
4450 } else {
4451 // scale nodes as an "object":
4453 Geom::Rect box = sp_node_selected_bbox (nodepath);
4454 if (!center) {
4455 center = box.midpoint();
4456 }
4457 Geom::Matrix t =
4458 Geom::Matrix (Geom::Translate(- *center)) *
4459 Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) *
4460 Geom::Matrix (Geom::Translate(*center));
4462 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4463 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4464 n->pos *= t;
4465 n->n.pos *= t;
4466 n->p.pos *= t;
4467 sp_node_update_handles(n, false);
4468 }
4469 }
4471 sp_nodepath_update_repr(nodepath, _("Flip nodes"));
4472 }
4474 Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
4475 {
4476 g_assert (nodepath->selected);
4478 Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
4479 Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node
4480 for (GList *l = nodepath->selected; l != NULL; l = l->next) {
4481 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
4482 box.expandTo (n->pos); // contain all selected nodes
4483 }
4484 return box;
4485 }
4487 //-----------------------------------------------
4488 /**
4489 * Return new subpath under given nodepath.
4490 */
4491 static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath)
4492 {
4493 g_assert(nodepath);
4494 g_assert(nodepath->desktop);
4496 Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1);
4498 s->nodepath = nodepath;
4499 s->closed = FALSE;
4500 s->nodes = NULL;
4501 s->first = NULL;
4502 s->last = NULL;
4504 // using prepend here saves up to 10% of time on paths with many subpaths, but requires that
4505 // the caller reverses the list after it's ready (this is done in sp_nodepath_new)
4506 nodepath->subpaths = g_list_prepend (nodepath->subpaths, s);
4508 return s;
4509 }
4511 /**
4512 * Destroy nodes in subpath, then subpath itself.
4513 */
4514 static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath)
4515 {
4516 g_assert(subpath);
4517 g_assert(subpath->nodepath);
4518 g_assert(g_list_find(subpath->nodepath->subpaths, subpath));
4520 while (subpath->nodes) {
4521 sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data);
4522 }
4524 subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath);
4526 g_free(subpath);
4527 }
4529 /**
4530 * Link head to tail in subpath.
4531 */
4532 static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp)
4533 {
4534 g_assert(!sp->closed);
4535 g_assert(sp->last != sp->first);
4536 g_assert(sp->first->code == NR_MOVETO);
4538 sp->closed = TRUE;
4540 //Link the head to the tail
4541 sp->first->p.other = sp->last;
4542 sp->last->n.other = sp->first;
4543 sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos);
4544 sp->first = sp->last;
4546 //Remove the extra end node
4547 sp_nodepath_node_destroy(sp->last->n.other);
4548 }
4550 /**
4551 * Open closed (loopy) subpath at node.
4552 */
4553 static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n)
4554 {
4555 g_assert(sp->closed);
4556 g_assert(n->subpath == sp);
4557 g_assert(sp->first == sp->last);
4559 /* We create new startpoint, current node will become last one */
4561 Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO,
4562 &n->pos, &n->pos, &n->n.pos);
4565 sp->closed = FALSE;
4567 //Unlink to make a head and tail
4568 sp->first = new_path;
4569 sp->last = n;
4570 n->n.other = NULL;
4571 new_path->p.other = NULL;
4572 }
4574 /**
4575 * Return new node in subpath with given properties.
4576 * \param pos Position of node.
4577 * \param ppos Handle position in previous direction
4578 * \param npos Handle position in previous direction
4579 */
4580 Inkscape::NodePath::Node *
4581 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)
4582 {
4583 g_assert(sp);
4584 g_assert(sp->nodepath);
4585 g_assert(sp->nodepath->desktop);
4587 if (nodechunk == NULL)
4588 nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE);
4590 Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk);
4592 n->subpath = sp;
4594 if (type != Inkscape::NodePath::NODE_NONE) {
4595 // use the type from sodipodi:nodetypes
4596 n->type = type;
4597 } else {
4598 if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) {
4599 // points are (almost) collinear
4600 if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) {
4601 // endnode, or a node with a retracted handle
4602 n->type = Inkscape::NodePath::NODE_CUSP;
4603 } else {
4604 n->type = Inkscape::NodePath::NODE_SMOOTH;
4605 }
4606 } else {
4607 n->type = Inkscape::NodePath::NODE_CUSP;
4608 }
4609 }
4611 n->code = code;
4612 n->selected = FALSE;
4613 n->pos = *pos;
4614 n->p.pos = *ppos;
4615 n->n.pos = *npos;
4617 n->dragging_out = NULL;
4619 Inkscape::NodePath::Node *prev;
4620 if (next) {
4621 //g_assert(g_list_find(sp->nodes, next));
4622 prev = next->p.other;
4623 } else {
4624 prev = sp->last;
4625 }
4627 if (prev)
4628 prev->n.other = n;
4629 else
4630 sp->first = n;
4632 if (next)
4633 next->p.other = n;
4634 else
4635 sp->last = n;
4637 n->p.other = prev;
4638 n->n.other = next;
4640 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"));
4641 sp_knot_set_position(n->knot, *pos, 0);
4643 n->knot->setAnchor (GTK_ANCHOR_CENTER);
4644 n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI);
4645 n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI);
4647 sp_nodepath_update_node_knot(n);
4649 g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n);
4650 g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n);
4651 g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n);
4652 g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n);
4653 g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n);
4654 sp_knot_show(n->knot);
4656 // We only create handle knots and lines on demand
4657 n->p.knot = NULL;
4658 n->p.line = NULL;
4659 n->n.knot = NULL;
4660 n->n.line = NULL;
4662 sp->nodes = g_list_prepend(sp->nodes, n);
4664 return n;
4665 }
4667 /**
4668 * Destroy node and its knots, link neighbors in subpath.
4669 */
4670 static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node)
4671 {
4672 g_assert(node);
4673 g_assert(node->subpath);
4674 g_assert(SP_IS_KNOT(node->knot));
4676 Inkscape::NodePath::SubPath *sp = node->subpath;
4678 if (node->selected) { // first, deselect
4679 g_assert(g_list_find(node->subpath->nodepath->selected, node));
4680 node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node);
4681 }
4683 node->subpath->nodes = g_list_remove(node->subpath->nodes, node);
4685 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node);
4686 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node);
4687 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node);
4688 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node);
4689 g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node);
4690 g_object_unref(G_OBJECT(node->knot));
4692 if (node->p.knot) {
4693 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4694 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4695 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4696 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4697 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4698 g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4699 g_object_unref(G_OBJECT(node->p.knot));
4700 node->p.knot = NULL;
4701 }
4703 if (node->n.knot) {
4704 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node);
4705 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node);
4706 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node);
4707 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node);
4708 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node);
4709 g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node);
4710 g_object_unref(G_OBJECT(node->n.knot));
4711 node->n.knot = NULL;
4712 }
4714 if (node->p.line)
4715 gtk_object_destroy(GTK_OBJECT(node->p.line));
4716 if (node->n.line)
4717 gtk_object_destroy(GTK_OBJECT(node->n.line));
4719 if (sp->nodes) { // there are others nodes on the subpath
4720 if (sp->closed) {
4721 if (sp->first == node) {
4722 g_assert(sp->last == node);
4723 sp->first = node->n.other;
4724 sp->last = sp->first;
4725 }
4726 node->p.other->n.other = node->n.other;
4727 node->n.other->p.other = node->p.other;
4728 } else {
4729 if (sp->first == node) {
4730 sp->first = node->n.other;
4731 sp->first->code = NR_MOVETO;
4732 }
4733 if (sp->last == node) sp->last = node->p.other;
4734 if (node->p.other) node->p.other->n.other = node->n.other;
4735 if (node->n.other) node->n.other->p.other = node->p.other;
4736 }
4737 } else { // this was the last node on subpath
4738 sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp);
4739 }
4741 g_mem_chunk_free(nodechunk, node);
4742 }
4744 /**
4745 * Returns one of the node's two sides.
4746 * \param which Indicates which side.
4747 * \return Pointer to previous node side if which==-1, next if which==1.
4748 */
4749 static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which)
4750 {
4751 g_assert(node);
4752 Inkscape::NodePath::NodeSide * result = 0;
4753 switch (which) {
4754 case -1:
4755 result = &node->p;
4756 break;
4757 case 1:
4758 result = &node->n;
4759 break;
4760 default:
4761 g_assert_not_reached();
4762 }
4764 return result;
4765 }
4767 /**
4768 * Return the other side of the node, given one of its sides.
4769 */
4770 static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me)
4771 {
4772 g_assert(node);
4773 Inkscape::NodePath::NodeSide *result = 0;
4775 if (me == &node->p) {
4776 result = &node->n;
4777 } else if (me == &node->n) {
4778 result = &node->p;
4779 } else {
4780 g_assert_not_reached();
4781 }
4783 return result;
4784 }
4786 /**
4787 * Return NRPathcode on the given side of the node.
4788 */
4789 static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me)
4790 {
4791 g_assert(node);
4793 NRPathcode result = NR_END;
4794 if (me == &node->p) {
4795 if (node->p.other) {
4796 result = (NRPathcode)node->code;
4797 } else {
4798 result = NR_MOVETO;
4799 }
4800 } else if (me == &node->n) {
4801 if (node->n.other) {
4802 result = (NRPathcode)node->n.other->code;
4803 } else {
4804 result = NR_MOVETO;
4805 }
4806 } else {
4807 g_assert_not_reached();
4808 }
4810 return result;
4811 }
4813 /**
4814 * Return node with the given index
4815 */
4816 Inkscape::NodePath::Node *
4817 sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index)
4818 {
4819 Inkscape::NodePath::Node *e = NULL;
4821 if (!nodepath) {
4822 return e;
4823 }
4825 //find segment
4826 for (GList *l = nodepath->subpaths; l ; l=l->next) {
4828 Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data;
4829 int n = g_list_length(sp->nodes);
4830 if (sp->closed) {
4831 n++;
4832 }
4834 //if the piece belongs to this subpath grab it
4835 //otherwise move onto the next subpath
4836 if (index < n) {
4837 e = sp->first;
4838 for (int i = 0; i < index; ++i) {
4839 e = e->n.other;
4840 }
4841 break;
4842 } else {
4843 if (sp->closed) {
4844 index -= (n+1);
4845 } else {
4846 index -= n;
4847 }
4848 }
4849 }
4851 return e;
4852 }
4854 /**
4855 * Returns plain text meaning of node type.
4856 */
4857 static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node)
4858 {
4859 unsigned retracted = 0;
4860 bool endnode = false;
4862 for (int which = -1; which <= 1; which += 2) {
4863 Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which);
4864 if (side->other && Geom::L2(side->pos - node->pos) < 1e-6)
4865 retracted ++;
4866 if (!side->other)
4867 endnode = true;
4868 }
4870 if (retracted == 0) {
4871 if (endnode) {
4872 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4873 return _("end node");
4874 } else {
4875 switch (node->type) {
4876 case Inkscape::NodePath::NODE_CUSP:
4877 // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial
4878 return _("cusp");
4879 case Inkscape::NodePath::NODE_SMOOTH:
4880 // TRANSLATORS: "smooth" is an adjective here
4881 return _("smooth");
4882 case Inkscape::NodePath::NODE_AUTO:
4883 return _("auto");
4884 case Inkscape::NodePath::NODE_SYMM:
4885 return _("symmetric");
4886 }
4887 }
4888 } else if (retracted == 1) {
4889 if (endnode) {
4890 // TRANSLATORS: "end" is an adjective here (NOT a verb)
4891 return _("end node, handle retracted (drag with <b>Shift</b> to extend)");
4892 } else {
4893 return _("one handle retracted (drag with <b>Shift</b> to extend)");
4894 }
4895 } else {
4896 return _("both handles retracted (drag with <b>Shift</b> to extend)");
4897 }
4899 return NULL;
4900 }
4902 /**
4903 * Handles content of statusbar as long as node tool is active.
4904 */
4905 void
4906 sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection
4907 {
4908 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");
4909 gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node");
4911 gint total_nodes = sp_nodepath_get_node_count(nodepath);
4912 gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath);
4913 gint total_subpaths = sp_nodepath_get_subpath_count(nodepath);
4914 gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath);
4916 SPDesktop *desktop = NULL;
4917 if (nodepath) {
4918 desktop = nodepath->desktop;
4919 } else {
4920 desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above
4921 }
4923 SPEventContext *ec = desktop->event_context;
4924 if (!ec) return;
4926 Inkscape::MessageContext *mc = get_message_context(ec);
4927 if (!mc) return;
4929 inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
4931 if (selected_nodes == 0) {
4932 Inkscape::Selection *sel = desktop->selection;
4933 if (!sel || sel->isEmpty()) {
4934 mc->setF(Inkscape::NORMAL_MESSAGE,
4935 _("Select a single object to edit its nodes or handles."));
4936 } else {
4937 if (nodepath) {
4938 mc->setF(Inkscape::NORMAL_MESSAGE,
4939 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.",
4940 "<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.",
4941 total_nodes),
4942 total_nodes);
4943 } else {
4944 if (g_slist_length((GSList *)sel->itemList()) == 1) {
4945 mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it."));
4946 } else {
4947 mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles."));
4948 }
4949 }
4950 }
4951 } else if (nodepath && selected_nodes == 1) {
4952 mc->setF(Inkscape::NORMAL_MESSAGE,
4953 ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.",
4954 "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.",
4955 total_nodes),
4956 selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one);
4957 } else {
4958 if (selected_subpaths > 1) {
4959 mc->setF(Inkscape::NORMAL_MESSAGE,
4960 ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4961 "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.",
4962 total_nodes),
4963 selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected);
4964 } else {
4965 mc->setF(Inkscape::NORMAL_MESSAGE,
4966 ngettext("<b>%i</b> of <b>%i</b> node selected. %s.",
4967 "<b>%i</b> of <b>%i</b> nodes selected. %s.",
4968 total_nodes),
4969 selected_nodes, total_nodes, when_selected);
4970 }
4971 }
4972 }
4974 /*
4975 * returns a *copy* of the curve of that object.
4976 */
4977 SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
4978 if (!object)
4979 return NULL;
4981 SPCurve *curve = NULL;
4982 if (SP_IS_PATH(object)) {
4983 SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
4984 curve = curve_new->copy();
4985 } else if ( IS_LIVEPATHEFFECT(object) && key) {
4986 const gchar *svgd = object->repr->attribute(key);
4987 if (svgd) {
4988 Geom::PathVector pv = sp_svg_read_pathv(svgd);
4989 SPCurve *curve_new = new SPCurve(pv);
4990 if (curve_new) {
4991 curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
4992 }
4993 }
4994 }
4996 return curve;
4997 }
4999 void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) {
5000 if (!np || !np->object || !curve)
5001 return;
5003 if (SP_IS_PATH(np->object)) {
5004 if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) {
5005 sp_path_set_original_curve(SP_PATH(np->object), curve, true, false);
5006 } else {
5007 sp_shape_set_curve(SP_SHAPE(np->object), curve, true);
5008 }
5009 } else if ( IS_LIVEPATHEFFECT(np->object) ) {
5010 Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe();
5011 if (lpe) {
5012 Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) );
5013 if (pathparam) {
5014 pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG
5015 np->object->requestModified(SP_OBJECT_MODIFIED_FLAG);
5016 }
5017 }
5018 }
5019 }
5021 /*
5022 SPCanvasItem *
5023 sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) {
5024 return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path));
5025 }
5026 */
5028 /*
5029 SPCanvasItem *
5030 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const SPItem *item, guint32 color = 0xff0000ff) {
5031 SPCurve *flash_curve = curve->copy();
5032 Geom::Matrix i2d = item ? sp_item_i2d_affine(item) : Geom::identity();
5033 flash_curve->transform(i2d);
5034 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5035 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5036 // unless we also flash the nodes...
5037 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5038 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5039 sp_canvas_item_show(canvasitem);
5040 flash_curve->unref();
5041 return canvasitem;
5042 }
5044 SPCanvasItem *
5045 sp_nodepath_generate_helperpath(SPDesktop *desktop, SPPath *path) {
5046 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5047 return sp_nodepath_generate_helperpath(desktop, sp_path_get_curve_for_edit(path), SP_ITEM(path),
5048 prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff));
5049 }
5050 */
5052 SPCanvasItem *
5053 sp_nodepath_helperpath_from_path(SPDesktop *desktop, SPPath *path) {
5054 SPCurve *flash_curve = sp_path_get_curve_for_edit(path)->copy();
5055 Geom::Matrix i2d = sp_item_i2d_affine(SP_ITEM(path));
5056 flash_curve->transform(i2d);
5057 SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve);
5058 // would be nice if its color could be XORed or something, now it is invisible for red stroked objects...
5059 // unless we also flash the nodes...
5060 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
5061 guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff);
5062 sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
5063 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO);
5064 sp_canvas_item_show(canvasitem);
5065 flash_curve->unref();
5066 return canvasitem;
5067 }
5069 // TODO: Merge this with sp_nodepath_make_helper_item()!
5070 void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
5071 np->show_helperpath = show;
5073 if (show) {
5074 SPCurve *helper_curve = np->curve->copy();
5075 helper_curve->transform(np->i2d);
5076 if (!np->helper_path) {
5077 //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!!
5079 np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve);
5080 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);
5081 sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
5082 sp_canvas_item_move_to_z(np->helper_path, 0);
5083 sp_canvas_item_show(np->helper_path);
5084 } else {
5085 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
5086 }
5087 helper_curve->unref();
5088 } else {
5089 if (np->helper_path) {
5090 GtkObject *temp = np->helper_path;
5091 np->helper_path = NULL;
5092 gtk_object_destroy(temp);
5093 }
5094 }
5095 }
5097 /* sp_nodepath_make_straight_path:
5098 * Prevents user from curving the path by dragging a segment or activating handles etc.
5099 * The resulting path is a linear interpolation between nodal points, with only straight segments.
5100 * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved
5101 */
5102 void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
5103 np->straight_path = true;
5104 np->show_handles = false;
5105 g_message("add code to make the path straight.");
5106 // do sp_nodepath_convert_node_type on all nodes?
5107 // coding tip: search for this text : "Make selected segments lines"
5108 }
5110 /*
5111 Local Variables:
5112 mode:c++
5113 c-file-style:"stroustrup"
5114 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
5115 indent-tabs-mode:nil
5116 fill-column:99
5117 End:
5118 */
5119 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :