Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
desktop-events.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 1999-2002 Lauris Kaplinski
11 * Copyright (C) 1999-2010 Others
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#include "desktop-events.h"
17
18#include <map>
19#include <string>
20#include <2geom/line.h>
21#include <2geom/angle.h>
22#include <glibmm/i18n.h>
23#include <gdkmm/devicetool.h>
24#include <gdkmm/display.h>
25#include <gdkmm/seat.h>
26#include <glibmm/i18n.h>
27#include <glibmm/ustring_hash.h>
28#include <gtk/gtk.h>
29
30#include "desktop.h"
31#include "document-undo.h"
32#include "message-context.h"
33#include "preferences.h"
34#include "snap.h"
37#include "object/sp-guide.h"
38#include "object/sp-namedview.h"
39#include "ui/cursor-utils.h"
40#include "ui/dialog/guides.h"
41#include "ui/tools/tool-base.h"
42#include "ui/tools/node-tool.h"
44#include "ui/widget/canvas.h"
47
50
51static void snoop_extended(Inkscape::CanvasEvent const &event, SPDesktop *desktop);
52static void init_extended();
53
55{
56 if constexpr (Inkscape::DEBUG_EVENTS) {
57 Inkscape::dump_event(event, "sp_desktop_root_handler");
58 }
59
60 static bool watch = false;
61 static bool first = true;
62
63 if (first) {
64 auto prefs = Inkscape::Preferences::get();
65 if (prefs->getBool("/options/useextinput/value", true) &&
66 prefs->getBool("/options/switchonextinput/value"))
67 {
68 watch = true;
70 }
71 first = false;
72 }
73
74 if (watch) {
75 snoop_extended(event, desktop);
76 }
77
78 if (auto const tool = desktop->getTool()) {
79 return tool->start_root_handler(event);
80 }
81
82 return false;
83}
84
87static bool guide_moved = false;
88
90{
91 if constexpr (Inkscape::DEBUG_EVENTS) {
92 Inkscape::dump_event(event, "sp_dt_guide_event");
93 }
94
95 bool ret = false;
96
97 auto desktop = guide_item->get_canvas()->get_desktop();
98 if (!desktop) {
99 std::cerr << "sp_dt_guide_event: No desktop!" << std::endl;
100 return false;
101 }
102
103 // Limit to select/node tools only.
104 if (!dynamic_cast<Inkscape::UI::Tools::SelectTool *>(desktop->getTool()) &&
105 !dynamic_cast<Inkscape::UI::Tools::NodeTool *>(desktop->getTool()))
106 {
107 return false;
108 }
109
110 auto apply_snap = [desktop, guide] (Geom::Point &event_dt, unsigned modifiers) {
111 // This is for snapping while dragging existing guidelines. New guidelines,
112 // which are dragged off the ruler, are being snapped in sp_dt_ruler_event
113 auto &m = desktop->getNamedView()->snap_manager;
114 m.setup(desktop, true, guide, nullptr);
116 // If we snap in guideConstrainedSnap() below, then motion_dt will
117 // be forced to be on the guide. If we don't snap however, then
118 // the origin should still be constrained to the guide. So let's do
119 // that explicitly first:
120 auto const line = Geom::Line(guide->getPoint(), guide->angle());
121 auto const t = line.nearestTime(event_dt);
122 event_dt = line.pointAt(t);
123 if (!(modifiers & GDK_SHIFT_MASK)) {
124 m.guideConstrainedSnap(event_dt, *guide);
125 }
126 } else if (!(drag_type == SP_DRAG_ROTATE && modifiers & GDK_CONTROL_MASK)) {
127 // Cannot use shift here to disable snapping, because we already use it for rotating the guide.
128 Geom::Point tmp;
129 if (drag_type == SP_DRAG_ROTATE) {
130 tmp = guide->getPoint();
131 m.guideFreeSnap(event_dt, tmp, true, false);
132 guide->moveto(tmp, false);
133 } else {
134 tmp = guide->getNormal();
135 m.guideFreeSnap(event_dt, tmp, false, true);
136 guide->set_normal(tmp, false);
137 }
138 }
139 m.unSetup();
140 };
141
142 auto move_guide = [guide] (Geom::Point const &event_dt, unsigned modifiers, bool flag) {
143 switch (drag_type) {
146 guide->moveto(event_dt, flag);
147 break;
148 case SP_DRAG_ROTATE: {
149 auto angle = Geom::Angle(event_dt - guide->getPoint());
150 if (modifiers & GDK_CONTROL_MASK) {
151 auto prefs = Inkscape::Preferences::get();
152 if (auto snaps = std::abs(prefs->getInt("/options/rotationsnapsperpi/value", 12))) {
153 if (prefs->getBool("/options/relativeguiderotationsnap/value", false)) {
154 auto orig_angle = Geom::Angle(guide->getNormal());
155 auto snap_angle = angle - orig_angle;
156 double sections = std::floor(snap_angle.radians0() * snaps / M_PI + 0.5);
157 angle = M_PI / snaps * sections + orig_angle.radians0();
158 } else {
159 double sections = std::floor(angle.radians0() * snaps / M_PI + 0.5);
160 angle = M_PI / snaps * sections;
161 }
162 }
163 }
164 guide->set_normal(Geom::Point::polar(angle).cw(), flag);
165 break;
166 }
167 default:
168 assert(false);
169 break;
170 }
171 };
172
173 inspect_event(event,
174 [&] (Inkscape::ButtonPressEvent const &event) {
175 if (event.num_press == 2) {
176 if (event.button == 1) {
177 drag_type = SP_DRAG_NONE;
178 desktop->getTool()->discard_delayed_snap_event();
179 guide_item->ungrab();
180 Inkscape::UI::Dialog::GuidelinePropertiesDialog::showDialog(guide, desktop);
181 ret = true;
182 }
183 } else if (event.num_press == 1) {
184 if (event.button == 1 && !guide->getLocked()) {
185 auto const event_dt = desktop->w2d(event.pos);
186
187 // Due to the tolerance allowed when grabbing a guide, event_dt will generally
188 // be close to the guide but not just exactly on it. The drag origin calculated
189 // here must be exactly on the guide line though, otherwise
190 // small errors will occur once we snap, see
191 // https://bugs.launchpad.net/inkscape/+bug/333762
192 drag_origin = Geom::projection(event_dt, Geom::Line(guide->getPoint(), guide->angle()));
193
194 if (event.modifiers & GDK_SHIFT_MASK) {
195 // with shift we rotate the guide
196 drag_type = SP_DRAG_ROTATE;
197 } else if (event.modifiers & GDK_CONTROL_MASK) {
198 drag_type = SP_DRAG_MOVE_ORIGIN;
199 } else {
200 drag_type = SP_DRAG_TRANSLATE;
201 }
202
203 if (drag_type == SP_DRAG_ROTATE || drag_type == SP_DRAG_TRANSLATE) {
204 guide_item->grab(EventType::BUTTON_RELEASE |
205 EventType::BUTTON_PRESS |
206 EventType::MOTION);
207 }
208 ret = true;
209 }
210 }
211 },
212
213 [&] (Inkscape::MotionEvent const &event) {
214 if (drag_type == SP_DRAG_NONE) {
215 return;
216 }
217
218 desktop->getTool()->snap_delay_handler(guide_item, guide, event,
220
221 auto event_dt = desktop->w2d(event.pos);
222 apply_snap(event_dt, event.modifiers);
223 move_guide(event_dt, event.modifiers, false);
224
225 guide_moved = true;
227 desktop->getCanvas()->grab_focus();
228
229 ret = true;
230 },
231
232 [&] (Inkscape::ButtonReleaseEvent const &event) {
233 if (drag_type == SP_DRAG_NONE || event.button != 1) {
234 return;
235 }
236
238
239 if (guide_moved) {
240 auto event_dt = desktop->w2d(event.pos);
241 apply_snap(event_dt, event.modifiers);
242
243 if (guide_item->get_canvas()->world_point_inside_canvas(event.pos)) {
244 move_guide(event_dt, event.modifiers, true);
245 DocumentUndo::done(desktop->getDocument(), _("Move guide"), "");
246 } else {
247 // Undo movement of any attached shapes.
248 guide->moveto(guide->getPoint(), false);
249 guide->set_normal(guide->getNormal(), false);
250 guide->remove();
251 guide_item = nullptr;
253
254 DocumentUndo::done(desktop->getDocument(), _("Delete guide"), "");
255 }
256
257 guide_moved = false;
259 }
260
262 if (guide_item) {
263 guide_item->ungrab();
264 }
265
266 ret = true;
267 },
268
269 [&] (Inkscape::EnterEvent const &event) {
270 auto const canvas = desktop->getCanvas();
271 g_assert(canvas);
272
273 // This is a UX thing. Check if the canvas has focus, so the user knows they can
274 // use hotkeys. See issue: https://gitlab.com/inkscape/inkscape/-/issues/2439
275 if (!guide->getLocked() && canvas->has_focus()) {
276 guide_item->set_stroke(guide->getHiColor());
277 }
278
279 // set move or rotate cursor
280 if (guide->getLocked()) {
281 Inkscape::set_svg_cursor(*canvas, "select.svg");
282 } else if (event.modifiers & GDK_SHIFT_MASK && drag_type != SP_DRAG_MOVE_ORIGIN) {
283 Inkscape::set_svg_cursor(*canvas, "rotate.svg");
284 } else {
285 canvas->set_cursor("grab");
286 }
287
288 auto guide_description = guide->description();
289 desktop->guidesMessageContext()->setF(Inkscape::NORMAL_MESSAGE, _("<b>Guideline</b>: %s"), guide_description);
290 g_free(guide_description);
291 },
292
293 [&] (Inkscape::LeaveEvent const &event) {
294 guide_item->set_stroke(guide->getColor());
295
296 // restore event context's cursor
298
300 },
301
302 [&] (Inkscape::KeyPressEvent const &event) {
304 case GDK_KEY_Delete:
305 case GDK_KEY_KP_Delete:
306 case GDK_KEY_BackSpace:
307 if (!guide->getLocked()) {
308 auto doc = guide->document;
309 guide->remove();
310 guide_item = nullptr;
311 DocumentUndo::done(doc, _("Delete guide"), "");
312 ret = true;
315 }
316 break;
317 case GDK_KEY_Shift_L:
318 case GDK_KEY_Shift_R:
320 Inkscape::set_svg_cursor(*desktop->getCanvas(), "rotate.svg");
321 ret = true;
322 break;
323 }
324 default:
325 break;
326 }
327 },
328
329 [&] (Inkscape::KeyReleaseEvent const &event) {
331 case GDK_KEY_Shift_L:
332 case GDK_KEY_Shift_R: {
333 desktop->getCanvas()->set_cursor("grab");
334 break;
335 }
336 default:
337 break;
338 }
339 },
340
341 [&] (Inkscape::CanvasEvent const &event) {}
342 );
343
344 return ret;
345}
346
347static constexpr bool DEBUG_TOOL_SWITCHER = false;
348
349static std::unordered_map<Glib::ustring, Glib::ustring> name_to_tool;
350static Glib::ustring last_name;
351static Gdk::InputSource last_source = Gdk::InputSource::MOUSE;
352
353static void init_extended()
354{
355 auto display = Gdk::Display::get_default();
356 auto seat = display->get_default_seat();
357 auto const devices = seat->get_devices(Gdk::Seat::Capabilities::ALL);
358
359 for (auto const &dev : devices) {
360 auto const src = dev->get_source();
361 if (src == Gdk::InputSource::MOUSE) continue;
362
363 auto const &name = dev->get_name();
364 if (name.empty() || name == "pad") continue;
365
366 auto const tool = dev->property_tool().get_value();
367 if (!tool) continue;
368
369 // Set the initial tool for the device.
370 switch (tool->get_tool_type()) {
371 case Gdk::DeviceTool::Type::PEN:
372 name_to_tool[name] = "Calligraphic";
373 break;
374 case Gdk::DeviceTool::Type::ERASER:
375 name_to_tool[name] = "Eraser";
376 break;
377 case Gdk::DeviceTool::Type::MOUSE:
378 name_to_tool[name] = "Select";
379 break;
380 default:
381 break;
382 }
383 }
384}
385
386// Switch tool based on device that generated event.
387// For example, switch to Calligraphy or Eraser tool when using a Wacom tablet pen.
388// Enabled in "Input" section of preferences dialog.
390{
391 // Restrict to events we're interested in.
392 switch (event.type()) {
393 case EventType::MOTION:
394 case EventType::BUTTON_PRESS:
395 case EventType::BUTTON_RELEASE:
396 case EventType::SCROLL:
397 break; // Good
398 default:
399 return;
400 }
401
402 // Extract information about the device of the event.
403 auto device = event.device.get();
404 if (!device) {
405 // Not all event structures include a GdkDevice field but the above should!
406 std::cerr << "snoop_extended: missing source device! " << (int)event.type() << std::endl;
407 return;
408 }
409
410 auto source = device->get_source();
411 auto name = device->get_name();
412
413 if (name.empty()) {
414 std::cerr << "snoop_extended: name empty!" << std::endl;
415 return;
416 } else if (source == last_source && name == last_name) {
417 // Device has not changed.
418 return;
419 }
420
421 if constexpr (DEBUG_TOOL_SWITCHER) {
422 std::cout << "Changed device: " << last_name << " -> " << name << std::endl;
423 }
424
425 // Save the tool currently selected for next time the device shows up.
426 if (auto it = name_to_tool.find(last_name); it != name_to_tool.end()) {
427 it->second = get_active_tool(desktop);
428 }
429
430 // Select the tool that was selected last time the device was seen.
431 if (auto it = name_to_tool.find(name); it != name_to_tool.end()) {
432 set_active_tool(desktop, it->second);
433 }
434
435 last_name = name;
436 last_source = source;
437}
438
439/*
440 Local Variables:
441 mode:c++
442 c-file-style:"stroustrup"
443 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
444 indent-tabs-mode:nil
445 fill-column:99
446 End:
447*/
448// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Glib::ustring get_active_tool(InkscapeWindow *win)
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
Various trigoniometric helper functions.
Inkscape canvas widget.
Wrapper for angular values.
Definition angle.h:73
Infinite line on a plane.
Definition line.h:53
Two-dimensional point that doubles as a vector.
Definition point.h:66
UI::Widget::Canvas * get_canvas() const
Definition canvas-item.h:61
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void setF(MessageType type, char const *format,...) G_GNUC_PRINTF(3
pushes a message on the stack using prinf-style formatting, and replacing our old message
void clear()
removes our current message from the stack
static Preferences * get()
Access the singleton Preferences object.
void use_tool_cursor()
Uses the saved cursor, based on the saved filename.
void snap_delay_handler(gpointer item, gpointer item2, MotionEvent const &event, DelayedSnapEvent::Origin origin)
Analyses the current event, calculates the mouse speed, turns snapping off (temporarily) if the mouse...
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
SPDesktop * get_desktop() const
Definition canvas.h:73
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
SPDocument * getDocument() const
Definition desktop.h:189
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
void set_coordinate_status(Geom::Point const &p)
Sets the coordinate status to a given point.
Definition desktop.cpp:331
Inkscape::MessageContext * guidesMessageContext() const
Definition desktop.h:290
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
double angle() const
Definition sp-guide.h:84
Geom::Point getNormal() const
Definition sp-guide.h:55
void moveto(Geom::Point const point_on_line, bool const commit)
Definition sp-guide.cpp:367
void set_normal(Geom::Point const normal_to_line, bool const commit)
Definition sp-guide.cpp:412
Geom::Point getPoint() const
Definition sp-guide.h:54
SnapManager snap_manager
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
static constexpr bool DEBUG_TOOL_SWITCHER
static Geom::Point drag_origin
bool sp_desktop_root_handler(Inkscape::CanvasEvent const &event, SPDesktop *desktop)
static void snoop_extended(Inkscape::CanvasEvent const &event, SPDesktop *desktop)
static void init_extended()
static SPGuideDragType drag_type
static bool guide_moved
static Gdk::InputSource last_source
static std::unordered_map< Glib::ustring, Glib::ustring > name_to_tool
static Glib::ustring last_name
bool sp_dt_guide_event(Inkscape::CanvasEvent const &event, Inkscape::CanvasItemGuideLine *guide_item, SPGuide *guide)
Editable view implementation.
TODO: insert short description here.
Infinite straight line.
Interface for locally managing a current status message.
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.
EventType
The type of a CanvasEvent.
Definition enums.h:22
void set_svg_cursor(Gtk::Widget &widget, std::string const &file_name, std::optional< Colors::Color > fill, std::optional< Colors::Color > stroke)
Loads an SVG cursor from the specified file name, and sets it as the cursor of the given widget.
void dump_event(CanvasEvent const &event, char const *prefix, bool merge=true)
Print an event to stdout.
Definition debug.h:29
@ NORMAL_MESSAGE
Definition message.h:26
constexpr bool DEBUG_EVENTS
Whether event debug printing is enabled.
Definition debug.h:20
New node tool with support for multiple path editing.
Singleton class to access the preferences file in a convenient way.
SPGuideDragType
Definition snap.h:32
@ SP_DRAG_TRANSLATE
Definition snap.h:33
@ SP_DRAG_MOVE_ORIGIN
Definition snap.h:35
@ SP_DRAG_NONE
Definition snap.h:36
@ SP_DRAG_ROTATE
Definition snap.h:34
SPGuide – a guideline.
A mouse button (left/right/middle) is pressed.
A mouse button (left/right/middle) is released.
Abstract base class for events.
virtual EventType type() const =0
Return the dynamic type of the CanvasEvent.
The pointer has entered a widget or item.
A key has been pressed.
A key has been released.
The pointer has exited a widget or item.
Movement of the mouse pointer.
SPDesktop * desktop
Glib::ustring name
Definition toolbars.cpp:55
Debug printing of event data.