Code

Reduce libsigc++ usage to partially fix performance regressions
[inkscape.git] / src / ui / tool / path-manipulator.cpp
1 /** @file
2  * Path manipulator - implementation
3  */
4 /* Authors:
5  *   Krzysztof KosiƄski <tweenk.pl@gmail.com>
6  *
7  * Copyright (C) 2009 Authors
8  * Released under GNU GPL, read the file 'COPYING' for more information
9  */
11 #include <string>
12 #include <sstream>
13 #include <deque>
14 #include <stdexcept>
15 #include <boost/shared_ptr.hpp>
16 #include <2geom/bezier-curve.h>
17 #include <2geom/bezier-utils.h>
18 #include <2geom/svg-path.h>
19 #include <glibmm.h>
20 #include <glibmm/i18n.h>
21 #include "ui/tool/path-manipulator.h"
22 #include "desktop.h"
23 #include "desktop-handles.h"
24 #include "display/sp-canvas.h"
25 #include "display/sp-canvas-util.h"
26 #include "display/curve.h"
27 #include "display/canvas-bpath.h"
28 #include "document.h"
29 #include "live_effects/effect.h"
30 #include "live_effects/lpeobject.h"
31 #include "live_effects/parameter/path.h"
32 #include "sp-path.h"
33 #include "helper/geom.h"
34 #include "preferences.h"
35 #include "style.h"
36 #include "ui/tool/control-point-selection.h"
37 #include "ui/tool/curve-drag-point.h"
38 #include "ui/tool/event-utils.h"
39 #include "ui/tool/multi-path-manipulator.h"
40 #include "xml/node.h"
41 #include "xml/node-observer.h"
43 namespace Inkscape {
44 namespace UI {
46 namespace {
47 /// Types of path changes that we must react to.
48 enum PathChange {
49     PATH_CHANGE_D,
50     PATH_CHANGE_TRANSFORM
51 };
53 } // anonymous namespace
55 /**
56  * Notifies the path manipulator when something changes the path being edited
57  * (e.g. undo / redo)
58  */
59 class PathManipulatorObserver : public Inkscape::XML::NodeObserver {
60 public:
61     PathManipulatorObserver(PathManipulator *p) : _pm(p), _blocked(false) {}
62     virtual void notifyAttributeChanged(Inkscape::XML::Node &, GQuark attr,
63         Util::ptr_shared<char>, Util::ptr_shared<char>)
64     {
65         // do nothing if blocked
66         if (_blocked) return;
68         GQuark path_d = g_quark_from_static_string("d");
69         GQuark path_transform = g_quark_from_static_string("transform");
70         GQuark lpe_quark = _pm->_lpe_key.empty() ? 0 : g_quark_from_string(_pm->_lpe_key.data());
72         // only react to "d" (path data) and "transform" attribute changes
73         if (attr == lpe_quark || attr == path_d) {
74             _pm->_externalChange(PATH_CHANGE_D);
75         } else if (attr == path_transform) {
76             _pm->_externalChange(PATH_CHANGE_TRANSFORM);
77         }
78     }
79     void block() { _blocked = true; }
80     void unblock() { _blocked = false; }
81 private:
82     PathManipulator *_pm;
83     bool _blocked;
84 };
86 void build_segment(Geom::PathBuilder &, Node *, Node *);
88 PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path,
89         Geom::Matrix const &et, guint32 outline_color, Glib::ustring lpe_key)
90     : PointManipulator(mpm._path_data.node_data.desktop, *mpm._path_data.node_data.selection)
91     , _subpaths(*this)
92     , _multi_path_manipulator(mpm)
93     , _path(path)
94     , _spcurve(NULL)
95     , _dragpoint(new CurveDragPoint(*this))
96     , _observer(new PathManipulatorObserver(this))
97     , _edit_transform(et)
98     , _num_selected(0)
99     , _show_handles(true)
100     , _show_outline(false)
101     , _show_path_direction(false)
102     , _live_outline(true)
103     , _live_objects(true)
104     , _lpe_key(lpe_key)
106     if (_lpe_key.empty()) {
107         _i2d_transform = sp_item_i2d_affine(SP_ITEM(path));
108     } else {
109         _i2d_transform = Geom::identity();
110     }
111     _d2i_transform = _i2d_transform.inverse();
112     _dragpoint->setVisible(false);
114     _getGeometry();
116     _outline = sp_canvas_bpath_new(_multi_path_manipulator._path_data.outline_group, NULL);
117     sp_canvas_item_hide(_outline);
118     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(_outline), outline_color, 1.0,
119         SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
120     sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(_outline), 0, SP_WIND_RULE_NONZERO);
122     _selection.signal_update.connect(
123         sigc::mem_fun(*this, &PathManipulator::update));
124     _selection.signal_point_changed.connect(
125         sigc::mem_fun(*this, &PathManipulator::_selectionChanged));
126     _desktop->signal_zoom_changed.connect(
127         sigc::hide( sigc::mem_fun(*this, &PathManipulator::_updateOutlineOnZoomChange)));
129     _createControlPointsFromGeometry();
131     _path->repr->addObserver(*_observer);
134 PathManipulator::~PathManipulator()
136     delete _dragpoint;
137     if (_path) _path->repr->removeObserver(*_observer);
138     delete _observer;
139     gtk_object_destroy(_outline);
140     if (_spcurve) _spcurve->unref();
141     clear();
144 /** Handle motion events to update the position of the curve drag point. */
145 bool PathManipulator::event(GdkEvent *event)
147     if (empty()) return false;
149     switch (event->type)
150     {
151     case GDK_MOTION_NOTIFY:
152         _updateDragPoint(event_point(event->motion));
153         break;
154     default: break;
155     }
156     return false;
159 /** Check whether the manipulator has any nodes. */
160 bool PathManipulator::empty() {
161     return !_path || _subpaths.empty();
164 /** Update the display and the outline of the path. */
165 void PathManipulator::update()
167     _createGeometryFromControlPoints();
170 /** Store the changes to the path in XML. */
171 void PathManipulator::writeXML()
173     if (!_path) return;
174     _observer->block();
175     if (!empty()) {
176         SP_OBJECT(_path)->updateRepr();
177         _getXMLNode()->setAttribute(_nodetypesKey().data(), _createTypeString().data());
178     } else {
179         // this manipulator will have to be destroyed right after this call
180         _getXMLNode()->removeObserver(*_observer);
181         sp_object_ref(_path);
182         _path->deleteObject(true, true);
183         sp_object_unref(_path);
184         _path = 0;
185     }
186     _observer->unblock();
188     if (!empty()) {
189         if (!_live_outline)
190             _updateOutline();
191         if (!_live_objects)
192             _setGeometry();
193     }
196 /** Remove all nodes from the path. */
197 void PathManipulator::clear()
199     // no longer necessary since nodes remove themselves from selection on destruction
200     //_removeNodesFromSelection();
201     _subpaths.clear();
204 /** Select all nodes in subpaths that have something selected. */
205 void PathManipulator::selectSubpaths()
207     for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
208         NodeList::iterator sp_start = (*i)->begin(), sp_end = (*i)->end();
209         for (NodeList::iterator j = sp_start; j != sp_end; ++j) {
210             if (j->selected()) {
211                 // if at least one of the nodes from this subpath is selected,
212                 // select all nodes from this subpath
213                 for (NodeList::iterator ins = sp_start; ins != sp_end; ++ins)
214                     _selection.insert(ins.ptr());
215                 continue;
216             }
217         }
218     }
221 /** Move the selection forward or backward by one node in each subpath, based on the sign
222  * of the parameter. */
223 void PathManipulator::shiftSelection(int dir)
225     if (dir == 0) return;
226     if (_num_selected == 0) {
227         // select the first node of the path.
228         SubpathList::iterator s = _subpaths.begin();
229         if (s == _subpaths.end()) return;
230         NodeList::iterator n = (*s)->begin();
231         if (n != (*s)->end())
232             _selection.insert(n.ptr());
233         return;
234     }
235     // We cannot do any tricks here, like iterating in different directions based on
236     // the sign and only setting the selection of nodes behind us, because it would break
237     // for closed paths.
238     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
239         std::deque<bool> sels; // I hope this is specialized for bools!
240         unsigned num = 0;
241         
242         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
243             sels.push_back(j->selected());
244             _selection.erase(j.ptr());
245             ++num;
246         }
247         if (num == 0) continue; // should never happen! zero-node subpaths are not allowed
249         num = 0;
250         // In closed subpath, shift the selection cyclically. In an open one,
251         // let the selection 'slide into nothing' at ends.
252         if (dir > 0) {
253             if ((*i)->closed()) {
254                 bool last = sels.back();
255                 sels.pop_back();
256                 sels.push_front(last);
257             } else {
258                 sels.push_front(false);
259             }
260         } else {
261             if ((*i)->closed()) {
262                 bool first = sels.front();
263                 sels.pop_front();
264                 sels.push_back(first);
265             } else {
266                 sels.push_back(false);
267                 num = 1;
268             }
269         }
271         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
272             if (sels[num]) _selection.insert(j.ptr());
273             ++num;
274         }
275     }
278 /** Invert selection in the selected subpaths. */
279 void PathManipulator::invertSelectionInSubpaths()
281     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
282         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
283             if (j->selected()) {
284                 // found selected node - invert selection in this subpath
285                 for (NodeList::iterator k = (*i)->begin(); k != (*i)->end(); ++k) {
286                     if (k->selected()) _selection.erase(k.ptr());
287                     else _selection.insert(k.ptr());
288                 }
289                 // next subpath
290                 break;
291             }
292         }
293     }
296 /** Insert a new node in the middle of each selected segment. */
297 void PathManipulator::insertNodes()
299     if (_num_selected < 2) return;
301     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
302         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
303             NodeList::iterator k = j.next();
304             if (k && j->selected() && k->selected()) {
305                 j = subdivideSegment(j, 0.5);
306                 _selection.insert(j.ptr());
307             }
308         }
309     }
312 /** Replace contiguous selections of nodes in each subpath with one node. */
313 void PathManipulator::weldNodes(NodeList::iterator preserve_pos)
315     if (_num_selected < 2) return;
316     hideDragPoint();
318     bool pos_valid = preserve_pos;
319     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
320         SubpathPtr sp = *i;
321         unsigned num_selected = 0, num_unselected = 0;
322         for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
323             if (j->selected()) ++num_selected;
324             else ++num_unselected;
325         }
326         if (num_selected < 2) continue;
327         if (num_unselected == 0) {
328             // if all nodes in a subpath are selected, the operation doesn't make much sense
329             continue;
330         }
332         // Start from unselected node in closed paths, so that we don't start in the middle
333         // of a selection
334         NodeList::iterator sel_beg = sp->begin(), sel_end;
335         if (sp->closed()) {
336             while (sel_beg->selected()) ++sel_beg;
337         }
339         // Work loop
340         while (num_selected > 0) {
341             // Find selected node
342             while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
343             if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
344                 "but there are still nodes to process!");
346             // note: this is initialized to zero, because the loop below counts sel_beg as well
347             // the loop conditions are simpler that way
348             unsigned num_points = 0;
349             bool use_pos = false;
350             Geom::Point back_pos, front_pos;
351             back_pos = *sel_beg->back();
353             for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
354                 ++num_points;
355                 front_pos = *sel_end->front();
356                 if (pos_valid && sel_end == preserve_pos) use_pos = true;
357             }
358             if (num_points > 1) {
359                 Geom::Point joined_pos;
360                 if (use_pos) {
361                     joined_pos = preserve_pos->position();
362                     pos_valid = false;
363                 } else {
364                     joined_pos = Geom::middle_point(back_pos, front_pos);
365                 }
366                 sel_beg->setType(NODE_CUSP, false);
367                 sel_beg->move(joined_pos);
368                 // do not move handles if they aren't degenerate
369                 if (!sel_beg->back()->isDegenerate()) {
370                     sel_beg->back()->setPosition(back_pos);
371                 }
372                 if (!sel_end.prev()->front()->isDegenerate()) {
373                     sel_beg->front()->setPosition(front_pos);
374                 }
375                 sel_beg = sel_beg.next();
376                 while (sel_beg != sel_end) {
377                     NodeList::iterator next = sel_beg.next();
378                     sp->erase(sel_beg);
379                     sel_beg = next;
380                     --num_selected;
381                 }
382             }
383             --num_selected; // for the joined node or single selected node
384         }
385     }
388 /** Remove nodes in the middle of selected segments. */
389 void PathManipulator::weldSegments()
391     if (_num_selected < 2) return;
392     hideDragPoint();
394     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
395         SubpathPtr sp = *i;
396         unsigned num_selected = 0, num_unselected = 0;
397         for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
398             if (j->selected()) ++num_selected;
399             else ++num_unselected;
400         }
401         if (num_selected < 3) continue;
402         if (num_unselected == 0 && sp->closed()) {
403             // if all nodes in a closed subpath are selected, the operation doesn't make much sense
404             continue;
405         }
407         // Start from unselected node in closed paths, so that we don't start in the middle
408         // of a selection
409         NodeList::iterator sel_beg = sp->begin(), sel_end;
410         if (sp->closed()) {
411             while (sel_beg->selected()) ++sel_beg;
412         }
414         // Work loop
415         while (num_selected > 0) {
416             // Find selected node
417             while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
418             if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
419                 "but there are still nodes to process!");
421             // note: this is initialized to zero, because the loop below counts sel_beg as well
422             // the loop conditions are simpler that way
423             unsigned num_points = 0;
425             // find the end of selected segment
426             for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
427                 ++num_points;
428             }
429             if (num_points > 2) {
430                 // remove nodes in the middle
431                 sel_beg = sel_beg.next();
432                 while (sel_beg != sel_end.prev()) {
433                     NodeList::iterator next = sel_beg.next();
434                     sp->erase(sel_beg);
435                     sel_beg = next;
436                 }
437                 sel_beg = sel_end;
438             }
439             num_selected -= num_points;
440         }
441     }
444 /** Break the subpath at selected nodes. It also works for single node closed paths. */
445 void PathManipulator::breakNodes()
447     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
448         SubpathPtr sp = *i;
449         NodeList::iterator cur = sp->begin(), end = sp->end();
450         if (!sp->closed()) {
451             // Each open path must have at least two nodes so no checks are required.
452             // For 2-node open paths, cur == end
453             ++cur;
454             --end;
455         }
456         for (; cur != end; ++cur) {
457             if (!cur->selected()) continue;
458             SubpathPtr ins;
459             bool becomes_open = false;
461             if (sp->closed()) {
462                 // Move the node to break at to the beginning of path
463                 if (cur != sp->begin())
464                     sp->splice(sp->begin(), *sp, cur, sp->end());
465                 sp->setClosed(false);
466                 ins = sp;
467                 becomes_open = true;
468             } else {
469                 SubpathPtr new_sp(new NodeList(_subpaths));
470                 new_sp->splice(new_sp->end(), *sp, sp->begin(), cur);
471                 _subpaths.insert(i, new_sp);
472                 ins = new_sp;
473             }
475             Node *n = new Node(_multi_path_manipulator._path_data.node_data, cur->position());
476             ins->insert(ins->end(), n);
477             cur->setType(NODE_CUSP, false);
478             n->back()->setRelativePos(cur->back()->relativePos());
479             cur->back()->retract();
480             n->sink();
482             if (becomes_open) {
483                 cur = sp->begin(); // this will be increased to ++sp->begin()
484                 end = --sp->end();
485             }
486         }
487     }
490 /** Delete selected nodes in the path, optionally substituting deleted segments with bezier curves
491  * in a way that attempts to preserve the original shape of the curve. */
492 void PathManipulator::deleteNodes(bool keep_shape)
494     if (_num_selected == 0) return;
495     hideDragPoint();
497     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
498         SubpathPtr sp = *i;
500         // If there are less than 2 unselected nodes in an open subpath or no unselected nodes
501         // in a closed one, delete entire subpath.
502         unsigned num_unselected = 0, num_selected = 0;
503         for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
504             if (j->selected()) ++num_selected;
505             else ++num_unselected;
506         }
507         if (num_selected == 0) {
508             ++i;
509             continue;
510         }
511         if (sp->closed() ? (num_unselected < 1) : (num_unselected < 2)) {
512             _subpaths.erase(i++);
513             continue;
514         }
516         // In closed paths, start from an unselected node - otherwise we might start in the middle
517         // of a selected stretch and the resulting bezier fit would be suboptimal
518         NodeList::iterator sel_beg = sp->begin(), sel_end;
519         if (sp->closed()) {
520             while (sel_beg->selected()) ++sel_beg;
521         }
522         sel_end = sel_beg;
523         
524         while (num_selected > 0) {
525             while (!sel_beg->selected()) {
526                 sel_beg = sel_beg.next();
527             }
528             sel_end = sel_beg;
530             while (sel_end && sel_end->selected()) {
531                 sel_end = sel_end.next();
532             }
533             
534             num_selected -= _deleteStretch(sel_beg, sel_end, keep_shape);
535         }
536         ++i;
537     }
540 /** @brief Delete nodes between the two iterators.
541  * The given range can cross the beginning of the subpath in closed subpaths.
542  * @param start      Beginning of the range to delete
543  * @param end        End of the range
544  * @param keep_shape Whether to fit the handles at surrounding nodes to approximate
545  *                   the shape before deletion
546  * @return Number of deleted nodes */
547 unsigned PathManipulator::_deleteStretch(NodeList::iterator start, NodeList::iterator end, bool keep_shape)
549     unsigned const samples_per_segment = 10;
550     double const t_step = 1.0 / samples_per_segment;
552     unsigned del_len = 0;
553     for (NodeList::iterator i = start; i != end; i = i.next()) {
554         ++del_len;
555     }
556     if (del_len == 0) return 0;
558     // set surrounding node types to cusp if:
559     // 1. keep_shape is on, or
560     // 2. we are deleting at the end or beginning of an open path
561     if ((keep_shape || !end) && start.prev()) start.prev()->setType(NODE_CUSP, false);
562     if ((keep_shape || !start.prev()) && end) end->setType(NODE_CUSP, false);
564     if (keep_shape && start.prev() && end) {
565         unsigned num_samples = (del_len + 1) * samples_per_segment + 1;
566         Geom::Point *bezier_data = new Geom::Point[num_samples];
567         Geom::Point result[4];
568         unsigned seg = 0;
570         for (NodeList::iterator cur = start.prev(); cur != end; cur = cur.next()) {
571             Geom::CubicBezier bc(*cur, *cur->front(), *cur.next(), *cur.next()->back());
572             for (unsigned s = 0; s < samples_per_segment; ++s) {
573                 bezier_data[seg * samples_per_segment + s] = bc.pointAt(t_step * s);
574             }
575             ++seg;
576         }
577         // Fill last point
578         bezier_data[num_samples - 1] = end->position();
579         // Compute replacement bezier curve
580         // TODO the fitting algorithm sucks - rewrite it to be awesome
581         bezier_fit_cubic(result, bezier_data, num_samples, 0.5);
582         delete[] bezier_data;
584         start.prev()->front()->setPosition(result[1]);
585         end->back()->setPosition(result[2]);
586     }
588     // We can't use nl->erase(start, end), because it would break when the stretch
589     // crosses the beginning of a closed subpath
590     NodeList *nl = start->list();
591     while (start != end) {
592         NodeList::iterator next = start.next();
593         nl->erase(start);
594         start = next;
595     }
597     return del_len;
600 /** Removes selected segments */
601 void PathManipulator::deleteSegments()
603     if (_num_selected == 0) return;
604     hideDragPoint();
606     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
607         SubpathPtr sp = *i;
608         bool has_unselected = false;
609         unsigned num_selected = 0;
610         for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
611             if (j->selected()) {
612                 ++num_selected;
613             } else {
614                 has_unselected = true;
615             }
616         }
617         if (!has_unselected) {
618             _subpaths.erase(i++);
619             continue;
620         }
622         NodeList::iterator sel_beg = sp->begin();
623         if (sp->closed()) {
624             while (sel_beg && sel_beg->selected()) ++sel_beg;
625         }
626         while (num_selected > 0) {
627             if (!sel_beg->selected()) {
628                 sel_beg = sel_beg.next();
629                 continue;
630             }
631             NodeList::iterator sel_end = sel_beg;
632             unsigned num_points = 0;
633             while (sel_end && sel_end->selected()) {
634                 sel_end = sel_end.next();
635                 ++num_points;
636             }
637             if (num_points >= 2) {
638                 // Retract end handles
639                 sel_end.prev()->setType(NODE_CUSP, false);
640                 sel_end.prev()->back()->retract();
641                 sel_beg->setType(NODE_CUSP, false);
642                 sel_beg->front()->retract();
643                 if (sp->closed()) {
644                     // In closed paths, relocate the beginning of the path to the last selected
645                     // node and then unclose it. Remove the nodes from the first selected node
646                     // to the new end of path.
647                     if (sel_end.prev() != sp->begin())
648                         sp->splice(sp->begin(), *sp, sel_end.prev(), sp->end());
649                     sp->setClosed(false);
650                     sp->erase(sel_beg.next(), sp->end());
651                 } else {
652                     // for open paths:
653                     // 1. At end or beginning, delete including the node on the end or beginning
654                     // 2. In the middle, delete only inner nodes
655                     if (sel_beg == sp->begin()) {
656                         sp->erase(sp->begin(), sel_end.prev());
657                     } else if (sel_end == sp->end()) {
658                         sp->erase(sel_beg.next(), sp->end());
659                     } else {
660                         SubpathPtr new_sp(new NodeList(_subpaths));
661                         new_sp->splice(new_sp->end(), *sp, sp->begin(), sel_beg.next());
662                         _subpaths.insert(i, new_sp);
663                         if (sel_end.prev())
664                             sp->erase(sp->begin(), sel_end.prev());
665                     }
666                 }
667             }
668             sel_beg = sel_end;
669             num_selected -= num_points;
670         }
671         ++i;
672     }
675 /** Reverse subpaths of the path.
676  * @param selected_only If true, only paths that have at least one selected node
677  *                      will be reversed. Otherwise all subpaths will be reversed. */
678 void PathManipulator::reverseSubpaths(bool selected_only)
680     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
681         if (selected_only) {
682             for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
683                 if (j->selected()) {
684                     (*i)->reverse();
685                     break; // continue with the next subpath
686                 }
687             }
688         } else {
689             (*i)->reverse();
690         }
691     }
694 /** Make selected segments curves / lines. */
695 void PathManipulator::setSegmentType(SegmentType type)
697     if (_num_selected == 0) return;
698     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
699         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
700             NodeList::iterator k = j.next();
701             if (!(k && j->selected() && k->selected())) continue;
702             switch (type) {
703             case SEGMENT_STRAIGHT:
704                 if (j->front()->isDegenerate() && k->back()->isDegenerate())
705                     break;
706                 j->front()->move(*j);
707                 k->back()->move(*k);
708                 break;
709             case SEGMENT_CUBIC_BEZIER:
710                 if (!j->front()->isDegenerate() || !k->back()->isDegenerate())
711                     break;
712                 j->front()->move(j->position() + (k->position() - j->position()) / 3);
713                 k->back()->move(k->position() + (j->position() - k->position()) / 3);
714                 break;
715             }
716         }
717     }
720 /** Set the visibility of handles. */
721 void PathManipulator::showHandles(bool show)
723     if (show == _show_handles) return;
724     if (show) {
725         for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
726             for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
727                 if (!j->selected()) continue;
728                 j->showHandles(true);
729                 if (j.prev()) j.prev()->showHandles(true);
730                 if (j.next()) j.next()->showHandles(true);
731             }
732         }
733     } else {
734         for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
735             for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
736                 j->showHandles(false);
737             }
738         }
739     }
740     _show_handles = show;
743 /** Set the visibility of outline. */
744 void PathManipulator::showOutline(bool show)
746     if (show == _show_outline) return;
747     _show_outline = show;
748     _updateOutline();
751 void PathManipulator::showPathDirection(bool show)
753     if (show == _show_path_direction) return;
754     _show_path_direction = show;
755     _updateOutline();
758 void PathManipulator::setLiveOutline(bool set)
760     _live_outline = set;
763 void PathManipulator::setLiveObjects(bool set)
765     _live_objects = set;
768 void PathManipulator::setControlsTransform(Geom::Matrix const &tnew)
770     Geom::Matrix delta = _i2d_transform.inverse() * _edit_transform.inverse() * tnew * _i2d_transform;
771     _edit_transform = tnew;
772     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
773         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
774             j->transform(delta);
775         }
776     }
777     _createGeometryFromControlPoints();
780 /** Hide the curve drag point until the next motion event.
781  * This should be called at the beginning of every method that can delete nodes.
782  * Otherwise the invalidated iterator in the dragpoint can cause crashes. */
783 void PathManipulator::hideDragPoint()
785     _dragpoint->setVisible(false);
786     _dragpoint->setIterator(NodeList::iterator());
789 /** Insert a node in the segment beginning with the supplied iterator,
790  * at the given time value */
791 NodeList::iterator PathManipulator::subdivideSegment(NodeList::iterator first, double t)
793     if (!first) throw std::invalid_argument("Subdivide after invalid iterator");
794     NodeList &list = NodeList::get(first);
795     NodeList::iterator second = first.next();
796     if (!second) throw std::invalid_argument("Subdivide after last node in open path");
798     // We need to insert the segment after 'first'. We can't simply use 'second'
799     // as the point of insertion, because when 'first' is the last node of closed path,
800     // the new node will be inserted as the first node instead.
801     NodeList::iterator insert_at = first;
802     ++insert_at;
804     NodeList::iterator inserted;
805     if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
806         // for a line segment, insert a cusp node
807         Node *n = new Node(_multi_path_manipulator._path_data.node_data,
808             Geom::lerp(t, first->position(), second->position()));
809         n->setType(NODE_CUSP, false);
810         inserted = list.insert(insert_at, n);
811     } else {
812         // build bezier curve and subdivide
813         Geom::CubicBezier temp(first->position(), first->front()->position(),
814             second->back()->position(), second->position());
815         std::pair<Geom::CubicBezier, Geom::CubicBezier> div = temp.subdivide(t);
816         std::vector<Geom::Point> seg1 = div.first.points(), seg2 = div.second.points();
818         // set new handle positions
819         Node *n = new Node(_multi_path_manipulator._path_data.node_data, seg2[0]);
820         n->back()->setPosition(seg1[2]);
821         n->front()->setPosition(seg2[1]);
822         n->setType(NODE_SMOOTH, false);
823         inserted = list.insert(insert_at, n);
825         first->front()->move(seg1[1]);
826         second->back()->move(seg2[2]);
827     }
828     return inserted;
831 /** Find the node that is closest/farthest from the origin
832  * @param origin Point of reference
833  * @param search_selected Consider selected nodes
834  * @param search_unselected Consider unselected nodes
835  * @param closest If true, return closest node, if false, return farthest
836  * @return The matching node, or an empty iterator if none found
837  */
838 NodeList::iterator PathManipulator::extremeNode(NodeList::iterator origin, bool search_selected,
839     bool search_unselected, bool closest)
841     NodeList::iterator match;
842     double extr_dist = closest ? HUGE_VAL : -HUGE_VAL;
843     if (_num_selected == 0 && !search_unselected) return match;
845     for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
846         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
847             if(j->selected()) {
848                 if (!search_selected) continue;
849             } else {
850                 if (!search_unselected) continue;
851             }
852             double dist = Geom::distance(*j, *origin);
853             bool cond = closest ? (dist < extr_dist) : (dist > extr_dist);
854             if (cond) {
855                 match = j;
856                 extr_dist = dist;
857             }
858         }
859     }
860     return match;
863 /** Called by the XML observer when something else than us modifies the path. */
864 void PathManipulator::_externalChange(unsigned type)
866     switch (type) {
867     case PATH_CHANGE_D: {
868         _getGeometry();
870         // ugly: stored offsets of selected nodes in a vector
871         // vector<bool> should be specialized so that it takes only 1 bit per value
872         std::vector<bool> selpos;
873         for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
874             for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
875                 selpos.push_back(j->selected());
876             }
877         }
878         unsigned size = selpos.size(), curpos = 0;
880         _createControlPointsFromGeometry();
882         for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
883             for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
884                 if (curpos >= size) goto end_restore;
885                 if (selpos[curpos]) _selection.insert(j.ptr());
886                 ++curpos;
887             }
888         }
889         end_restore:
891         _updateOutline();
892         } break;
893     case PATH_CHANGE_TRANSFORM: {
894         Geom::Matrix i2d_change = _d2i_transform;
895         _i2d_transform = sp_item_i2d_affine(SP_ITEM(_path));
896         _d2i_transform = _i2d_transform.inverse();
897         i2d_change *= _i2d_transform;
898         for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
899             for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
900                 j->transform(i2d_change);
901             }
902         }
903         _updateOutline();
904         } break;
905     default: break;
906     }
909 /** Create nodes and handles based on the XML of the edited path. */
910 void PathManipulator::_createControlPointsFromGeometry()
912     clear();
914     // sanitize pathvector and store it in SPCurve,
915     // so that _updateDragPoint doesn't crash on paths with naked movetos
916     Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(_spcurve->get_pathvector());
917     for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {
918         if (i->empty()) pathv.erase(i++);
919         else ++i;
920     }
921     _spcurve->set_pathvector(pathv);
923     pathv *= (_edit_transform * _i2d_transform);
925     // in this loop, we know that there are no zero-segment subpaths
926     for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
927         // prepare new subpath
928         SubpathPtr subpath(new NodeList(_subpaths));
929         _subpaths.push_back(subpath);
931         Node *previous_node = new Node(_multi_path_manipulator._path_data.node_data, pit->initialPoint());
932         subpath->push_back(previous_node);
933         Geom::Curve const &cseg = pit->back_closed();
934         bool fuse_ends = pit->closed()
935             && Geom::are_near(cseg.initialPoint(), cseg.finalPoint());
937         for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) {
938             Geom::Point pos = cit->finalPoint();
939             Node *current_node;
940             // if the closing segment is degenerate and the path is closed, we need to move
941             // the handle of the first node instead of creating a new one
942             if (fuse_ends && cit == --(pit->end_open())) {
943                 current_node = subpath->begin().get_pointer();
944             } else {
945                 /* regardless of segment type, create a new node at the end
946                  * of this segment (unless this is the last segment of a closed path
947                  * with a degenerate closing segment */
948                 current_node = new Node(_multi_path_manipulator._path_data.node_data, pos);
949                 subpath->push_back(current_node);
950             }
951             // if this is a bezier segment, move handles appropriately
952             if (Geom::CubicBezier const *cubic_bezier =
953                 dynamic_cast<Geom::CubicBezier const*>(&*cit))
954             {
955                 std::vector<Geom::Point> points = cubic_bezier->points();
957                 previous_node->front()->setPosition(points[1]);
958                 current_node ->back() ->setPosition(points[2]);
959             }
960             previous_node = current_node;
961         }
962         // If the path is closed, make the list cyclic
963         if (pit->closed()) subpath->setClosed(true);
964     }
966     // we need to set the nodetypes after all the handles are in place,
967     // so that pickBestType works correctly
968     // TODO maybe migrate to inkscape:node-types?
969     gchar const *nts_raw = _path ? _path->repr->attribute(_nodetypesKey().data()) : 0;
970     std::string nodetype_string = nts_raw ? nts_raw : "";
971     /* Calculate the needed length of the nodetype string.
972      * For closed paths, the entry is duplicated for the starting node,
973      * so we can just use the count of segments including the closing one
974      * to include the extra end node. */
975     std::string::size_type nodetype_len = 0;
976     for (Geom::PathVector::const_iterator i = pathv.begin(); i != pathv.end(); ++i) {
977         if (i->empty()) continue;
978         nodetype_len += i->size_closed();
979     }
980     /* pad the string to required length with a bogus value.
981      * 'b' and any other letter not recognized by the parser causes the best fit to be set
982      * as the node type */
983     if (nodetype_len > nodetype_string.size()) {
984         nodetype_string.append(nodetype_len - nodetype_string.size(), 'b');
985     }
986     std::string::iterator tsi = nodetype_string.begin();
987     for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
988         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
989             j->setType(Node::parse_nodetype(*tsi++), false);
990         }
991         if ((*i)->closed()) {
992             // STUPIDITY ALERT: it seems we need to use the duplicate type symbol instead of
993             // the first one to remain backward compatible.
994             (*i)->begin()->setType(Node::parse_nodetype(*tsi++), false);
995         }
996     }
999 /** Construct the geometric representation of nodes and handles, update the outline
1000  * and display */
1001 void PathManipulator::_createGeometryFromControlPoints()
1003     Geom::PathBuilder builder;
1004     for (std::list<SubpathPtr>::iterator spi = _subpaths.begin(); spi != _subpaths.end(); ) {
1005         SubpathPtr subpath = *spi;
1006         if (subpath->empty()) {
1007             _subpaths.erase(spi++);
1008             continue;
1009         }
1010         NodeList::iterator prev = subpath->begin();
1011         builder.moveTo(prev->position());
1013         for (NodeList::iterator i = ++subpath->begin(); i != subpath->end(); ++i) {
1014             build_segment(builder, prev.ptr(), i.ptr());
1015             prev = i;
1016         }
1017         if (subpath->closed()) {
1018             // Here we link the last and first node if the path is closed.
1019             // If the last segment is Bezier, we add it.
1020             if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()) {
1021                 build_segment(builder, prev.ptr(), subpath->begin().ptr());
1022             }
1023             // if that segment is linear, we just call closePath().
1024             builder.closePath();
1025         }
1026         ++spi;
1027     }
1028     builder.finish();
1029     _spcurve->set_pathvector(builder.peek() * (_edit_transform * _i2d_transform).inverse());
1030     if (_live_outline)
1031         _updateOutline();
1032     if (_live_objects)
1033         _setGeometry();
1036 /** Build one segment of the geometric representation.
1037  * @relates PathManipulator */
1038 void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)
1040     if (cur_node->back()->isDegenerate() && prev_node->front()->isDegenerate())
1041     {
1042         // NOTE: It seems like the renderer cannot correctly handle vline / hline segments,
1043         // and trying to display a path using them results in funny artifacts.
1044         builder.lineTo(cur_node->position());
1045     } else {
1046         // this is a bezier segment
1047         builder.curveTo(
1048             prev_node->front()->position(),
1049             cur_node->back()->position(),
1050             cur_node->position());
1051     }
1054 /** Construct a node type string to store in the sodipodi:nodetypes attribute. */
1055 std::string PathManipulator::_createTypeString()
1057     // precondition: no single-node subpaths
1058     std::stringstream tstr;
1059     for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
1060         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
1061             tstr << j->type();
1062         }
1063         // nodestring format peculiarity: first node is counted twice for closed paths
1064         if ((*i)->closed()) tstr << (*i)->begin()->type();
1065     }
1066     return tstr.str();
1069 /** Update the path outline. */
1070 void PathManipulator::_updateOutline()
1072     if (!_show_outline) {
1073         sp_canvas_item_hide(_outline);
1074         return;
1075     }
1077     Geom::PathVector pv = _spcurve->get_pathvector();
1078     pv *= (_edit_transform * _i2d_transform);
1079     // This SPCurve thing has to be killed with extreme prejudice
1080     SPCurve *_hc = new SPCurve();
1081     if (_show_path_direction) {
1082         // To show the direction, we append additional subpaths which consist of a single
1083         // linear segment that starts at the time value of 0.5 and extends for 10 pixels
1084         // at an angle 150 degrees from the unit tangent. This creates the appearance
1085         // of little 'harpoons' that show the direction of the subpaths.
1086         Geom::PathVector arrows;
1087         for (Geom::PathVector::iterator i = pv.begin(); i != pv.end(); ++i) {
1088             Geom::Path &path = *i;
1089             for (Geom::Path::const_iterator j = path.begin(); j != path.end_default(); ++j) {
1090                 Geom::Point at = j->pointAt(0.5);
1091                 Geom::Point ut = j->unitTangentAt(0.5);
1092                 // rotate the point 
1093                 ut *= Geom::Rotate(150.0 / 180.0 * M_PI);
1094                 Geom::Point arrow_end = _desktop->w2d(
1095                     _desktop->d2w(at) + Geom::unit_vector(_desktop->d2w(ut)) * 10.0);
1097                 Geom::Path arrow(at);
1098                 arrow.appendNew<Geom::LineSegment>(arrow_end);
1099                 arrows.push_back(arrow);
1100             }
1101         }
1102         pv.insert(pv.end(), arrows.begin(), arrows.end());
1103     }
1104     _hc->set_pathvector(pv);
1105     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(_outline), _hc);
1106     sp_canvas_item_show(_outline);
1107     _hc->unref();
1110 /** Retrieve the geometry of the edited object from the object tree */
1111 void PathManipulator::_getGeometry()
1113     using namespace Inkscape::LivePathEffect;
1114     if (!_lpe_key.empty()) {
1115         Effect *lpe = LIVEPATHEFFECT(_path)->get_lpe();
1116         if (lpe) {
1117             PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
1118             if (!_spcurve)
1119                 _spcurve = new SPCurve(pathparam->get_pathvector());
1120             else
1121                 _spcurve->set_pathvector(pathparam->get_pathvector());
1122         }
1123     } else {
1124         if (_spcurve) _spcurve->unref();
1125         _spcurve = sp_path_get_curve_for_edit(_path);
1126     }
1129 /** Set the geometry of the edited object in the object tree, but do not commit to XML */
1130 void PathManipulator::_setGeometry()
1132     using namespace Inkscape::LivePathEffect;
1133     if (empty()) return;
1135     if (!_lpe_key.empty()) {
1136         // copied from nodepath.cpp
1137         // NOTE: if we are editing an LPE param, _path is not actually an SPPath, it is
1138         // a LivePathEffectObject. (mad laughter)
1139         Effect *lpe = LIVEPATHEFFECT(_path)->get_lpe();
1140         if (lpe) {
1141             PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
1142             pathparam->set_new_value(_spcurve->get_pathvector(), false);
1143             LIVEPATHEFFECT(_path)->requestModified(SP_OBJECT_MODIFIED_FLAG);
1144         }
1145     } else {
1146         if (_path->repr->attribute("inkscape:original-d"))
1147             sp_path_set_original_curve(_path, _spcurve, true, false);
1148         else
1149             sp_shape_set_curve(SP_SHAPE(_path), _spcurve, false);
1150     }
1153 /** Figure out in what attribute to store the nodetype string. */
1154 Glib::ustring PathManipulator::_nodetypesKey()
1156     if (_lpe_key.empty()) return "sodipodi:nodetypes";
1157     return _lpe_key + "-nodetypes";
1160 /** Return the XML node we are editing.
1161  * This method is wrong but necessary at the moment. */
1162 Inkscape::XML::Node *PathManipulator::_getXMLNode()
1164     if (_lpe_key.empty()) return _path->repr;
1165     return LIVEPATHEFFECT(_path)->repr;
1168 bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event)
1170     if (event->button != 1) return false;
1171     if (held_alt(*event) && held_control(*event)) {
1172         // Ctrl+Alt+click: delete nodes
1173         hideDragPoint();
1174         NodeList::iterator iter = NodeList::get_iterator(n);
1175         NodeList *nl = iter->list();
1177         if (nl->size() <= 1 || (nl->size() <= 2 && !nl->closed())) {
1178             // Removing last node of closed path - delete it
1179             nl->kill();
1180         } else {
1181             // In other cases, delete the node under cursor
1182             _deleteStretch(iter, iter.next(), true);
1183         }
1185         if (!empty()) { 
1186             update();
1187         }
1188         // We need to call MPM's method because it could have been our last node
1189         _multi_path_manipulator._doneWithCleanup(_("Delete node"));
1191         return true;
1192     } else if (held_control(*event)) {
1193         // Ctrl+click: cycle between node types
1194         if (n->isEndNode()) {
1195             if (n->type() == NODE_CUSP) {
1196                 n->setType(NODE_SMOOTH);
1197             } else {
1198                 n->setType(NODE_CUSP);
1199             }
1200         } else {
1201             n->setType(static_cast<NodeType>((n->type() + 1) % NODE_LAST_REAL_TYPE));
1202         }
1203         update();
1204         _commit(_("Cycle node type"));
1205         return true;
1206     }
1207     return false;
1210 void PathManipulator::_handleGrabbed()
1212     _selection.hideTransformHandles();
1215 void PathManipulator::_handleUngrabbed()
1217     _selection.restoreTransformHandles();
1218     _commit(_("Drag handle"));
1221 bool PathManipulator::_handleClicked(Handle *h, GdkEventButton *event)
1223     // retracting by Ctrl+click
1224     if (event->button == 1 && held_control(*event)) {
1225         h->move(h->parent()->position());
1226         update();
1227         _commit(_("Retract handle"));
1228         return true;
1229     }
1230     return false;
1233 void PathManipulator::_selectionChanged(SelectableControlPoint *p, bool selected)
1235     if (selected) ++_num_selected;
1236     else --_num_selected;
1238     // don't do anything if we do not show handles
1239     if (!_show_handles) return;
1241     // only do something if a node changed selection state
1242     Node *node = dynamic_cast<Node*>(p);
1243     if (!node) return;
1245     // update handle display
1246     NodeList::iterator iters[5];
1247     iters[2] = NodeList::get_iterator(node);
1248     iters[1] = iters[2].prev();
1249     iters[3] = iters[2].next();
1250     if (selected) {
1251         // selection - show handles on this node and adjacent ones
1252         node->showHandles(true);
1253         if (iters[1]) iters[1]->showHandles(true);
1254         if (iters[3]) iters[3]->showHandles(true);
1255     } else {
1256         /* Deselection is more complex.
1257          * The change might affect 3 nodes - this one and two adjacent.
1258          * If the node and both its neighbors are deselected, hide handles.
1259          * Otherwise, leave as is. */
1260         if (iters[1]) iters[0] = iters[1].prev();
1261         if (iters[3]) iters[4] = iters[3].next();
1262         bool nodesel[5];
1263         for (int i = 0; i < 5; ++i) {
1264             nodesel[i] = iters[i] && iters[i]->selected();
1265         }
1266         for (int i = 1; i < 4; ++i) {
1267             if (iters[i] && !nodesel[i-1] && !nodesel[i] && !nodesel[i+1]) {
1268                 iters[i]->showHandles(false);
1269             }
1270         }
1271     }
1274 /** Removes all nodes belonging to this manipulator from the control pont selection */
1275 void PathManipulator::_removeNodesFromSelection()
1277     // remove this manipulator's nodes from selection
1278     for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
1279         for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
1280             _selection.erase(j.get_pointer());
1281         }
1282     }
1285 /** Update the XML representation and put the specified annotation on the undo stack */
1286 void PathManipulator::_commit(Glib::ustring const &annotation)
1288     writeXML();
1289     sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, annotation.data());
1292 /** Update the position of the curve drag point such that it is over the nearest
1293  * point of the path. */
1294 void PathManipulator::_updateDragPoint(Geom::Point const &evp)
1296     // TODO find a way to make this faster (no transform required)
1297     Geom::PathVector pv = _spcurve->get_pathvector() * (_edit_transform * _i2d_transform);
1298     boost::optional<Geom::PathVectorPosition> pvp
1299         = Geom::nearestPoint(pv, _desktop->w2d(evp));
1300     if (!pvp) return;
1301     Geom::Point nearest_point = _desktop->d2w(pv.at(pvp->path_nr).pointAt(pvp->t));
1302     
1303     double fracpart;
1304     std::list<SubpathPtr>::iterator spi = _subpaths.begin();
1305     for (unsigned i = 0; i < pvp->path_nr; ++i, ++spi) {}
1306     NodeList::iterator first = (*spi)->before(pvp->t, &fracpart);
1307     
1308     double stroke_tolerance = _getStrokeTolerance();
1309     if (Geom::distance(evp, nearest_point) < stroke_tolerance) {
1310         _dragpoint->setVisible(true);
1311         _dragpoint->setPosition(_desktop->w2d(nearest_point));
1312         _dragpoint->setSize(2 * stroke_tolerance);
1313         _dragpoint->setTimeValue(fracpart);
1314         _dragpoint->setIterator(first);
1315     } else {
1316         _dragpoint->setVisible(false);
1317     }
1320 /// This is called on zoom change to update the direction arrows
1321 void PathManipulator::_updateOutlineOnZoomChange()
1323     if (_show_path_direction) _updateOutline();
1326 /** Compute the radius from the edge of the path where clicks chould initiate a curve drag
1327  * or segment selection, in window coordinates. */
1328 double PathManipulator::_getStrokeTolerance()
1330     /* Stroke event tolerance is equal to half the stroke's width plus the global
1331      * drag tolerance setting.  */
1332     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1333     double ret = prefs->getIntLimited("/options/dragtolerance/value", 2, 0, 100);
1334     if (_path && !SP_OBJECT_STYLE(_path)->stroke.isNone()) {
1335         ret += SP_OBJECT_STYLE(_path)->stroke_width.computed * 0.5
1336             * (_edit_transform * _i2d_transform).descrim() // scale to desktop coords
1337             * _desktop->current_zoom(); // == _d2w.descrim() - scale to window coords
1338     }
1339     return ret;
1342 } // namespace UI
1343 } // namespace Inkscape
1345 /*
1346   Local Variables:
1347   mode:c++
1348   c-file-style:"stroustrup"
1349   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1350   indent-tabs-mode:nil
1351   fill-column:99
1352   End:
1353 */
1354 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :