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