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