Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
path-manipulator.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2009 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include <2geom/bezier-utils.h>
15#include <2geom/path-sink.h>
16#include <2geom/point.h>
17
18#include <utility>
19#include <vector>
20
21#include "display/curve.h"
23
24#include <2geom/forward.h>
25#include "helper/geom.h"
26
31
32#include "object/sp-path.h"
33#include "style.h"
34
35#include "ui/icon-names.h"
39#include "ui/tool/node-types.h"
41#include "ui/tools/node-tool.h"
44#include "xml/node-observer.h"
45
46namespace Inkscape::UI {
47
48namespace {
49
51enum PathChange {
52 PATH_CHANGE_D,
53 PATH_CHANGE_TRANSFORM
54};
55
56} // anonymous namespace
57static constexpr double BSPLINE_TOL = 0.001;
58static constexpr double NO_POWER = 0.0;
59static constexpr double DEFAULT_START_POWER = 1.0/3.0;
60
65class PathManipulatorObserver : public Inkscape::XML::NodeObserver {
66public:
67 PathManipulatorObserver(PathManipulator *p, Inkscape::XML::Node *node)
68 : _pm(p)
69 , _node(node)
70 , _blocked(false)
71 {
73 _node->addObserver(*this);
74 }
75
76 ~PathManipulatorObserver() override {
77 _node->removeObserver(*this);
79 }
80
81 void notifyAttributeChanged(Inkscape::XML::Node &/*node*/, GQuark attr,
83 {
84 // do nothing if blocked
85 if (_blocked) return;
86
87 GQuark path_d = g_quark_from_static_string("d");
88 GQuark path_transform = g_quark_from_static_string("transform");
89 GQuark lpe_quark = _pm->_lpe_key.empty() ? 0 : g_quark_from_string(_pm->_lpe_key.data());
90
91 // only react to "d" (path data) and "transform" attribute changes
92 if (attr == lpe_quark || attr == path_d) {
93 _pm->_externalChange(PATH_CHANGE_D);
94 } else if (attr == path_transform) {
95 _pm->_externalChange(PATH_CHANGE_TRANSFORM);
96 }
97 }
98
99 void block() { _blocked = true; }
100 void unblock() { _blocked = false; }
101private:
102 PathManipulator *_pm;
103 Inkscape::XML::Node *_node;
104 bool _blocked;
105};
106
109 Geom::Affine const &et, guint32 outline_color, Glib::ustring lpe_key)
110 : PointManipulator(mpm._path_data.node_data.desktop, *mpm._path_data.node_data.selection)
111 , _subpaths(*this)
112 , _multi_path_manipulator(mpm)
113 , _path(path)
114 , _dragpoint(new CurveDragPoint(*this))
115 , /* XML Tree being used here directly while it shouldn't be*/_observer(new PathManipulatorObserver(this, path->getRepr()))
116 , _edit_transform(et)
117 , _lpe_key(std::move(lpe_key))
118{
119 auto lpeobj = cast<LivePathEffectObject>(_path);
120 auto pathshadow = cast<SPPath>(_path);
121 if (!lpeobj) {
122 _i2d_transform = pathshadow->i2dt_affine();
123 } else {
125 }
127 _dragpoint->setVisible(false);
128
129 _getGeometry();
130
131 _outline = make_canvasitem<Inkscape::CanvasItemBpath>(_multi_path_manipulator._path_data.outline_group);
132 _outline->set_visible(false);
133 _outline->set_stroke(outline_color);
134 _outline->set_fill(0x0, SP_WIND_RULE_NONZERO);
135
137 sigc::bind(sigc::mem_fun(*this, &PathManipulator::update), false));
139 sigc::mem_fun(*this, &PathManipulator::_selectionChangedM));
141 sigc::hide( sigc::mem_fun(*this, &PathManipulator::_updateOutlineOnZoomChange)));
142
143 //Define if the path is BSpline on construction
146}
147
149{
150 delete _dragpoint;
151 delete _observer;
152 _outline.reset();
153 clear();
154}
155
158{
159 if (empty()) {
160 return false;
161 }
162
164 [&] (MotionEvent const &event) {
166 },
167 [&] (CanvasEvent const &event) {}
168 );
169
170 return false;
171}
172
175 return !_path || _subpaths.empty();
176}
177
181void PathManipulator::update(bool alert_LPE)
182{
183 _observer->block();
185 _observer->unblock();
186}
187
190{
191 if (!_live_outline)
193
194 _setGeometry();
195 if (!_path) {
196 return;
197 }
198
200 if (!node) {
201 return;
202 }
203
204 _observer->block();
205 if (!empty()) {
206 _path->updateRepr();
208 } else {
209 // this manipulator will have to be destroyed right after this call
211 _path->deleteObject(true, true);
212 _path = nullptr;
213 }
214 _observer->unblock();
215}
216
219{
220 // no longer necessary since nodes remove themselves from selection on destruction
221 //_removeNodesFromSelection();
222 _subpaths.clear();
223}
224
227{
228 for (auto & _subpath : _subpaths) {
229 NodeList::iterator sp_start = _subpath->begin(), sp_end = _subpath->end();
230 for (NodeList::iterator j = sp_start; j != sp_end; ++j) {
231 if (j->selected()) {
232 // if at least one of the nodes from this subpath is selected,
233 // select all nodes from this subpath
234 for (NodeList::iterator ins = sp_start; ins != sp_end; ++ins)
235 _selection.insert(ins.ptr());
236 continue;
237 }
238 }
239 }
240}
241
244{
245 for (auto & _subpath : _subpaths) {
246 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
247 if (j->selected()) {
248 // found selected node - invert selection in this subpath
249 for (NodeList::iterator k = _subpath->begin(); k != _subpath->end(); ++k) {
250 if (k->selected()) _selection.erase(k.ptr());
251 else _selection.insert(k.ptr());
252 }
253 // next subpath
254 break;
255 }
256 }
257 }
258}
259
262{
263 if (_selection.size() < 2) return;
264
265 for (auto & _subpath : _subpaths) {
266 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
267 NodeList::iterator k = j.next();
268 if (k && j->selected() && k->selected()) {
269 j = subdivideSegment(j, 0.5);
270 _selection.insert(j.ptr());
271 }
272 }
273 }
274}
275
277{
278 Geom::Coord dist = _updateDragPoint(pt);
279 if (dist < 1e-5) { // 1e-6 is too small, as observed occasionally when inserting a node at a snapped intersection of paths
281 }
282}
283
284void PathManipulator::insertNode(NodeList::iterator first, double t, bool take_selection)
285{
286 NodeList::iterator inserted = subdivideSegment(first, t);
287 if (take_selection) {
289 }
290 _selection.insert(inserted.ptr());
291
292 update(true);
293 _commit(_("Add node"));
294}
295
296static void
297add_or_replace_if_extremum(std::vector< std::pair<NodeList::iterator, double> > &vec,
298 double & extrvalue, double testvalue, NodeList::iterator const& node, double t)
299{
300 if (testvalue > extrvalue) {
301 // replace all extreme nodes with the new one
302 vec.clear();
303 vec.emplace_back( node, t );
304 extrvalue = testvalue;
305 } else if ( Geom::are_near(testvalue, extrvalue) ) {
306 // very rare but: extremum node at the same extreme value!!! so add it to the list
307 vec.emplace_back( node, t );
308 }
309}
310
313{
314 if (_selection.size() < 2) return;
315
316 double sign = (extremum == EXTR_MIN_X || extremum == EXTR_MIN_Y) ? -1. : 1.;
317 Geom::Dim2 dim = (extremum == EXTR_MIN_X || extremum == EXTR_MAX_X) ? Geom::X : Geom::Y;
318
319 for (auto & _subpath : _subpaths) {
320 Geom::Coord extrvalue = - Geom::infinity();
321 std::vector< std::pair<NodeList::iterator, double> > extremum_vector;
322
323 for (NodeList::iterator first = _subpath->begin(); first != _subpath->end(); ++first) {
324 NodeList::iterator second = first.next();
325 if (second && first->selected() && second->selected()) {
326 add_or_replace_if_extremum(extremum_vector, extrvalue, sign * first->position()[dim], first, 0.);
327 add_or_replace_if_extremum(extremum_vector, extrvalue, sign * second->position()[dim], first, 1.);
328 if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
329 // a line segment has is extrema at the start and end, no node should be added
330 continue;
331 } else {
332 // build 1D cubic bezier curve
333 Geom::Bezier temp1d(first->position()[dim], first->front()->position()[dim],
334 second->back()->position()[dim], second->position()[dim]);
335 // and determine extremum
336 Geom::Bezier deriv1d = derivative(temp1d);
337 std::vector<double> rs = deriv1d.roots();
338 for (double & r : rs) {
339 add_or_replace_if_extremum(extremum_vector, extrvalue, sign * temp1d.valueAt(r), first, r);
340 }
341 }
342 }
343 }
344
345 for (auto & i : extremum_vector) {
346 // don't insert node at the start or end of a segment, i.e. round values for extr_t
347 double t = i.second;
348 if ( !Geom::are_near(t - std::floor(t+0.5),0.) ) // std::floor(t+0.5) is another way of writing round(t)
349 {
350 _selection.insert( subdivideSegment(i.first, t).ptr() );
351 }
352 }
353 }
354}
355
359{
360 if (_selection.empty()) return;
361
362 for (auto & _subpath : _subpaths) {
363 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
364 if (j->selected()) {
365 NodeList::iterator k = j.next();
366 Node *n = new Node(_multi_path_manipulator._path_data.node_data, j->position());
367
368 if (k) {
369 // Move the new node to the bottom of the Z-order. This way you can drag all
370 // nodes that were selected before this operation without deselecting
371 // everything because there is a new node above.
372 n->sink();
373 }
374
375 n->front()->setPosition(j->front()->position());
376 j->front()->retract();
377 j->setType(NODE_CUSP, false);
378 _subpath->insert(k, n);
379
380 if (k) {
381 // We need to manually call the selection change callback to refresh
382 // the handle display correctly.
383 // This call changes num_selected, but we call this once for a selected node
384 // and once for an unselected node, so in the end the number stays correct.
385 _selectionChanged(j.ptr(), true);
386 _selectionChanged(n, false);
387 } else {
388 // select the new end node instead of the node just before it
389 _selection.erase(j.ptr());
391 break; // this was the end node, nothing more to do
392 }
393 }
394 }
395 }
396}
397
405{
406 // Ignore LivePathEffect paths
407 if (!_path || cast<LivePathEffectObject>(_path))
408 return;
409 // Rebuild the selected parts of each subpath
410 for (auto &subpath : _subpaths) {
411 Node *prev = nullptr;
412 bool is_last_node = false;
413 for (auto &node : *subpath) {
414 if (node.selected()) {
415 // The node positions are already transformed
416 if (!builder->inPath() || !prev) {
417 builder->moveTo(node.position());
418 } else {
419 build_segment(*builder, prev, &node);
420 }
421 prev = &node;
422 is_last_node = true;
423 } else {
424 is_last_node = false;
425 }
426 }
427
428 // Complete the path, especially for closed sub paths where the last node is selected
429 if (subpath->closed() && is_last_node) {
430 if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()) {
431 build_segment(*builder, prev, subpath->begin().ptr());
432 }
433 // if that segment is linear, we just call closePath().
434 builder->closePath();
435 }
436 }
437 builder->flush();
438}
439
442{
443 if (_selection.size() < 2) return;
445
446 bool pos_valid = preserve_pos;
447 for (auto sp : _subpaths) {
448 unsigned num_selected = 0, num_unselected = 0;
449 for (auto & j : *sp) {
450 if (j.selected()) ++num_selected;
451 else ++num_unselected;
452 }
453 if (num_selected < 2) continue;
454 if (num_unselected == 0) {
455 // if all nodes in a subpath are selected, the operation doesn't make much sense
456 continue;
457 }
458
459 // Start from unselected node in closed paths, so that we don't start in the middle
460 // of a selection
461 NodeList::iterator sel_beg = sp->begin(), sel_end;
462 if (sp->closed()) {
463 while (sel_beg->selected()) ++sel_beg;
464 }
465
466 // Work loop
467 while (num_selected > 0) {
468 // Find selected node
469 while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
470 if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
471 "but there are still nodes to process!");
472
473 // note: this is initialized to zero, because the loop below counts sel_beg as well
474 // the loop conditions are simpler that way
475 unsigned num_points = 0;
476 bool use_pos = false;
477 Geom::Point back_pos, front_pos;
478 back_pos = sel_beg->back()->position();
479
480 for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
481 ++num_points;
482 front_pos = sel_end->front()->position();
483 if (pos_valid && sel_end == preserve_pos) use_pos = true;
484 }
485 if (num_points > 1) {
486 Geom::Point joined_pos;
487 if (use_pos) {
488 joined_pos = preserve_pos->position();
489 pos_valid = false;
490 } else {
491 joined_pos = Geom::middle_point(back_pos, front_pos);
492 }
493 sel_beg->setType(NODE_CUSP, false);
494 sel_beg->move(joined_pos);
495 // do not move handles if they aren't degenerate
496 if (!sel_beg->back()->isDegenerate()) {
497 sel_beg->back()->setPosition(back_pos);
498 }
499 if (!sel_end.prev()->front()->isDegenerate()) {
500 sel_beg->front()->setPosition(front_pos);
501 }
502 sel_beg = sel_beg.next();
503 while (sel_beg != sel_end) {
504 NodeList::iterator next = sel_beg.next();
505 sp->erase(sel_beg);
506 sel_beg = next;
507 --num_selected;
508 }
509 }
510 --num_selected; // for the joined node or single selected node
511 }
512 }
513}
514
517{
518 if (_selection.size() < 2) return;
520
521 for (auto sp : _subpaths) {
522 unsigned num_selected = 0, num_unselected = 0;
523 for (auto & j : *sp) {
524 if (j.selected()) ++num_selected;
525 else ++num_unselected;
526 }
527
528 // if 2 or fewer nodes are selected, there can't be any middle points to remove.
529 if (num_selected <= 2) continue;
530
531 if (num_unselected == 0 && sp->closed()) {
532 // if all nodes in a closed subpath are selected, the operation doesn't make much sense
533 continue;
534 }
535
536 // Start from unselected node in closed paths, so that we don't start in the middle
537 // of a selection
538 NodeList::iterator sel_beg = sp->begin(), sel_end;
539 if (sp->closed()) {
540 while (sel_beg->selected()) ++sel_beg;
541 }
542
543 // Work loop
544 while (num_selected > 0) {
545 // Find selected node
546 while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
547 if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
548 "but there are still nodes to process!");
549
550 // note: this is initialized to zero, because the loop below counts sel_beg as well
551 // the loop conditions are simpler that way
552 unsigned num_points = 0;
553
554 // find the end of selected segment
555 for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
556 ++num_points;
557 }
558 if (num_points > 2) {
559 // remove nodes in the middle
560 // TODO: fit bezier to the former shape
561 sel_beg = sel_beg.next();
562 while (sel_beg != sel_end.prev()) {
563 NodeList::iterator next = sel_beg.next();
564 sp->erase(sel_beg);
565 sel_beg = next;
566 }
567 }
568 sel_beg = sel_end;
569 // decrease num_selected by the number of points processed
570 num_selected -= num_points;
571 }
572 }
573}
574
576void PathManipulator::breakNodes(bool new_nodes)
577{
578 for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
579 SubpathPtr sp = *i;
580 NodeList::iterator cur = sp->begin(), end = sp->end();
581 if (!sp->closed()) {
582 // Each open path must have at least two nodes so no checks are required.
583 // For 2-node open paths, cur == end
584 ++cur;
585 --end;
586 }
587 for (; cur != end; ++cur) {
588 if (!cur->selected()) continue;
589 SubpathPtr ins;
590 bool becomes_open = false;
591
592 if (sp->closed()) {
593 // Move the node to break at to the beginning of path
594 if (cur != sp->begin())
595 sp->splice(sp->begin(), *sp, cur, sp->end());
596 sp->setClosed(false);
597 ins = sp;
598 becomes_open = true;
599 } else {
600 SubpathPtr new_sp(new NodeList(_subpaths));
601 new_sp->splice(new_sp->end(), *sp, sp->begin(), cur);
602 _subpaths.insert(i, new_sp);
603 ins = new_sp;
604 }
605
606 // When cutting nodes, we don't want to make new nodes, they'd just be deleted anyway
607 if (new_nodes) {
608 Node *n = new Node(_multi_path_manipulator._path_data.node_data, cur->position());
609 ins->insert(ins->end(), n);
610 cur->setType(NODE_CUSP, false);
611 n->back()->setRelativePos(cur->back()->relativePos());
612 cur->back()->retract();
613 n->sink();
614 } else {
615 cur->setType(NODE_CUSP, false);
616 cur->back()->retract();
617 }
618
619 if (becomes_open) {
620 cur = sp->begin(); // this will be increased to ++sp->begin()
621 end = --sp->end();
622 }
623 }
624 }
625}
626
630{
631 if (_selection.empty()) return;
633
634 if (delete_mode == NodeDeleteMode::gap_nodes) {
635 // Break nodes first to create gaps
636 breakNodes(false);
637 } else if (delete_mode == NodeDeleteMode::gap_lines) {
638 // Delete nodes but preserve end points
639 _deleteSegments(true);
640 return;
641 }
642
643 for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
644 SubpathPtr sp = *i;
645
646 // If there are less than 2 unselected nodes in an open subpath or no unselected nodes
647 // in a closed one, delete entire subpath.
648 unsigned num_unselected = 0, num_selected = 0;
649 for (auto & j : *sp) {
650 if (j.selected()) ++num_selected;
651 else ++num_unselected;
652 }
653 if (num_selected == 0) {
654 ++i;
655 continue;
656 }
657 if (sp->closed() ? (num_unselected < 1) : (num_unselected < 2)) {
658 _subpaths.erase(i++);
659 continue;
660 }
661
662 // In closed paths, start from an unselected node - otherwise we might start in the middle
663 // of a selected stretch and the resulting bezier fit would be suboptimal
664 NodeList::iterator sel_beg = sp->begin(), sel_end;
665 if (sp->closed()) {
666 while (sel_beg->selected()) ++sel_beg;
667 }
668 sel_end = sel_beg;
669
670 while (num_selected > 0) {
671 while (sel_beg && !sel_beg->selected()) {
672 sel_beg = sel_beg.next();
673 }
674 sel_end = sel_beg;
675
676 while (sel_end && sel_end->selected()) {
677 sel_end = sel_end.next();
678 }
679
680 num_selected -= _deleteStretch(sel_beg, sel_end, delete_mode);
681 sel_beg = sel_end;
682 }
683 ++i;
684 }
685}
686
687double get_angle(const Geom::Point& p0, const Geom::Point& p1, const Geom::Point& p2) {
688 auto d1 = p1 - p0;
689 auto d2 = p1 - p2;
690 if (d1.isZero() || d2.isZero()) return M_PI;
691
692 auto a1 = atan2(d1);
693 auto a2 = atan2(d2);
694 return a1 - a2;
695}
696
707{
708 unsigned const samples_per_segment = 10;
709 double const t_step = 1.0 / samples_per_segment;
710
711 unsigned del_len = 0;
712 for (NodeList::iterator i = start; i != end; i = i.next()) {
713 ++del_len;
714 }
715 if (del_len == 0) return 0;
716
718
720 auto angle_flat = Inkscape::Preferences::get()->getDoubleLimited("/tools/node/flat-cusp-angle", 135, 1, 180);
721 for (NodeList::iterator cur = start; cur != end; cur = cur.next()) {
722 auto back = cur->back() ->isDegenerate() ? cur.prev()->position() : cur->back() ->position();
723 auto front = cur->front()->isDegenerate() ? cur.next()->position() : cur->front()->position();
724 auto angle = get_angle(back, cur->position(), front);
725 auto a = fmod(fabs(angle), 2*M_PI);
726 auto diff = fabs(a - M_PI);
727 auto tolerance = (180 - angle_flat) * M_PI / 180;
728 bool flat = diff < tolerance; // flat if *somewhat* close to 180 degrees (+-tolerance)
729 if (!flat && Geom::distance(back, front) > 1) {
730 // detected a cusp, so we'll try to remove nodes and insert line segment, rather than fitting a curve
731 // if in auto mode, or the opposite in inverse_auto
732 keep_shape = !keep_shape;
733 break;
734 }
735 }
736 }
737
738 // set surrounding node types to cusp if:
739 // we are deleting at the end or beginning of an open path
740 if (!end && start.prev()) {
741 auto p = start.prev();
742 p->setType(NODE_CUSP, false);
743 p->front()->retract();
744 }
745 if (!start.prev() && end) {
746 end->setType(NODE_CUSP, false);
747 end->back()->retract();
748 }
749
750 if (keep_shape && start.prev() && end) {
751 std::vector<InputPoint> input;
754 unsigned seg = 0;
755
756 for (NodeList::iterator cur = start.prev(); cur != end; cur = cur.next()) {
757 auto bc = Geom::CubicBezier(cur->position(), cur->front()->position(), cur.next()->back()->position(), cur.next()->position());
758 for (unsigned s = 0; s < samples_per_segment; ++s) {
759 auto t = t_step * s;
760 input.emplace_back(bc.pointAt(t), t);
761 }
762 ++seg;
763 }
764 // Fill last point
765 // last point + its slope
766 input.emplace_back(end->position(), Geom::Point(), end->back()->position(), 1.0);
767
768 // get slope for the first point
769 input.front() = InputPoint(start.prev()->position(), start.prev()->front()->position(), Geom::Point(), 0.0);
770
771 // Compute replacement bezier curve
772 bezier_fit(result, input);
773
774 start.prev()->front()->setPosition(result[1]);
775 end->back()->setPosition(result[2]);
776 }
777
778 // We can't use nl->erase(start, end), because it would break when the stretch
779 // crosses the beginning of a closed subpath
780 NodeList &nl = start->nodeList();
781 while (start != end) {
783 nl.erase(start);
784 start = next;
785 }
786 // if we are removing, we readjust the handlers
787 if (!keep_shape && _isBSpline()){
788 if(start.prev()){
789 double bspline_weight = _bsplineHandlePosition(start.prev()->back(), false);
790 start.prev()->front()->setPosition(_bsplineHandleReposition(start.prev()->front(), bspline_weight));
791 }
792 if(end){
793 double bspline_weight = _bsplineHandlePosition(end->front(), false);
794 end->back()->setPosition(_bsplineHandleReposition(end->back(),bspline_weight));
795 }
796 } else if (mode == NodeDeleteMode::line_segment) {
797 // Handle line straigtening
798 if (start.prev()) {
799 start.prev()->setType(NodeType::NODE_CUSP);
800 start.prev()->front()->move(start.prev()->position());
801 }
802 if (end) {
803 end->setType(NodeType::NODE_CUSP);
804 end->back()->move(end->position());
805 }
806 }
807
808 return del_len;
809}
810
815
817void PathManipulator::_deleteSegments(bool delete_singles)
818{
819 if (_selection.empty()) return;
821
822 for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
823 SubpathPtr sp = *i;
824 bool has_unselected = false;
825 unsigned num_selected = 0;
826 for (auto & j : *sp) {
827 if (j.selected()) {
828 ++num_selected;
829 } else {
830 has_unselected = true;
831 }
832 }
833 if (!has_unselected) {
834 _subpaths.erase(i++);
835 continue;
836 }
837
838 NodeList::iterator sel_beg = sp->begin();
839 if (sp->closed()) {
840 while (sel_beg && sel_beg->selected()) ++sel_beg;
841 }
842 while (num_selected > 0) {
843 if (!sel_beg->selected()) {
844 sel_beg = sel_beg.next();
845 continue;
846 }
847 NodeList::iterator sel_end = sel_beg;
848 unsigned num_points = 0;
849 while (sel_end && sel_end->selected()) {
850 sel_end = sel_end.next();
851 ++num_points;
852 }
853 if (num_points >= (2 - delete_singles)) {
854 // Retract end handles
855 sel_end.prev()->setType(NODE_CUSP, false);
856 sel_end.prev()->back()->retract();
857 sel_beg->setType(NODE_CUSP, false);
858 sel_beg->front()->retract();
859 if (sp->closed()) {
860 // In closed paths, relocate the beginning of the path to the last selected
861 // node and then unclose it. Remove the nodes from the first selected node
862 // to the new end of path.
863 if (sel_end.prev() != sp->begin())
864 sp->splice(sp->begin(), *sp, sel_end.prev(), sp->end());
865 sp->setClosed(false);
866 sp->erase(sel_beg.next(), sp->end());
867 } else {
868 // for open paths:
869 // 1. At end or beginning, delete including the node on the end or beginning
870 // 2. In the middle, delete only inner nodes
871 if (sel_beg == sp->begin()) {
872 sp->erase(sp->begin(), sel_end.prev());
873 } else if (sel_end == sp->end()) {
874 sp->erase(sel_beg.next(), sp->end());
875 } else {
876 SubpathPtr new_sp(new NodeList(_subpaths));
877 new_sp->splice(new_sp->end(), *sp, sp->begin(), sel_beg.next());
878 _subpaths.insert(i, new_sp);
879 if (sel_end.prev())
880 sp->erase(sp->begin(), sel_end.prev());
881 }
882 }
883 }
884 sel_beg = sel_end;
885 num_selected -= num_points;
886 }
887 ++i;
888 }
889}
890
894void PathManipulator::reverseSubpaths(bool selected_only)
895{
896 for (auto & _subpath : _subpaths) {
897 if (selected_only) {
898 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
899 if (j->selected()) {
900 _subpath->reverse();
901 break; // continue with the next subpath
902 }
903 }
904 } else {
905 _subpath->reverse();
906 }
907 }
908}
909
912{
913 if (_selection.empty()) return;
914 for (auto & _subpath : _subpaths) {
915 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
916 NodeList::iterator k = j.next();
917 if (!(k && j->selected() && k->selected())) continue;
918 switch (type) {
919 case SEGMENT_STRAIGHT:
920 if (j->front()->isDegenerate() && k->back()->isDegenerate())
921 break;
922 j->front()->move(j->position());
923 k->back()->move(k->position());
924 break;
926 if (!j->front()->isDegenerate() || !k->back()->isDegenerate())
927 break;
928 // move both handles to 1/3 of the line
929 j->front()->move(j->position() + (k->position() - j->position()) / 3);
930 k->back()->move(k->position() + (j->position() - k->position()) / 3);
931 break;
932 }
933 }
934 }
935}
936
937void PathManipulator::scaleHandle(Node *n, int which, int dir, bool pixel)
938{
939 if (n->type() == NODE_SYMMETRIC || n->type() == NODE_AUTO) {
940 n->setType(NODE_SMOOTH);
941 }
942 Handle *h = _chooseHandle(n, which);
943 double length_change;
944
945 if (pixel) {
946 length_change = 1.0 / _desktop->current_zoom() * dir;
947 } else {
949 length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000, "px");
950 length_change *= dir;
951 }
952
953 Geom::Point relpos;
954 if (h->isDegenerate()) {
955 if (dir < 0) return;
956 Node *nh = n->nodeToward(h);
957 if (!nh) return;
958 relpos = Geom::unit_vector(nh->position() - n->position()) * length_change;
959 } else {
960 relpos = h->relativePos();
961 double rellen = relpos.length();
962 relpos *= ((rellen + length_change) / rellen);
963 }
964 h->setRelativePos(relpos);
965 update();
966 gchar const *key = which < 0 ? "handle:scale:left" : "handle:scale:right";
967 _commit(_("Scale handle"), key);
968}
969
970void PathManipulator::rotateHandle(Node *n, int which, int dir, bool pixel)
971{
972 if (n->type() != NODE_CUSP) {
973 n->setType(NODE_CUSP);
974 }
975 Handle *h = _chooseHandle(n, which);
976 if (h->isDegenerate()) return;
977
978 double angle;
979 if (pixel) {
980 // Rotate by "one pixel"
981 angle = atan2(1.0 / _desktop->current_zoom(), h->length()) * dir;
982 } else {
984 int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
985 angle = M_PI * dir / snaps;
986 }
987
988 h->setRelativePos(h->relativePos() * Geom::Rotate(angle));
989 update();
990 gchar const *key = which < 0 ? "handle:rotate:left" : "handle:rotate:right";
991 _commit(_("Rotate handle"), key);
992}
993
995{
997 Node *prev = i.prev().ptr();
998 Node *next = i.next().ptr();
999
1000 // on an endnode, the remaining handle automatically wins
1001 if (!next) return n->back();
1002 if (!prev) return n->front();
1003
1004 // compare X coord offline segments
1005 Geom::Point npos = next->position();
1006 Geom::Point ppos = prev->position();
1007 if (which < 0) {
1008 // pick left handle.
1009 // we just swap the handles and pick the right handle below.
1010 std::swap(npos, ppos);
1011 }
1012
1013 if (npos[Geom::X] >= ppos[Geom::X]) {
1014 return n->front();
1015 } else {
1016 return n->back();
1017 }
1018}
1019
1022{
1023 if (show == _show_handles) return;
1024 if (show) {
1025 for (auto & _subpath : _subpaths) {
1026 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
1027 if (!j->selected()) continue;
1028 j->showHandles(true);
1029 if (j.prev()) j.prev()->showHandles(true);
1030 if (j.next()) j.next()->showHandles(true);
1031 }
1032 }
1033 } else {
1034 for (auto & _subpath : _subpaths) {
1035 for (auto & j : *_subpath) {
1036 j.showHandles(false);
1037 }
1038 }
1039 }
1040 _show_handles = show;
1041}
1042
1045{
1046 if (show == _show_outline) return;
1047 _show_outline = show;
1049}
1050
1052{
1053 if (show == _show_path_direction) return;
1054 _show_path_direction = show;
1056}
1057
1062
1067
1069{
1070 for (auto & _subpath : _subpaths) {
1071 for (auto & j : *_subpath) {
1072 j.updateHandles();
1073 }
1074 }
1075}
1076
1078{
1080 _edit_transform = tnew;
1081 for (auto & _subpath : _subpaths) {
1082 for (auto & j : *_subpath) {
1083 j.transform(delta);
1084 }
1085 }
1087}
1088
1097
1101{
1102 if (!first) throw std::invalid_argument("Subdivide after invalid iterator");
1103 NodeList &list = NodeList::get(first);
1104 NodeList::iterator second = first.next();
1105 if (!second) throw std::invalid_argument("Subdivide after last node in open path");
1106 if (first->type() == NODE_SYMMETRIC)
1107 first->setType(NODE_SMOOTH, false);
1108 if (second->type() == NODE_SYMMETRIC)
1109 second->setType(NODE_SMOOTH, false);
1110
1111 // We need to insert the segment after 'first'. We can't simply use 'second'
1112 // as the point of insertion, because when 'first' is the last node of closed path,
1113 // the new node will be inserted as the first node instead.
1114 NodeList::iterator insert_at = first;
1115 ++insert_at;
1116
1117 NodeList::iterator inserted;
1118 if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
1119 // for a line segment, insert a cusp node
1121 Geom::lerp(t, first->position(), second->position()));
1122 n->setType(NODE_CUSP, false);
1123 inserted = list.insert(insert_at, n);
1124 } else {
1125 // build bezier curve and subdivide
1126 Geom::CubicBezier temp(first->position(), first->front()->position(),
1127 second->back()->position(), second->position());
1128 std::pair<Geom::CubicBezier, Geom::CubicBezier> div = temp.subdivide(t);
1129 std::vector<Geom::Point> seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints();
1130
1131 // set new handle positions
1133 if(!_isBSpline()){
1134 n->back()->setPosition(seg1[2]);
1135 n->front()->setPosition(seg2[1]);
1136 n->setType(NODE_SMOOTH, false);
1137 } else {
1138 Geom::D2< Geom::SBasis > sbasis_inside_nodes;
1139 SPCurve line_inside_nodes;
1140 if(second->back()->isDegenerate()){
1141 line_inside_nodes.moveto(n->position());
1142 line_inside_nodes.lineto(second->position());
1143 sbasis_inside_nodes = line_inside_nodes.first_segment()->toSBasis();
1144 Geom::Point next = sbasis_inside_nodes.valueAt(DEFAULT_START_POWER);
1145 line_inside_nodes.reset();
1146 n->front()->setPosition(next);
1147 }else{
1148 n->front()->setPosition(seg2[1]);
1149 }
1150 if(first->front()->isDegenerate()){
1151 line_inside_nodes.moveto(n->position());
1152 line_inside_nodes.lineto(first->position());
1153 sbasis_inside_nodes = line_inside_nodes.first_segment()->toSBasis();
1154 Geom::Point previous = sbasis_inside_nodes.valueAt(DEFAULT_START_POWER);
1155 n->back()->setPosition(previous);
1156 }else{
1157 n->back()->setPosition(seg1[2]);
1158 }
1159 n->setType(NODE_CUSP, false);
1160 }
1161 inserted = list.insert(insert_at, n);
1162
1163 first->front()->move(seg1[1]);
1164 second->back()->move(seg2[2]);
1165 }
1166 return inserted;
1167}
1168
1177 bool search_unselected, bool closest)
1178{
1179 NodeList::iterator match;
1180 double extr_dist = closest ? HUGE_VAL : -HUGE_VAL;
1181 if (_selection.empty() && !search_unselected) return match;
1182
1183 for (auto & _subpath : _subpaths) {
1184 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
1185 if(j->selected()) {
1186 if (!search_selected) continue;
1187 } else {
1188 if (!search_unselected) continue;
1189 }
1190 double dist = Geom::distance(j->position(), origin->position());
1191 bool cond = closest ? (dist < extr_dist) : (dist > extr_dist);
1192 if (cond) {
1193 match = j;
1194 extr_dist = dist;
1195 }
1196 }
1197 }
1198 return match;
1199}
1200
1201/* Called when a process updates the path in-situe */
1203{
1204 _externalChange(PATH_CHANGE_D);
1205}
1206
1209{
1210 hideDragPoint();
1211
1212 switch (type) {
1213 case PATH_CHANGE_D: {
1214 _getGeometry();
1215
1216 // ugly: stored offsets of selected nodes in a vector
1217 // vector<bool> should be specialized so that it takes only 1 bit per value
1218 std::vector<bool> selpos;
1219 for (auto & _subpath : _subpaths) {
1220 for (auto & j : *_subpath) {
1221 selpos.push_back(j.selected());
1222 }
1223 }
1224 unsigned size = selpos.size(), curpos = 0;
1225
1227
1228 for (auto & _subpath : _subpaths) {
1229 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
1230 if (curpos >= size) goto end_restore;
1231 if (selpos[curpos]) _selection.insert(j.ptr());
1232 ++curpos;
1233 }
1234 }
1235 end_restore:
1236
1238 } break;
1239 case PATH_CHANGE_TRANSFORM: {
1240 auto path = cast<SPPath>(_path);
1241 if (path) {
1242 Geom::Affine i2d_change = _d2i_transform;
1243 _i2d_transform = path->i2dt_affine();
1245 i2d_change *= _i2d_transform;
1246 for (auto & _subpath : _subpaths) {
1247 for (auto & j : *_subpath) {
1248 j.transform(i2d_change);
1249 }
1250 }
1252 }
1253 } break;
1254 default: break;
1255 }
1256}
1257
1262
1265{
1266 clear();
1267
1268 // sanitize pathvector and store it in SPCurve,
1269 // so that _updateDragPoint doesn't crash on paths with naked movetos
1270 Geom::PathVector pathv;
1271 if (_is_bspline) {
1273 } else {
1275 }
1276 for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {
1277 // NOTE: this utilizes the fact that Geom::PathVector is an std::vector.
1278 // When we erase an element, the next one slides into position,
1279 // so we do not increment the iterator even though it is theoretically invalidated.
1280 if (i->empty()) {
1281 i = pathv.erase(i);
1282 } else {
1283 ++i;
1284 }
1285 }
1286 if (pathv.empty()) {
1287 return;
1288 }
1289 _spcurve = SPCurve(pathv);
1290
1291 pathv *= _getTransform();
1292
1293 // in this loop, we know that there are no zero-segment subpaths
1294 for (auto & pit : pathv) {
1295 // prepare new subpath
1296 SubpathPtr subpath(new NodeList(_subpaths));
1297 _subpaths.push_back(subpath);
1298
1299 Node *previous_node = new Node(_multi_path_manipulator._path_data.node_data, pit.initialPoint());
1300 subpath->push_back(previous_node);
1301
1302 bool closed = pit.closed();
1303
1304 for (Geom::Path::iterator cit = pit.begin(); cit != pit.end(); ++cit) {
1305 Geom::Point pos = cit->finalPoint();
1306 Node *current_node;
1307 // if the closing segment is degenerate and the path is closed, we need to move
1308 // the handle of the first node instead of creating a new one
1309 if (closed && cit == --(pit.end())) {
1310 current_node = subpath->begin().get_pointer();
1311 } else {
1312 /* regardless of segment type, create a new node at the end
1313 * of this segment (unless this is the last segment of a closed path
1314 * with a degenerate closing segment */
1315 current_node = new Node(_multi_path_manipulator._path_data.node_data, pos);
1316 subpath->push_back(current_node);
1317 }
1318 // if this is a bezier segment, move handles appropriately
1319 // TODO: I don't know why the dynamic cast below doesn't want to work
1320 // when I replace BezierCurve with CubicBezier. Might be a bug
1321 // somewhere in pathv_to_linear_and_cubic_beziers
1322 Geom::BezierCurve const *bezier = dynamic_cast<Geom::BezierCurve const*>(&*cit);
1323 if (bezier && bezier->order() == 3)
1324 {
1325 previous_node->front()->setPosition((*bezier)[1]);
1326 current_node ->back() ->setPosition((*bezier)[2]);
1327 }
1328 previous_node = current_node;
1329 }
1330 // If the path is closed, make the list cyclic
1331 if (pit.closed()) subpath->setClosed(true);
1332 }
1333
1334 // we need to set the nodetypes after all the handles are in place,
1335 // so that pickBestType works correctly
1336 // TODO maybe migrate to inkscape:node-types?
1337 // TODO move this into SPPath - do not manipulate directly
1338
1339 //XML Tree being used here directly while it shouldn't be.
1340 gchar const *nts_raw = _path ? _path->getRepr()->attribute(_nodetypesKey().data()) : nullptr;
1341 /* Calculate the needed length of the nodetype string.
1342 * For closed paths, the entry is duplicated for the starting node,
1343 * so we can just use the count of segments including the closing one
1344 * to include the extra end node. */
1345 /* pad the string to required length with a bogus value.
1346 * 'b' and any other letter not recognized by the parser causes the best fit to be set
1347 * as the node type */
1348 auto const *tsi = nts_raw ? nts_raw : "";
1349 for (auto & _subpath : _subpaths) {
1350 for (auto & j : *_subpath) {
1351 char nodetype = (*tsi) ? (*tsi++) : 'b';
1352 j.setType(Node::parse_nodetype(nodetype), false);
1353 }
1354 if (_subpath->closed() && *tsi) {
1355 // STUPIDITY ALERT: it seems we need to use the duplicate type symbol instead of
1356 // the first one to remain backward compatible.
1357 _subpath->begin()->setType(Node::parse_nodetype(*tsi++), false);
1358 }
1359 }
1360}
1361
1362//determines if the trace has a bspline effect and the number of steps that it takes
1364
1365 LivePathEffect::LPEBSpline const *lpe_bsp = nullptr;
1366
1367 auto path = cast<SPLPEItem>(_path);
1368 if (path){
1369 if(path->hasPathEffect()){
1370 Inkscape::LivePathEffect::Effect const *this_effect =
1371 path->getFirstPathEffectOfType(Inkscape::LivePathEffect::BSPLINE);
1372 if(this_effect){
1373 lpe_bsp = dynamic_cast<LivePathEffect::LPEBSpline const*>(this_effect->getLPEObj()->get_lpe());
1374 }
1375 }
1376 }
1377 int steps = 0;
1378 if(lpe_bsp){
1379 steps = lpe_bsp->steps+1;
1380 }
1381 return steps;
1382}
1383
1384// determines if the trace has bspline effect
1386 auto path = cast<SPPath>(_path);
1387 if (path && path->hasPathEffect()) {
1388 Inkscape::LivePathEffect::Effect const *this_effect =
1389 path->getFirstPathEffectOfType(Inkscape::LivePathEffect::BSPLINE);
1390 if(this_effect){
1391 _is_bspline = true;
1392 return;
1393 }
1394 }
1395 _is_bspline = false;
1396}
1397
1399 return _is_bspline;
1400}
1401
1402// returns the corresponding strength to the position of the handlers
1404{
1405 using Geom::X;
1406 using Geom::Y;
1407 double pos = NO_POWER;
1408 Node *n = h->parent();
1409 Node * next_node = nullptr;
1410 next_node = n->nodeToward(h);
1411 if(next_node){
1412 SPCurve line_inside_nodes;
1413 line_inside_nodes.moveto(n->position());
1414 line_inside_nodes.lineto(next_node->position());
1415 if(!are_near(h->position(), n->position())){
1416 pos = Geom::nearest_time(h->position(), *line_inside_nodes.first_segment());
1417 }
1418 }
1419 if (Geom::are_near(pos, NO_POWER, BSPLINE_TOL) && check_other){
1420 return _bsplineHandlePosition(h->other(), false);
1421 }
1422 return pos;
1423}
1424
1425// give the location for the handler in the corresponding position
1427{
1428 double pos = this->_bsplineHandlePosition(h, check_other);
1429 return _bsplineHandleReposition(h,pos);
1430}
1431
1432// give the location for the handler to the specified position
1434 using Geom::X;
1435 using Geom::Y;
1436 Geom::Point ret = h->position();
1437 Node *n = h->parent();
1438 Geom::D2< Geom::SBasis > sbasis_inside_nodes;
1439 SPCurve line_inside_nodes;
1440 Node * next_node = nullptr;
1441 next_node = n->nodeToward(h);
1442 if(next_node && !Geom::are_near(pos, NO_POWER, BSPLINE_TOL)){
1443 line_inside_nodes.moveto(n->position());
1444 line_inside_nodes.lineto(next_node->position());
1445 sbasis_inside_nodes = line_inside_nodes.first_segment()->toSBasis();
1446 ret = sbasis_inside_nodes.valueAt(pos);
1447 } else{
1449 ret = n->position();
1450 }
1451 }
1452 return ret;
1453}
1454
1460{
1462 //Refresh if is bspline some times -think on path change selection, this value get lost
1464 for (std::list<SubpathPtr>::iterator spi = _subpaths.begin(); spi != _subpaths.end(); ) {
1465 SubpathPtr subpath = *spi;
1466 if (subpath->empty()) {
1467 _subpaths.erase(spi++);
1468 continue;
1469 }
1470 NodeList::iterator prev = subpath->begin();
1471 builder.moveTo(prev->position());
1472 for (NodeList::iterator i = ++subpath->begin(); i != subpath->end(); ++i) {
1473 build_segment(builder, prev.ptr(), i.ptr());
1474 prev = i;
1475 }
1476 if (subpath->closed()) {
1477 // Here we link the last and first node if the path is closed.
1478 // If the last segment is Bezier, we add it.
1479 if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()) {
1480 build_segment(builder, prev.ptr(), subpath->begin().ptr());
1481 }
1482 // if that segment is linear, we just call closePath().
1483 builder.closePath();
1484 }
1485 ++spi;
1486 }
1487 builder.flush();
1488 Geom::PathVector pathv = builder.peek() * _getTransform().inverse();
1489 for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {
1490 // NOTE: this utilizes the fact that Geom::PathVector is an std::vector.
1491 // When we erase an element, the next one slides into position,
1492 // so we do not increment the iterator even though it is theoretically invalidated.
1493 if (i->empty()) {
1494 i = pathv.erase(i);
1495 } else {
1496 ++i;
1497 }
1498 }
1499 if (pathv.empty()) {
1500 return;
1501 }
1502
1503 if (_spcurve.get_pathvector() == pathv) {
1504 return;
1505 }
1506 _spcurve = SPCurve(pathv);
1507 if (alert_LPE) {
1509 auto path = cast<SPPath>(_path);
1510 if (path && path->hasPathEffect()) {
1511 Inkscape::LivePathEffect::Effect *this_effect =
1512 path->getFirstPathEffectOfType(Inkscape::LivePathEffect::POWERSTROKE);
1513 LivePathEffect::LPEPowerStroke *lpe_pwr = dynamic_cast<LivePathEffect::LPEPowerStroke*>(this_effect);
1514 if (lpe_pwr) {
1515 lpe_pwr->adjustForNewPath();
1516 }
1517 }
1518 }
1519 if (_live_outline) {
1521 }
1522 if (_live_objects) {
1523 _setGeometry();
1524 }
1525}
1526
1529void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)
1530{
1531 if (cur_node->back()->isDegenerate() && prev_node->front()->isDegenerate())
1532 {
1533 // NOTE: It seems like the renderer cannot correctly handle vline / hline segments,
1534 // and trying to display a path using them results in funny artifacts.
1535 builder.lineTo(cur_node->position());
1536 } else {
1537 // this is a bezier segment
1538 builder.curveTo(
1539 prev_node->front()->position(),
1540 cur_node->back()->position(),
1541 cur_node->position());
1542 }
1543}
1544
1547{
1548 // precondition: no single-node subpaths
1549 std::stringstream tstr;
1550 for (auto & _subpath : _subpaths) {
1551 for (auto & j : *_subpath) {
1552 tstr << j.type();
1553 }
1554 // nodestring format peculiarity: first node is counted twice for closed paths
1555 if (_subpath->closed()) tstr << _subpath->begin()->type();
1556 }
1557 return tstr.str();
1558}
1559
1562{
1563 if (!_show_outline) {
1564 _outline->set_visible(false);
1565 return;
1566 }
1567
1568 auto pv = _spcurve.get_pathvector() * _getTransform();
1569 // This SPCurve thing has to be killed with extreme prejudice
1571 // To show the direction, we append additional subpaths which consist of a single
1572 // linear segment that starts at the time value of 0.5 and extends for 10 pixels
1573 // at an angle 150 degrees from the unit tangent. This creates the appearance
1574 // of little 'harpoons' that show the direction of the subpaths.
1575 auto rot_scale_w2d = Geom::Rotate(210.0 / 180.0 * M_PI) * Geom::Scale(10.0) * _desktop->w2d();
1576 Geom::PathVector arrows;
1577 for (auto & path : pv) {
1578 for (Geom::Path::iterator j = path.begin(); j != path.end_default(); ++j) {
1579 Geom::Point at = j->pointAt(0.5);
1580 Geom::Point ut = j->unitTangentAt(0.5);
1581 Geom::Point arrow_end = at + (Geom::unit_vector(_desktop->d2w(ut)) * rot_scale_w2d);
1582
1583 Geom::Path arrow(at);
1584 arrow.appendNew<Geom::LineSegment>(arrow_end);
1585 arrows.push_back(arrow);
1586 }
1587 }
1588 pv.insert(pv.end(), arrows.begin(), arrows.end());
1589 }
1590 auto tmp = SPCurve(std::move(pv));
1591 _outline->set_bpath(&tmp);
1592 _outline->set_visible(true);
1593}
1594
1597{
1598 using namespace Inkscape::LivePathEffect;
1599 auto lpeobj = cast<LivePathEffectObject>(_path);
1600 auto path = cast<SPPath>(_path);
1601 if (lpeobj) {
1602 Effect *lpe = lpeobj->get_lpe();
1603 if (lpe) {
1604 PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
1605 _spcurve = SPCurve(pathparam->get_pathvector());
1606 }
1607 } else if (path) {
1608 if (path->curveForEdit()) {
1609 _spcurve = *path->curveForEdit();
1610 } else {
1611 _spcurve = SPCurve();
1612 }
1613 }
1614}
1615
1618{
1619 using namespace Inkscape::LivePathEffect;
1620 auto lpeobj = cast<LivePathEffectObject>(_path);
1621 auto path = cast<SPPath>(_path);
1622 if (lpeobj) {
1623 // copied from nodepath.cpp
1624 // NOTE: if we are editing an LPE param, _path is not actually an SPPath, it is
1625 // a LivePathEffectObject. (mad laughter)
1626 Effect *lpe = lpeobj->get_lpe();
1627 if (lpe) {
1628 PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
1629 if (pathparam->get_pathvector() == _spcurve.get_pathvector()) {
1630 return; //False we dont update LPE
1631 }
1632 pathparam->set_new_value(_spcurve.get_pathvector(), false);
1633 lpeobj->requestModified(SP_OBJECT_MODIFIED_FLAG);
1634 }
1635 } else if (path) {
1636 // return true to leave the decision on empty to the caller.
1637 // Maybe the path become empty and we want to update to empty
1638 if (empty()) return;
1639 if (path->curveBeforeLPE()) {
1640 path->setCurveBeforeLPE(&_spcurve);
1641 if (path->hasPathEffectRecursive()) {
1642 sp_lpe_item_update_patheffect(path, true, false);
1643 }
1644 } else {
1645 path->setCurve(&_spcurve);
1646 }
1647 }
1648}
1649
1652{
1653 auto lpeobj = cast<LivePathEffectObject>(_path);
1654 if (!lpeobj) {
1655 return "sodipodi:nodetypes";
1656 } else {
1657 return _lpe_key + "-nodetypes";
1658 }
1659}
1660
1664{
1665 //XML Tree being used here directly while it shouldn't be.
1666 auto lpeobj = cast<LivePathEffectObject>(_path);
1667 if (!lpeobj)
1668 return _path->getRepr();
1669 //XML Tree being used here directly while it shouldn't be.
1670 return lpeobj->getRepr();
1671}
1672
1674{
1675 if (event.button != 1) return false;
1676 if (held_alt(event) && held_ctrl(event)) {
1677 // Ctrl+Alt+click: delete nodes
1678 hideDragPoint();
1680 NodeList &nl = iter->nodeList();
1681
1682 if (nl.size() <= 1 || (nl.size() <= 2 && !nl.closed())) {
1683 // Removing last node of closed path - delete it
1684 nl.kill();
1685 } else {
1686 // In other cases, delete the node under cursor
1688 }
1689
1690 if (!empty()) {
1691 update(true);
1692 }
1693
1694 // We need to call MPM's method because it could have been our last node
1695 _multi_path_manipulator._doneWithCleanup(_("Delete node"));
1696
1697 return true;
1698 } else if (held_ctrl(event)) {
1699 // Ctrl+click: cycle between node types
1700 if (!n->isEndNode()) {
1701 n->setType(static_cast<NodeType>((n->type() + 1) % NODE_LAST_REAL_TYPE));
1702 update();
1703 _commit(_("Cycle node type"));
1704 }
1705 return true;
1706 }
1707 return false;
1708}
1709
1714
1716{
1718 _commit(_("Drag handle"));
1719}
1720
1722{
1723 // retracting by Alt+Click
1724 if (event.button == 1 && held_alt(event)) {
1725 h->move(h->parent()->position());
1726 update();
1727 _commit(_("Retract handle"));
1728 return true;
1729 }
1730 return false;
1731}
1732
1733void PathManipulator::_selectionChangedM(std::vector<SelectableControlPoint *> pvec, bool selected) {
1734 for (auto & n : pvec) {
1735 _selectionChanged(n, selected);
1736 }
1737}
1738
1740{
1741 // don't do anything if we do not show handles
1742 if (!_show_handles) return;
1743
1744 // only do something if a node changed selection state
1745 Node *node = dynamic_cast<Node*>(p);
1746 if (!node) return;
1747
1748 // update handle display
1749 NodeList::iterator iters[5];
1750 iters[2] = NodeList::get_iterator(node);
1751 iters[1] = iters[2].prev();
1752 iters[3] = iters[2].next();
1753 if (selected) {
1754 // selection - show handles on this node and adjacent ones
1755 node->showHandles(true);
1756 if (iters[1]) iters[1]->showHandles(true);
1757 if (iters[3]) iters[3]->showHandles(true);
1758 } else {
1759 /* Deselection is more complex.
1760 * The change might affect 3 nodes - this one and two adjacent.
1761 * If the node and both its neighbors are deselected, hide handles.
1762 * Otherwise, leave as is. */
1763 if (iters[1]) iters[0] = iters[1].prev();
1764 if (iters[3]) iters[4] = iters[3].next();
1765 bool nodesel[5];
1766 for (int i = 0; i < 5; ++i) {
1767 nodesel[i] = iters[i] && iters[i]->selected();
1768 }
1769 for (int i = 1; i < 4; ++i) {
1770 if (iters[i] && !nodesel[i-1] && !nodesel[i] && !nodesel[i+1]) {
1771 iters[i]->showHandles(false);
1772 }
1773 }
1774 }
1775}
1776
1779{
1780 // remove this manipulator's nodes from selection
1781 for (auto & _subpath : _subpaths) {
1782 for (NodeList::iterator j = _subpath->begin(); j != _subpath->end(); ++j) {
1783 _selection.erase(j.get_pointer());
1784 }
1785 }
1786}
1787
1789void PathManipulator::_commit(Glib::ustring const &annotation)
1790{
1791 writeXML();
1792 if (_desktop) {
1793 DocumentUndo::done(_desktop->getDocument(), annotation.data(), INKSCAPE_ICON("tool-node-editor"));
1794 }
1795}
1796
1797void PathManipulator::_commit(Glib::ustring const &annotation, gchar const *key)
1798{
1799 writeXML();
1800 DocumentUndo::maybeDone(_desktop->getDocument(), key, annotation.data(), INKSCAPE_ICON("tool-node-editor"));
1801}
1802
1806{
1807 auto &m = _desktop->getNamedView()->snap_manager;
1808 m.setup(_desktop);
1810 Inkscape::SnappedPoint sp = m.freeSnap(scp, Geom::OptRect(), false);
1811 m.unSetup();
1812
1813 Geom::Coord dist = HUGE_VAL;
1814
1815 Geom::Affine to_desktop = _getTransform();
1817 std::optional<Geom::PathVectorTime> pvp = pv.nearestTime(sp.getPoint() * to_desktop.inverse());
1818 if (!pvp) return dist;
1819
1820 Geom::Point nearest_pt_dt = pv.pointAt(*pvp) * to_desktop;
1821 Geom::Point nearest_pt = _desktop->d2w(nearest_pt_dt);
1822 dist = Geom::distance(_desktop->d2w(sp.getPoint()), nearest_pt);
1823 double stroke_tolerance = _getStrokeTolerance();
1824
1825 bool drag_point_updated = false;
1826 if (dist < stroke_tolerance) {
1827
1828 double fracpart = pvp->t;
1829 std::list<SubpathPtr>::iterator spi = _subpaths.begin();
1830 for (unsigned i = 0; i < pvp->path_index; ++i, ++spi) {}
1831 NodeList::iterator first = (*spi)->before(pvp->asPathTime());
1832
1833 if (first && first.next() &&
1834 fracpart != 0.0 &&
1835 fracpart != 1.0) {
1836
1837 drag_point_updated = true;
1838 // stroke_tolerance is at least two.
1839 int tolerance = std::max(2, (int)stroke_tolerance);
1840 _dragpoint->setPosition(_desktop->w2d(nearest_pt));
1841 _dragpoint->setSize(2 * tolerance - 1);
1842 _dragpoint->setTimeValue(fracpart);
1843 _dragpoint->setIterator(first);
1844 }
1845 }
1846
1847 _dragpoint->setVisible(drag_point_updated);
1848
1849 return dist;
1850}
1851
1857
1861{
1862 /* Stroke event tolerance is equal to half the stroke's width plus the global
1863 * drag tolerance setting. */
1865 double ret = prefs->getIntLimited("/options/dragtolerance/value", 2, 0, 100);
1866 if (_path && _path->style && !_path->style->stroke.isNone()) {
1867 ret += _path->style->stroke_width.computed * 0.5
1868 * _getTransform().descrim() // scale to desktop coords
1869 * _desktop->current_zoom(); // == _d2w.descrim() - scale to window coords
1870 }
1871 return ret;
1872}
1873
1874} // namespace Inkscape::UI
1875
1876/*
1877 Local Variables:
1878 mode:c++
1879 c-file-style:"stroustrup"
1880 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1881 indent-tabs-mode:nil
1882 fill-column:99
1883 End:
1884*/
1885// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Cartesian point / 2D vector and related operations.
Point origin
Definition aa.cpp:227
int bezier_fit(Geom::Point bezier[4], const std::vector< InputPoint > &data)
Bezier fitting algorithms.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
std::pair< BezierCurveN, BezierCurveN > subdivide(Coord t) const
Divide a Bezier curve into two curves.
Two-dimensional Bezier curve of arbitrary order.
unsigned order() const
Get the order of the Bezier curve.
Polynomial in Bernstein-Bezier basis.
Definition bezier.h:126
std::vector< Coord > roots() const
Definition bezier.cpp:105
Coord valueAt(double t) const
Definition bezier.h:277
virtual D2< SBasis > toSBasis() const =0
Convert the curve to a symmetric power basis polynomial.
Point valueAt(double t) const
Definition d2.h:133
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Store paths to a PathVector.
Definition path-sink.h:226
Sequence of subpaths.
Definition pathvector.h:122
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
Point pointAt(Coord t) const
iterator erase(iterator i)
Remove a path from the vector.
Definition pathvector.h:187
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
std::optional< PathVectorTime > nearestTime(Point const &p, Coord *dist=NULL) const
iterator begin()
Definition pathvector.h:151
iterator end()
Definition pathvector.h:152
Sequence::iterator iterator
Definition pathvector.h:126
Sequence of contiguous curves, aka spline.
Definition path.h:353
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
Parameter * getParameter(const char *key)
Definition effect.cpp:1948
LivePathEffectObject * getLPEObj()
Definition effect.h:151
Geom::PathVector const & get_pathvector() const
Definition path.cpp:99
void set_new_value(Geom::PathVector const &newpath, bool write_to_svg)
Definition path.cpp:370
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
Class to store data for points which are snap candidates, either as a source or as a target.
Class describing the result of an attempt to snap.
Geom::Point getPoint() const
std::pair< iterator, bool > insert(const value_type &x, bool notify=true, bool to_update=true)
Add a control point to the selection.
sigc::signal< void()> signal_update
Fires when the display needs to be updated to reflect changes.
sigc::signal< void(std::vector< SelectableControlPoint * >, bool)> signal_selection_changed
void erase(iterator pos, bool to_update=true)
Remove a point from the selection.
void clear()
Remove all points from the selection, making it empty.
Geom::Point const & position() const
Current position of the control point.
virtual void setVisible(bool v)
Set the visibility of the control point.
virtual void setPosition(Geom::Point const &pos)
Relocate the control point without side effects.
An invisible point used to drag curves.
void setIterator(NodeList::iterator i)
NodeList::iterator getIterator()
Geom::Point relativePos() const
Definition node.h:460
void setRelativePos(Geom::Point const &p)
Definition node.h:463
bool isDegenerate() const
Definition node.h:68
Handle * other()
Definition node.cpp:603
Node * parent()
Definition node.h:79
void setPosition(Geom::Point const &p) override
Relocate the control point without side effects.
Definition node.cpp:292
double length() const
Definition node.h:466
void move(Geom::Point const &p) override
Move the control point to new position with side effects.
Definition node.cpp:206
SPDesktop *const _desktop
Definition manipulator.h:43
Manipulator that manages multiple path manipulators active at the same time.
void _doneWithCleanup(gchar const *reason, bool alert_LPE=false)
Commits changes to XML, adds undo stack entry and removes empty manipulators.
self prev() const
Definition node.h:316
self next() const
Definition node.h:311
size_type size() const
Definition node.cpp:1754
static NodeList & get(Node *n)
Definition node.cpp:1917
bool closed() const
Definition node.h:368
iterator insert(iterator pos, Node *x)
insert a node before pos.
Definition node.cpp:1785
static iterator get_iterator(Node *n)
Definition node.h:422
iterator erase(iterator pos)
Definition node.cpp:1892
static NodeType parse_nodetype(char x)
Definition node.cpp:1155
Handle * front()
Definition node.h:164
Handle * back()
Definition node.h:165
void _updateOutline()
Update the path outline.
CanvasItemPtr< Inkscape::CanvasItemBpath > _outline
void update(bool alert_LPE=false)
Update the display and the outline of the path.
Inkscape::XML::Node * _getXMLNode()
Return the XML node we are editing.
std::string _createTypeString()
Construct a node type string to store in the sodipodi:nodetypes attribute.
void _createGeometryFromControlPoints(bool alert_LPE=false)
Construct the geometric representation of nodes and handles, update the outline and display.
Glib::ustring _nodetypesKey()
Figure out in what attribute to store the nodetype string.
void setSegmentType(SegmentType)
Make selected segments curves / lines.
void hideDragPoint()
Hide the curve drag point until the next motion event.
void _selectionChanged(SelectableControlPoint *p, bool selected)
void insertNodeAtExtremum(ExtremumType extremum)
Insert a new node at the extremum of the selected segments.
Geom::Coord _updateDragPoint(Geom::Point const &)
Update the position of the curve drag point such that it is over the nearest point of the path.
std::shared_ptr< NodeList > SubpathPtr
void _commit(Glib::ustring const &annotation)
Update the XML representation and put the specified annotation on the undo stack.
Geom::Point _bsplineHandleReposition(Handle *h, bool check_other=true)
void rotateHandle(Node *n, int which, int dir, bool pixel)
void _externalChange(unsigned type)
Called by the XML observer when something else than us modifies the path.
void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)
Build one segment of the geometric representation.
void reverseSubpaths(bool selected_only)
Reverse subpaths of the path.
void _removeNodesFromSelection()
Removes all nodes belonging to this manipulator from the control point selection.
void _getGeometry()
Retrieve the geometry of the edited object from the object tree.
bool _nodeClicked(Node *, ButtonReleaseEvent const &)
void _updateOutlineOnZoomChange()
This is called on zoom change to update the direction arrows.
void _deleteSegments(bool delete_singles)
Removes selected segments.
bool _handleClicked(Handle *, ButtonReleaseEvent const &)
void showHandles(bool show)
Set the visibility of handles.
double _getStrokeTolerance()
Compute the radius from the edge of the path where clicks should initiate a curve drag or segment sel...
void weldNodes(NodeList::iterator preserve_pos=NodeList::iterator())
Replace contiguous selections of nodes in each subpath with one node.
void selectSubpaths()
Select all nodes in subpaths that have something selected.
void setControlsTransform(Geom::Affine const &)
bool empty()
Check whether the manipulator has any nodes.
void showOutline(bool show)
Set the visibility of outline.
void invertSelectionInSubpaths()
Invert selection in the selected subpaths.
unsigned _deleteStretch(NodeList::iterator first, NodeList::iterator last, NodeDeleteMode mode)
Delete nodes between the two iterators.
void duplicateNodes()
Insert new nodes exactly at the positions of selected nodes while preserving shape.
void clear()
Remove all nodes from the path.
void _setGeometry()
Set the geometry of the edited object in the object tree, but do not commit to XML.
Geom::Affine _i2d_transform
item-to-desktop transform, inverse of _d2i_transform
Geom::Affine _edit_transform
additional transform to apply to editing controls
bool event(Inkscape::UI::Tools::ToolBase *tool, CanvasEvent const &event) override
Handle motion events to update the position of the curve drag point.
Geom::Affine _d2i_transform
desktop-to-item transform
PathManipulatorObserver * _observer
void deleteNodes(NodeDeleteMode mode)
Delete selected nodes in the path, optionally substituting deleted segments with bezier curves in a w...
void insertNodes()
Insert a new node in the middle of each selected segment.
PathManipulator(MultiPathManipulator &mpm, SPObject *path, Geom::Affine const &edit_trans, uint32_t outline_color, Glib::ustring lpe_key)
void scaleHandle(Node *n, int which, int dir, bool pixel)
void weldSegments()
Remove nodes in the middle of selected segments.
Handle * _chooseHandle(Node *n, int which)
NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected, bool search_unselected, bool closest)
Find the node that is closest/farthest from the origin.
MultiPathManipulator & _multi_path_manipulator
void writeXML()
Store the changes to the path in XML.
void _selectionChangedM(std::vector< SelectableControlPoint * > pvec, bool selected)
NodeList::iterator subdivideSegment(NodeList::iterator after, double t)
Insert a node in the segment beginning with the supplied iterator, at the given time value.
void _createControlPointsFromGeometry()
Create nodes and handles based on the XML of the edited path.
void copySelectedPath(Geom::PathBuilder *builder)
Copy the selected nodes using the PathBuilder.
SPObject * _path
can be an SPPath or an Inkscape::LivePathEffect::Effect !!!
double _bsplineHandlePosition(Handle *h, bool check_other=true)
Tool component that edits something on the canvas using selectable control points.
Definition manipulator.h:53
ExtremumType
Type of extremum points to add in PathManipulator::insertNodeAtExtremum.
Definition manipulator.h:62
ControlPointSelection & _selection
Definition manipulator.h:70
Desktop-bound selectable control object.
Base class for Event processors.
Definition tool-base.h:107
Interface for XML node observers.
Interface for refcounted XML nodes.
Definition node.h:80
virtual void setPosition(int pos)=0
Set the position of this node in parent's child order.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual unsigned position() const =0
Get the index of this node in parent's child order.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
Inkscape::LivePathEffect::Effect * get_lpe()
Definition lpeobject.h:51
Wrapper around a Geom::PathVector object.
Definition curve.h:26
void moveto(Geom::Point const &p)
Perform a moveto to a point, thus starting a new subpath.
Definition curve.cpp:138
void reset()
Set curve to empty curve.
Definition curve.cpp:118
Geom::PathVector const & get_pathvector() const
Definition curve.cpp:52
void lineto(Geom::Point const &p)
Adds a line to the current subpath.
Definition curve.cpp:149
Geom::Curve const * first_segment() const
Return first pathsegment in PathVector or NULL.
Definition curve.cpp:317
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
SPNamedView * getNamedView() const
Definition desktop.h:191
sigc::signal< void(double)> signal_zoom_changed
Emitted when the zoom factor changes (not emitted when scrolling).
Definition desktop.h:251
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
SnapManager snap_manager
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
vector< vpsc::Rectangle * > rs
Control point selection - stores a set of control points and applies transformations to them.
Css & result
unsigned int guint32
Contains forward declarations of 2geom types.
BezierCurveN< 3 > CubicBezier
Cubic (order 3) Bezier curve.
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Definition coord.h:97
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Geom::PathVector pathv_to_cubicbezier(Geom::PathVector const &pathv, bool nolines)
Definition geom.cpp:692
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:588
Specific geometry functions for Inkscape, not provided my lib2geom.
Macro for icon names used in Inkscape.
Inkscape::XML::Node * node
PowerStroke LPE effect, see lpe-powerstroke.cpp.
Multi path manipulator - a tool component that edits multiple paths at once.
Coord nearest_time(Point const &p, Curve const &c)
Definition curve.h:354
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
Affine identity()
Create an identity matrix.
Definition affine.h:210
Point unit_vector(Point const &a)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Point middle_point(LineSegment const &_segment)
static R & anchor(R &r)
Increments the reference count of a anchored object.
Definition gc-anchored.h:92
static R & release(R &r)
Decrements the reference count of a anchored object.
Live Path Effects code.
User interface code.
Definition desktop.h:113
const double NO_POWER
Definition node.cpp:138
void build_segment(Geom::PathBuilder &, Node *, Node *)
NodeType
Types of nodes supported in the node tool.
Definition node-types.h:20
@ NODE_CUSP
Cusp node - no handle constraints.
Definition node-types.h:21
@ NODE_SYMMETRIC
Symmetric node - handles must be colinear and of equal length.
Definition node-types.h:24
@ NODE_AUTO
Auto node - handles adjusted automatically based on neighboring nodes.
Definition node-types.h:23
@ NODE_LAST_REAL_TYPE
Last real type of node - used for ctrl+click on a node.
Definition node-types.h:25
@ NODE_SMOOTH
Smooth node - handles must be colinear.
Definition node-types.h:22
double get_angle(const Geom::Point &p0, const Geom::Point &p1, const Geom::Point &p2)
SegmentType
Types of segments supported in the node tool.
Definition node-types.h:30
@ SEGMENT_STRAIGHT
Straight linear segment.
Definition node-types.h:31
@ SEGMENT_CUBIC_BEZIER
Bezier curve with two control points.
Definition node-types.h:32
const double BSPLINE_TOL
Definition node.cpp:137
const double DEFAULT_START_POWER
Definition node.cpp:139
static void add_or_replace_if_extremum(std::vector< std::pair< NodeList::iterator, double > > &vec, double &extrvalue, double testvalue, NodeList::iterator const &node, double t)
bool held_ctrl(CanvasEvent const &event)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_OTHER_HANDLE
Definition snap-enums.h:56
bool held_alt(CanvasEvent const &event)
STL namespace.
Interface for XML node observers.
New node tool with support for multiple path editing.
Node types and other small enums.
static cairo_user_data_key_t key
int mode
Path manipulator - a component that edits a single path on-canvas.
callback interface for SVG path data
static double sign(double const x)
Returns -1 or 1 according to the sign of x.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
guint32 GQuark
A mouse button (left/right/middle) is released.
Abstract base class for events.
Movement of the mouse pointer.
Inkscape::CanvasItemGroup * outline_group
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
SPStyle - a style object for SPItem objects.
int delta
SPDesktop * desktop
Glib::RefPtr< Gtk::Builder > builder