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