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