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