Context for the "About" label
[inkscape.git] / src / ui / knot / knot.cpp
blob121ae126cde6cb5774a842e1c6e80a89a96bb7df
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * SPKnot implementation
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * bulia byak <buliabyak@users.sf.net>
8 * Abhishek Sharma
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.
16 #include "knot.h"
18 #include <utility>
19 #include <gdk/gdkkeysyms.h>
20 #include <glibmm/i18n.h>
22 #include "desktop.h"
23 #include "document-undo.h"
24 #include "knot-ptr.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 |
40 EventType::MOTION |
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) {
50 delete knot;
54 SPKnot::SPKnot(SPDesktop *desktop, char const *tip, Inkscape::CanvasItemCtrlType type, Glib::ustring const &name)
55 : desktop(desktop)
57 if (tip) {
58 _tip = tip;
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);
74 SPKnot::~SPKnot()
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
78 ctrl->ungrab();
79 ctrl.reset();
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)
88 // save drag origin
89 xyp = xy;
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);
100 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)) {
112 return true;
115 bool key_press_event_unconsumed = false;
117 SPKnot::ref(this);
119 auto prefs = Inkscape::Preferences::get();
120 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
122 bool consumed = false;
124 inspect_event(event,
125 [&] (Inkscape::ButtonPressEvent const &event) {
126 if (event.button != 1) {
127 return;
129 if (event.num_press == 2) {
130 doubleclicked_signal.emit(this, event.modifiers);
131 grabbed = false;
132 moved = false;
133 consumed = true;
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);
138 consumed = true;
142 [&] (Inkscape::ButtonReleaseEvent const &event) {
143 if (event.button == 1 &&
144 desktop &&
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();
150 pressure = 0;
152 if (transform_escaped) {
153 transform_escaped = false;
154 consumed = true;
155 } else {
156 setFlag(SP_KNOT_GRABBED, false);
158 if (!nograb && ctrl) {
159 ctrl->ungrab();
162 if (moved) {
163 setFlag(SP_KNOT_DRAGGING, false);
164 ungrabbed_signal.emit(this, event.modifiers);
165 } else {
166 click_signal.emit(this, event.modifiers);
169 grabbed = false;
170 moved = false;
171 consumed = true;
174 Inkscape::UI::Tools::sp_update_helperpath(desktop);
177 [&] (Inkscape::MotionEvent const &event) {
178 if (!(event.modifiers & GDK_BUTTON1_MASK) && flags & SP_KNOT_DRAGGING) {
179 pressure = 0;
181 if (transform_escaped) {
182 transform_escaped = false;
183 consumed = true;
184 } else {
185 setFlag(SP_KNOT_GRABBED, false);
187 if (!nograb && ctrl) {
188 ctrl->ungrab();
191 if (moved) {
192 setFlag(SP_KNOT_DRAGGING, false);
193 ungrabbed_signal.emit(this, event.modifiers);
194 } else {
195 click_signal.emit(this, event.modifiers);
198 grabbed = false;
199 moved = false;
200 consumed = true;
201 Inkscape::UI::Tools::sp_update_helperpath(desktop);
203 } else if (grabbed && desktop && desktop->getTool() &&
204 !desktop->getTool()->is_space_panning())
206 consumed = true;
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);
219 } else {
220 pressure = 0.5;
223 if (!moved) {
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);
230 moved = true;
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]);
243 grabbed = false;
244 moved = false;
245 consumed = true;
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]);
257 grabbed = false;
258 moved = false;
259 consumed = true;
262 [&] (Inkscape::KeyPressEvent const &event) {
263 // keybindings for knot
264 switch (Inkscape::UI::Tools::get_latin_keyval(event)) {
265 case GDK_KEY_Escape:
266 setFlag(SP_KNOT_GRABBED, false);
268 if (!nograb && ctrl) {
269 ctrl->ungrab();
272 if (moved) {
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;
280 consumed = true;
283 grabbed = false;
284 moved = false;
286 desktop->getTool()->discard_delayed_snap_event();
287 break;
288 default:
289 consumed = false;
290 key_press_event_unconsumed = true;
291 break;
295 [&] (Inkscape::CanvasEvent const &event) {}
298 SPKnot::unref(this);
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)
302 } else {
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);
322 void SPKnot::show()
324 setFlag(SP_KNOT_VISIBLE, true);
327 void SPKnot::hide()
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.
337 if (!done) {
338 setPosition(p, state);
342 void SPKnot::setPosition(Geom::Point const &p, unsigned state)
344 pos = p;
346 if (ctrl) {
347 ctrl->set_position(p);
350 moved_signal.emit(this, p, state);
353 void SPKnot::moveto(Geom::Point const &p)
355 pos = p;
357 if (ctrl) {
358 ctrl->set_position(p);
362 void SPKnot::setFlag(guint flag, bool set) {
363 if (set) {
364 this->flags |= flag;
365 } else {
366 this->flags &= ~flag;
369 switch (flag) {
370 case SP_KNOT_VISIBLE:
371 if (ctrl) {
372 ctrl->set_visible(set);
374 break;
375 case SP_KNOT_MOUSEOVER:
376 case SP_KNOT_DRAGGING:
377 case SP_KNOT_SELECTED:
378 this->_setCtrlState();
379 break;
380 case SP_KNOT_GRABBED:
381 break;
382 default:
383 g_assert_not_reached();
384 break;
388 // TODO: Look at removing this and setting ctrl parameters directly.
389 void SPKnot::updateCtrl() {
391 if (ctrl) {
392 if (size_set) {
393 ctrl->set_size(_size);
395 ctrl->set_angle(angle);
396 ctrl->set_anchor(anchor);
399 _setCtrlState();
402 void SPKnot::_setCtrlState() {
403 if (ctrl) {
404 ctrl->set_normal(this->flags & SP_KNOT_SELECTED);
405 if (this->flags & SP_KNOT_DRAGGING) {
406 ctrl->set_click();
407 } else if (this->flags & SP_KNOT_MOUSEOVER) {
408 ctrl->set_hover();
413 void SPKnot::setSize(Inkscape::HandleSize size) {
414 _size = size;
415 size_set = true;
418 void SPKnot::setAnchor(guint i) {
419 anchor = (SPAnchorType) i;
422 void SPKnot::setAngle(double i) {
423 angle = 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);
444 Local Variables:
445 mode:c++
446 c-file-style:"stroustrup"
447 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
448 indent-tabs-mode:nil
449 fill-column:99
450 End:
452 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :