Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
node.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/* Authors:
3 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
4 * Jon A. Cruz <jon@joncruz.org>
5 *
6 * Copyright (C) 2009 Authors
7 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8 */
9
10#include "node.h"
11
12#include <atomic>
13#include <gdk/gdkkeysyms.h>
14#include <glib/gi18n.h>
15#include <iostream>
16#include <vector>
17
18#include "desktop.h"
21#include "object/sp-namedview.h"
22#include "snap.h"
23#include "ui/modifiers.h"
26#include "ui/tools/node-tool.h"
28#include "util/units.h"
29
30namespace {
31
33{
35 switch (type) {
38 break;
41 break;
44 break;
46 default:
48 break;
49 }
50 return result;
51}
52
59class SvgOutputPrecisionWatcher : public Inkscape::Preferences::Observer
60{
61public:
63 static double error_of(double value) { return value * instance().rel_error; }
64
65 void notify(const Inkscape::Preferences::Entry &new_val) override
66 {
67 int digits = new_val.getIntLimited(6, 1, 16);
68 set_numeric_precision(digits);
69 }
70
71private:
72 SvgOutputPrecisionWatcher()
73 : Observer("/options/svgoutput/numericprecision")
74 , rel_error(1)
75 {
77 int digits = Inkscape::Preferences::get()->getIntLimited("/options/svgoutput/numericprecision", 6, 1, 16);
78 set_numeric_precision(digits);
79 }
80
81 ~SvgOutputPrecisionWatcher() override { Inkscape::Preferences::get()->removeObserver(*this); }
83 void set_numeric_precision(int digits)
84 {
85 double relative_error = 0.5; // the error is half of last digit
86 while (digits > 0) {
87 relative_error /= 10;
88 digits--;
89 }
90 rel_error = relative_error;
91 }
92
93 static SvgOutputPrecisionWatcher &instance()
94 {
95 static SvgOutputPrecisionWatcher _instance;
96 return _instance;
97 }
98
99 std::atomic<double> rel_error;
100};
101
103double serializing_error_of(const Geom::Point &point)
104{
105 return SvgOutputPrecisionWatcher::error_of(point.length());
106}
107
117bool are_collinear_within_serializing_error(const Geom::Point &A, const Geom::Point &B, const Geom::Point &C)
118{
119 const double tolerance_factor = 10; // to account other factors which increase uncertainty
120 const double tolerance_A = serializing_error_of(A) * tolerance_factor;
121 const double tolerance_B = serializing_error_of(B) * tolerance_factor;
122 const double tolerance_C = serializing_error_of(C) * tolerance_factor;
123 const double CB_length = (B - C).length();
124 const double AB_length = (B - A).length();
125 Geom::Point C_reflect_scaled = B + (B - C) / CB_length * AB_length;
126 double tolerance_C_reflect_scaled = tolerance_B + (tolerance_B + tolerance_C) *
127 (1 + (tolerance_A + tolerance_B) / AB_length) *
128 (1 + (tolerance_C + tolerance_B) / CB_length);
129 return Geom::are_near(C_reflect_scaled, A, tolerance_C_reflect_scaled + tolerance_A);
130}
131
132} // namespace
133
134namespace Inkscape {
135namespace UI {
136
137const double BSPLINE_TOL = 0.001;
138const double NO_POWER = 0.0;
139const double DEFAULT_START_POWER = 1.0 / 3.0;
140
141std::ostream &operator<<(std::ostream &out, NodeType type)
142{
143 switch (type) {
144 case NODE_CUSP:
145 out << 'c';
146 break;
147 case NODE_SMOOTH:
148 out << 's';
149 break;
150 case NODE_AUTO:
151 out << 'a';
152 break;
153 case NODE_SYMMETRIC:
154 out << 'z';
155 break;
156 default:
157 out << 'b';
158 break;
159 }
160 return out;
161}
162
164static Geom::Point direction(Geom::Point const &first, Geom::Point const &second)
165{
166 return Geom::unit_vector(second - first);
167}
168
171
172double Handle::_saved_length = 0.0;
173
174bool Handle::_drag_out = false;
175
178 data.handle_group)
179 , _handle_line(make_canvasitem<CanvasItemCurve>(data.handle_line_group))
180 , _parent(parent)
181 , _degenerate(true)
182{
183 setVisible(false);
184}
185
186Handle::~Handle() = default;
187
189{
191 _handle_line->set_visible(v);
193}
194
196{
197 // move the handle and its opposite the same proportion
198 if (_pm()._isBSpline()) {
199 setPosition(_pm()._bsplineHandleReposition(this, false));
200 double bspline_weight = _pm()._bsplineHandlePosition(this, false);
201 other()->setPosition(_pm()._bsplineHandleReposition(other(), bspline_weight));
202 _pm().update();
203 }
204}
205
206void Handle::move(Geom::Point const &new_pos)
207{
208 Handle *other = this->other();
209 Node *node_towards = _parent->nodeToward(this); // node in direction of this handle
210 Node *node_away = _parent->nodeAwayFrom(this); // node in the opposite direction
211 Handle *towards = node_towards ? node_towards->handleAwayFrom(_parent) : nullptr;
212 Handle *towards_second = node_towards ? node_towards->handleToward(_parent) : nullptr;
213 if (Geom::are_near(new_pos, _parent->position())) {
214 // The handle becomes degenerate.
215 // Adjust node type as necessary.
216 if (other->isDegenerate()) {
217 // If both handles become degenerate, convert to parent cusp node
218 _parent->setType(NODE_CUSP, false);
219 } else {
220 // Only 1 handle becomes degenerate
221 switch (_parent->type()) {
222 case NODE_AUTO:
223 case NODE_SYMMETRIC:
224 _parent->setType(NODE_SMOOTH, false);
225 break;
226 default:
227 // do nothing for other node types
228 break;
229 }
230 }
231 // If the segment between the handle and the node in its direction becomes linear,
232 // and there are smooth nodes at its ends, make their handles collinear with the segment.
233 if (towards && towards_second->isDegenerate()) {
234 if (node_towards->type() == NODE_SMOOTH) {
235 towards->setDirection(_parent->position(), node_towards->position());
236 }
237 if (_parent->type() == NODE_SMOOTH) {
238 other->setDirection(node_towards->position(), _parent->position());
239 }
240 }
241 setPosition(new_pos);
242
243 // move the handle and its opposite the same proportion
245 return;
246 }
247
248 if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
249 // restrict movement to the line joining the nodes
250 Geom::Point direction = _parent->position() - node_away->position();
251 Geom::Point delta = new_pos - _parent->position();
252 // project the relative position on the direction line
253 Geom::Coord direction_length = Geom::L2sq(direction);
254 Geom::Point new_delta;
255 if (direction_length == 0) {
256 // joining line has zero length - any direction is okay, prevent division by zero
257 new_delta = delta;
258 } else {
259 new_delta = (Geom::dot(delta, direction) / direction_length) * direction;
260 }
261 setRelativePos(new_delta);
262
263 // move the handle and its opposite the same proportion
265
266 return;
267 }
268
269 switch (_parent->type()) {
270 case NODE_AUTO:
271 _parent->setType(NODE_SMOOTH, false);
272 // fall through - auto nodes degrade into smooth nodes
273 case NODE_SMOOTH: {
274 // for smooth nodes, we need to rotate the opposite handle
275 // so that it's collinear with the dragged one, while conserving length.
276 other->setDirection(new_pos, _parent->position());
277 } break;
278 case NODE_SYMMETRIC:
279 // for symmetric nodes, place the other handle on the opposite side
280 other->setRelativePos(-(new_pos - _parent->position()));
281 break;
282 default:
283 break;
284 }
285 setPosition(new_pos);
286
287 // move the handle and its opposite the same proportion
290}
291
293{
295 _handle_line->set_coords(_parent->position(), position());
296
297 // update degeneration info and visibility
299 _degenerate = true;
300 else
301 _degenerate = false;
302
304 setVisible(true);
305 } else {
306 setVisible(false);
307 }
308}
309
311{
312 if (isDegenerate())
313 return;
315 setRelativePos(dir * len);
316}
317
319{
321}
322
323void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)
324{
325 setDirection(to - from);
326}
327
329{
330 Geom::Point unitdir = Geom::unit_vector(dir);
331 setRelativePos(unitdir * length());
332}
333
338{
339 switch (type) {
340 case NODE_CUSP:
341 return _("Corner node handle");
342 case NODE_SMOOTH:
343 return _("Smooth node handle");
344 case NODE_SYMMETRIC:
345 return _("Symmetric node handle");
346 case NODE_AUTO:
347 return _("Auto-smooth node handle");
348 default:
349 return "";
350 }
351}
352
353bool Handle::_eventHandler(Tools::ToolBase *event_context, CanvasEvent const &event)
354{
355 bool ret = false;
357 event,
358 [&](KeyPressEvent const &event) {
359 switch (event.keyval) {
360 case GDK_KEY_s:
361 case GDK_KEY_S:
362 /* if Shift+S is pressed while hovering over a cusp node handle,
363 hold the handle in place; otherwise, process normally.
364 this handle is guaranteed not to be degenerate. */
365
366 if (mod_shift_only(event) && _parent->_type == NODE_CUSP) {
367 // make opposite handle collinear,
368 // but preserve length, unless degenerate
369 if (other()->isDegenerate())
370 other()->setRelativePos(-relativePos());
371 else
372 other()->setDirection(-relativePos());
373 _parent->setType(NODE_SMOOTH, false);
374
375 // update display
376 _parent->_pm().update();
377
378 // update undo history
379 _parent->_pm()._commit(_("Change node type"));
380
381 ret = true;
382 }
383 break;
384
385 case GDK_KEY_y:
386 case GDK_KEY_Y:
387
388 /* if Shift+Y is pressed while hovering over a cusp, smooth, or auto node handle,
389 hold the handle in place; otherwise, process normally.
390 this handle is guaranteed not to be degenerate. */
391
392 if (mod_shift_only(event) &&
394 // make opposite handle collinear, and of equal length
397
398 // update display
399 _parent->_pm().update();
400
401 // update undo history
402 _parent->_pm()._commit(_("Change node type"));
403
404 ret = true;
405 }
406 break;
407 default:
408 break;
409 }
410 },
411
412 [&](ButtonPressEvent const &event) {
413 if (event.num_press != 2) {
414 return;
415 }
416
417 // double-click event to set the handles of a node
418 // to the position specified by DEFAULT_START_POWER
419 handle_2button_press();
420 },
421
422 [&](CanvasEvent const &event) {});
423
424 return ControlPoint::_eventHandler(event_context, event);
425}
426
427// this function moves the handle and its opposite to the position specified by DEFAULT_START_POWER
428void Handle::handle_2button_press()
429{
430 if (_pm()._isBSpline()) {
431 setPosition(_pm()._bsplineHandleReposition(this, DEFAULT_START_POWER));
432 this->other()->setPosition(_pm()._bsplineHandleReposition(this->other(), DEFAULT_START_POWER));
433 _pm().update();
434 }
435}
436
437bool Handle::grabbed(MotionEvent const &)
438{
439 _saved_other_pos = other()->position();
440 _saved_length = _drag_out ? 0 : length();
441 _saved_dir = Geom::unit_vector(_last_drag_origin() - _parent->position());
442 _pm()._handleGrabbed();
443 return false;
444}
445
446void Handle::dragged(Geom::Point &new_pos, MotionEvent const &event)
447{
448 Geom::Point parent_pos = _parent->position();
449 Geom::Point origin = _last_drag_origin();
450 SnapManager &sm = _desktop->getNamedView()->snap_manager;
451 bool snap = mod_shift(event) ? false : sm.someSnapperMightSnap();
452 std::optional<Inkscape::Snapper::SnapConstraint> ctrl_constraint;
453
454 if (mod_alt(event)) {
455 // with Alt, preserve length of the handle
456 new_pos = parent_pos + Geom::unit_vector(new_pos - parent_pos) * _saved_length;
457 snap = false;
458 _saved_dir = Geom::unit_vector(relativePos());
459 } else {
460 // with nothing pressed we update the lengths
461 _saved_length = _drag_out ? 0 : length();
462 _saved_dir = Geom::unit_vector(relativePos());
463 }
464
465 if (_parent->type() != NODE_CUSP && mod_shift(event) && !mod_alt(event)) {
466 // if we hold Shift, and node is not cusp, link the two handles
467 other()->setRelativePos(-relativePos());
468 }
469
470 // with Ctrl, constrain to M_PI/rotationsnapsperpi increments from vertical
471 // and the original position.
472 if (mod_ctrl(event)) {
474 int snaps = 2 * prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
475
476 // note: if snapping to the original position is only desired in the original
477 // direction of the handle, use Geom::Ray instead of Geom::Line
478 Geom::Line original_line(parent_pos, origin);
479 Geom::Line perp_line(parent_pos, parent_pos + Geom::rot90(origin - parent_pos));
480 Geom::Point snap_pos =
481 parent_pos + Geom::constrain_angle(Geom::Point(0, 0), new_pos - parent_pos, snaps, Geom::Point(1, 0));
482 Geom::Point orig_pos = original_line.pointAt(original_line.nearestTime(new_pos));
483 Geom::Point perp_pos = perp_line.pointAt(perp_line.nearestTime(new_pos));
484
485 Geom::Point result = snap_pos;
486 ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - snap_pos);
487 if (Geom::distance(orig_pos, new_pos) < Geom::distance(result, new_pos)) {
488 result = orig_pos;
489 ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - orig_pos);
490 }
491 if (Geom::distance(perp_pos, new_pos) < Geom::distance(result, new_pos)) {
492 result = perp_pos;
493 ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - perp_pos);
494 }
495 new_pos = result;
496 // move the handle and its opposite in X fixed positions depending on parameter "steps with control"
497 // by default in live BSpline
498 if (_pm()._isBSpline()) {
499 setPosition(new_pos);
500 int steps = _pm()._bsplineGetSteps();
501 new_pos =
502 _pm()._bsplineHandleReposition(this, ceilf(_pm()._bsplineHandlePosition(this, false) * steps) / steps);
503 }
504 }
505
506 std::vector<Inkscape::SnapCandidatePoint> unselected;
507 // If the snapping is active and we're not working with a B-spline
508 if (snap && !_pm()._isBSpline()) {
509 // We will only snap this handle to stationary path segments; some path segments may move as we move the
510 // handle; those path segments are connected to the parent node of this handle.
511 ControlPointSelection::Set &nodes = _parent->_selection.allPoints();
512 for (auto node : nodes) {
513 Node *n = static_cast<Node *>(node);
514 if (_parent != n) { // We're adding all nodes in the path, except the parent node of this handle
515 unselected.push_back(n->snapCandidatePoint());
516 }
517 }
518 sm.setupIgnoreSelection(_desktop, true, &unselected);
519
520 Node *node_away = _parent->nodeAwayFrom(this);
521 if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
522 Inkscape::Snapper::SnapConstraint cl(_parent->position(), _parent->position() - node_away->position());
525 new_pos = p.getPoint();
526 } else if (ctrl_constraint) {
527 // NOTE: this is subtly wrong.
528 // We should get all possible constraints and snap along them using
529 // multipleConstrainedSnaps, instead of first snapping to angle and then to objects
531 p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), *ctrl_constraint);
532 new_pos = p.getPoint();
533 } else {
535 }
536 sm.unSetup();
537 }
538
539 // with Shift, if the node is cusp, rotate the other handle as well
540 if (_parent->type() == NODE_CUSP && !_drag_out) {
541 if (mod_shift(event)) {
542 Geom::Point other_relpos = _saved_other_pos - parent_pos;
543 other_relpos *= Geom::Rotate(Geom::angle_between(origin - parent_pos, new_pos - parent_pos));
544 other()->setRelativePos(other_relpos);
545 } else {
546 // restore the position
547 other()->setPosition(_saved_other_pos);
548 }
549 }
550 // if it is BSpline, but SHIFT or CONTROL are not pressed, fix it in the original position
551 if (_pm()._isBSpline() && !mod_shift(event) && !mod_ctrl(event)) {
552 new_pos = _last_drag_origin();
553 }
554 _pm().update();
555}
556
557void Handle::ungrabbed(ButtonReleaseEvent const *event)
558{
559 // hide the handle if it's less than dragtolerance away from the node
560 // however, never do this for cancelled drag / broken grab
561 // TODO is this actually a good idea?
562 if (event) {
563 auto prefs = Preferences::get();
564 int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
565
566 auto const dist = _desktop->d2w(_parent->position()) - _desktop->d2w(position());
567 if (dist.length() <= drag_tolerance) {
568 move(_parent->position());
569 }
570 }
571
572 // HACK: If the handle was dragged out, call parent's ungrabbed handler,
573 // so that transform handles reappear
574 if (_drag_out) {
575 _parent->ungrabbed(event);
576 }
577 _drag_out = false;
578 Tools::sp_update_helperpath(_desktop);
579 _pm()._handleUngrabbed();
580}
581
582bool Handle::clicked(ButtonReleaseEvent const &event)
583{
584 if (mod_ctrl(event) && !mod_alt(event)) {
585 // we want to skip the Node Auto when we cycle between nodes
586 if (_parent->type() == NODE_SMOOTH) {
587 _parent->setType(NODE_AUTO, false);
588 }
589 }
590
591 if (_pm()._nodeClicked(this->parent(), event)) {
592 return true;
593 }
594 _pm()._handleClicked(this, event);
595 return true;
596}
597
598Handle const *Handle::other() const
599{
600 return const_cast<Handle *>(this)->other();
601}
602
603Handle *Handle::other()
604{
605 if (this == &_parent->_front) {
606 return &_parent->_back;
607 } else {
608 return &_parent->_front;
609 }
610}
611
613{
615 int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
616 return 180.0 / snaps;
617}
618
619Glib::ustring Handle::_getTip(unsigned state) const
620{
621 /* a trick to mark as BSpline if the node has no strength;
622 we are going to use it later to show the appropriate messages.
623 we cannot do it in any different way because the function is constant. */
624 Handle *h = const_cast<Handle *>(this);
625 bool isBSpline = _pm()._isBSpline();
626 bool can_shift_rotate = _parent->type() == NODE_CUSP && !other()->isDegenerate();
627 Glib::ustring s = C_("Status line hint",
628 "node control handle"); // not expected
629
630 if (mod_alt(state) && !isBSpline) {
631 if (mod_ctrl(state)) {
632 if (mod_shift(state) && can_shift_rotate) {
633 s = format_tip(C_("Status line hint", "<b>Shift+Ctrl+Alt</b>: "
634 "preserve length and snap rotation angle to %g° increments, "
635 "and rotate both handles"),
637 } else {
638 s = format_tip(C_("Status line hint", "<b>Ctrl+Alt</b>: "
639 "preserve length and snap rotation angle to %g° increments"),
641 }
642 } else {
643 if (mod_shift(state) && can_shift_rotate) {
644 s = C_("Path handle tip", "<b>Shift+Alt</b>: preserve handle length and rotate both handles");
645 } else {
646 s = C_("Path handle tip", "<b>Alt</b>: preserve handle length while dragging");
647 }
648 }
649 } else {
650 if (mod_ctrl(state)) {
651 if (mod_shift(state) && can_shift_rotate && !isBSpline) {
652 s = format_tip(C_("Path handle tip", "<b>Shift+Ctrl</b>: "
653 "snap rotation angle to %g° increments, and rotate both handles"),
655 } else if (isBSpline) {
656 s = C_("Path handle tip", "<b>Ctrl</b>: "
657 "Snap handle to steps defined in BSpline Live Path Effect");
658 } else {
659 s = format_tip(C_("Path handle tip", "<b>Ctrl</b>: "
660 "snap rotation angle to %g° increments, click to retract"),
662 }
663 } else if (mod_shift(state) && can_shift_rotate && !isBSpline) {
664 s = C_("Path handle tip", "<b>Shift</b>: rotate both handles by the same angle");
665 } else if (mod_shift(state) && isBSpline) {
666 s = C_("Path handle tip", "<b>Shift</b>: move handle");
667 } else {
668 char const *handletype = handle_type_to_localized_string(_parent->_type);
669 char const *more;
670
671 if (can_shift_rotate && !isBSpline) {
672 more = C_("Status line hint", "Shift, Ctrl, Alt");
673 } else if (isBSpline) {
674 more = C_("Status line hint", "Shift, Ctrl");
675 } else {
676 more = C_("Status line hint", "Ctrl, Alt");
677 }
678 if (isBSpline) {
679 double power = _pm()._bsplineHandlePosition(h);
680 s = format_tip(C_("Status line hint", "<b>BSpline node handle</b> (%.3g power): "
681 "Shift-drag to move, "
682 "double-click to reset. "
683 "(more: %s)"),
684 power, more);
685 } else if (_parent->type() == NODE_CUSP) {
686 s = format_tip(C_("Status line hint", "<b>%s</b>: "
687 "drag to shape the path"
688 ", "
689 "hover to lock"
690 ", "
691 "Shift+S to make smooth"
692 ", "
693 "Shift+Y to make symmetric"
694 ". "
695 "(more: %s)"),
696 handletype, more);
697 } else if (_parent->type() == NODE_SMOOTH) {
698 s = format_tip(C_("Status line hint", "<b>%s</b>: "
699 "drag to shape the path"
700 ", "
701 "hover to lock"
702 ", "
703 "Shift+Y to make symmetric"
704 ". "
705 "(more: %s)"),
706 handletype, more);
707 } else if (_parent->type() == NODE_AUTO) {
708 s = format_tip(C_("Status line hint", "<b>%s</b>: "
709 "drag to make smooth, "
710 "hover to lock"
711 ", "
712 "Shift+Y to make symmetric"
713 ". "
714 "(more: %s)"),
715 handletype, more);
716 } else if (_parent->type() == NODE_SYMMETRIC) {
717 s = format_tip(C_("Status line hint", "<b>%s</b>: "
718 "drag to shape the path"
719 ". "
720 "(more: %s)"),
721 handletype, more);
722 } else {
723 s = C_("Status line hint",
724 "<b>unknown node handle</b>"); // not expected
725 }
726 }
727 }
728
729 return (s);
730}
731
732Glib::ustring Handle::_getDragTip(MotionEvent const & /*event*/) const
733{
734 Geom::Point dist = position() - _last_drag_origin();
735 // report angle in mathematical convention
736 double angle = Geom::angle_between(Geom::Point(-1, 0), position() - _parent->position());
737 angle += M_PI; // angle is (-M_PI...M_PI] - offset by +pi and scale to 0...360
738 angle *= 360.0 / (2 * M_PI);
739
743 Glib::ustring x = x_q.string(_desktop->getNamedView()->display_units);
744 Glib::ustring y = y_q.string(_desktop->getNamedView()->display_units);
745 Glib::ustring len = len_q.string(_desktop->getNamedView()->display_units);
746 Glib::ustring ret = format_tip(C_("Status line hint", "Move handle by %s, %s; angle %.2f°, length %s"), x.c_str(),
747 y.c_str(), angle, len.c_str());
748 return ret;
749}
750
751Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos)
753 *data.selection, data.node_group)
754 , _front(data, initial_pos, this)
755 , _back(data, initial_pos, this)
756 , _type(NODE_CUSP)
757 , _handles_shown(false)
758{
759 _canvas_item_ctrl->set_name("CanvasItemCtrl:Node");
760 // NOTE we do not set type here, because the handles are still degenerate
761}
762
763Node const *Node::_next() const
764{
765 return const_cast<Node *>(this)->_next();
766}
767
768// NOTE: not using iterators won't make this much quicker because iterators can be 100% inlined.
770{
772 if (n) {
773 return n.ptr();
774 } else {
775 return nullptr;
776 }
777}
778
779Node const *Node::_prev() const
780{
781 return const_cast<Node *>(this)->_prev();
782}
783
785{
787 if (p) {
788 return p.ptr();
789 } else {
790 return nullptr;
791 }
792}
793
794void Node::move(Geom::Point const &new_pos)
795{
796 // move handles when the node moves.
797 Geom::Point delta = new_pos - position();
798
799 // save the previous nodes strength to apply it again once the node is moved
800 double nodeWeight = NO_POWER;
801 double nextNodeWeight = NO_POWER;
802 double prevNodeWeight = NO_POWER;
803 Node *n = this;
804 Node *nextNode = n->nodeToward(n->front());
805 Node *prevNode = n->nodeToward(n->back());
806 nodeWeight = fmax(_pm()._bsplineHandlePosition(n->front(), false), _pm()._bsplineHandlePosition(n->back(), false));
807 if (prevNode) {
808 prevNodeWeight = _pm()._bsplineHandlePosition(prevNode->front());
809 }
810 if (nextNode) {
811 nextNodeWeight = _pm()._bsplineHandlePosition(nextNode->back());
812 }
813
814 // Save original position for post-processing
815 _unfixed_pos = std::optional<Geom::Point>(position());
816
817 setPosition(new_pos);
820
821 // move the affected handles. First the node ones, later the adjoining ones.
822 if (_pm()._isBSpline()) {
823 _front.setPosition(_pm()._bsplineHandleReposition(this->front(), nodeWeight));
824 _back.setPosition(_pm()._bsplineHandleReposition(this->back(), nodeWeight));
825 if (prevNode) {
826 prevNode->front()->setPosition(_pm()._bsplineHandleReposition(prevNode->front(), prevNodeWeight));
827 }
828 if (nextNode) {
829 nextNode->back()->setPosition(_pm()._bsplineHandleReposition(nextNode->back(), nextNodeWeight));
830 }
831 }
833}
834
836{
837 // save the previous nodes strength to apply it again once the node is moved
838 double nodeWeight = NO_POWER;
839 double nextNodeWeight = NO_POWER;
840 double prevNodeWeight = NO_POWER;
841 Node *n = this;
842 Node *nextNode = n->nodeToward(n->front());
843 Node *prevNode = n->nodeToward(n->back());
844 nodeWeight = _pm()._bsplineHandlePosition(n->front());
845 if (prevNode) {
846 prevNodeWeight = _pm()._bsplineHandlePosition(prevNode->front());
847 }
848 if (nextNode) {
849 nextNodeWeight = _pm()._bsplineHandlePosition(nextNode->back());
850 }
851
852 // Save original position for post-processing
853 _unfixed_pos = std::optional<Geom::Point>(position());
854
855 setPosition(position() * m);
858
859 // move the involved handles. First the node ones, later the adjoining ones.
860 if (_pm()._isBSpline()) {
861 _front.setPosition(_pm()._bsplineHandleReposition(this->front(), nodeWeight));
862 _back.setPosition(_pm()._bsplineHandleReposition(this->back(), nodeWeight));
863 if (prevNode) {
864 prevNode->front()->setPosition(_pm()._bsplineHandleReposition(prevNode->front(), prevNodeWeight));
865 }
866 if (nextNode) {
867 nextNode->back()->setPosition(_pm()._bsplineHandleReposition(nextNode->back(), nextNodeWeight));
868 }
869 }
870}
871
873{
877 return b;
878}
879
888{
889 if (!_unfixed_pos)
890 return;
891
892 Geom::Point const new_pos = position();
893
894 // This method restores handle invariants for neighboring nodes,
895 // and invariants that are based on positions of those nodes for this one.
896
897 // Fix auto handles
898 if (_type == NODE_AUTO)
900 if (*_unfixed_pos != new_pos) {
901 if (_next() && _next()->_type == NODE_AUTO)
903 if (_prev() && _prev()->_type == NODE_AUTO)
905 }
906
907 /* Fix smooth handles at the ends of linear segments.
908 Rotate the appropriate handle to be collinear with the segment.
909 If there is a smooth node at the other end of the segment, rotate it too. */
910 Handle *handle, *other_handle;
911 Node *other;
912 if (_is_line_segment(this, _next())) {
913 handle = &_back;
914 other = _next();
915 other_handle = &_next()->_front;
916 } else if (_is_line_segment(_prev(), this)) {
917 handle = &_front;
918 other = _prev();
919 other_handle = &_prev()->_back;
920 } else
921 return;
922
923 if (_type == NODE_SMOOTH && !handle->isDegenerate()) {
924 handle->setDirection(other->position(), new_pos);
925 }
926 // also update the handle on the other end of the segment
927 if (other->_type == NODE_SMOOTH && !other_handle->isDegenerate()) {
928 other_handle->setDirection(new_pos, other->position());
929 }
930
931 _unfixed_pos.reset();
932}
933
935{
936 // Recompute the position of automatic handles. For endnodes, retract both handles.
937 // (It's only possible to create an end auto node through the XML editor.)
938 if (isEndNode()) {
939 _front.retract();
940 _back.retract();
941 return;
942 }
943
944 // auto nodes automatically adjust their handles to give
945 // an appearance of smoothness, no matter what their surroundings are.
946 Geom::Point vec_next = _next()->position() - position();
947 Geom::Point vec_prev = _prev()->position() - position();
948 double len_next = vec_next.length(), len_prev = vec_prev.length();
949 if (len_next > 0 && len_prev > 0) {
950 // "dir" is an unit vector perpendicular to the bisector of the angle created
951 // by the previous node, this auto node and the next node.
952 Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
953 // Handle lengths are equal to 1/3 of the distance from the adjacent node.
954 _back.setRelativePos(-dir * (len_prev / 3));
955 _front.setRelativePos(dir * (len_next / 3));
956 } else {
957 // If any of the adjacent nodes coincides, retract both handles.
958 _front.retract();
959 _back.retract();
960 }
961}
962
964{
965 _handles_shown = v;
966 if (!_front.isDegenerate()) {
968 }
969 if (!_back.isDegenerate()) {
970 _back.setVisible(v);
971 }
972}
973
981
982void Node::setType(NodeType type, bool update_handles)
983{
984 if (type == NODE_PICK_BEST) {
985 pickBestType();
986 updateState(); // The size of the control might have changed
987 return;
988 }
989
990 // if update_handles is true, adjust handle positions to match the node type
991 // handle degenerate handles appropriately
992 if (update_handles) {
993 switch (type) {
994 case NODE_CUSP:
995 // nothing to do
996 break;
997 case NODE_AUTO:
998 // auto handles make no sense for endnodes
999 if (isEndNode())
1000 return;
1002 break;
1003 case NODE_SMOOTH: {
1004 // ignore attempts to make smooth endnodes.
1005 if (isEndNode())
1006 return;
1007 // rotate handles to be collinear
1008 // for degenerate nodes set positions like auto handles
1009 bool prev_line = _is_line_segment(_prev(), this);
1010 bool next_line = _is_line_segment(this, _next());
1011 if (_type == NODE_SMOOTH) {
1012 // For a node that is already smooth and has a degenerate handle,
1013 // drag out the second handle without changing the direction of the first one.
1014 if (_front.isDegenerate()) {
1015 double dist = Geom::distance(_next()->position(), position());
1017 }
1018 if (_back.isDegenerate()) {
1019 double dist = Geom::distance(_prev()->position(), position());
1021 }
1022 } else if (isDegenerate()) {
1024 } else if (_front.isDegenerate()) {
1025 // if the front handle is degenerate and next path segment is a line, make back collinear;
1026 // otherwise, pull out the other handle to 1/3 of distance to prev.
1027 if (next_line) {
1029 } else if (_prev()) {
1032 }
1033 } else if (_back.isDegenerate()) {
1034 if (prev_line) {
1036 } else if (_next()) {
1039 }
1040 } else {
1041 /* both handles are extended. make collinear while keeping length.
1042 first make back collinear with the vector front ---> back,
1043 then make front collinear with back ---> node.
1044 (not back ---> front, because back's position was changed in the first call) */
1047 }
1048 } break;
1049 case NODE_SYMMETRIC:
1050 if (isEndNode())
1051 return; // symmetric handles make no sense for endnodes
1052 if (isDegenerate()) {
1053 // similar to auto handles but set the same length for both
1054 Geom::Point vec_next = _next()->position() - position();
1055 Geom::Point vec_prev = _prev()->position() - position();
1056
1057 if (vec_next.length() == 0 || vec_prev.length() == 0) {
1058 // Don't change a degenerate node if it overlaps a neighbor.
1059 // (One could, but it seems pointless. In this case the
1060 // calculation of 'dir' below needs to be special cased to
1061 // avoid divide by zero.)
1062 return;
1063 }
1064
1065
1066 double len_next = vec_next.length(), len_prev = vec_prev.length();
1067 double len = (len_next + len_prev) / 6; // take 1/3 of average
1068 if (len == 0) {
1069 return;
1070 }
1071
1072 Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
1073 _back.setRelativePos(-dir * len);
1074 _front.setRelativePos(dir * len);
1075 } else {
1076 // At least one handle is extended. Compute average length, use direction from
1077 // back handle to front handle. This also works correctly for degenerates
1078 double len = (_front.length() + _back.length()) / 2;
1080 _front.setRelativePos(dir * len);
1081 _back.setRelativePos(-dir * len);
1082 }
1083 break;
1084 default:
1085 break;
1086 }
1087 // in node type changes, for BSpline traces, we can either maintain them
1088 // with NO_POWER power in border mode, or give them the default power in curve mode.
1089 if (_pm()._isBSpline()) {
1090 double weight = NO_POWER;
1091 if (!Geom::are_near(_pm()._bsplineHandlePosition(this->front()), NO_POWER, BSPLINE_TOL)) {
1093 }
1094 _front.setPosition(_pm()._bsplineHandleReposition(this->front(), weight));
1095 _back.setPosition(_pm()._bsplineHandleReposition(this->back(), weight));
1096 }
1097 }
1098 _type = type;
1099 _setControlType(nodeTypeToCtrlType(_type));
1100 updateState();
1101}
1102
1104{
1105 _type = NODE_CUSP;
1106 bool front_degen = _front.isDegenerate();
1107 bool back_degen = _back.isDegenerate();
1108 bool both_degen = front_degen && back_degen;
1109 bool neither_degen = !front_degen && !back_degen;
1110 do {
1111 // if both handles are degenerate, do nothing
1112 if (both_degen)
1113 break;
1114 // if neither are degenerate, check their respective positions
1115 if (neither_degen) {
1116 // for now do not automatically make nodes symmetric, it can be annoying
1117 /*if (Geom::are_near(front_delta, -back_delta)) {
1118 _type = NODE_SYMMETRIC;
1119 break;
1120 }*/
1121 if (are_collinear_within_serializing_error(_front.position(), position(), _back.position())) {
1123 break;
1124 }
1125 }
1126 // check whether the handle aligns with the previous line segment.
1127 // we know that if front is degenerate, back isn't, because
1128 // both_degen was false
1129 if (front_degen && _next() && _next()->_back.isDegenerate()) {
1130 if (are_collinear_within_serializing_error(_next()->position(), position(), _back.position())) {
1132 break;
1133 }
1134 } else if (back_degen && _prev() && _prev()->_front.isDegenerate()) {
1135 if (are_collinear_within_serializing_error(_prev()->position(), position(), _front.position())) {
1137 break;
1138 }
1139 }
1140 } while (false);
1141 _setControlType(nodeTypeToCtrlType(_type));
1142 updateState();
1143}
1144
1146{
1147 return !_prev() || !_next();
1148}
1149
1151{
1152 _canvas_item_ctrl->lower_to_bottom();
1153}
1154
1156{
1157 switch (x) {
1158 case 'a':
1159 return NODE_AUTO;
1160 case 'c':
1161 return NODE_CUSP;
1162 case 's':
1163 return NODE_SMOOTH;
1164 case 'z':
1165 return NODE_SYMMETRIC;
1166 default:
1167 return NODE_PICK_BEST;
1168 }
1169}
1170
1171bool Node::_eventHandler(Tools::ToolBase *event_context, CanvasEvent const &event)
1172{
1173 int dir = 0;
1174 int state = 0;
1175
1177 event,
1178 [&](ScrollEvent const &event) {
1179 state = event.modifiers;
1180 dir = Geom::sgn(event.delta.y());
1181 },
1182 [&](KeyPressEvent const &event) {
1183 state = event.modifiers;
1184 switch (event.keyval) {
1185 case GDK_KEY_Page_Up:
1186 dir = 1;
1187 break;
1188 case GDK_KEY_Page_Down:
1189 dir = -1;
1190 break;
1191 default:
1192 break;
1193 }
1194 },
1195 [&](CanvasEvent const &event) {});
1196
1197 using namespace Inkscape::Modifiers;
1198 auto linear_grow = Modifier::get(Modifiers::Type::NODE_GROW_LINEAR)->active(state);
1199 auto spatial_grow = Modifier::get(Modifiers::Type::NODE_GROW_SPATIAL)->active(state);
1200
1201 if (dir && (linear_grow || spatial_grow)) {
1202 if (linear_grow) {
1203 _linearGrow(dir);
1204 } else if (spatial_grow) {
1205 _selection.spatialGrow(this, dir);
1206 }
1207 return true;
1208 }
1209
1210 return ControlPoint::_eventHandler(event_context, event);
1211}
1212
1214{
1215 // Interestingly, we do not need any help from PathManipulator when doing linear grow.
1216 // First handle the trivial case of growing over an unselected node.
1217 if (!selected() && dir > 0) {
1218 _selection.insert(this);
1219 return;
1220 }
1221
1223 NodeList::iterator fwd = this_iter, rev = this_iter;
1224 double distance_back = 0, distance_front = 0;
1225
1226 // Linear grow is simple. We find the first unselected nodes in each direction
1227 // and compare the linear distances to them.
1228 if (dir > 0) {
1229 if (!selected()) {
1230 _selection.insert(this);
1231 return;
1232 }
1233
1234 // find first unselected nodes on both sides
1235 while (fwd && fwd->selected()) {
1236 NodeList::iterator n = fwd.next();
1237 distance_front +=
1238 Geom::bezier_length(fwd->position(), fwd->_front.position(), n->_back.position(), n->position());
1239 fwd = n;
1240 if (fwd == this_iter)
1241 // there is no unselected node in this cyclic subpath
1242 return;
1243 }
1244 // do the same for the second direction. Do not check for equality with
1245 // this node, because there is at least one unselected node in the subpath,
1246 // so we are guaranteed to stop.
1247 while (rev && rev->selected()) {
1248 NodeList::iterator p = rev.prev();
1249 distance_back +=
1250 Geom::bezier_length(rev->position(), rev->_back.position(), p->_front.position(), p->position());
1251 rev = p;
1252 }
1253
1254 NodeList::iterator t; // node to select
1255 if (fwd && rev) {
1256 if (distance_front <= distance_back)
1257 t = fwd;
1258 else
1259 t = rev;
1260 } else {
1261 if (fwd)
1262 t = fwd;
1263 if (rev)
1264 t = rev;
1265 }
1266 if (t)
1267 _selection.insert(t.ptr());
1268
1269 // Linear shrink is more complicated. We need to find the farthest selected node.
1270 // This means we have to check the entire subpath. We go in the direction in which
1271 // the distance we traveled is lower. We do this until we run out of nodes (ends of path)
1272 // or the two iterators meet. On the way, we store the last selected node and its distance
1273 // in each direction (if any). At the end, we choose the one that is farther and deselect it.
1274 } else {
1275 // both iterators that store last selected nodes are initially empty
1276 NodeList::iterator last_fwd, last_rev;
1277 double last_distance_back = 0, last_distance_front = 0;
1278
1279 while (rev || fwd) {
1280 if (fwd && (!rev || distance_front <= distance_back)) {
1281 if (fwd->selected()) {
1282 last_fwd = fwd;
1283 last_distance_front = distance_front;
1284 }
1285 NodeList::iterator n = fwd.next();
1286 if (n)
1287 distance_front += Geom::bezier_length(fwd->position(), fwd->_front.position(), n->_back.position(),
1288 n->position());
1289 fwd = n;
1290 } else if (rev && (!fwd || distance_front > distance_back)) {
1291 if (rev->selected()) {
1292 last_rev = rev;
1293 last_distance_back = distance_back;
1294 }
1295 NodeList::iterator p = rev.prev();
1296 if (p)
1297 distance_back += Geom::bezier_length(rev->position(), rev->_back.position(), p->_front.position(),
1298 p->position());
1299 rev = p;
1300 }
1301 // Check whether we walked the entire cyclic subpath.
1302 // This is initially true because both iterators start from this node,
1303 // so this check cannot go in the while condition.
1304 // When this happens, we need to check the last node, pointed to by the iterators.
1305 if (fwd && fwd == rev) {
1306 if (!fwd->selected())
1307 break;
1308 NodeList::iterator fwdp = fwd.prev(), revn = rev.next();
1309 double df = distance_front + Geom::bezier_length(fwdp->position(), fwdp->_front.position(),
1310 fwd->_back.position(), fwd->position());
1311 double db = distance_back + Geom::bezier_length(revn->position(), revn->_back.position(),
1312 rev->_front.position(), rev->position());
1313 if (df > db) {
1314 last_fwd = fwd;
1315 last_distance_front = df;
1316 } else {
1317 last_rev = rev;
1318 last_distance_back = db;
1319 }
1320 break;
1321 }
1322 }
1323
1325 if (last_fwd && last_rev) {
1326 if (last_distance_front >= last_distance_back)
1327 t = last_fwd;
1328 else
1329 t = last_rev;
1330 } else {
1331 if (last_fwd)
1332 t = last_fwd;
1333 if (last_rev)
1334 t = last_rev;
1335 }
1336 if (t)
1337 _selection.erase(t.ptr());
1338 }
1339}
1340
1342{
1343 // change node size to match type and selection state
1345 switch (state) {
1346 // These were used to set "active" and "prelight" flags but the flags weren't being used.
1347 case STATE_NORMAL:
1348 case STATE_MOUSEOVER:
1349 break;
1350 case STATE_CLICKED:
1351 // show the handles when selecting the nodes
1352 if (_pm()._isBSpline()) {
1353 this->front()->setPosition(_pm()._bsplineHandleReposition(this->front()));
1354 this->back()->setPosition(_pm()._bsplineHandleReposition(this->back()));
1355 }
1356 break;
1357 }
1359}
1360
1361bool Node::grabbed(MotionEvent const &event)
1362{
1364 return true;
1365 }
1366
1367 // Dragging out handles with Shift + drag on a node.
1368 if (!mod_shift(event)) {
1369 return false;
1370 }
1371
1372 Geom::Point evp = event.pos;
1373 Geom::Point rel_evp = evp - _last_click_event_point();
1374
1375 // This should work even if dragtolerance is zero and evp coincides with node position.
1376 double angle_next = HUGE_VAL;
1377 double angle_prev = HUGE_VAL;
1378 bool has_degenerate = false;
1379 // determine which handle to drag out based on degeneration and the direction of drag
1380 if (_front.isDegenerate() && _next()) {
1381 Geom::Point next_relpos = _desktop->d2w(_next()->position()) - _desktop->d2w(position());
1382 angle_next = fabs(Geom::angle_between(rel_evp, next_relpos));
1383 has_degenerate = true;
1384 }
1385 if (_back.isDegenerate() && _prev()) {
1386 Geom::Point prev_relpos = _desktop->d2w(_prev()->position()) - _desktop->d2w(position());
1387 angle_prev = fabs(Geom::angle_between(rel_evp, prev_relpos));
1388 has_degenerate = true;
1389 }
1390 if (!has_degenerate) {
1391 return false;
1392 }
1393
1394 Handle *h = angle_next < angle_prev ? &_front : &_back;
1395
1396 h->setPosition(_desktop->w2d(evp));
1397 h->setVisible(true);
1398 h->transferGrab(this, event);
1399 Handle::_drag_out = true;
1400 return true;
1401}
1402
1403void Node::dragged(Geom::Point &new_pos, MotionEvent const &event)
1404{
1405 // For a note on how snapping is implemented in Inkscape, see snap.h.
1406 auto &sm = _desktop->getNamedView()->snap_manager;
1407 // even if we won't really snap, we might still call the one of the
1408 // constrainedSnap() methods to enforce the constraints, so we need
1409 // to setup the snapmanager anyway; this is also required for someSnapperMightSnap()
1410 sm.setup(_desktop);
1411
1412 // do not snap when Shift is pressed
1413 bool snap = !mod_shift(event) && sm.someSnapperMightSnap();
1414
1416 std::vector<Inkscape::SnapCandidatePoint> unselected;
1417 if (snap) {
1418 /* setup
1419 * TODO We are doing this every time a snap happens. It should once be done only once
1420 * per drag - maybe in the grabbed handler?
1421 * TODO Unselected nodes vector must be valid during the snap run, because it is not
1422 * copied. Fix this in snap.h and snap.cpp, then the above.
1423 * TODO Snapping to unselected segments of selected paths doesn't work yet. */
1424
1425 // Build the list of unselected nodes.
1426 for (auto node : _selection.allPoints()) {
1427 if (!node->selected()) {
1428 auto n = static_cast<Node *>(node);
1429 unselected.emplace_back(n->position(), n->_snapSourceType(), n->_snapTargetType());
1430 }
1431 }
1432 sm.unSetup();
1433 sm.setupIgnoreSelection(_desktop, true, &unselected);
1434 }
1435
1436 // Snap candidate point for free snapping; this will consider snapping tangentially
1437 // and perpendicularly and therefore the origin or direction vector must be set
1438 Inkscape::SnapCandidatePoint scp_free(new_pos, _snapSourceType());
1439
1440 std::optional<Geom::Point> front_direction, back_direction;
1442 Geom::Point dummy_cp;
1443 if (_front.isDegenerate()) { // If there is no handle for the path segment towards the next node, then this segment
1444 // may be straight
1445 if (_is_line_segment(this, _next())) {
1446 front_direction = _next()->position() - origin;
1447 if (_next()->selected()) {
1448 dummy_cp = _next()->position() - position();
1449 scp_free.addVector(dummy_cp);
1450 } else {
1451 dummy_cp = _next()->position();
1452 scp_free.addOrigin(dummy_cp);
1453 }
1454 }
1455 } else { // .. this path segment is curved
1456 front_direction = _front.relativePos();
1457 scp_free.addVector(*front_direction);
1458 }
1459
1460 if (_back.isDegenerate()) { // If there is no handle for the path segment towards the previous node, then this
1461 // segment may be straight
1462 if (_is_line_segment(_prev(), this)) {
1463 back_direction = _prev()->position() - origin;
1464 if (_prev()->selected()) {
1465 dummy_cp = _prev()->position() - position();
1466 scp_free.addVector(dummy_cp);
1467 } else {
1468 dummy_cp = _prev()->position();
1469 scp_free.addOrigin(dummy_cp);
1470 }
1471 }
1472 } else { // .. this path segment is curved
1473 back_direction = _back.relativePos();
1474 scp_free.addVector(*back_direction);
1475 }
1476
1477 if (mod_ctrl(event)) {
1478 // We're about to consider a constrained snap, which is already limited to 1D
1479 // Therefore tangential or perpendicular snapping will not be considered, and therefore
1480 // all calls above to scp_free.addVector() and scp_free.addOrigin() can be neglected
1481 std::vector<Inkscape::Snapper::SnapConstraint> constraints;
1482 if (mod_alt(event)) { // with Ctrl+Alt, constrain to handle lines
1484 int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
1485 double min_angle = M_PI / snaps;
1486
1487 if (front_direction) { // We only have a front_point if the front handle is extracted, or if it is not
1488 // extracted but the path segment is straight (see above)
1489 constraints.emplace_back(origin, *front_direction);
1490 }
1491
1492 if (back_direction) {
1493 constraints.emplace_back(origin, *back_direction);
1494 }
1495
1496 // For smooth nodes, we will also snap to normals of handle lines. For cusp nodes this would be unintuitive
1497 // and confusing Only snap to the normals when they are further than snap increment away from the second
1498 // handle constraint
1499 if (_type != NODE_CUSP) {
1500 std::optional<Geom::Point> front_normal = Geom::rot90(*front_direction);
1501 if (front_normal && (!back_direction ||
1502 (fabs(Geom::angle_between(*front_normal, *back_direction)) > min_angle &&
1503 fabs(Geom::angle_between(*front_normal, *back_direction)) < M_PI - min_angle))) {
1504 constraints.emplace_back(origin, *front_normal);
1505 }
1506
1507 std::optional<Geom::Point> back_normal = Geom::rot90(*back_direction);
1508 if (back_normal && (!front_direction ||
1509 (fabs(Geom::angle_between(*back_normal, *front_direction)) > min_angle &&
1510 fabs(Geom::angle_between(*back_normal, *front_direction)) < M_PI - min_angle))) {
1511 constraints.emplace_back(origin, *back_normal);
1512 }
1513 }
1514
1515 sp = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), constraints,
1516 mod_shift(event));
1517 } else {
1518 // with Ctrl and no Alt: constrain to axes
1519 constraints.emplace_back(origin, Geom::Point(1, 0));
1520 constraints.emplace_back(origin, Geom::Point(0, 1));
1521 sp = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), constraints,
1522 mod_shift(event));
1523 }
1524 new_pos = sp.getPoint();
1525 } else if (snap) {
1526 Inkscape::SnappedPoint sp = sm.freeSnap(scp_free);
1527 new_pos = sp.getPoint();
1528 }
1529
1530 sm.unSetup();
1531
1532 SelectableControlPoint::dragged(new_pos, event);
1533}
1534
1536{
1537 if (_pm()._nodeClicked(this, event)) {
1538 return true;
1539 }
1540 return SelectableControlPoint::clicked(event);
1541}
1542
1549
1556
1561
1563{
1564 if (_next() == to) {
1565 return front();
1566 }
1567 if (_prev() == to) {
1568 return back();
1569 }
1570 g_error("Node::handleToward(): second node is not adjacent!");
1571 return nullptr;
1572}
1573
1575{
1576 if (front() == dir) {
1577 return _next();
1578 }
1579 if (back() == dir) {
1580 return _prev();
1581 }
1582 g_error("Node::nodeToward(): handle is not a child of this node!");
1583 return nullptr;
1584}
1585
1587{
1588 if (_next() == to) {
1589 return back();
1590 }
1591 if (_prev() == to) {
1592 return front();
1593 }
1594 g_error("Node::handleAwayFrom(): second node is not adjacent!");
1595 return nullptr;
1596}
1597
1599{
1600 if (front() == h) {
1601 return _prev();
1602 }
1603 if (back() == h) {
1604 return _next();
1605 }
1606 g_error("Node::nodeAwayFrom(): handle is not a child of this node!");
1607 return nullptr;
1608}
1609
1610Glib::ustring Node::_getTip(unsigned state) const
1611{
1612 bool isBSpline = _pm()._isBSpline();
1613 Handle *h = const_cast<Handle *>(&_front);
1614 Glib::ustring s = C_("Path node tip",
1615 "node handle"); // not expected
1616
1617 if (mod_shift(state)) {
1618 bool can_drag_out = (_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate());
1619
1620 if (can_drag_out) {
1621 /*if (state_held_control(state)) {
1622 s = format_tip(C_("Path node tip",
1623 "<b>Shift+Ctrl:</b> drag out a handle and snap its angle "
1624 "to %f° increments"), snap_increment_degrees());
1625 }*/
1626 s = C_("Path node tip", "<b>Shift</b>: drag out a handle, click to toggle selection");
1627 } else {
1628 s = C_("Path node tip", "<b>Shift</b>: click to toggle selection");
1629 }
1630 }
1631
1632 else if (mod_ctrl(state)) {
1633 if (mod_alt(state)) {
1634 s = C_("Path node tip", "<b>Ctrl+Alt</b>: move along handle lines or line segment, click to delete node");
1635 } else {
1636 s = C_("Path node tip", "<b>Ctrl</b>: move along axes, click to change node type");
1637 }
1638 }
1639
1640 else if (mod_alt(state)) {
1641 s = C_("Path node tip", "<b>Alt</b>: sculpt nodes");
1642 }
1643
1644 else { // No modifiers: assemble tip from node type
1645 char const *nodetype = node_type_to_localized_string(_type);
1646 double power = _pm()._bsplineHandlePosition(h);
1647
1649 if (_selection.size() == 1) {
1650 if (!isBSpline) {
1651 s = format_tip(C_("Path node tip", "<b>%s</b>: "
1652 "drag to shape the path"
1653 ". "
1654 "(more: Shift, Ctrl, Alt)"),
1655 nodetype);
1656 } else {
1657 s = format_tip(C_("Path node tip", "<b>BSpline node</b> (%.3g power): "
1658 "drag to shape the path"
1659 ". "
1660 "(more: Shift, Ctrl, Alt)"),
1661 power);
1662 }
1663 } else {
1664 s = format_tip(C_("Path node tip", "<b>%s</b>: "
1665 "drag to shape the path"
1666 ", "
1667 "click to toggle scale/rotation handles"
1668 ". "
1669 "(more: Shift, Ctrl, Alt)"),
1670 nodetype);
1671 }
1672 } else if (!isBSpline) {
1673 s = format_tip(C_("Path node tip", "<b>%s</b>: "
1674 "drag to shape the path"
1675 ", "
1676 "click to select only this node"
1677 ". "
1678 "(more: Shift, Ctrl, Alt)"),
1679 nodetype);
1680 } else {
1681 s = format_tip(C_("Path node tip", "<b>BSpline node</b> (%.3g power): "
1682 "drag to shape the path"
1683 ", "
1684 "click to select only this node"
1685 ". "
1686 "(more: Shift, Ctrl, Alt)"),
1687 power);
1688 }
1689 }
1690
1691 return (s);
1692}
1693
1694Glib::ustring Node::_getDragTip(MotionEvent const & /*event*/) const
1695{
1697
1700 Glib::ustring x = x_q.string(_desktop->getNamedView()->display_units);
1701 Glib::ustring y = y_q.string(_desktop->getNamedView()->display_units);
1702 Glib::ustring ret = format_tip(C_("Path node tip", "Move node by %s, %s"), x.c_str(), y.c_str());
1703 return ret;
1704}
1705
1710{
1711 switch (type) {
1712 case NODE_CUSP:
1713 return _("Corner node");
1714 case NODE_SMOOTH:
1715 return _("Smooth node");
1716 case NODE_SYMMETRIC:
1717 return _("Symmetric node");
1718 case NODE_AUTO:
1719 return _("Auto-smooth node");
1720 default:
1721 return "";
1722 }
1723}
1724
1725bool Node::_is_line_segment(Node *first, Node *second)
1726{
1727 if (!first || !second)
1728 return false;
1729 if (first->_next() == second)
1730 return first->_front.isDegenerate() && second->_back.isDegenerate();
1731 if (second->_next() == first)
1732 return second->_front.isDegenerate() && first->_back.isDegenerate();
1733 return false;
1734}
1735
1737 : _list(splist)
1738{
1739 this->ln_list = this;
1740 this->ln_next = this;
1741 this->ln_prev = this;
1742}
1743
1745{
1746 clear();
1747}
1748
1750{
1751 return ln_next == this;
1752}
1753
1755{
1756 size_type sz = 0;
1757 for (ListNode *ln = ln_next; ln != this; ln = ln->ln_next)
1758 ++sz;
1759 return sz;
1760}
1761
1763{
1764 return closed() ? empty() : ++begin() == end();
1765}
1766
1767NodeList::iterator NodeList::before(double t, double *fracpart)
1768{
1769 double intpart;
1770 *fracpart = std::modf(t, &intpart);
1771 int index = intpart;
1772
1773 iterator ret = begin();
1774 std::advance(ret, index);
1775 return ret;
1776}
1777
1779{
1780 iterator ret = begin();
1781 std::advance(ret, pvp.curve_index);
1782 return ret;
1783}
1784
1786{
1787 ListNode *ins = pos._node;
1788 x->ln_next = ins;
1789 x->ln_prev = ins->ln_prev;
1790 ins->ln_prev->ln_next = x;
1791 ins->ln_prev = x;
1792 x->ln_list = this;
1793 return iterator(x);
1794}
1795
1797{
1798 splice(pos, list, list.begin(), list.end());
1799}
1800
1802{
1803 NodeList::iterator j = i;
1804 ++j;
1805 splice(pos, list, i, j);
1806}
1807
1808void NodeList::splice(iterator pos, NodeList & /*list*/, iterator first, iterator last)
1809{
1810 ListNode *ins_beg = first._node, *ins_end = last._node, *at = pos._node;
1811 for (ListNode *ln = ins_beg; ln != ins_end; ln = ln->ln_next) {
1812 ln->ln_list = this;
1813 }
1814 ins_beg->ln_prev->ln_next = ins_end;
1815 ins_end->ln_prev->ln_next = at;
1816 at->ln_prev->ln_next = ins_beg;
1817
1818 ListNode *atprev = at->ln_prev;
1819 at->ln_prev = ins_end->ln_prev;
1820 ins_end->ln_prev = ins_beg->ln_prev;
1821 ins_beg->ln_prev = atprev;
1822}
1823
1825{
1826 // 1. make the list perfectly cyclic
1829 // 2. find new begin
1830 ListNode *new_begin = ln_next;
1831 if (n > 0) {
1832 for (; n > 0; --n)
1833 new_begin = new_begin->ln_next;
1834 } else {
1835 for (; n < 0; ++n)
1836 new_begin = new_begin->ln_prev;
1837 }
1838 // 3. relink begin to list
1839 ln_next = new_begin;
1840 ln_prev = new_begin->ln_prev;
1841 new_begin->ln_prev->ln_next = this;
1842 new_begin->ln_prev = this;
1843}
1844
1846{
1847 for (ListNode *ln = ln_next; ln != this; ln = ln->ln_prev) {
1848 std::swap(ln->ln_next, ln->ln_prev);
1849 Node *node = static_cast<Node *>(ln);
1850 Geom::Point save_pos = node->front()->position();
1851 node->front()->setPosition(node->back()->position());
1852 node->back()->setPosition(save_pos);
1853 }
1854 std::swap(ln_next, ln_prev);
1855}
1856
1858{
1859 // ugly but more efficient clearing mechanism
1860 std::vector<ControlPointSelection *> to_clear;
1861 std::vector<std::pair<SelectableControlPoint *, long>> nodes;
1862 long in = -1;
1863 for (iterator i = begin(); i != end(); ++i) {
1864 SelectableControlPoint *rm = static_cast<Node *>(i._node);
1865 if (std::find(to_clear.begin(), to_clear.end(), &rm->_selection) == to_clear.end()) {
1866 to_clear.push_back(&rm->_selection);
1867 ++in;
1868 }
1869 nodes.emplace_back(rm, in);
1870 }
1871 for (auto const &node : nodes) {
1872 to_clear[node.second]->erase(node.first, false);
1873 }
1874 std::vector<std::vector<SelectableControlPoint *>> emission;
1875 for (long i = 0, e = to_clear.size(); i != e; ++i) {
1876 emission.emplace_back();
1877 for (auto const &node : nodes) {
1878 if (node.second != i)
1879 break;
1880 emission[i].push_back(node.first);
1881 }
1882 }
1883
1884 for (size_t i = 0, e = emission.size(); i != e; ++i) {
1885 to_clear[i]->signal_selection_changed.emit(emission[i], false);
1886 }
1887
1888 for (iterator i = begin(); i != end();)
1889 erase(i++);
1890}
1891
1893{
1894 // some gymnastics are required to ensure that the node is valid when deleted;
1895 // otherwise the code that updates handle visibility will break
1896 Node *rm = static_cast<Node *>(i._node);
1897 ListNode *rmnext = rm->ln_next, *rmprev = rm->ln_prev;
1898 ++i;
1899 delete rm;
1900 rmprev->ln_next = rmnext;
1901 rmnext->ln_prev = rmprev;
1902 return i;
1903}
1904
1905// TODO this method is very ugly!
1906// converting SubpathList to an intrusive list might allow us to get rid of it
1908{
1909 for (SubpathList::iterator i = _list.begin(); i != _list.end(); ++i) {
1910 if (i->get() == this) {
1911 _list.erase(i);
1912 return;
1913 }
1914 }
1915}
1916
1918{
1919 return n->nodeList();
1920}
1922{
1923 return *(i._node->ln_list);
1924}
1925
1926} // namespace UI
1927} // namespace Inkscape
1928
1929/*
1930 Local Variables:
1931 mode:c++
1932 c-file-style:"stroustrup"
1933 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1934 indent-tabs-mode:nil
1935 fill-column:99
1936 End:
1937*/
1938// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
auto make_canvasitem(Args &&... args)
Convienence function to create a CanvasItemPtr, like std::make_unique.
3x3 matrix representing an affine transformation.
Definition affine.h:70
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
Infinite line on a plane.
Definition line.h:53
Coord nearestTime(Point const &p) const
Find a point on the line closest to the query point.
Definition line.h:252
Point pointAt(Coord t) const
Definition line.h:231
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Data type representing a typeless value of a preference.
int getIntLimited(int def=0, int min=INT_MIN, int max=INT_MAX) const
Interpret the preference as a limited integer.
Base class for preference observers.
Definition preferences.h:87
virtual void notify(Preferences::Entry const &new_val)=0
Notification about a preference change.
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
static Preferences * _instance
void removeObserver(Observer &)
Remove an observer an prevent further notifications to it.
void addObserver(Observer &)
Register a preference observer.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
Class to store data for points which are snap candidates, either as a source or as a target.
void addOrigin(Geom::Point pt)
void addVector(Geom::Point v)
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.
void erase(iterator pos, bool to_update=true)
Remove a point from the selection.
void spatialGrow(SelectableControlPoint *origin, int dir)
Draggable point, the workhorse of on-canvas editing.
static Geom::Point const & _last_click_event_point()
void transferGrab(ControlPoint *from, MotionEvent const &event)
Transfer the grab to another point.
void _setControlType(Inkscape::CanvasItemCtrlType type)
Geom::Point const & position() const
Current position of the control point.
static Glib::ustring format_tip(char const *format,...) G_GNUC_PRINTF(1
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.
State
Enumeration representing the possible states of the control point, used to determine its appearance.
@ STATE_CLICKED
First mouse button pressed over the control point.
@ STATE_MOUSEOVER
Mouse is hovering over the control point.
SPDesktop *const _desktop
The desktop this control point resides on.
static Glib::ustring virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, CanvasEvent const &event)
static Geom::Point const & _last_drag_origin()
void set_selected_appearance(bool selected)
CanvasItemPtr< Inkscape::CanvasItemCtrl > _canvas_item_ctrl
Visual representation of the control point.
Geom::Point relativePos() const
Definition node.h:460
bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, CanvasEvent const &event) override
Definition node.cpp:353
void _update_bspline_handles()
Definition node.cpp:195
void setDirection(Geom::Point const &from, Geom::Point const &to)
Definition node.cpp:323
void setRelativePos(Geom::Point const &p)
Definition node.h:463
static double _saved_length
Definition node.h:116
static Geom::Point _saved_dir
Definition node.h:115
static Geom::Point _saved_other_pos
Control point of a cubic Bezier curve in a path.
Definition node.h:114
bool isDegenerate() const
Definition node.h:68
Handle * other()
Definition node.cpp:603
void setVisible(bool) override
Set the visibility of the control point.
Definition node.cpp:188
static bool _drag_out
Definition node.h:117
void setLength(double len)
Definition node.cpp:310
PathManipulator & _pm()
Definition node.h:469
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
static char const * handle_type_to_localized_string(NodeType type)
See also: Node::node_type_to_localized_string(NodeType type)
Definition node.cpp:337
void move(Geom::Point const &p) override
Move the control point to new position with side effects.
Definition node.cpp:206
CanvasItemPtr< CanvasItemCurve > _handle_line
Definition node.h:105
self prev() const
Definition node.h:316
self next() const
Definition node.h:311
iterator begin()
Definition node.h:358
NodeIterator< value_type > iterator
Definition node.h:343
size_type size() const
Definition node.cpp:1754
static NodeList & get(Node *n)
Definition node.cpp:1917
bool closed() const
Definition node.h:368
NodeList(SubpathList &_list)
An editable list of nodes representing a subpath.
Definition node.cpp:1736
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
bool degenerate() const
A subpath is degenerate if it has no segments - either one node in an open path or no nodes in a clos...
Definition node.cpp:1762
std::size_t size_type
Definition node.h:337
void shift(int n)
Definition node.cpp:1824
SubpathList & _list
Definition node.h:428
void splice(iterator pos, NodeList &list)
Definition node.cpp:1796
iterator before(double t, double *fracpart=nullptr)
Definition node.cpp:1767
iterator end()
Definition node.h:359
bool empty() const
Definition node.cpp:1749
iterator erase(iterator pos)
Definition node.cpp:1892
Node * nodeAwayFrom(Handle *h)
Gets the node in the direction opposite to the given handle.
Definition node.cpp:1598
static NodeType parse_nodetype(char x)
Definition node.cpp:1155
Node * _prev()
Definition node.cpp:784
Node * nodeToward(Handle *h)
Gets the node in the direction of the given handle.
Definition node.cpp:1574
Handle * handleAwayFrom(Node *to)
Gets the handle that goes in the direction opposite to the given adjacent node.
Definition node.cpp:1586
Glib::ustring _getTip(unsigned state) const override
Definition node.cpp:1610
void fixNeighbors() override
Affine transforms keep handle invariants for smooth and symmetric nodes, but smooth nodes at ends of ...
Definition node.cpp:887
Glib::ustring _getDragTip(MotionEvent const &event) const override
Definition node.cpp:1694
void _linearGrow(int dir)
Select or deselect a node in this node's subpath based on its path distance from this node.
Definition node.cpp:1213
void move(Geom::Point const &p) override
Move the control point to new position with side effects.
Definition node.cpp:794
Geom::Rect bounds() const override
Definition node.cpp:872
bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, CanvasEvent const &event) override
Customized event handler to catch scroll events needed for selection grow/shrink.
Definition node.cpp:1171
Handle * front()
Definition node.h:164
Node * _next()
Definition node.cpp:769
bool clicked(ButtonReleaseEvent const &event) override
Called when the control point is clicked, at mouse button release.
Definition node.cpp:1535
void setType(NodeType type, bool update_handles=true)
Sets the node type and optionally restores the invariants associated with the given type.
Definition node.cpp:982
static bool _is_line_segment(Node *first, Node *second)
Determine whether two nodes are joined by a linear segment.
Definition node.cpp:1725
bool isDegenerate() const
Definition node.h:162
std::optional< Geom::Point > _unfixed_pos
Definition node.h:250
bool grabbed(MotionEvent const &event) override
Called when the user moves the point beyond the drag tolerance with the first button held down.
Definition node.cpp:1361
void dragged(Geom::Point &new_pos, MotionEvent const &event) override
Called while dragging, but before moving the knot to new position.
Definition node.cpp:1403
Handle _front
Node handle in the backward direction of the path.
Definition node.h:243
void sink()
Move the node to the bottom of its canvas group.
Definition node.cpp:1150
static char const * node_type_to_localized_string(NodeType type)
See also: Handle::handle_type_to_localized_string(NodeType type)
Definition node.cpp:1709
Handle _back
Node handle in the forward direction of the path.
Definition node.h:244
void pickBestType()
Pick the best type for this node, based on the position of its handles.
Definition node.cpp:1103
Handle * back()
Definition node.h:165
void showHandles(bool v)
Definition node.cpp:963
NodeType _type
Type of node - cusp, smooth...
Definition node.h:245
void _updateAutoHandles()
Definition node.cpp:934
Inkscape::SnapTargetType _snapTargetType() const
Definition node.cpp:1550
void transform(Geom::Affine const &m) override
Apply an arbitrary affine transformation to a control point.
Definition node.cpp:835
PathManipulator & _pm()
Definition node.h:475
NodeType type() const
Definition node.h:140
bool _handles_shown
Definition node.h:246
Handle * handleToward(Node *to)
Gets the handle that faces the given adjacent node.
Definition node.cpp:1562
bool isEndNode() const
Definition node.cpp:1145
void _setState(State state) override
Change the state of the knot.
Definition node.cpp:1341
void updateHandles()
Definition node.cpp:974
Inkscape::SnapCandidatePoint snapCandidatePoint()
Definition node.cpp:1557
Inkscape::SnapSourceType _snapSourceType() const
Definition node.cpp:1543
void update(bool alert_LPE=false)
Update the display and the outline of the path.
void _commit(Glib::ustring const &annotation)
Update the XML representation and put the specified annotation on the undo stack.
double _bsplineHandlePosition(Handle *h, bool check_other=true)
Desktop-bound selectable control object.
bool grabbed(MotionEvent const &event) override
Called when the user moves the point beyond the drag tolerance with the first button held down.
void dragged(Geom::Point &new_pos, MotionEvent const &event) override
Called while dragging, but before moving the knot to new position.
bool clicked(ButtonReleaseEvent const &event) override
Called when the control point is clicked, at mouse button release.
void _setState(State state) override
Change the state of the knot.
List of node lists.
Definition node.h:442
Base class for Event processors.
Definition tool-base.h:107
Glib::ustring string(Unit const *u) const
Return a printable string of the value in the specified unit.
Definition units.cpp:578
virtual void setPosition(int pos)=0
Set the position of this node in parent's child order.
virtual unsigned position() const =0
Get the index of this node in parent's child order.
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
SPNamedView * getNamedView() const
Definition desktop.h:191
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
SnapManager snap_manager
Inkscape::Util::Unit const * display_units
Class to coordinate snapping operations.
Definition snap.h:80
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
void setupIgnoreSelection(SPDesktop const *desktop, bool snapindicator=true, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Setup, taking the list of items to ignore from the desktop's selection.
Definition snap.cpp:701
void freeSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:135
SPNamedView const * getNamedView() const
Definition snap.h:371
Inkscape::SnappedPoint constrainedSnap(Inkscape::SnapCandidatePoint const &p, Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point along a constraint line to grids, guides or objects.
Definition snap.cpp:242
void unSetup()
Definition snap.h:147
bool someSnapperMightSnap(bool immediately=true) const
Return true if any snapping might occur, whether its to grids, guides or objects.
Definition snap.cpp:100
Control point selection - stores a set of control points and applies transformations to them.
Css & result
Editable view implementation.
static char const *const parent
Definition dir-util.cpp:70
@ SP_ANCHOR_CENTER
Definition enums.h:28
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Inkscape::XML::Node * node
static InkscapeApplication * _instance
Coord length(LineSegment const &seg)
int sgn(const T &x)
Sign function - indicates the sign of a numeric type.
Definition math-utils.h:51
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
Coord bezier_length(std::vector< Point > const &points, Coord tolerance=0.01)
Compute the length of a bezier curve given by a vector of its control points.
Point constrain_angle(Point const &A, Point const &B, unsigned n=4, Point const &dir={1, 0})
double angle_between(Line const &l1, Line const &l2)
Definition line.h:456
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
Point unit_vector(Point const &a)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
void sp_update_helperpath(SPDesktop *desktop)
static double snap_increment_degrees()
Definition node.cpp:612
const double NO_POWER
Definition node.cpp:138
static Geom::Point direction(Geom::Point const &first, Geom::Point const &second)
Computes an unit vector of the direction from first to second control point.
Definition node.cpp:164
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_PICK_BEST
Select type based on handle positions.
Definition node-types.h:26
@ 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_SMOOTH
Smooth node - handles must be colinear.
Definition node-types.h:22
const double BSPLINE_TOL
Definition node.cpp:137
const double DEFAULT_START_POWER
Definition node.cpp:139
Helper class to stream background task notifications as a series of messages.
bool mod_alt(unsigned modifiers)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
SnapSourceType
enumerations of snap source types and snap target types.
Definition snap-enums.h:18
@ SNAPSOURCE_NODE_CUSP
Definition snap-enums.h:37
@ SNAPSOURCE_NODE_SMOOTH
Definition snap-enums.h:36
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool mod_ctrl(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
@ SNAPTARGET_NODE_CUSP
Definition snap-enums.h:83
@ SNAPTARGET_NODE_SMOOTH
Definition snap-enums.h:82
@ CANVAS_ITEM_CTRL_TYPE_NODE_AUTO
@ CANVAS_ITEM_CTRL_TYPE_NODE_CUSP
@ CANVAS_ITEM_CTRL_TYPE_NODE_SMOOTH
@ CANVAS_ITEM_CTRL_TYPE_NODE_SYMMETRICAL
@ CANVAS_ITEM_CTRL_TYPE_ROTATE
bool mod_shift(unsigned modifiers)
New node tool with support for multiple path editing.
Path manipulator - a component that edits a single path on-canvas.
unsigned long weight
Definition quantize.cpp:37
auto len
Definition safe-printf.h:21
static const Point data[]
Generalized time value in the path.
Definition path.h:139
size_type curve_index
Index of the curve in the path.
Definition path.h:143
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
A key has been pressed.
Movement of the mouse pointer.
Scroll the item or widget by the provided amount.
ListNode * ln_prev
Definition node.h:48
NodeList * ln_list
Definition node.h:49
ListNode * ln_next
Definition node.h:47
int delta
int index
SPDesktop * desktop