Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
pen-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors:
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * bulia byak <buliabyak@users.sf.net>
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 2000 Lauris Kaplinski
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 * Copyright (C) 2002 Lauris Kaplinski
15 * Copyright (C) 2004 Monash University
16 *
17 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
18 */
19
20#include <cstring>
21#include <string>
22
23#include <gdk/gdkkeysyms.h>
24#include <glibmm/i18n.h>
25
26#include "context-fns.h"
27#include "desktop.h"
28#include "message-context.h"
29#include "message-stack.h"
30#include "selection-chemistry.h"
31#include "selection.h"
32
33#include "display/curve.h"
37
38#include "object/sp-path.h"
39
40#include "ui/draw-anchor.h"
41#include "ui/tools/pen-tool.h"
43
44// we include the necessary files for BSpline & Spiro
45#include "live_effects/effect.h" // for Effect
46#include "live_effects/lpeobject.h" // for LivePathEffectObject
47
48#define INKSCAPE_LPE_SPIRO_C
49#include "live_effects/lpe-spiro.h" // for sp_spiro_do_effect
50
51#define INKSCAPE_LPE_BSPLINE_C
52#include "live_effects/lpe-bspline.h" // for sp_bspline_do_effect
53
54// Given an optionally-present SPCurve, e.g. a smart/raw pointer or an optional,
55// return a copy of its pathvector if present, or a blank pathvector otherwise.
56template <typename T>
58{
59 if (p) {
60 return p->get_pathvector();
61 } else {
62 return {};
63 }
64}
65
66namespace Inkscape {
67namespace UI {
68namespace Tools {
69
71static bool pen_within_tolerance = false;
72
73PenTool::PenTool(SPDesktop *desktop, std::string &&prefs_path, std::string &&cursor_filename)
74 : FreehandBase(desktop, std::move(prefs_path), std::move(cursor_filename))
75 , _acc_to_line{"tool.pen.to-line"}
76 , _acc_to_curve{"tool.pen.to-curve"}
77 , _acc_to_guides{"tool.pen.to-guides"}
78{
79 tablet_enabled = false;
80
81 // Pen indicators (temporary handles shown when adding a new node).
82 auto canvas = desktop->getCanvasControls();
83
84 cl0 = make_canvasitem<CanvasItemCurve>(canvas);
85 cl1 = make_canvasitem<CanvasItemCurve>(canvas);
86 cl0->set_visible(false);
87 cl1->set_visible(false);
88
89 for (int i = 0; i < 4; i++) {
90 ctrl[i] = make_canvasitem<CanvasItemCtrl>(canvas, ctrl_types[i]);
91 ctrl[i]->set_visible(false);
92 }
93
94 sp_event_context_read(this, "mode");
95
96 this->anchor_statusbar = false;
97
98 this->setPolylineMode();
99
101 if (prefs->getBool("/tools/freehand/pen/selcue")) {
102 this->enableSelectionCue();
103 }
104
106}
107
109 _desktop_destroy.disconnect();
111
112 if (this->npoints != 0) {
113 // switching context - finish path
114 this->ea = nullptr; // unset end anchor if set (otherwise crashes)
115 if (state != State::DEAD) {
116 _finish(false);
117 }
118 }
119
120 for (auto &c : ctrl) {
121 c.reset();
122 }
123 cl0.reset();
124 cl1.reset();
125
126 if (this->waiting_item && this->expecting_clicks_for_LPE > 0) {
127 // we received too few clicks to sanely set the parameter path so we remove the LPE from the item
129 }
130}
131
134 guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0);
135 // change the nodes to make space for bspline mode
136 this->polylines_only = (mode == 3 || mode == 4);
137 this->polylines_paraxial = (mode == 4);
138 this->spiro = (mode == 1);
139 this->bspline = (mode == 2);
140 this->_bsplineSpiroColor();
141 if (!this->green_bpaths.empty()) {
142 this->_redrawAll();
143 }
144}
145
146
148 this->state = PenTool::STOP;
149 this->_resetColors();
150 for (auto &c : ctrl) {
151 c->set_visible(false);
152 }
153 cl0->set_visible(false);
154 cl1->set_visible(false);
155 this->message_context->clear();
156 this->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled"));
157 _redo_stack.clear();
158}
159
164 Glib::ustring name = val.getEntryName();
165
166 if (name == "mode") {
167 if ( val.getString() == "drag" ) {
168 this->mode = MODE_DRAG;
169 } else {
170 this->mode = MODE_CLICK;
171 }
172 }
173}
174
176 // note: waiting_LPE_type is defined in SPDrawContext
177 return (this->waiting_LPE != nullptr ||
179}
180
184void PenTool::_endpointSnap(Geom::Point &p, guint const state) {
185 // Paraxial kicks in after first line has set the angle (before then it's a free line)
186 bool poly = this->polylines_paraxial && !this->green_curve->is_unset();
187
188 if ((state & GDK_CONTROL_MASK) && !poly) { //CTRL enables angular snapping
189 if (this->npoints > 0) {
191 } else {
192 std::optional<Geom::Point> origin = std::optional<Geom::Point>();
194 }
195 } else {
196 // We cannot use shift here to disable snapping because the shift-key is already used
197 // to toggle the paraxial direction; if the user wants to disable snapping (s)he will
198 // have to use the %-key, the menu, or the snap toolbar
199 if ((this->npoints > 0) && poly) {
200 // snap constrained
202 } else {
203 // snap freely
204 std::optional<Geom::Point> origin = this->npoints > 0 ? p_array[0] : std::optional<Geom::Point>();
205 spdc_endpoint_snap_free(this, p, origin); // pass the origin, to allow for perpendicular / tangential snapping
206 }
207 }
208}
209
213void PenTool::_endpointSnapHandle(Geom::Point &p, guint const state) {
214 g_return_if_fail(( this->npoints == 2 ||
215 this->npoints == 5 ));
216
217 if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping
218 spdc_endpoint_snap_rotation(this, p, p_array[this->npoints - 2], state);
219 } else {
220 if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
221 std::optional<Geom::Point> origin = p_array[this->npoints - 2];
223 }
224 }
225}
226
228{
229 bool ret = false;
230
231 inspect_event(event,
232 [&] (ButtonPressEvent const &event) {
233 ret = _handleButtonPress(event);
234 },
235 [&] (ButtonReleaseEvent const &event) {
236 ret = _handleButtonRelease(event);
237 },
238 [&] (CanvasEvent const &event) {}
239 );
240
241 return ret || FreehandBase::item_handler(item, event);
242}
243
248{
249 bool ret = false;
250
251 inspect_event(event,
252 [&] (ButtonPressEvent const &event) {
253 if (event.num_press == 1) {
254 ret = _handleButtonPress(event);
255 } else if (event.num_press == 2) {
256 ret = _handle2ButtonPress(event);
257 }
258 },
259 [&] (MotionEvent const &event) {
260 ret = _handleMotionNotify(event);
261 },
262 [&] (ButtonReleaseEvent const &event) {
263 ret = _handleButtonRelease(event);
264 },
265 [&] (KeyPressEvent const &event) {
266 ret = _handleKeyPress(event);
267 },
268 [&] (CanvasEvent const &event) {}
269 );
270
271 return ret || FreehandBase::root_handler(event);
272}
273
278
279 if (events_disabled) {
280 // skip event processing if events are disabled
281 return false;
282 }
283
284 Geom::Point const event_w(event.pos);
285 Geom::Point event_dt(_desktop->w2d(event_w));
286 //Test whether we hit any anchor.
287 SPDrawAnchor * const anchor = spdc_test_inside(this, event_w);
288
289 //with this we avoid creating a new point over the existing one
290 if(event.button != 3 && (spiro || bspline) && npoints > 0 && p_array[0] == p_array[3]){
291 if( anchor && anchor == sa && green_curve->is_unset()){
292 //remove the following line to avoid having one node on top of another
293 _finishSegment(event_dt, event.modifiers);
294 _finish(true);
295 return true;
296 }
297 return false;
298 }
299
300 bool ret = false;
301
302 if (event.button == 1 &&
303 expecting_clicks_for_LPE != 1) { // Make sure this is not the last click for a waiting LPE (otherwise we want to finish the path)
304
306 return true;
307 }
308
310
311 pen_drag_origin_w = event_w;
313
314 switch (mode) {
315
317 // In click mode we add point on release
318 switch (state) {
319 case PenTool::POINT:
320 case PenTool::CONTROL:
321 case PenTool::CLOSE:
322 break;
323 case PenTool::STOP:
324 // This is allowed, if we just canceled curve
326 break;
327 default:
328 break;
329 }
330 break;
332 switch (state) {
333 case PenTool::STOP:
334 // This is allowed, if we just canceled curve
335 case PenTool::POINT:
336 if (npoints == 0) {
338 Geom::Point p;
339 if ((event.modifiers & GDK_CONTROL_MASK) && (polylines_only || polylines_paraxial)) {
340 p = event_dt;
341 if (!(event.modifiers & GDK_SHIFT_MASK)) {
342 auto &m = _desktop->getNamedView()->snap_manager;
343 m.setup(_desktop);
344 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
345 m.unSetup();
346 }
347 spdc_create_single_dot(this, p, "/tools/freehand/pen", event.modifiers);
348 ret = true;
349 break;
350 }
351
352 // TODO: Perhaps it would be nicer to rearrange the following case
353 // distinction so that the case of a waiting LPE is treated separately
354
355 // Set start anchor
356
357 sa = anchor;
358 if (anchor) {
359 //Put the start overwrite curve always on the same direction
360 if (anchor->start) {
361 sa_overwrited = std::make_shared<SPCurve>(sa->curve->reversed());
362 } else {
363 sa_overwrited = std::make_shared<SPCurve>(*sa->curve);
364 }
365 _bsplineSpiroStartAnchor(event.modifiers & GDK_SHIFT_MASK);
366 }
367 if (anchor && (!hasWaitingLPE()|| bspline || spiro)) {
368 // Adjust point to anchor if needed; if we have a waiting LPE, we need
369 // a fresh path to be created so don't continue an existing one
370 p = anchor->dp;
371 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path"));
372 } else {
373 // This is the first click of a new curve; deselect item so that
374 // this curve is not combined with it (unless it is drawn from its
375 // anchor, which is handled by the sibling branch above)
377 if (!(event.modifiers & GDK_SHIFT_MASK) || hasWaitingLPE()) {
378 // if we have a waiting LPE, we need a fresh path to be created
379 // so don't append to an existing one
380 selection->clear();
381 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
382 } else if (selection->singleItem() && is<SPPath>(selection->singleItem())) {
383 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path"));
384 }
385
386 // Create green anchor
387 p = event_dt;
388 _endpointSnap(p, event.modifiers);
389 green_anchor = std::make_unique<SPDrawAnchor>(this, green_curve, true, p);
390 }
392 } else {
393 // Set end anchor
394 ea = anchor;
395 Geom::Point p;
396 if (anchor) {
397 p = anchor->dp;
398 // we hit an anchor, will finish the curve (either with or without closing)
399 // in release handler
401
402 if (green_anchor && green_anchor->active) {
403 // we clicked on the current curve start, so close it even if
404 // we drag a handle away from it
405 green_closed = true;
406 }
407 ret = true;
408 break;
409
410 } else {
411 p = event_dt;
412 _endpointSnap(p, event.modifiers); // Snap node only if not hitting anchor.
413 _setSubsequentPoint(p, true);
414 }
415 }
416 // avoid the creation of a control point so a node is created in the release event
418 ret = true;
419 break;
420 case PenTool::CONTROL:
421 g_warning("Button down in CONTROL state");
422 break;
423 case PenTool::CLOSE:
424 g_warning("Button down in CLOSE state");
425 break;
426 default:
427 break;
428 }
429 break;
430 default:
431 break;
432 }
433 } else if (expecting_clicks_for_LPE == 1 && npoints != 0) {
434 // when the last click for a waiting LPE occurs we want to finish the path
435 _finishSegment(event_dt, event.modifiers);
436 if (green_closed) {
437 // finishing at the start anchor, close curve
438 _finish(true);
439 } else {
440 // finishing at some other anchor, finish curve but not close
441 _finish(false);
442 }
443
444 ret = true;
445 } else if (event.button == 3 && npoints != 0 && !_button1on) {
446 // right click - finish path, but only if the left click isn't pressed.
447 ea = nullptr; // unset end anchor if set (otherwise crashes)
448 _finish(false);
449 ret = true;
450 }
451
452 if (expecting_clicks_for_LPE > 0) {
454 }
455
456 return ret;
457}
458
463 bool ret = false;
464 // Only end on LMB double click. Otherwise horizontal scrolling causes ending of the path
465 if (npoints != 0 && event.button == 1 && state != PenTool::CLOSE) {
466 _finish(false);
467 ret = true;
468 }
469 return ret;
470}
471
476 bool ret = false;
477
478 if (event.modifiers & GDK_BUTTON2_MASK) {
479 // allow scrolling
480 return false;
481 }
482
483 if (events_disabled) {
484 // skip motion events if pen events are disabled
485 return false;
486 }
487
488 Geom::Point const event_w(event.pos);
489
490 //we take out the function the const "tolerance" because we need it later
492 gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
493
495 if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance ) {
496 return false; // Do not drag if we're within tolerance from origin.
497 }
498 }
499 // Once the user has moved farther than tolerance from the original location
500 // (indicating they intend to move the object, not click), then always process the
501 // motion notify coordinates as given (no snapping back to origin)
502 pen_within_tolerance = false;
503
504 // Find desktop coordinates
505 Geom::Point p = _desktop->w2d(event_w);
506
507 // Test, whether we hit any anchor
508 SPDrawAnchor *anchor = spdc_test_inside(this, event_w);
509
510 switch (mode) {
512 switch (state) {
513 case PenTool::POINT:
514 if ( npoints != 0 ) {
515 // Only set point, if we are already appending
516 _endpointSnap(p, event.modifiers);
517 _setSubsequentPoint(p, true);
518 ret = true;
519 } else if (!sp_event_context_knot_mouseover()) {
521 m.setup(_desktop);
523 m.unSetup();
524 }
525 break;
526 case PenTool::CONTROL:
527 case PenTool::CLOSE:
528 // Placing controls is last operation in CLOSE state
529 _endpointSnap(p, event.modifiers);
530 _setCtrl(p, event.modifiers);
531 ret = true;
532 break;
533 case PenTool::STOP:
536 m.setup(_desktop);
538 m.unSetup();
539 }
540 break;
541 default:
542 break;
543 }
544 break;
546 switch (state) {
547 case PenTool::POINT:
548 if ( npoints > 0 ) {
549 // Only set point, if we are already appending
550
551 if (!anchor) { // Snap node only if not hitting anchor
552 _endpointSnap(p, event.modifiers);
553 _setSubsequentPoint(p, true, event.modifiers);
554 } else if (green_anchor && green_anchor->active && green_curve && !green_curve->is_unset()) {
555 // The green anchor is the end point, use the initial point explicitly.
556 _setSubsequentPoint(green_curve->first_path()->initialPoint(), false, event.modifiers);
557 } else {
558 _setSubsequentPoint(anchor->dp, false, event.modifiers);
559 }
560
561 if (anchor && !anchor_statusbar) {
562 if(!spiro && !bspline){
563 message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path."));
564 }else{
565 message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to close and finish the path. Shift+Click make a cusp node"));
566 }
567 anchor_statusbar = true;
568 } else if (!anchor && anchor_statusbar) {
569 message_context->clear();
570 anchor_statusbar = false;
571 }
572
573 ret = true;
574 } else {
575 if (anchor && !anchor_statusbar) {
576 if(!spiro && !bspline){
577 message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point."));
578 }else{
579 message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> or <b>click and drag</b> to continue the path from this point. Shift+Click make a cusp node"));
580 }
581 anchor_statusbar = true;
582 } else if (!anchor && anchor_statusbar) {
583 message_context->clear();
584 anchor_statusbar = false;
585
586 }
589 m.setup(_desktop);
591 m.unSetup();
592 }
593 }
594 break;
595 case PenTool::CONTROL:
596 case PenTool::CLOSE:
597 // Placing controls is last operation in CLOSE state
598
599 // snap the handle
600
602
603 if (!polylines_only) {
604 _setCtrl(p, event.modifiers);
605 } else {
606 _setCtrl(p_array[1], event.modifiers);
607 }
608
609 gobble_motion_events(GDK_BUTTON1_MASK);
610 ret = true;
611 break;
612 case PenTool::STOP:
613 // Don't break; fall through to default to do preSnapping
614 default:
617 m.setup(_desktop);
619 m.unSetup();
620 }
621 break;
622 }
623 break;
624 default:
625 break;
626 }
627 // calls the function "bspline_spiro_motion" when the mouse starts or stops moving
628 if (bspline) {
630 } else {
631 if ( Geom::LInfty( event_w - pen_drag_origin_w ) > (tolerance/2)) {
633 pen_drag_origin_w = event_w;
634 }
635 }
636
637 return ret;
638}
639
644
645 if (events_disabled) {
646 // skip event processing if events are disabled
647 return false;
648 }
649
650 bool ret = false;
651
652 if (event.button == 1) {
653 Geom::Point const event_w(event.pos);
654
655 // Find desktop coordinates
656 Geom::Point p = _desktop->w2d(event_w);
657
658 // Test whether we hit any anchor.
659
660 SPDrawAnchor *anchor = spdc_test_inside(this, event_w);
661 // if we try to create a node in the same place as another node, we skip
662 if((!anchor || anchor == sa) && (spiro || bspline) && npoints > 0 && p_array[0] == p_array[3]){
663 return true;
664 }
665
666 switch (mode) {
668 switch (state) {
669 case PenTool::POINT:
670 ea = anchor;
671 if (anchor) {
672 p = anchor->dp;
673 }
675 break;
676 case PenTool::CONTROL:
677 // End current segment
678 _endpointSnap(p, event.modifiers);
679 _finishSegment(p, event.modifiers);
681 break;
682 case PenTool::CLOSE:
683 // End current segment
684 if (!anchor) { // Snap node only if not hitting anchor
685 _endpointSnap(p, event.modifiers);
686 }
687 _finishSegment(p, event.modifiers);
688 // hude the guide of the penultimate node when closing the curve
689 if(spiro){
690 ctrl[1]->set_visible(false);
691 }
692 _finish(true);
694 break;
695 case PenTool::STOP:
696 // This is allowed, if we just canceled curve
698 break;
699 default:
700 break;
701 }
702 break;
704 switch (state) {
705 case PenTool::POINT:
706 case PenTool::CONTROL:
707 _endpointSnap(p, event.modifiers);
708 _finishSegment(p, event.modifiers);
709 break;
710 case PenTool::CLOSE:
711 _endpointSnap(p, event.modifiers);
712 _finishSegment(p, event.modifiers);
713 // hide the penultimate node guide when closing the curve
714 if(spiro){
715 ctrl[1]->set_visible(false);
716 }
717 if (green_closed) {
718 // finishing at the start anchor, close curve
719 _finish(true);
720 } else {
721 // finishing at some other anchor, finish curve but not close
722 _finish(false);
723 }
724 break;
725 case PenTool::STOP:
726 // This is allowed, if we just cancelled curve
727 break;
728 default:
729 break;
730 }
732 break;
733 default:
734 break;
735 }
736
738
739 ret = true;
740
741 green_closed = false;
742 }
743
744 // TODO: can we be sure that the path was created correctly?
745 // TODO: should we offer an option to collect the clicks in a list?
748
750
751 if (waiting_LPE) {
752 // we have an already created LPE waiting for a path
755 waiting_LPE = nullptr;
756 } else {
757 // the case that we need to create a new LPE and apply it to the just-drawn path is
758 // handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp
759 }
760 }
761
762 return ret;
763}
764
766 // green
767 if (! green_bpaths.empty()) {
768 // remove old piecewise green canvasitems
769 green_bpaths.clear();
770
771 // one canvas bpath for all of green_curve
773 canvas_shape->set_stroke(green_color);
774 canvas_shape->set_fill(0x0, SP_WIND_RULE_NONZERO);
775 green_bpaths.emplace_back(canvas_shape);
776 }
777 if (green_anchor) {
778 green_anchor->ctrl->set_position(green_anchor->dp);
779 }
780
784 red_bpath->set_bpath(&red_curve, true);
785
786 for (auto &c : ctrl) {
787 c->set_visible(false);
788 }
789 // handles
790 // hide the handlers in bspline and spiro modes
791 if (npoints == 5) {
792 ctrl[0]->set_position(p_array[0]);
793 ctrl[0]->set_visible(true);
794 ctrl[3]->set_position(p_array[3]);
795 ctrl[3]->set_visible(true);
796 }
797
798 if (p_array[0] != p_array[1] && !spiro && !bspline) {
799 ctrl[1]->set_position(p_array[1]);
800 ctrl[1]->set_visible(true);
801 cl1->set_coords(p_array[0], p_array[1]);
802 cl1->set_visible(true);
803 } else {
804 cl1->set_visible(false);
805 }
806
807 Geom::Curve const * last_seg = green_curve->last_segment();
808 if (last_seg) {
809 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>( last_seg );
810 // hide the handlers in bspline and spiro modes
811 if ( cubic &&
812 (*cubic)[2] != p_array[0] && !spiro && !bspline )
813 {
814 Geom::Point p2 = (*cubic)[2];
815 ctrl[2]->set_position(p2);
816 ctrl[2]->set_visible(true);
817 cl0->set_coords(p2, p_array[0]);
818 cl0->set_visible(true);
819 } else {
820 cl0->set_visible(false);
821 }
822 }
823
824 // simply redraw the spiro. because its a redrawing, we don't call the global function,
825 // but we call the redrawing at the ending.
827}
828
829void PenTool::_lastpointMove(gdouble x, gdouble y) {
830 if (npoints != 5)
831 return;
832
833 y *= -_desktop->yaxisdir();
834 auto delta = Geom::Point(x,y);
835
836 auto prefs = Inkscape::Preferences::get();
837 bool const rotated = prefs->getBool("/options/moverotated/value", true);
838 if (rotated) {
840 }
841
842 // green
843 if (!green_curve->is_unset()) {
844 green_curve->last_point_additive_move(delta);
845 } else {
846 // start anchor too
847 if (green_anchor) {
848 green_anchor->dp += delta;
849 }
850 }
851
852 // red
853 p_array[0] += delta;
854 p_array[1] += delta;
855 _redrawAll();
856}
857
858void PenTool::_lastpointMoveScreen(gdouble x, gdouble y) {
860}
861
863 // avoid that if the "red_curve" contains only two points ( rect ), it doesn't stop here.
864 if (this->npoints != 5 && !this->spiro && !this->bspline)
865 return;
866
867 p_array[1] = this->red_curve.last_segment()->initialPoint() + (1./3.)*(*this->red_curve.last_point() - this->red_curve.last_segment()->initialPoint());
868 //modificate the last segment of the green curve so it creates the type of node we need
869 if (this->spiro||this->bspline) {
870 if (!this->green_curve->is_unset()) {
871 Geom::Point A(0,0);
872 Geom::Point B(0,0);
873 Geom::Point C(0,0);
874 Geom::Point D(0,0);
875 Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>( this->green_curve->last_segment() );
876 //We obtain the last segment 4 points in the previous curve
877 if ( cubic ){
878 A = (*cubic)[0];
879 B = (*cubic)[1];
880 if (this->spiro) {
881 C = p_array[0] + (p_array[0] - p_array[1]);
882 } else {
883 C = *this->green_curve->last_point() + (1./3.)*(this->green_curve->last_segment()->initialPoint() - *this->green_curve->last_point());
884 }
885 D = (*cubic)[3];
886 } else {
887 A = this->green_curve->last_segment()->initialPoint();
888 B = this->green_curve->last_segment()->initialPoint();
889 if (this->spiro) {
890 C = p_array[0] + (p_array[0] - p_array[1]);
891 } else {
892 C = *this->green_curve->last_point() + (1./3.)*(this->green_curve->last_segment()->initialPoint() - *this->green_curve->last_point());
893 }
894 D = *this->green_curve->last_point();
895 }
896 auto previous = std::make_shared<SPCurve>();
897 previous->moveto(A);
898 previous->curveto(B, C, D);
899 if (green_curve->get_segment_count() == 1) {
900 green_curve = std::move(previous);
901 } else {
902 //we eliminate the last segment
903 green_curve->backspace();
904 //and we add it again with the recreation
905 green_curve->append_continuous(*previous);
906 }
907 }
908 //if the last node is an union with another curve
909 if (this->green_curve->is_unset() && this->sa && !this->sa->curve->is_unset()) {
910 this->_bsplineSpiroStartAnchor(false);
911 }
912 }
913
914 this->_redrawAll();
915}
916
917
919 // avoid that if the "red_curve" contains only two points ( rect) it doesn't stop here.
920 if (this->npoints != 5 && !this->bspline)
921 return;
922
923 // modify the last segment of the green curve so the type of node we want is created.
924 if(this->spiro || this->bspline){
925 if(!this->green_curve->is_unset()){
926 Geom::Point A(0,0);
927 Geom::Point B(0,0);
928 Geom::Point C(0,0);
929 Geom::Point D(0,0);
930 auto previous = std::make_shared<SPCurve>();
931 if (auto const cubic = dynamic_cast<Geom::CubicBezier const *>(green_curve->last_segment())) {
932 A = green_curve->last_segment()->initialPoint();
933 B = (*cubic)[1];
934 C = *green_curve->last_point();
935 D = C;
936 } else {
937 //We obtain the last segment 4 points in the previous curve
938 A = green_curve->last_segment()->initialPoint();
939 B = A;
940 C = *green_curve->last_point();
941 D = C;
942 }
943 previous->moveto(A);
944 previous->curveto(B, C, D);
945 if (green_curve->get_segment_count() == 1){
946 green_curve = std::move(previous);
947 }else{
948 //we eliminate the last segment
949 green_curve->backspace();
950 //and we add it again with the recreation
951 green_curve->append_continuous(*previous);
952 }
953 }
954 // if the last node is an union with another curve
955 if (green_curve->is_unset() && sa && !sa->curve->is_unset()) {
957 }
958 }
959
960 p_array[1] = p_array[0];
961 this->_redrawAll();
962}
963
964
966{
967 bool ret = false;
968 auto prefs = Preferences::get();
969 double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px
970
971 // Check for undo/redo.
972 if (npoints > 0 && _acc_undo.isTriggeredBy(event)) {
973 return _undoLastPoint(true);
974 } else if (_acc_redo.isTriggeredBy(event)) {
975 return _redoLastPoint();
976 }
977 if (_acc_to_line.isTriggeredBy(event)) {
978 this->_lastpointToLine();
979 ret = true;
980 } else if (_acc_to_curve.isTriggeredBy(event)) {
981 this->_lastpointToCurve();
982 ret = true;
983 }
984 if (_acc_to_guides.isTriggeredBy(event)) {
986 ret = true;
987 }
988
989 switch (get_latin_keyval(event)) {
990 case GDK_KEY_Left: // move last point left
991 case GDK_KEY_KP_Left:
992 if (!mod_ctrl(event)) { // not ctrl
993 if (mod_alt(event)) { // alt
994 if (mod_shift(event)) {
995 this->_lastpointMoveScreen(-10, 0); // shift
996 }
997 else {
998 this->_lastpointMoveScreen(-1, 0); // no shift
999 }
1000 }
1001 else { // no alt
1002 if (mod_shift(event)) {
1003 this->_lastpointMove(-10*nudge, 0); // shift
1004 }
1005 else {
1006 this->_lastpointMove(-nudge, 0); // no shift
1007 }
1008 }
1009 ret = true;
1010 }
1011 break;
1012 case GDK_KEY_Up: // move last point up
1013 case GDK_KEY_KP_Up:
1014 if (!mod_ctrl(event)) { // not ctrl
1015 if (mod_alt(event)) { // alt
1016 if (mod_shift(event)) {
1017 this->_lastpointMoveScreen(0, 10); // shift
1018 }
1019 else {
1020 this->_lastpointMoveScreen(0, 1); // no shift
1021 }
1022 }
1023 else { // no alt
1024 if (mod_shift(event)) {
1025 this->_lastpointMove(0, 10*nudge); // shift
1026 }
1027 else {
1028 this->_lastpointMove(0, nudge); // no shift
1029 }
1030 }
1031 ret = true;
1032 }
1033 break;
1034 case GDK_KEY_Right: // move last point right
1035 case GDK_KEY_KP_Right:
1036 if (!mod_ctrl(event)) { // not ctrl
1037 if (mod_alt(event)) { // alt
1038 if (mod_shift(event)) {
1039 this->_lastpointMoveScreen(10, 0); // shift
1040 }
1041 else {
1042 this->_lastpointMoveScreen(1, 0); // no shift
1043 }
1044 }
1045 else { // no alt
1046 if (mod_shift(event)) {
1047 this->_lastpointMove(10*nudge, 0); // shift
1048 }
1049 else {
1050 this->_lastpointMove(nudge, 0); // no shift
1051 }
1052 }
1053 ret = true;
1054 }
1055 break;
1056 case GDK_KEY_Down: // move last point down
1057 case GDK_KEY_KP_Down:
1058 if (!mod_ctrl(event)) { // not ctrl
1059 if (mod_alt(event)) { // alt
1060 if (mod_shift(event)) {
1061 this->_lastpointMoveScreen(0, -10); // shift
1062 }
1063 else {
1064 this->_lastpointMoveScreen(0, -1); // no shift
1065 }
1066 }
1067 else { // no alt
1068 if (mod_shift(event)) {
1069 this->_lastpointMove(0, -10*nudge); // shift
1070 }
1071 else {
1072 this->_lastpointMove(0, -nudge); // no shift
1073 }
1074 }
1075 ret = true;
1076 }
1077 break;
1078
1079/*TODO: this is not yet enabled?? looks like some traces of the Geometry tool
1080 case GDK_KEY_P:
1081 case GDK_KEY_p:
1082 if (MOD__SHIFT_ONLY(event)) {
1083 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PARALLEL, 2);
1084 ret = true;
1085 }
1086 break;
1087
1088 case GDK_KEY_C:
1089 case GDK_KEY_c:
1090 if (MOD__SHIFT_ONLY(event)) {
1091 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::CIRCLE_3PTS, 3);
1092 ret = true;
1093 }
1094 break;
1095
1096 case GDK_KEY_B:
1097 case GDK_KEY_b:
1098 if (MOD__SHIFT_ONLY(event)) {
1099 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PERP_BISECTOR, 2);
1100 ret = true;
1101 }
1102 break;
1103
1104 case GDK_KEY_A:
1105 case GDK_KEY_a:
1106 if (MOD__SHIFT_ONLY(event)) {
1107 sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::ANGLE_BISECTOR, 3);
1108 ret = true;
1109 }
1110 break;
1111*/
1112
1113 case GDK_KEY_Return:
1114 case GDK_KEY_KP_Enter:
1115 if (this->npoints != 0) {
1116 this->ea = nullptr; // unset end anchor if set (otherwise crashes)
1117 if(mod_shift_only(event)) {
1118 // All this is needed to stop the last control
1119 // point dispeating and stop making an n-1 shape.
1120 Geom::Point const p(0, 0);
1121 if(this->red_curve.is_unset()) {
1122 this->red_curve.moveto(p);
1123 }
1124 this->_finishSegment(p, 0);
1125 this->_finish(true);
1126 } else {
1127 this->_finish(false);
1128 }
1129 ret = true;
1130 }
1131 break;
1132 case GDK_KEY_Escape:
1133 if (this->npoints != 0) {
1134 // if drawing, cancel, otherwise pass it up for deselecting
1135 this->_cancel ();
1136 ret = true;
1137 }
1138 break;
1139 case GDK_KEY_BackSpace:
1140 case GDK_KEY_Delete:
1141 case GDK_KEY_KP_Delete:
1142 ret = _undoLastPoint();
1143 break;
1144 default:
1145 break;
1146 }
1147 return ret;
1148}
1149
1151 // Red
1152 this->red_curve.reset();
1153 this->red_bpath->set_bpath(nullptr);
1154
1155 // Blue
1156 blue_curve.reset();
1157 blue_bpath->set_bpath(nullptr);
1158
1159 // Green
1160 this->green_bpaths.clear();
1161 this->green_curve->reset();
1162 this->green_anchor.reset();
1163
1164 this->sa = nullptr;
1165 this->ea = nullptr;
1166
1167 if (this->sa_overwrited) {
1168 this->sa_overwrited->reset();
1169 }
1170
1171 this->npoints = 0;
1172 this->red_curve_is_valid = false;
1173}
1174
1175
1177 g_assert( this->npoints == 0 );
1178
1179 p_array[0] = p;
1180 p_array[1] = p;
1181 this->npoints = 2;
1182 this->red_bpath->set_bpath(nullptr);
1183}
1184
1190void PenTool::_setAngleDistanceStatusMessage(Geom::Point const p, int pc_point_to_compare, gchar const *message) {
1191 g_assert((pc_point_to_compare == 0) || (pc_point_to_compare == 3)); // exclude control handles
1192 g_assert(message != nullptr);
1193
1194 Geom::Point rel = p - p_array[pc_point_to_compare];
1196 Glib::ustring dist = q.string(_desktop->getNamedView()->display_units);
1197 double angle = atan2(rel[Geom::Y], rel[Geom::X]) * 180 / M_PI;
1199 if (prefs->getBool("/options/compassangledisplay/value", false) != 0) {
1200 angle = 90 - angle;
1201
1202 if (_desktop->is_yaxisdown()) {
1203 angle = 180 - angle;
1204 }
1205
1206 if (angle < 0) {
1207 angle += 360;
1208 }
1209 }
1210
1211 this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, message, angle, dist.c_str());
1212}
1213
1214// this function changes the colors red, green and blue making them transparent or not, depending on if spiro is being used.
1216{
1218 auto highlight = currentLayer()->highlight_color();
1219 auto other = prefs->getColor("/tools/nodes/highlight_color", "#ff0000ff");
1220 if (this->spiro){
1221 this->red_color = 0xff000000;
1222 this->green_color = 0x00ff0000;
1223 } else if(this->bspline) {
1224 highlight_color = highlight.toRGBA();
1225 if(other == highlight) {
1226 this->green_color = 0xff00007f;
1227 this->red_color = 0xff00007f;
1228 } else {
1229 this->green_color = this->highlight_color;
1230 this->red_color = this->highlight_color;
1231 }
1232 } else {
1233 highlight_color = highlight.toRGBA();
1234 this->red_color = 0xff00007f;
1235 if(other == highlight) {
1236 this->green_color = 0x00ff007f;
1237 } else {
1238 this->green_color = this->highlight_color;
1239 }
1240 blue_bpath->set_visible(false);
1241 }
1242
1243 //We erase all the "green_bpaths" to recreate them after with the colour
1244 //transparency recently modified
1245 if (!this->green_bpaths.empty()) {
1246 // remove old piecewise green canvasitems
1247 this->green_bpaths.clear();
1248
1249 // one canvas bpath for all of green_curve
1251 canvas_shape->set_stroke(green_color);
1252 canvas_shape->set_fill(0x0, SP_WIND_RULE_NONZERO);
1253 green_bpaths.emplace_back(canvas_shape);
1254 }
1255
1256 this->red_bpath->set_stroke(red_color);
1257}
1258
1259
1261{
1262 if(!this->spiro && !this->bspline){
1263 return;
1264 }
1265
1266 shift?this->_bsplineSpiroOff():this->_bsplineSpiroOn();
1267 this->_bsplineSpiroBuild();
1268}
1269
1271{
1272 if(!this->red_curve.is_unset()){
1273 this->npoints = 5;
1274 p_array[0] = *this->red_curve.first_point();
1275 p_array[3] = this->red_curve.first_segment()->finalPoint();
1276 p_array[2] = p_array[3] + (1./3)*(p_array[0] - p_array[3]);
1277 _bsplineSpiroMotion(GDK_ALT_MASK);
1278 }
1279}
1280
1282{
1283 if(!this->red_curve.is_unset()){
1284 this->npoints = 5;
1285 p_array[0] = *this->red_curve.first_point();
1286 p_array[3] = this->red_curve.first_segment()->finalPoint();
1287 p_array[2] = p_array[3];
1288 }
1289}
1290
1292{
1293 if(this->sa->curve->is_unset()){
1294 return;
1295 }
1296
1297 LivePathEffect::LPEBSpline *lpe_bsp = nullptr;
1298
1299 if (is<SPLPEItem>(this->white_item) && cast<SPLPEItem>(this->white_item)->hasPathEffect()){
1301 cast<SPLPEItem>(this->white_item)->getFirstPathEffectOfType(Inkscape::LivePathEffect::BSPLINE);
1302 if(thisEffect){
1303 lpe_bsp = dynamic_cast<LivePathEffect::LPEBSpline*>(thisEffect->getLPEObj()->get_lpe());
1304 }
1305 }
1306 if(lpe_bsp){
1307 this->bspline = true;
1308 }else{
1309 this->bspline = false;
1310 }
1311 LivePathEffect::LPESpiro *lpe_spi = nullptr;
1312
1313 if (is<SPLPEItem>(this->white_item) && cast<SPLPEItem>(this->white_item)->hasPathEffect()){
1315 cast<SPLPEItem>(this->white_item)->getFirstPathEffectOfType(Inkscape::LivePathEffect::SPIRO);
1316 if(thisEffect){
1317 lpe_spi = dynamic_cast<LivePathEffect::LPESpiro*>(thisEffect->getLPEObj()->get_lpe());
1318 }
1319 }
1320 if(lpe_spi){
1321 this->spiro = true;
1322 }else{
1323 this->spiro = false;
1324 }
1325 if(!this->spiro && !this->bspline){
1327 return;
1328 }
1329 if(shift){
1331 } else {
1333 }
1334}
1335
1337{
1338 using Geom::X;
1339 using Geom::Y;
1340 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*this->sa_overwrited->last_segment());
1341 auto last_segment = std::make_shared<SPCurve>();
1342 Geom::Point point_a = this->sa_overwrited->last_segment()->initialPoint();
1343 Geom::Point point_d = *this->sa_overwrited->last_point();
1344 Geom::Point point_c = point_d + (1./3)*(point_a - point_d);
1345 if (cubic) {
1346 last_segment->moveto(point_a);
1347 last_segment->curveto((*cubic)[1],point_c,point_d);
1348 } else {
1349 last_segment->moveto(point_a);
1350 last_segment->curveto(point_a,point_c,point_d);
1351 }
1352 if ( this->sa_overwrited->get_segment_count() == 1){
1353 this->sa_overwrited = std::move(last_segment);
1354 } else {
1355 //we eliminate the last segment
1356 this->sa_overwrited->backspace();
1357 //and we add it again with the recreation
1358 sa_overwrited->append_continuous(*last_segment);
1359 }
1360}
1361
1363{
1364 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*this->sa_overwrited->last_segment());
1365 if(cubic){
1366 auto last_segment = std::make_shared<SPCurve>();
1367 last_segment->moveto((*cubic)[0]);
1368 last_segment->curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
1369 if( this->sa_overwrited->get_segment_count() == 1){
1370 this->sa_overwrited = std::move(last_segment);
1371 }else{
1372 //we eliminate the last segment
1373 this->sa_overwrited->backspace();
1374 //and we add it again with the recreation
1375 sa_overwrited->append_continuous(*last_segment);
1376 }
1377 }
1378}
1379
1380void PenTool::_bsplineSpiroMotion(guint const state){
1381 bool shift = state & GDK_SHIFT_MASK;
1382 if(!this->spiro && !this->bspline){
1383 return;
1384 }
1385 using Geom::X;
1386 using Geom::Y;
1387 if(this->red_curve.is_unset()) return;
1388 this->npoints = 5;
1389 SPCurve tmp_curve;
1390 p_array[2] = p_array[3] + (1./3)*(p_array[0] - p_array[3]);
1391 if (this->green_curve->is_unset() && !this->sa) {
1392 p_array[1] = p_array[0] + (1./3)*(p_array[3] - p_array[0]);
1393 if (shift) {
1394 p_array[2] = p_array[3];
1395 }
1396 } else if (!this->green_curve->is_unset()){
1397 tmp_curve = *green_curve;
1398 } else {
1399 tmp_curve = *sa_overwrited;
1400 }
1401 if ((state & GDK_ALT_MASK ) && previous != Geom::Point(0,0)) { //ALT drag
1402 p_array[0] = p_array[0] + (p_array[3] - previous);
1403 }
1404 if(!tmp_curve.is_unset()){
1405 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(tmp_curve.last_segment());
1406 if ((state & GDK_ALT_MASK ) && !Geom::are_near(*tmp_curve.last_point(), p_array[0], 0.1))
1407 {
1408 SPCurve previous_weight_power;
1409 previous_weight_power.moveto(tmp_curve.last_segment()->initialPoint());
1410 previous_weight_power.lineto(p_array[0]);
1411 auto SBasisweight_power = previous_weight_power.first_segment()->toSBasis();
1412 if (tmp_curve.get_segment_count() == 1) {
1413 Geom::Point initial = tmp_curve.last_segment()->initialPoint();
1414 tmp_curve.reset();
1415 tmp_curve.moveto(initial);
1416 } else {
1417 tmp_curve.backspace();
1418 }
1419 if(this->bspline && cubic && !Geom::are_near((*cubic)[2],(*cubic)[3])){
1420 tmp_curve.curveto(SBasisweight_power.valueAt(0.33334), SBasisweight_power.valueAt(0.66667), p_array[0]);
1421 } else if(this->bspline && cubic) {
1422 tmp_curve.curveto(SBasisweight_power.valueAt(0.33334), p_array[0], p_array[0]);
1423 } else if (cubic && !Geom::are_near((*cubic)[2],(*cubic)[3])) {
1424 tmp_curve.curveto((*cubic)[1], (*cubic)[2] + (p_array[3] - previous), p_array[0]);
1425 } else if (cubic){
1426 tmp_curve.curveto((*cubic)[1], p_array[0], p_array[0]);
1427 } else {
1428 tmp_curve.lineto(p_array[0]);
1429 }
1430 cubic = dynamic_cast<Geom::CubicBezier const*>(tmp_curve.last_segment());
1431 if (sa && green_curve->is_unset()) {
1432 sa_overwrited = std::make_shared<SPCurve>(tmp_curve);
1433 }
1434 green_curve = std::make_shared<SPCurve>(std::move(tmp_curve));
1435 }
1436 if (cubic) {
1437 if (this->bspline) {
1438 SPCurve weight_power;
1439 weight_power.moveto(red_curve.last_segment()->initialPoint());
1440 weight_power.lineto(*red_curve.last_point());
1441 auto SBasisweight_power = weight_power.first_segment()->toSBasis();
1442 p_array[1] = SBasisweight_power.valueAt(0.33334);
1443 if (Geom::are_near(p_array[1],p_array[0])) {
1444 p_array[1] = p_array[0];
1445 }
1446 if (shift) {
1447 p_array[2] = p_array[3];
1448 }
1449 if(Geom::are_near((*cubic)[3], (*cubic)[2])) {
1450 p_array[1] = p_array[0];
1451 }
1452 } else {
1453 p_array[1] = (*cubic)[3] + ((*cubic)[3] - (*cubic)[2] );
1454 }
1455 } else {
1456 p_array[1] = p_array[0];
1457 if (shift) {
1458 p_array[2] = p_array[3];
1459 }
1460 }
1462 SPCurve red;
1463 red.moveto(p_array[0]);
1464 red.curveto(p_array[1],p_array[2],p_array[3]);
1465 red_bpath->set_bpath(&red, true);
1466 }
1467
1468 if(this->anchor_statusbar && !this->red_curve.is_unset()){
1469 if(shift){
1471 }else{
1473 }
1474 }
1475
1476 // remove old piecewise green canvasitems
1477 green_bpaths.clear();
1478
1479 // one canvas bpath for all of green_curve
1481 canvas_shape->set_stroke(green_color);
1482 canvas_shape->set_fill(0x0, SP_WIND_RULE_NONZERO);
1483 green_bpaths.emplace_back(canvas_shape);
1484
1485 this->_bsplineSpiroBuild();
1486}
1487
1489{
1490
1491 using Geom::X;
1492 using Geom::Y;
1493 p_array[2] = p_array[3] + (1./3)*(p_array[0] - p_array[3]);
1494 SPCurve tmp_curve;
1495 SPCurve last_segment;
1496 Geom::Point point_c(0,0);
1497 if( green_anchor && green_anchor->active ){
1498 tmp_curve = green_curve->reversed();
1499 if (green_curve->get_segment_count() == 0) {
1500 return;
1501 }
1502 } else if(this->sa){
1503 tmp_curve = sa_overwrited->reversed();
1504 }else{
1505 return;
1506 }
1507 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(tmp_curve.last_segment());
1508 if(this->bspline){
1509 point_c = *tmp_curve.last_point() + (1./3)*(tmp_curve.last_segment()->initialPoint() - *tmp_curve.last_point());
1510 } else {
1511 point_c = p_array[3] + p_array[3] - p_array[2];
1512 }
1513 if (cubic) {
1514 last_segment.moveto((*cubic)[0]);
1515 last_segment.curveto((*cubic)[1],point_c,(*cubic)[3]);
1516 } else {
1517 last_segment.moveto(tmp_curve.last_segment()->initialPoint());
1518 last_segment.lineto(*tmp_curve.last_point());
1519 }
1520 if ( tmp_curve.get_segment_count() == 1){
1521 tmp_curve = std::move(last_segment);
1522 } else {
1523 //we eliminate the last segment
1524 tmp_curve.backspace();
1525 //and we add it again with the recreation
1526 tmp_curve.append_continuous(std::move(last_segment));
1527 }
1528 tmp_curve.reverse();
1529 if (green_anchor && green_anchor->active) {
1530 green_curve->reset();
1531 green_curve = std::make_shared<SPCurve>(std::move(tmp_curve));
1532 } else {
1533 sa_overwrited->reset();
1534 sa_overwrited = std::make_shared<SPCurve>(std::move(tmp_curve));
1535 }
1536}
1537
1539{
1540 SPCurve tmp_curve;
1541 SPCurve last_segment;
1542 p_array[2] = p_array[3];
1543 if (green_anchor && green_anchor->active) {
1544 tmp_curve = green_curve->reversed();
1545 if (green_curve->get_segment_count() == 0) {
1546 return;
1547 }
1548 } else if (sa) {
1549 tmp_curve = sa_overwrited->reversed();
1550 } else {
1551 return;
1552 }
1553 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(tmp_curve.last_segment());
1554 if (cubic) {
1555 last_segment.moveto((*cubic)[0]);
1556 last_segment.curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
1557 } else {
1558 last_segment.moveto(tmp_curve.last_segment()->initialPoint());
1559 last_segment.lineto(*tmp_curve.last_point());
1560 }
1561 if ( tmp_curve.get_segment_count() == 1){
1562 tmp_curve = std::move(last_segment);
1563 } else{
1564 //we eliminate the last segment
1565 tmp_curve.backspace();
1566 //and we add it again with the recreation
1567 tmp_curve.append_continuous(std::move(last_segment));
1568 }
1569 tmp_curve.reverse();
1570
1571 if (green_anchor && green_anchor->active) {
1572 green_curve->reset();
1573 green_curve = std::make_shared<SPCurve>(std::move(tmp_curve));
1574 } else {
1575 sa_overwrited->reset();
1576 sa_overwrited = std::make_shared<SPCurve>(std::move(tmp_curve));
1577 }
1578}
1579
1580//prepares the curves for its transformation into BSpline curve.
1582{
1583 if (!spiro && !bspline){
1584 return;
1585 }
1586
1587 //We create the base curve
1588 SPCurve curve;
1589 //If we continuate the existing curve we add it at the start
1590 if (sa && !sa->curve->is_unset()){
1592 }
1593
1594 if (!green_curve->is_unset()) {
1595 curve.append_continuous(*green_curve);
1596 }
1597
1598 //and the red one
1599 if (!this->red_curve.is_unset()){
1600 this->red_curve.reset();
1601 this->red_curve.moveto(p_array[0]);
1602 if(this->anchor_statusbar && !this->sa && !(this->green_anchor && this->green_anchor->active)){
1603 this->red_curve.curveto(p_array[1],p_array[3],p_array[3]);
1604 }else{
1605 this->red_curve.curveto(p_array[1],p_array[2],p_array[3]);
1606 }
1607 red_bpath->set_bpath(&red_curve, true);
1609 }
1610 previous = *this->red_curve.last_point();
1611 if(!curve.is_unset()){
1612 // close the curve if the final points of the curve are close enough
1613 if(Geom::are_near(curve.first_path()->initialPoint(), curve.last_path()->finalPoint())){
1614 curve.closepath_current();
1615 }
1616 //TODO: CALL TO CLONED FUNCTION SPIRO::doEffect IN lpe-spiro.cpp
1617 //For example
1618 //using namespace Inkscape::LivePathEffect;
1619 //LivePathEffectObject *lpeobj = static_cast<LivePathEffectObject*> (curve);
1620 //Effect *spr = static_cast<Effect*> ( new LPEbspline(lpeobj) );
1621 //spr->doEffect(curve);
1622 if (bspline) {
1625 Glib::ustring pref_path = "/live_effects/bspline/uniform";
1626 bool uniform = prefs->getBool(pref_path, false);
1628 } else {
1630 }
1631
1632 blue_bpath->set_bpath(&curve, true);
1633 blue_bpath->set_stroke(blue_color);
1634 blue_bpath->set_visible(true);
1635
1636 blue_curve.reset();
1637 //We hide the holders that doesn't contribute anything
1638 for (auto &c : ctrl) {
1639 c->set_visible(false);
1640 }
1641 if (spiro){
1642 ctrl[1]->set_position(p_array[0]);
1643 ctrl[1]->set_visible(true);
1644 }
1645 cl0->set_visible(false);
1646 cl1->set_visible(false);
1647 } else {
1648 //if the curve is empty
1649 blue_bpath->set_visible(false);
1650 }
1651}
1652
1653void PenTool::_setSubsequentPoint(Geom::Point const p, bool statusbar, guint status) {
1654 g_assert( this->npoints != 0 );
1655
1656 // todo: Check callers to see whether 2 <= npoints is guaranteed.
1657
1658 p_array[2] = p;
1659 p_array[3] = p;
1660 p_array[4] = p;
1661 this->npoints = 5;
1662 this->red_curve.reset();
1663 bool is_curve;
1664 this->red_curve.moveto(p_array[0]);
1665 if (this->polylines_paraxial && !statusbar) {
1666 // we are drawing horizontal/vertical lines and hit an anchor;
1667 Geom::Point const origin = p_array[0];
1668 // if the previous point and the anchor are not aligned either horizontally or vertically...
1669 if ((std::abs(p[Geom::X] - origin[Geom::X]) > 1e-9) && (std::abs(p[Geom::Y] - origin[Geom::Y]) > 1e-9)) {
1670 // ...then we should draw an L-shaped path, consisting of two paraxial segments
1671 Geom::Point intermed = p;
1672 this->_setToNearestHorizVert(intermed, status);
1673 this->red_curve.lineto(intermed);
1674 }
1675 this->red_curve.lineto(p);
1676 is_curve = false;
1677 } else {
1678 // one of the 'regular' modes
1679 if (p_array[1] != p_array[0] || this->spiro) {
1680 this->red_curve.curveto(p_array[1], p, p);
1681 is_curve = true;
1682 } else {
1683 this->red_curve.lineto(p);
1684 is_curve = false;
1685 }
1686 }
1687
1688 red_bpath->set_bpath(&red_curve, true);
1689
1690 if (statusbar) {
1691 gchar *message;
1692 if(this->spiro || this->bspline){
1693 message = is_curve ?
1694 _("<b>Curve segment</b>: angle %3.2f&#176;; <b>Shift+Click</b> creates cusp node, <b>ALT</b> moves previous, <b>Enter</b> or <b>Shift+Enter</b> to finish" ):
1695 _("<b>Line segment</b>: angle %3.2f&#176;; <b>Shift+Click</b> creates cusp node, <b>ALT</b> moves previous, <b>Enter</b> or <b>Shift+Enter</b> to finish");
1696 this->_setAngleDistanceStatusMessage(p, 0, message);
1697 } else {
1698 message = is_curve ?
1699 _("<b>Curve segment</b>: angle %3.2f&#176;, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> or <b>Shift+Enter</b> to finish the path" ):
1700 _("<b>Line segment</b>: angle %3.2f&#176;, distance %s; with <b>Ctrl</b> to snap angle, <b>Enter</b> or <b>Shift+Enter</b> to finish the path");
1701 this->_setAngleDistanceStatusMessage(p, 0, message);
1702 }
1703
1704 }
1705}
1706
1707void PenTool::_setCtrl(Geom::Point const q, guint const state)
1708{
1709 // use 'q' as 'p' use to shadow member variable.
1710 for (auto &c : ctrl) {
1711 c->set_visible(false);
1712 }
1713
1714 ctrl[1]->set_visible(true);
1715 cl1->set_visible(true);
1716
1717 if ( this->npoints == 2 ) {
1718 p_array[1] = q;
1719 cl0->set_visible(false);
1720 ctrl[1]->set_position(p_array[1]);
1721 ctrl[1]->set_visible(true);
1722 cl1->set_coords(p_array[0], p_array[1]);
1723 this->_setAngleDistanceStatusMessage(q, 0, _("<b>Curve handle</b>: angle %3.2f&#176;, length %s; with <b>Ctrl</b> to snap angle"));
1724 } else if ( this->npoints == 5 ) {
1725 p_array[4] = q;
1726 cl0->set_visible(true);
1727 bool is_symm = false;
1728 if ( ( ( this->mode == PenTool::MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) ||
1729 ( ( this->mode == PenTool::MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) {
1730 Geom::Point delta = q - p_array[3];
1731 p_array[2] = p_array[3] - delta;
1732 is_symm = true;
1733 this->red_curve.reset();
1734 this->red_curve.moveto(p_array[0]);
1735 this->red_curve.curveto(p_array[1], p_array[2], p_array[3]);
1736 red_bpath->set_bpath(&red_curve, true);
1737 }
1738 // Avoid conflicting with initial point ctrl
1739 if (green_curve->get_segment_count() > 0) {
1740 ctrl[0]->set_position(p_array[0]);
1741 ctrl[0]->set_visible(true);
1742 }
1743 ctrl[3]->set_position(p_array[3]);
1744 ctrl[3]->set_visible(true);
1745 ctrl[2]->set_position(p_array[2]);
1746 ctrl[2]->set_visible(true);
1747 ctrl[1]->set_position(p_array[4]);
1748 ctrl[1]->set_visible(true);
1749
1750 cl0->set_coords(p_array[3], p_array[2]);
1751 cl1->set_coords(p_array[3], p_array[4]);
1752
1753 gchar *message = is_symm ?
1754 _("<b>Curve handle, symmetric</b>: angle %3.2f&#176;, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only") :
1755 _("<b>Curve handle</b>: angle %3.2f&#176;, length %s; with <b>Ctrl</b> to snap angle, with <b>Shift</b> to move this handle only");
1756 this->_setAngleDistanceStatusMessage(q, 3, message);
1757 } else {
1758 g_warning("Something bad happened - npoints is %d", this->npoints);
1759 }
1760}
1761
1762void PenTool::_finishSegment(Geom::Point const q, guint const state) { // use 'q' as 'p' shadows member variable.
1763 if (this->polylines_paraxial) {
1764 this->nextParaxialDirection(q, p_array[0], state);
1765 }
1766
1767 if (!this->red_curve.is_unset()) {
1768 this->_bsplineSpiro(state & GDK_SHIFT_MASK);
1769 if(!this->green_curve->is_unset() &&
1770 !Geom::are_near(*this->green_curve->last_point(),p_array[0]))
1771 {
1772 SPCurve lsegment;
1773 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*this->green_curve->last_segment());
1774 if (cubic) {
1775 lsegment.moveto((*cubic)[0]);
1776 lsegment.curveto((*cubic)[1], p_array[0] - ((*cubic)[2] - (*cubic)[3]), *this->red_curve.first_point());
1777 green_curve->backspace();
1778 green_curve->append_continuous(std::move(lsegment));
1779 }
1780 }
1781 green_curve->append_continuous(red_curve);
1782 auto curve = red_curve;
1783
1785 auto canvas_shape = new Inkscape::CanvasItemBpath(_desktop->getCanvasSketch(), curve.get_pathvector(), true);
1786 canvas_shape->set_stroke(green_color);
1787 canvas_shape->set_fill(0x0, SP_WIND_RULE_NONZERO);
1788 green_bpaths.emplace_back(canvas_shape);
1789
1790 p_array[0] = p_array[3];
1791 p_array[1] = p_array[4];
1792 this->npoints = 2;
1793
1794 red_curve.reset();
1795 _redo_stack.clear();
1796 }
1797}
1798
1799bool PenTool::_undoLastPoint(bool user_undo) {
1800 bool ret = false;
1801
1802 if ( this->green_curve->is_unset() || (this->green_curve->last_segment() == nullptr) ) {
1803 if (red_curve.is_unset()) {
1804 return ret; // do nothing; this event should be handled upstream
1805 }
1806 _cancel();
1807 ret = true;
1808 } else {
1809 red_curve.reset();
1810 if (user_undo) {
1811 if (_did_redo) {
1812 _redo_stack.clear();
1813 _did_redo = false;
1814 }
1815 _redo_stack.push_back(green_curve->get_pathvector());
1816 }
1817 // The code below assumes that this->green_curve has only ONE path !
1818 Geom::Curve const * crv = this->green_curve->last_segment();
1819 p_array[0] = crv->initialPoint();
1820 if ( Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const *>(crv)) {
1821 p_array[1] = (*cubic)[1];
1822
1823 } else {
1824 p_array[1] = p_array[0];
1825 }
1826
1827 // assign the value in a third of the distance of the last segment.
1828 if (this->bspline){
1829 p_array[1] = p_array[0] + (1./3)*(p_array[3] - p_array[0]);
1830 }
1831
1832 Geom::Point const pt( (this->npoints < 4) ? crv->finalPoint() : p_array[3] );
1833
1834 this->npoints = 2;
1835 // delete the last segment of the green curve and green bpath
1836 if (this->green_curve->get_segment_count() == 1) {
1837 this->npoints = 5;
1838 if (!this->green_bpaths.empty()) {
1839 this->green_bpaths.pop_back();
1840 }
1841 this->green_curve->reset();
1842 } else {
1843 this->green_curve->backspace();
1844 if (this->green_bpaths.size() > 1) {
1845 this->green_bpaths.pop_back();
1846 } else if (this->green_bpaths.size() == 1) {
1847 green_bpaths.back()->set_bpath(green_curve.get(), true);
1848 }
1849 }
1850
1851 // assign the value of p_array[1] to the opposite of the green line last segment
1852 if (this->spiro){
1853 Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(this->green_curve->last_segment());
1854 if ( cubic ) {
1855 p_array[1] = (*cubic)[3] + (*cubic)[3] - (*cubic)[2];
1856 ctrl[1]->set_position(p_array[0]);
1857 } else {
1858 p_array[1] = p_array[0];
1859 }
1860 }
1861
1862 for (auto &c : ctrl) {
1863 c->set_visible(false);
1864 }
1865 cl0->set_visible(false);
1866 cl1->set_visible(false);
1867 this->state = PenTool::POINT;
1868
1869 if(this->polylines_paraxial) {
1870 // We compare the point we're removing with the nearest horiz/vert to
1871 // see if the line was added with SHIFT or not.
1872 Geom::Point compare(pt);
1873 this->_setToNearestHorizVert(compare, 0);
1874 if ((std::abs(compare[Geom::X] - pt[Geom::X]) > 1e-9)
1875 || (std::abs(compare[Geom::Y] - pt[Geom::Y]) > 1e-9)) {
1876 this->paraxial_angle = this->paraxial_angle.cw();
1877 }
1878 }
1879 this->_setSubsequentPoint(pt, true);
1880
1881 //redraw
1882 this->_bsplineSpiroBuild();
1883 ret = true;
1884 }
1885
1886 return ret;
1887}
1888
1891{
1892 if (_redo_stack.empty()) {
1893 return false;
1894 }
1895
1896 auto old_green = std::move(_redo_stack.back());
1897 _redo_stack.pop_back();
1898 green_curve->set_pathvector(old_green);
1899
1900 if (auto const *last_seg = green_curve->last_segment()) {
1901 Geom::Path freshly_added;
1902 freshly_added.append(*last_seg);
1903 green_bpaths.emplace_back(make_canvasitem<CanvasItemBpath>(_desktop->getCanvasSketch(), freshly_added, true));
1904 }
1905 green_bpaths.back()->set_stroke(green_color);
1906 green_bpaths.back()->set_fill(0x0, SP_WIND_RULE_NONZERO);
1907
1908 auto const last_point = green_curve->last_point();
1909 if (last_point) {
1910 p_array[0] = p_array[1] = *last_point;
1911 }
1912 _setSubsequentPoint(p_array[3], true);
1914
1915 _did_redo = true;
1916 return true;
1917}
1918
1919void PenTool::_finish(gboolean const closed) {
1920 if (this->expecting_clicks_for_LPE > 1) {
1921 // don't let the path be finished before we have collected the required number of mouse clicks
1922 return;
1923 }
1924
1925 this->_disableEvents();
1926
1927 this->message_context->clear();
1928
1929 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Drawing finished"));
1930
1931 // cancelate line without a created segment
1932 this->red_curve.reset();
1933 spdc_concat_colors_and_flush(this, closed);
1934 this->sa = nullptr;
1935 this->ea = nullptr;
1936
1937 this->npoints = 0;
1938 this->state = PenTool::POINT;
1939
1940 for (auto &c : ctrl) {
1941 c->set_visible(false);
1942 }
1943
1944 cl0->set_visible(false);
1945 cl1->set_visible(false);
1946
1947 this->green_anchor.reset();
1948 _redo_stack.clear();
1949 this->_enableEvents();
1950}
1951
1953 this->events_disabled = true;
1954}
1955
1957 g_return_if_fail(this->events_disabled != 0);
1958
1959 this->events_disabled = false;
1960}
1961
1962void PenTool::waitForLPEMouseClicks(Inkscape::LivePathEffect::EffectType effect_type, unsigned int num_clicks, bool use_polylines) {
1963 if (effect_type == Inkscape::LivePathEffect::INVALID_LPE)
1964 return;
1965
1966 this->waiting_LPE_type = effect_type;
1967 this->expecting_clicks_for_LPE = num_clicks;
1968 this->polylines_only = use_polylines;
1969 this->polylines_paraxial = false; // TODO: think if this is correct for all cases
1970}
1971
1972void PenTool::nextParaxialDirection(Geom::Point const &pt, Geom::Point const &origin, guint state) {
1973 //
1974 // after the first mouse click we determine whether the mouse pointer is closest to a
1975 // horizontal or vertical segment; for all subsequent mouse clicks, we use the direction
1976 // orthogonal to the last one; pressing Shift toggles the direction
1977 //
1978 // num_clicks is not reliable because spdc_pen_finish_segment is sometimes called too early
1979 // (on first mouse release), in which case num_clicks immediately becomes 1.
1980 // if (this->num_clicks == 0) {
1981
1982 if (this->green_curve->is_unset()) {
1983 // first mouse click
1984 double h = pt[Geom::X] - origin[Geom::X];
1985 double v = pt[Geom::Y] - origin[Geom::Y];
1986 this->paraxial_angle = Geom::Point(h, v).ccw();
1987 }
1988 if(!(state & GDK_SHIFT_MASK)) {
1989 this->paraxial_angle = this->paraxial_angle.ccw();
1990 }
1991}
1992
1993void PenTool::_setToNearestHorizVert(Geom::Point &pt, guint const state) const {
1994 Geom::Point const origin = p_array[0];
1995 Geom::Point const target = (state & GDK_SHIFT_MASK) ? this->paraxial_angle : this->paraxial_angle.ccw();
1996
1997 // Create a horizontal or vertical constraint line
1999
2000 // Snap along the constraint line; if we didn't snap then still the constraint will be applied
2002
2004 // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
2005 // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
2006
2007 m.setup(_desktop, true, selection->singleItem());
2009 m.unSetup();
2010}
2011
2012}
2013}
2014}
2015
2016/*
2017 Local Variables:
2018 mode:c++
2019 c-file-style:"stroustrup"
2020 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2021 indent-tabs-mode:nil
2022 fill-column:99
2023 End:
2024*/
2025// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
Bezier curve with compile-time specified order.
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual D2< SBasis > toSBasis() const =0
Convert the curve to a symmetric power basis polynomial.
virtual Point initialPoint() const =0
Retrieve the start of the curve.
virtual Point finalPoint() const =0
Retrieve the end of the curve.
Sequence of subpaths.
Definition pathvector.h:122
Sequence of contiguous curves, aka spline.
Definition path.h:353
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Point ccw() const
Return a point like this point but rotated -90 degrees.
Definition point.h:130
constexpr Point cw() const
Return a point like this point but rotated +90 degrees.
Definition point.h:137
Rotate inverse() const
Definition transforms.h:209
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
Definition color.cpp:117
LivePathEffectObject * getLPEObj()
Definition effect.h:151
virtual void acceptParamPath(SPPath const *param_path)
If the effect expects a path parameter (specified by a number of mouse clicks) before it is applied,...
Definition effect.cpp:1608
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void clear()
Unselects all selected objects.
SPItem * singleItem()
Returns a single selected item.
Data type representing a typeless value of a preference.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
Glib::ustring getString(Glib::ustring const &def="") const
Interpret the preference as an UTF-8 string.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def="black")
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
Class to store data for points which are snap candidates, either as a source or as a target.
std::shared_ptr< SPCurve > sa_overwrited
LivePathEffect::EffectType waiting_LPE_type
CanvasItemPtr< CanvasItemBpath > red_bpath
bool root_handler(CanvasEvent const &event) override
std::unique_ptr< SPDrawAnchor > green_anchor
CanvasItemPtr< CanvasItemBpath > blue_bpath
std::shared_ptr< SPCurve > green_curve
std::vector< CanvasItemPtr< CanvasItemBpath > > green_bpaths
Util::ActionAccel _acc_to_curve
Definition pen-tool.h:166
sigc::connection _desktop_destroy
Definition pen-tool.h:159
void _endpointSnap(Geom::Point &p, guint const state)
Snaps new node relative to the previous node.
Definition pen-tool.cpp:184
bool _handleMotionNotify(MotionEvent const &event)
Handle motion_notify event.
Definition pen-tool.cpp:475
Inkscape::LivePathEffect::Effect * waiting_LPE
Definition pen-tool.h:77
void _setToNearestHorizVert(Geom::Point &pt, guint const state) const
bool root_handler(CanvasEvent const &event) override
Callback to handle all pen events.
Definition pen-tool.cpp:247
CanvasItemPtr< CanvasItemCurve > cl1
Definition pen-tool.h:86
bool _redoLastPoint()
Re-add the last undone point to the path being drawn.
void _bsplineSpiro(bool shift)
std::vector< Geom::PathVector > _redo_stack
History of undone events.
Definition pen-tool.h:162
Util::ActionAccel _acc_to_line
Definition pen-tool.h:165
void _finishSegment(Geom::Point p, guint state)
bool item_handler(SPItem *item, CanvasEvent const &event) override
Handles item specific events.
Definition pen-tool.cpp:227
void _lastpointMoveScreen(gdouble x, gdouble y)
Definition pen-tool.cpp:858
bool _handleButtonPress(ButtonPressEvent const &event)
Handle mouse single button press event.
Definition pen-tool.cpp:277
void _setCtrl(Geom::Point const p, guint state)
bool _handle2ButtonPress(ButtonPressEvent const &event)
Handle mouse double button press event.
Definition pen-tool.cpp:462
void waitForLPEMouseClicks(Inkscape::LivePathEffect::EffectType effect_type, unsigned int num_clicks, bool use_polylines=true)
void _setSubsequentPoint(Geom::Point const p, bool statusbar, guint status=0)
void _setInitialPoint(Geom::Point const p)
void nextParaxialDirection(Geom::Point const &pt, Geom::Point const &origin, guint state)
bool _handleKeyPress(KeyPressEvent const &event)
Definition pen-tool.cpp:965
Util::ActionAccel _acc_to_guides
Definition pen-tool.h:167
void _bsplineSpiroMotion(guint const state)
void _lastpointMove(gdouble x, gdouble y)
Definition pen-tool.cpp:829
CanvasItemPtr< CanvasItemCurve > cl0
Definition pen-tool.h:85
void set(Inkscape::Preferences::Entry const &val) override
Callback that sets key to value in pen context.
Definition pen-tool.cpp:163
unsigned int expecting_clicks_for_LPE
Definition pen-tool.h:76
gint npoints
\invar npoints in {0, 2, 5}.
Definition pen-tool.h:65
bool _handleButtonRelease(ButtonReleaseEvent const &event)
Handle mouse button release event.
Definition pen-tool.cpp:643
void _bsplineSpiroStartAnchor(bool shift)
void _finish(gboolean closed)
static constexpr std::array< CanvasItemCtrlType, 4 > ctrl_types
Definition pen-tool.h:81
CanvasItemPtr< CanvasItemCtrl > ctrl[4]
Definition pen-tool.h:80
bool _undoLastPoint(bool user_undo=false)
PenTool(SPDesktop *desktop, std::string &&prefs_path="/tools/freehand/pen", std::string &&cursor_filename="pen.svg")
Definition pen-tool.cpp:73
void _setAngleDistanceStatusMessage(Geom::Point const p, int pc_point_to_compare, gchar const *message)
Show the status message for the current line/curve segment.
void _endpointSnapHandle(Geom::Point &p, guint const state)
Snaps new node's handle relative to the new node.
Definition pen-tool.cpp:213
bool sp_event_context_knot_mouseover() const
Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case).
void ungrabCanvasEvents()
Ungrab events from the Canvas Catchall.
void grabCanvasEvents(EventMask mask=EventType::KEY_PRESS|EventType::BUTTON_RELEASE|EventType::MOTION|EventType::BUTTON_PRESS)
Grab events from the Canvas Catchall.
SPGroup * currentLayer() const
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
Util::ActionAccel _acc_undo
Definition tool-base.h:223
virtual bool item_handler(SPItem *item, CanvasEvent const &event)
Handles item specific events.
Util::ActionAccel _acc_redo
Definition tool-base.h:224
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
bool isTriggeredBy(KeyEvent const &key) const
Checks whether a given key event triggers this action.
Glib::ustring string(Unit const *u) const
Return a printable string of the value in the specified unit.
Definition units.cpp:578
Inkscape::LivePathEffect::Effect * get_lpe()
Definition lpeobject.h:51
Wrapper around a Geom::PathVector object.
Definition curve.h:28
size_t get_segment_count() const
Returns the number of segments of all paths summed.
Definition curve.cpp:50
Geom::Curve const * last_segment() const
Return last pathsegment (possibly the closing path segment) of the last path in PathVector or NULL.
Definition curve.cpp:246
void moveto(Geom::Point const &p)
Perform a moveto to a point, thus starting a new subpath.
Definition curve.cpp:95
bool is_unset() const
True if paths are in curve.
Definition curve.cpp:202
void reverse()
Reverse the direction of all paths.
Definition curve.cpp:368
void reset()
Set curve to empty curve.
Definition curve.cpp:75
SPCurve reversed() const
Return a copy with the direction of all paths reversed.
Definition curve.cpp:376
void lineto(Geom::Point const &p)
Adds a line to the current subpath.
Definition curve.cpp:106
std::optional< Geom::Point > last_point() const
Return last point of last subpath or nothing when the curve is empty.
Definition curve.cpp:356
std::optional< Geom::Point > first_point() const
Return first point of first subpath or nothing when the path is empty.
Definition curve.cpp:301
void backspace()
Remove last segment of curve.
Definition curve.cpp:464
void curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
Adds a bezier segment to the current subpath.
Definition curve.cpp:147
bool append_continuous(SPCurve const &c1, double tolerance=0.0625)
Append c1 to this with possible fusing of close endpoints.
Definition curve.cpp:425
Geom::Curve const * first_segment() const
Return first pathsegment in PathVector or NULL.
Definition curve.cpp:274
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Inkscape::CanvasItemGroup * getCanvasControls() const
Definition desktop.h:196
Inkscape::CanvasItemGroup * getCanvasSketch() const
Definition desktop.h:201
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
sigc::connection connectDestroy(F &&slot)
Definition desktop.h:253
Geom::Rotate const & current_rotation() const
Definition desktop.h:366
double yaxisdir() const
Definition desktop.h:426
bool is_yaxisdown() const
Definition desktop.h:427
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
The drawing anchor.
Definition draw-anchor.h:42
Geom::Point dp
Definition draw-anchor.h:49
std::shared_ptr< SPCurve > curve
Definition draw-anchor.h:46
Inkscape::Colors::Color highlight_color() const override
Generate a highlight colour if one isn't set and return it.
Base class for visual SVG elements.
Definition sp-item.h:109
SPLPEItem * removeCurrentPathEffect(bool keep_paths)
If keep_path is true, the item should not be updated, effectively 'flattening' the LPE.
SnapManager snap_manager
Inkscape::Util::Unit const * display_units
Class to coordinate snapping operations.
Definition snap.h:80
void constrainedSnapReturnByRef(Geom::Point &p, Inkscape::SnapSourceType const source_type, 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:233
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 preSnap(Inkscape::SnapCandidatePoint const &p, bool to_path_only=false)
Definition snap.cpp:161
void unSetup()
Definition snap.h:147
double c[8][4]
Editable view implementation.
The nodes at the ends of the path in the pen/pencil tools.
static Glib::ustring const prefs_path
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
SPItem * item
void shift(T &a, T &b, T const &c)
Interface for locally managing a current status message.
Raw stack of active status messages.
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Coord LInfty(Point const &p)
void sp_bspline_do_effect(SPCurve &curve, double helper_size, Geom::PathVector &hp, bool uniform)
void sp_spiro_do_effect(SPCurve &curve)
Definition lpe-spiro.cpp:30
static bool pen_within_tolerance
Definition pen-tool.cpp:71
static Geom::Point pen_drag_origin_w(0, 0)
void spdc_endpoint_snap_free(ToolBase *tool, Geom::Point &p, std::optional< Geom::Point > &start_of_line)
void spdc_endpoint_snap_rotation(ToolBase *tool, Geom::Point &p, Geom::Point const &o, unsigned state)
Snaps node or handle to PI/rotationsnapsperpi degree increments.
void gobble_motion_events(unsigned mask)
Definition tool-base.h:244
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
SPDrawAnchor * spdc_test_inside(FreehandBase *dc, Geom::Point const &p)
Returns FIRST active anchor (the activated one).
void spdc_concat_colors_and_flush(FreehandBase *dc, bool forceclosed)
Concats red, blue and green.
void spdc_create_single_dot(ToolBase *tool, Geom::Point const &pt, char const *path, unsigned event_state)
Create a single dot represented by a circle.
void sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
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.
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool mod_ctrl(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
bool mod_shift(unsigned modifiers)
@ NORMAL_MESSAGE
Definition message.h:26
@ IMMEDIATE_MESSAGE
Definition message.h:27
bool have_viable_layer(SPDesktop *desktop, MessageContext *message)
Check to see if the current layer is both unhidden and unlocked.
STL namespace.
static Geom::PathVector copy_pathvector_optional(T &p)
Definition pen-tool.cpp:57
PenTool: a context for pen tool events.
unsigned button
The button that was pressed/released. (Matches GDK_BUTTON_*.)
Geom::Point pos
Location of the cursor, in world coordinates.
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.
Geom::Point pos
Location of the cursor.
Definition curve.h:24
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
int delta
SPDesktop * desktop
Glib::ustring name
Definition toolbars.cpp:55
double uniform()