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