1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * SPKnot implementation
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
10 * Copyright (C) 1999-2005 authors
11 * Copyright (C) 2001-2002 Ximian, Inc.
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
19 #include <gdk/gdkkeysyms.h>
20 #include <glibmm/i18n.h>
23 #include "document-undo.h"
25 #include "message-context.h"
26 #include "message-stack.h"
28 #include "display/control/canvas-item-ctrl.h"
29 #include "ui/tools/tool-base.h"
30 #include "ui/tools/node-tool.h"
31 #include "ui/widget/canvas.h" // autoscroll
32 #include "ui/widget/events/canvas-event.h"
34 using Inkscape::DocumentUndo
;
35 using Inkscape::EventType
;
37 static constexpr auto KNOT_EVENT_MASK
=
38 EventType::BUTTON_PRESS
|
39 EventType::BUTTON_RELEASE
|
41 EventType::KEY_PRESS
|
42 EventType::KEY_RELEASE
;
44 static auto const nograbenv
= getenv("INKSCAPE_NO_GRAB");
45 static auto const nograb
= nograbenv
&& *nograbenv
&& *nograbenv
!= '0';
47 void SPKnot::unref(SPKnot
*knot
)
49 if (--knot
->ref_count
< 1) {
54 SPKnot::SPKnot(SPDesktop
*desktop
, char const *tip
, Inkscape::CanvasItemCtrlType type
, Glib::ustring
const &name
)
61 image
[SP_KNOT_STATE_NORMAL
] = nullptr;
62 image
[SP_KNOT_STATE_MOUSEOVER
] = nullptr;
63 image
[SP_KNOT_STATE_DRAGGING
] = nullptr;
64 image
[SP_KNOT_STATE_SELECTED
] = nullptr;
66 ctrl
= make_canvasitem
<Inkscape::CanvasItemCtrl
>(desktop
->getCanvasControls(), type
); // Shape, mode set
67 ctrl
->set_name("CanvasItemCtrl:Knot:" + name
);
69 _event_connection
= ctrl
->connect_event(sigc::mem_fun(*this, &SPKnot::eventHandler
));
71 knot_created_callback(this);
76 // Make sure the knot is not grabbed, as it's destructing can be deferred causing
77 // issues like https://gitlab.com/inkscape/inkscape/-/issues/4239
81 // FIXME: cannot snap to destroyed knot (lp:1309050)
82 // this->desktop->getTool()->discard_delayed_snap_event();
83 knot_deleted_callback(this);
86 void SPKnot::startDragging(Geom::Point
const &p
, Geom::IntPoint
const &xy
, uint32_t etime
)
90 within_tolerance
= true;
92 this->grabbed_rel_pos
= p
- this->pos
;
93 this->drag_origin
= this->pos
;
95 if (!nograb
&& ctrl
) {
96 ctrl
->grab(KNOT_EVENT_MASK
, _cursors
[SP_KNOT_STATE_DRAGGING
]);
98 this->setFlag(SP_KNOT_GRABBED
, true);
103 void SPKnot::selectKnot(bool select
)
105 setFlag(SP_KNOT_SELECTED
, select
);
108 bool SPKnot::eventHandler(Inkscape::CanvasEvent
const &event
)
110 // Run client universal event handler, if present.
111 if (event_signal
.emit(this, event
)) {
115 bool key_press_event_unconsumed
= false;
119 auto prefs
= Inkscape::Preferences::get();
120 tolerance
= prefs
->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
122 bool consumed
= false;
125 [&] (Inkscape::ButtonPressEvent
const &event
) {
126 if (event
.button
!= 1) {
129 if (event
.num_press
== 2) {
130 doubleclicked_signal
.emit(this, event
.modifiers
);
134 } else if (event
.num_press
== 1 && desktop
&& desktop
->getTool() && !desktop
->getTool()->is_space_panning()) {
135 auto const p
= desktop
->w2d(event
.pos
);
136 startDragging(p
, event
.pos
.floor(), event
.time
);
137 mousedown_signal
.emit(this, event
.modifiers
);
142 [&] (Inkscape::ButtonReleaseEvent
const &event
) {
143 if (event
.button
== 1 &&
145 desktop
->getTool() &&
146 !desktop
->getTool()->is_space_panning())
148 // If we have any pending snap event, then invoke it now
149 desktop
->getTool()->process_delayed_snap_event();
152 if (transform_escaped
) {
153 transform_escaped
= false;
156 setFlag(SP_KNOT_GRABBED
, false);
158 if (!nograb
&& ctrl
) {
163 setFlag(SP_KNOT_DRAGGING
, false);
164 ungrabbed_signal
.emit(this, event
.modifiers
);
166 click_signal
.emit(this, event
.modifiers
);
174 Inkscape::UI::Tools::sp_update_helperpath(desktop
);
177 [&] (Inkscape::MotionEvent
const &event
) {
178 if (!(event
.modifiers
& GDK_BUTTON1_MASK
) && flags
& SP_KNOT_DRAGGING
) {
181 if (transform_escaped
) {
182 transform_escaped
= false;
185 setFlag(SP_KNOT_GRABBED
, false);
187 if (!nograb
&& ctrl
) {
192 setFlag(SP_KNOT_DRAGGING
, false);
193 ungrabbed_signal
.emit(this, event
.modifiers
);
195 click_signal
.emit(this, event
.modifiers
);
201 Inkscape::UI::Tools::sp_update_helperpath(desktop
);
203 } else if (grabbed
&& desktop
&& desktop
->getTool() &&
204 !desktop
->getTool()->is_space_panning())
208 if (within_tolerance
&& Geom::LInfty(event
.pos
.floor() - xyp
) < tolerance
) {
209 return; // do not drag if we're within tolerance from origin
212 // Once the user has moved farther than tolerance from the original location
213 // (indicating they intend to move the object, not click), then always process the
214 // motion notify coordinates as given (no snapping back to origin)
215 within_tolerance
= false;
217 if (event
.extinput
.pressure
) {
218 pressure
= std::clamp(*event
.extinput
.pressure
, 0.0, 1.0);
224 setFlag(SP_KNOT_DRAGGING
, true);
225 grabbed_signal
.emit(this, event
.modifiers
);
228 desktop
->getTool()->snap_delay_handler(nullptr, this, event
, Inkscape::UI::Tools::DelayedSnapEvent::KNOT_HANDLER
);
229 handler_request_position(event
);
234 [&] (Inkscape::EnterEvent
const &event
) {
235 setFlag(SP_KNOT_MOUSEOVER
, true);
236 setFlag(SP_KNOT_GRABBED
, false);
238 if (!_tip
.empty() && desktop
&& desktop
->getTool()) {
239 desktop
->getTool()->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE
, _tip
.c_str());
241 desktop
->getTool()->use_cursor(_cursors
[SP_KNOT_STATE_MOUSEOVER
]);
248 [&] (Inkscape::LeaveEvent
const &event
) {
249 setFlag(SP_KNOT_MOUSEOVER
, false);
250 setFlag(SP_KNOT_GRABBED
, false);
252 if (!_tip
.empty() && desktop
&& desktop
->getTool()) {
253 desktop
->getTool()->defaultMessageContext()->clear();
255 desktop
->getTool()->use_cursor(_cursors
[SP_KNOT_STATE_NORMAL
]);
262 [&] (Inkscape::KeyPressEvent
const &event
) {
263 // keybindings for knot
264 switch (Inkscape::UI::Tools::get_latin_keyval(event
)) {
266 setFlag(SP_KNOT_GRABBED
, false);
268 if (!nograb
&& ctrl
) {
273 setFlag(SP_KNOT_DRAGGING
, false);
275 ungrabbed_signal
.emit(this, event
.modifiers
);
277 DocumentUndo::undo(desktop
->getDocument());
278 desktop
->messageStack()->flash(Inkscape::NORMAL_MESSAGE
, _("Node or handle drag canceled."));
279 transform_escaped
= true;
286 desktop
->getTool()->discard_delayed_snap_event();
290 key_press_event_unconsumed
= true;
295 [&] (Inkscape::CanvasEvent
const &event
) {}
300 if (key_press_event_unconsumed
) {
301 return false; // e.g. in case "%" was pressed to toggle snapping, or Q for quick zoom (while dragging a handle)
303 return consumed
|| grabbed
;
307 void SPKnot::handler_request_position(Inkscape::MotionEvent
const &event
)
309 auto const motion_w
= event
.pos
;
310 auto const motion_dt
= desktop
->w2d(motion_w
);
311 auto p
= motion_dt
- grabbed_rel_pos
;
313 requestPosition(p
, event
.modifiers
);
314 desktop
->getCanvas()->enable_autoscroll();
315 desktop
->set_coordinate_status(pos
); // display the coordinate of knot, not cursor - they may be different!
317 if (event
.modifiers
& GDK_BUTTON1_MASK
) {
318 Inkscape::UI::Tools::gobble_motion_events(GDK_BUTTON1_MASK
);
324 setFlag(SP_KNOT_VISIBLE
, true);
329 setFlag(SP_KNOT_VISIBLE
, false);
332 void SPKnot::requestPosition(Geom::Point
const &p
, unsigned state
)
334 bool done
= request_signal
.emit(this, &const_cast<Geom::Point
&>(p
), state
);
336 // If user did not complete, we simply move knot to new position.
338 setPosition(p
, state
);
342 void SPKnot::setPosition(Geom::Point
const &p
, unsigned state
)
347 ctrl
->set_position(p
);
350 moved_signal
.emit(this, p
, state
);
353 void SPKnot::moveto(Geom::Point
const &p
)
358 ctrl
->set_position(p
);
362 void SPKnot::setFlag(guint flag
, bool set
) {
366 this->flags
&= ~flag
;
370 case SP_KNOT_VISIBLE
:
372 ctrl
->set_visible(set
);
375 case SP_KNOT_MOUSEOVER
:
376 case SP_KNOT_DRAGGING
:
377 case SP_KNOT_SELECTED
:
378 this->_setCtrlState();
380 case SP_KNOT_GRABBED
:
383 g_assert_not_reached();
388 // TODO: Look at removing this and setting ctrl parameters directly.
389 void SPKnot::updateCtrl() {
393 ctrl
->set_size(_size
);
395 ctrl
->set_angle(angle
);
396 ctrl
->set_anchor(anchor
);
402 void SPKnot::_setCtrlState() {
404 ctrl
->set_normal(this->flags
& SP_KNOT_SELECTED
);
405 if (this->flags
& SP_KNOT_DRAGGING
) {
407 } else if (this->flags
& SP_KNOT_MOUSEOVER
) {
413 void SPKnot::setSize(Inkscape::HandleSize size
) {
418 void SPKnot::setAnchor(guint i
) {
419 anchor
= (SPAnchorType
) i
;
422 void SPKnot::setAngle(double i
) {
426 void SPKnot::setImage(guchar
* normal
, guchar
* mouseover
, guchar
* dragging
, guchar
* selected
) {
427 image
[SP_KNOT_STATE_NORMAL
] = normal
;
428 image
[SP_KNOT_STATE_MOUSEOVER
] = mouseover
;
429 image
[SP_KNOT_STATE_DRAGGING
] = dragging
;
430 image
[SP_KNOT_STATE_SELECTED
] = selected
;
433 void SPKnot::setCursor(SPKnotStateType type
, Glib::RefPtr
<Gdk::Cursor
> cursor
)
435 _cursors
[type
] = std::move(cursor
);
438 void SPKnot::setTip(Glib::ustring
&&tip
)
440 _tip
= std::move(tip
);
446 c-file-style:"stroustrup"
447 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
452 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :