Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
tool-base.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Main event handling, and related helper functions.
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * Frank Felfe <innerspace@iname.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Jon A. Cruz <jon@joncruz.org>
10 * Kris De Gussem <Kris.DeGussem@gmail.com>
11 *
12 * Copyright (C) 1999-2012 authors
13 * Copyright (C) 2001-2002 Ximian, Inc.
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include "ui/tools/tool-base.h"
19
20#include <set>
21#include <utility>
22#include <gdk/gdkkeysyms.h>
23#include <gdkmm/device.h>
24#include <gdkmm/display.h>
25#include <gdkmm/seat.h>
26#include <gtkmm/eventcontrollerkey.h>
27#include <gtkmm/window.h>
28#include <glibmm/i18n.h>
29
30#include "desktop-events.h"
31#include "desktop-style.h"
32#include "desktop.h"
33#include "gradient-drag.h"
35#include "layer-manager.h"
36#include "message-context.h"
37#include "rubberband.h"
38#include "selcue.h"
39#include "selection-chemistry.h"
40#include "selection.h"
42#include "display/control/canvas-item-catchall.h" // Grab/Ungrab
44#include "object/sp-guide.h"
45#include "object/sp-namedview.h"
46#include "ui/contextmenu.h"
47#include "ui/cursor-utils.h"
48#include "ui/interface.h"
49#include "ui/knot/knot.h"
50#include "ui/knot/knot-holder.h"
51#include "ui/knot/knot-ptr.h"
52#include "ui/modifiers.h"
53#include "ui/popup-menu.h"
54#include "ui/shape-editor.h"
55#include "ui/shortcuts.h"
59#include "ui/tools/node-tool.h"
61#include "ui/widget/canvas.h"
66
67// globals for temporary switching to selector by space
68static bool selector_toggled = false;
69static Glib::ustring switch_selector_to;
70
71// globals for temporary switching to dropper by 'D'
72static bool dropper_toggled = false;
73static Glib::ustring switch_dropper_to;
74
75// globals for keeping track of keyboard scroll events in order to accelerate
77static double scroll_multiply = 1;
78static unsigned scroll_keyval = 0;
79
80// globals for key processing
81static bool latin_keys_group_valid = false;
83static std::set<int> latin_keys_groups;
84
85namespace Inkscape {
86namespace UI {
87namespace Tools {
88
89static void set_event_location(SPDesktop *desktop, CanvasEvent const &event);
90
91DelayedSnapEvent::DelayedSnapEvent(ToolBase *tool, gpointer item, gpointer item2, MotionEvent const &event, Origin origin)
92 : _tool(tool)
93 , _item(item)
94 , _item2(item2)
95 , _origin(origin)
96{
97 static_assert(std::is_final_v<MotionEvent>); // Or the next line will slice!
98 _event = std::make_unique<MotionEvent>(event);
99 _event->time = GDK_CURRENT_TIME;
100}
101
102ToolBase::ToolBase(SPDesktop *desktop, std::string &&prefs_path, std::string &&cursor_filename, bool uses_snap)
103 : _prefs_path(std::move(prefs_path))
104 , _cursor_filename("none")
105 , _cursor_default(std::move(cursor_filename))
106 , _uses_snap(uses_snap)
107 , _desktop(desktop)
108 , _acc_undo{"doc.undo"}
109 , _acc_redo{"doc.redo"}
110 , _acc_quick_preview{"tool.all.quick-preview"}
111 , _acc_quick_zoom{"tool.all.quick-zoom"}
112 , _acc_quick_pan{"tool.all.quick-pan"}
113{
116 _desktop->getCanvas()->grab_focus();
117
118 message_context = std::make_unique<Inkscape::MessageContext>(*desktop->messageStack());
119
120 // Make sure no delayed snapping events are carried over after switching tools
121 // (this is only an additional safety measure against sloppy coding, because each
122 // tool should take care of this by itself)
124
125 sp_event_context_read(this, "changelayer");
126 sp_event_context_read(this, "changepage");
127
128}
129
134
139{
140 Glib::ustring entry_name = value.getEntryName();
141 if (entry_name == "changelayer") {
143 } else if (entry_name == "changepage") {
144 _desktop->getSelection()->setChangePage(value.getBool(false));
145 }
146}
147
152
156void ToolBase::set_cursor(std::string filename)
157{
158 if (filename != _cursor_filename) {
159 _cursor_filename = std::move(filename);
161 }
162}
163
170Glib::RefPtr<Gdk::Cursor> ToolBase::get_cursor(Gtk::Widget &widget, std::string const &filename) const
171{
172 auto fillColor = sp_desktop_get_color_tool(_desktop, getPrefsPath(), true);
173 if (fillColor) {
174 fillColor->addOpacity(sp_desktop_get_opacity_tool(_desktop, getPrefsPath(), true));
175 }
176
177 auto strokeColor = sp_desktop_get_color_tool(_desktop, getPrefsPath(), false);
178 if (strokeColor) {
179 strokeColor->addOpacity(sp_desktop_get_opacity_tool(_desktop, getPrefsPath(), false));
180 }
181 return load_svg_cursor(widget, filename, fillColor, strokeColor);
182}
183
188{
189 auto &widget = dynamic_cast<Gtk::Widget &>(*_desktop->getCanvas());
190 widget.set_cursor(get_cursor(widget, _cursor_filename));
191
192 _desktop->waiting_cursor = false;
193}
194
200void ToolBase::use_cursor(Glib::RefPtr<Gdk::Cursor> cursor)
201{
202 if (auto window = dynamic_cast<Gtk::Window *>(_desktop->getCanvas()->get_root())) {
203 window->set_cursor(cursor ? cursor : _cursor);
204 }
205}
206
212
213 if (!dt->getTool()) {
214 return;
215 }
216
217 if (dynamic_cast<Inkscape::UI::Tools::SelectTool *>(dt->getTool())) {
218 if (selector_toggled) {
220 selector_toggled = false;
221 }
222 } else {
223 selector_toggled = TRUE;
225 set_active_tool(dt, "Select");
226 }
227}
228
234{
235 if (!dt->getTool()) {
236 return;
237 }
238
239 if (dynamic_cast<Inkscape::UI::Tools::DropperTool *>(dt->getTool())) {
240 if (dropper_toggled) {
242 dropper_toggled = FALSE;
243 }
244 } else {
245 dropper_toggled = TRUE;
247 set_active_tool(dt, "Dropper");
248 }
249}
250
255static double accelerate_scroll(KeyEvent const &event, double acceleration)
256{
257 auto time_diff = event.time - scroll_event_time;
258
259 /* key pressed within 500ms ? (1/2 second) */
260 if (time_diff > 500 || event.keyval != scroll_keyval) {
261 scroll_multiply = 1; // abort acceleration
262 } else {
263 scroll_multiply += acceleration; // continue acceleration
264 }
265
266 scroll_event_time = event.time;
267 scroll_keyval = event.keyval;
268
269 return scroll_multiply;
270}
271
274bool ToolBase::_keyboardMove(KeyEvent const &event, Geom::Point const &dir)
275{
276 if (mod_ctrl(event)) return false;
277 unsigned num = 1 + gobble_key_events(event.keyval, 0);
278
279 auto prefs = Preferences::get();
280
281 Geom::Point delta = dir * num;
282
283 if (mod_shift(event)) {
284 delta *= 10;
285 }
286
287 if (mod_alt(event)) {
289 } else {
290 double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px");
291 delta *= nudge;
292 }
293
294 bool const rotated = prefs->getBool("/options/moverotated/value", true);
295 if (rotated) {
297 }
298
299 bool moved = false;
301 auto &knotholder = shape_editor->knotholder;
302 if (knotholder && knotholder->knot_selected()) {
303 knotholder->transform_selected(Geom::Translate(delta));
304 moved = true;
305 }
306 } else {
307 // TODO: eliminate this dynamic cast by using inheritance
308 auto nt = dynamic_cast<Inkscape::UI::Tools::NodeTool *>(_desktop->getTool());
309 if (nt) {
310 for (auto &_shape_editor : nt->_shape_editors) {
311 ShapeEditor *shape_editor = _shape_editor.second.get();
313 auto &knotholder = shape_editor->knotholder;
314 if (knotholder && knotholder->knot_selected()) {
315 knotholder->transform_selected(Geom::Translate(delta));
316 moved = true;
317 }
318 }
319 }
320 }
321 }
322
323 return moved;
324}
325
327{
328 if constexpr (DEBUG_EVENTS) {
329 dump_event(event, "ToolBase::root_handler");
330 }
331
332 static Geom::Point button_w;
333 static unsigned int panning_cursor = 0;
334 static unsigned int zoom_rb = 0;
335
336 auto prefs = Inkscape::Preferences::get();
337
339 // Todo: Make these into preference watchers, rather than fetching on every event.
340 tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
341 bool allow_panning = prefs->getBool("/options/spacebarpans/value");
342 bool ret = false;
343
344 auto compute_angle = [&] (Geom::Point const &pt) {
345 // Hack: Undo coordinate transformation applied by canvas to get events back to window coordinates.
346 // Real solution: Move all this functionality out of this file to somewhere higher up in the chain.
348 return Geom::deg_from_rad(Geom::atan2(cursor - Geom::Point(_desktop->getCanvas()->get_dimensions()) / 2.0));
349 };
350
351 inspect_event(event,
352 [&] (ButtonPressEvent const &event) {
353
354 if (event.num_press == 2) {
355 if (panning) {
356 panning = PANNING_NONE;
357 ungrabCanvasEvents();
358 ret = true;
359 }
360 } else if (event.num_press == 1) {
361 // save drag origin
362 xyp = event.pos.floor();
363 within_tolerance = true;
364
365 button_w = event.pos;
366
367 switch (event.button) {
368 case 1:
369 // TODO Does this make sense? Panning starts on passive mouse motion while space
370 // bar is pressed, it's not necessary to press the mouse button.
371 if (is_space_panning()) {
372 // When starting panning, make sure there are no snap events pending because these might disable the panning again
373 if (_uses_snap) {
374 discard_delayed_snap_event();
375 }
376 panning = PANNING_SPACE_BUTTON1;
377
378 grabCanvasEvents(EventType::KEY_RELEASE |
379 EventType::BUTTON_RELEASE |
380 EventType::MOTION);
381
382 ret = true;
383 }
384 break;
385
386 case 2:
387 if (event.modifiers & GDK_CONTROL_MASK && !_desktop->get_rotation_lock()) {
388 // Canvas ctrl + middle-click to rotate
389 rotating = true;
390
391 start_angle = current_angle = compute_angle(event.pos);
392
393 grabCanvasEvents(EventType::KEY_PRESS |
394 EventType::KEY_RELEASE |
395 EventType::BUTTON_RELEASE |
396 EventType::MOTION);
397
398 } else if (event.modifiers & GDK_SHIFT_MASK) {
399 zoom_rb = 2;
400 } else {
401 // When starting panning, make sure there are no snap events pending because these might disable the panning again
402 if (_uses_snap) {
403 discard_delayed_snap_event();
404 }
405 panning = PANNING_BUTTON2;
406
407 grabCanvasEvents(EventType::BUTTON_RELEASE | EventType::MOTION);
408 }
409
410 ret = true;
411 break;
412
413 case 3:
414 if (event.modifiers & (GDK_SHIFT_MASK | GDK_CONTROL_MASK)) {
415 // When starting panning, make sure there are no snap events pending because these might disable the panning again
416 if (_uses_snap) {
417 discard_delayed_snap_event();
418 }
419 panning = PANNING_BUTTON3;
420
421 grabCanvasEvents(EventType::BUTTON_RELEASE | EventType::MOTION);
422 ret = true;
423 } else if (!are_buttons_1_and_3_on(event)) {
424 menu_popup(event);
425 ret = true;
426 }
427 break;
428
429 default:
430 break;
431 }
432 }
433 },
434
435 [&] (MotionEvent const &event) {
436 if (panning) {
437 if (panning == 4 && !xyp.x() && !xyp.y()) {
438 // <Space> + mouse panning started, save location and grab canvas
439 xyp = event.pos.floor();
440 button_w = event.pos;
441
442 grabCanvasEvents(EventType::KEY_RELEASE |
445 }
446
447 if ((panning == 2 && !(event.modifiers & GDK_BUTTON2_MASK)) ||
448 (panning == 1 && !(event.modifiers & GDK_BUTTON1_MASK)) ||
449 (panning == 3 && !(event.modifiers & GDK_BUTTON3_MASK)))
450 {
451 // Gdk seems to lose button release for us sometimes :-(
452 panning = PANNING_NONE;
453 ungrabCanvasEvents();
454 ret = true;
455 } else {
456 // To fix https://bugs.launchpad.net/inkscape/+bug/1458200
457 // we increase the tolerance because no sensible data for panning
458 if (within_tolerance && Geom::LInfty(event.pos.floor() - xyp) < tolerance * 3) {
459 // do not drag if we're within tolerance from origin
460 return;
461 }
462
463 // Once the user has moved farther than tolerance from
464 // the original location (indicating they intend to move
465 // the object, not click), then always process the motion
466 // notify coordinates as given (no snapping back to origin)
467 within_tolerance = false;
468
469 // gobble subsequent motion events to prevent "sticking"
470 // when scrolling is slow
471 gobble_motion_events( panning == 2
472 ? GDK_BUTTON2_MASK
473 : panning == 1
474 ? GDK_BUTTON1_MASK
475 : GDK_BUTTON3_MASK);
476
477 if (panning_cursor == 0) {
478 panning_cursor = 1;
479 auto window = dynamic_cast<Gtk::Window *>(_desktop->getCanvas()->get_root());
480 auto cursor = Gdk::Cursor::create(Glib::ustring("move"));
481 window->set_cursor(cursor);
482 }
483
484 auto const motion_w = event.pos;
485 auto const moved_w = motion_w - button_w;
486 _desktop->scroll_relative(moved_w);
487 ret = true;
488 }
489 } else if (zoom_rb) {
490 if (!checkDragMoved(event.pos)) {
491 return;
492 }
493
494 if (auto rubberband = Inkscape::Rubberband::get(_desktop); rubberband->isStarted()) {
495 auto const motion_w = event.pos;
496 auto const motion_dt = _desktop->w2d(motion_w);
497
498 rubberband->move(motion_dt);
499 } else {
500 // Start the box where the mouse was clicked, not where it is now
501 // because otherwise our box would be offset by the amount of tolerance.
502 auto const motion_w = xyp;
503 auto const motion_dt = _desktop->w2d(motion_w);
504
505 rubberband->start(_desktop, motion_dt);
506 }
507
508 if (zoom_rb == 2) {
509 gobble_motion_events(GDK_BUTTON2_MASK);
510 }
511 } else if (rotating) {
512 auto angle = compute_angle(event.pos);
513
514 double constexpr rotation_snap = 15.0;
515 double delta_angle = angle - start_angle;
516 if (event.modifiers & GDK_SHIFT_MASK &&
517 event.modifiers & GDK_CONTROL_MASK) {
518 delta_angle = 0.0;
519 } else if (event.modifiers & GDK_SHIFT_MASK) {
520 delta_angle = std::round(delta_angle / rotation_snap) * rotation_snap;
521 } else if (event.modifiers & GDK_CONTROL_MASK) {
522 // ?
523 } else if (event.modifiers & GDK_ALT_MASK) {
524 // Decimal raw angle
525 } else {
526 delta_angle = std::floor(delta_angle);
527 }
528 angle = start_angle + delta_angle;
529
530 _desktop->rotate_relative_keep_point(_desktop->w2d(Geom::Rect(_desktop->getCanvas()->get_area_world()).midpoint()),
531 Geom::rad_from_deg(angle - current_angle));
532 current_angle = angle;
533 ret = true;
534 }
535 },
536
537 [&] (ButtonReleaseEvent const &event) {
538 bool middle_mouse_zoom = prefs->getBool("/options/middlemousezoom/value");
539
540 xyp = {};
541
542 if (panning_cursor == 1) {
543 panning_cursor = 0;
544 dynamic_cast<Gtk::Window &>(*_desktop->getCanvas()->get_root()).set_cursor(_cursor);
545 }
546
547 if (event.button == 2 && rotating) {
548 rotating = false;
549 ungrabCanvasEvents();
550 }
551
552 if (middle_mouse_zoom && within_tolerance && (panning || zoom_rb)) {
553 zoom_rb = 0;
554
555 if (panning) {
556 panning = PANNING_NONE;
557 ungrabCanvasEvents();
558 }
559
560 auto const event_w = event.pos;
561 auto const event_dt = _desktop->w2d(event_w);
562
563 double const zoom_inc = prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10);
564
565 _desktop->zoom_relative(event_dt, (event.modifiers & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc);
566 ret = true;
567 } else if (panning == event.button) {
568 panning = PANNING_NONE;
569 ungrabCanvasEvents();
570
571 // in slow complex drawings, some of the motion events are lost;
572 // to make up for this, we scroll it once again to the button-up event coordinates
573 // (i.e. canvas will always get scrolled all the way to the mouse release point,
574 // even if few intermediate steps were visible)
575 auto const motion_w = event.pos;
576 auto const moved_w = motion_w - button_w;
577
578 _desktop->scroll_relative(moved_w);
579 ret = true;
580 } else if (zoom_rb == event.button) {
581 zoom_rb = 0;
582
584 Inkscape::Rubberband::get(_desktop)->stop();
585
586 if (b && !within_tolerance) {
587 _desktop->set_display_area(*b, 10);
588 }
589
590 ret = true;
591 }
592 },
593
594 [&] (KeyPressEvent const &event) {
595 double const acceleration = prefs->getDoubleLimited("/options/scrollingacceleration/value", 0, 0, 6);
596 int const key_scroll = prefs->getIntLimited("/options/keyscroll/value", 10, 0, 1000);
597
598 if (_acc_quick_preview.isTriggeredBy(event)) {
599 _desktop->quick_preview(true);
600 ret = true;
601 }
602 if (_acc_quick_zoom.isTriggeredBy(event)) {
603 _desktop->zoom_quick(true);
604 ret = true;
605 }
606 if (_acc_quick_pan.isTriggeredBy(event) && allow_panning) {
607 xyp = {};
608 within_tolerance = true;
609 panning = PANNING_SPACE;
610 message_context->set(Inkscape::INFORMATION_MESSAGE, _("<b>Space+mouse move</b> to pan canvas"));
611 ret = true;
612 }
613
614
615 switch (get_latin_keyval(event)) {
616 // GDK insists on stealing the tab keys for cycling widgets in the
617 // editing window. So we resteal them back and run our regular shortcut
618 // invoker on them. Tab is hardcoded. When actions are triggered by tab,
619 // we end up stealing events from GTK widgets.
620 case GDK_KEY_Tab:
621 if (mod_ctrl(event)) {
622 _desktop->getDesktopWidget()->advanceTab(1);
623 } else {
624 sp_selection_item_next(_desktop);
625 }
626 ret = true;
627 break;
628 case GDK_KEY_ISO_Left_Tab:
629 if (mod_ctrl(event)) {
630 _desktop->getDesktopWidget()->advanceTab(-1);
631 } else {
632 sp_selection_item_prev(_desktop);
633 }
634 ret = true;
635 break;
636
637 case GDK_KEY_W:
638 case GDK_KEY_w:
639#ifndef __APPLE__
640 case GDK_KEY_F4:
641#endif
642 // Close tab
643 if (mod_ctrl_only(event)) {
645 app->destroyDesktop(_desktop, true); // Keep inkscape alive!
646 ret = true;
647 }
648 break;
649
650 case GDK_KEY_Left: // Ctrl Left
651 case GDK_KEY_KP_Left:
652 case GDK_KEY_KP_4:
653 if (mod_ctrl_only(event)) {
654 int i = std::floor(key_scroll * accelerate_scroll(event, acceleration));
655
656 gobble_key_events(get_latin_keyval(event), GDK_CONTROL_MASK);
657 _desktop->scroll_relative(Geom::Point(i, 0));
658 } else if (!_keyboardMove(event, Geom::Point(-1, 0))) {
660 }
661 ret = true;
662 break;
663
664 case GDK_KEY_Up: // Ctrl Up
665 case GDK_KEY_KP_Up:
666 case GDK_KEY_KP_8:
667 if (mod_ctrl_only(event)) {
668 int i = std::floor(key_scroll * accelerate_scroll(event, acceleration));
669
670 gobble_key_events(get_latin_keyval(event), GDK_CONTROL_MASK);
671 _desktop->scroll_relative(Geom::Point(0, i));
672 } else if (!_keyboardMove(event, Geom::Point(0, -_desktop->yaxisdir()))) {
674 }
675 ret = true;
676 break;
677
678 case GDK_KEY_Right: // Ctrl Right
679 case GDK_KEY_KP_Right:
680 case GDK_KEY_KP_6:
681 if (mod_ctrl_only(event)) {
682 int i = std::floor(key_scroll * accelerate_scroll(event, acceleration));
683
684 gobble_key_events(get_latin_keyval(event), GDK_CONTROL_MASK);
685 _desktop->scroll_relative(Geom::Point(-i, 0));
686 } else if (!_keyboardMove(event, Geom::Point(1, 0))) {
688 }
689 ret = true;
690 break;
691
692 case GDK_KEY_Down: // Ctrl Down
693 case GDK_KEY_KP_Down:
694 case GDK_KEY_KP_2:
695 if (mod_ctrl_only(event)) {
696 int i = std::floor(key_scroll * accelerate_scroll(event, acceleration));
697
698 gobble_key_events(get_latin_keyval(event), GDK_CONTROL_MASK);
699 _desktop->scroll_relative(Geom::Point(0, -i));
700 } else if (!_keyboardMove(event, Geom::Point(0, _desktop->yaxisdir()))) {
702 }
703 ret = true;
704 break;
705
706 case GDK_KEY_Menu:
707 menu_popup(event);
708 ret = true;
709 break;
710
711 case GDK_KEY_F10:
712 if (mod_shift_only(event)) {
713 menu_popup(event);
714 ret = true;
715 }
716 break;
717
718 case GDK_KEY_r:
719 case GDK_KEY_R:
720 if (mod_alt_only(event)) {
721 _desktop->rotate_grab_focus();
722 ret = false; // don't steal key events, so keyboard shortcut works, if defined
723 }
724 break;
725
726 case GDK_KEY_z:
727 case GDK_KEY_Z:
728 if (mod_alt_only(event)) {
729 _desktop->zoom_grab_focus();
730 ret = false; // don't steal key events, so keyboard shortcut works, if defined
731 }
732 break;
733
734 default:
735 break;
736 }
737 },
738
739 [&] (KeyReleaseEvent const &event) {
740 // Stop panning on any key release
741 if (is_space_panning()) {
742 message_context->clear();
743 }
744
745 if (panning) {
746 panning = PANNING_NONE;
747 xyp = {};
748
749 ungrabCanvasEvents();
750 }
751
752 if (panning_cursor == 1) {
753 panning_cursor = 0;
754 dynamic_cast<Gtk::Window &>(*_desktop->getCanvas()->get_root()).set_cursor(_cursor);
755 }
756
757 if (_acc_quick_preview.isTriggeredBy(event)) {
758 _desktop->quick_preview(false);
759 ret = true;
760 }
761 if (_acc_quick_zoom.isTriggeredBy(event) && _desktop->quick_zoomed()) {
762 _desktop->zoom_quick(false);
763 ret = true;
764 }
765
766 switch (get_latin_keyval(event)) {
767 case GDK_KEY_space:
768 if (within_tolerance) {
769 // Space was pressed, but not panned
770 sp_toggle_selector(_desktop);
771
772 // Be careful, sp_toggle_selector will delete ourselves.
773 // Thus, make sure we return immediately.
774 ret = true;
775 return;
776 }
777 break;
778
779 default:
780 break;
781 }
782 },
783
784 [&] (ScrollEvent const &event) {
785 // Factor of 2 for legacy reasons: previously we did two wheel_scrolls for each mouse scroll.
786 auto get_scroll_inc = [&] { return prefs->getIntLimited("/options/wheelscroll/value", 40, 0, 1000) * 2; };
787
788 using Modifiers::Type;
790 auto const action = Modifiers::Modifier::which(Triggers::CANVAS | Triggers::SCROLL, event.modifiers);
791
792 if (action == Type::CANVAS_ROTATE) {
793 // Rotate by the amount vertically scrolled.
794
795 if (_desktop->get_rotation_lock()) {
796 return;
797 }
798
799 double const delta_y = event.delta.y();
800 if (delta_y == 0) {
801 return;
802 }
803
804 double angle;
805 if (event.unit == Gdk::ScrollUnit::WHEEL) {
806 double rotate_inc = prefs->getDoubleLimited("/options/rotateincrement/value", 15, 1, 90, "°");
807 rotate_inc = Geom::rad_from_deg(rotate_inc);
808 angle = delta_y * rotate_inc;
809 } else {
810 angle = delta_y * (Geom::rad_from_deg(15) / 10.0); // logical pixels to radians, arbitrary
811 angle = std::clamp(angle, -1.0, 1.0); // values > 1 result in excessive rotating
812 }
813
814 _desktop->rotate_relative_keep_point(_desktop->point(), -angle);
815 ret = true;
816
817 } else if (action == Type::CANVAS_PAN_X) {
818 // Scroll horizontally by the amount vertically scrolled.
819
820 double delta_y = event.delta.y();
821 if (delta_y == 0) {
822 return;
823 }
824
825 if (event.unit == Gdk::ScrollUnit::WHEEL) {
826 delta_y *= get_scroll_inc();
827 } else {
828 delta_y *= 8; // subjective factor
829 }
830
831 _desktop->scroll_relative({-delta_y, 0});
832 ret = true;
833
834 } else if (action == Type::CANVAS_ZOOM) {
835 // Zoom by the amount vertically scrolled.
836
837 double const delta_y = event.delta.y();
838 if (delta_y == 0) {
839 return;
840 }
841
842 double scale;
843 if (event.unit == Gdk::ScrollUnit::WHEEL) {
844 double const zoom_inc = prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10);
845 scale = std::pow(zoom_inc, delta_y);
846 } else {
847 scale = delta_y / 10; // logical pixels to scale, arbitrary
848 scale = std::clamp(scale, -1.0, 1.0); // values > 1 result in excessive zooming
849 scale = std::pow(M_SQRT2, scale);
850 }
851
852 _desktop->zoom_relative(_desktop->point(), 1.0 / scale);
853 ret = true;
854
855 } else if (action == Type::CANVAS_PAN_Y) {
856 // Scroll both horizontally and vertically.
857
858 auto delta = event.delta;
859 if (delta == Geom::Point(0, 0)) {
860 return;
861 }
862
863 if (event.unit == Gdk::ScrollUnit::WHEEL) {
864 delta *= get_scroll_inc();
865 } else {
866 delta *= 8; // subjective factor
867 }
868
869 _desktop->scroll_relative(-delta);
870 ret = true;
871
872 } else {
873 g_warning("unhandled scroll event with scroll.state=0x%x", event.modifiers);
874 }
875 },
876
877 [&] (CanvasEvent const &event) {}
878 );
879
880 return ret;
881}
882
886void ToolBase::set_on_buttons(CanvasEvent const &event)
887{
888 inspect_event(event,
889 [&] (ButtonPressEvent const &event) {
890 if (event.num_press != 1) {
891 return;
892 }
893 switch (event.button) {
894 case 1:
895 _button1on = true;
896 break;
897 case 2:
898 _button2on = true;
899 break;
900 case 3:
901 _button3on = true;
902 break;
903 default:
904 break;
905 }
906 },
907 [&] (ButtonReleaseEvent const &event) {
908 switch (event.button) {
909 case 1:
910 _button1on = false;
911 break;
912 case 2:
913 _button2on = false;
914 break;
915 case 3:
916 _button3on = false;
917 break;
918 default:
919 break;
920 }
921 },
922 [&] (MotionEvent const &event) {
923 _button1on = event.modifiers & (unsigned)Gdk::ModifierType::BUTTON1_MASK;
924 _button2on = event.modifiers & (unsigned)Gdk::ModifierType::BUTTON2_MASK;
925 _button3on = event.modifiers & (unsigned)Gdk::ModifierType::BUTTON3_MASK;
926 },
927 [&] (CanvasEvent const &event) {}
928 );
929}
930
931bool ToolBase::are_buttons_1_and_3_on() const
932{
933 return _button1on && _button3on;
934}
935
936bool ToolBase::are_buttons_1_and_3_on(CanvasEvent const &event)
937{
938 set_on_buttons(event);
939 return are_buttons_1_and_3_on();
940}
941
948bool ToolBase::item_handler(SPItem *item, CanvasEvent const &event)
949{
950 if (event.type() != EventType::BUTTON_PRESS) {
951 return false;
952 }
953
954 auto &button = static_cast<ButtonPressEvent const &>(event);
955
956 if (!are_buttons_1_and_3_on(event) && button.button == 3 &&
957 !(button.modifiers & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) {
958 menu_popup(event);
959 return true;
960 } else if (button.button == 1 && shape_editor && shape_editor->has_knotholder()) {
961 // This allows users to select an arbitary position in a pattern to edit on canvas.
962 auto &knotholder = shape_editor->knotholder;
963 auto point = button.pos;
964 if (_desktop->getItemAtPoint(point, true) == knotholder->getItem()) {
965 return knotholder->set_item_clickpos(_desktop->w2d(point) * _desktop->dt2doc());
966 }
967 }
968
969 return false;
970}
971
975bool ToolBase::sp_event_context_knot_mouseover() const
976{
977 if (shape_editor) {
978 return shape_editor->knot_mouseover();
979 }
980
981 return false;
982}
983
987void ToolBase::enableSelectionCue(bool enable)
988{
989 if (enable) {
990 if (!_selcue) {
991 _selcue = new Inkscape::SelCue(_desktop);
992 }
993 } else {
994 delete _selcue;
995 _selcue = nullptr;
996 }
997}
998
999/*
1000 * Enables/disables the ToolBase's GrDrag.
1001 */
1002void ToolBase::enableGrDrag(bool enable)
1003{
1004 if (enable) {
1005 if (!_grdrag) {
1006 _grdrag = new GrDrag(_desktop);
1007 }
1008 } else {
1009 if (_grdrag) {
1010 delete _grdrag;
1011 _grdrag = nullptr;
1012 }
1013 }
1014}
1015
1019bool ToolBase::deleteSelectedDrag(bool just_one)
1020{
1021 if (_grdrag && !_grdrag->selected.empty()) {
1022 _grdrag->deleteSelected(just_one);
1023 return true;
1024 }
1025 return false;
1026}
1027
1031bool ToolBase::hasGradientDrag() const
1032{
1033 return _grdrag && _grdrag->isNonEmpty();
1034}
1035
1039void ToolBase::grabCanvasEvents(EventMask mask)
1040{
1041 _desktop->getCanvasCatchall()->grab(mask); // Cursor is null.
1042}
1043
1047void ToolBase::ungrabCanvasEvents()
1048{
1049 _desktop->getSnapIndicator()->remove_snaptarget();
1050 _desktop->getCanvasCatchall()->ungrab();
1051}
1052
1062void ToolBase::set_high_motion_precision(bool high_precision)
1063{
1064 // Todo: High-precision mode must now be implemented on a tool-by-tool basis.
1065 // This function stub allows us to see where this is required.
1066}
1067
1068void ToolBase::setup_for_drag_start(ButtonPressEvent const &ev)
1069{
1070 saveDragOrigin(ev.pos);
1071 item_to_select = sp_event_context_find_item(_desktop, ev.pos, ev.modifiers & GDK_ALT_MASK, true);
1072}
1073
1074void ToolBase::saveDragOrigin(Geom::Point const &pos)
1075{
1076 xyp = pos.floor();
1077 within_tolerance = true;
1078}
1079
1084bool ToolBase::checkDragMoved(Geom::Point const &pos)
1085{
1086 if (within_tolerance) {
1087 if (Geom::LInfty(pos.floor() - xyp) < tolerance) {
1088 // Do not drag if within tolerance from origin.
1089 return false;
1090 }
1091 // Mark drag as started.
1092 within_tolerance = false;
1093 }
1094 // Always return true once the drag has started.
1095 return true;
1096}
1097
1101void sp_event_context_read(ToolBase *tool, char const *key)
1102{
1103 if (!tool || !key) return;
1105 Inkscape::Preferences::Entry val = prefs->getEntry(tool->getPrefsPath() + '/' + key);
1106 tool->set(val);
1107}
1108
1112void ToolBase::_filterEventForSnapping(SPItem *item, CanvasEvent const &event, DelayedSnapEvent::Origin origin)
1113{
1114 inspect_event(event,
1115 [&] (MotionEvent const &event) {
1116 snap_delay_handler(item, nullptr, event, origin);
1117 },
1118 [&] (ButtonReleaseEvent const &event) {
1119 // If we have any pending snapping action, then invoke it now
1120 process_delayed_snap_event();
1121 },
1122 [&] (ButtonPressEvent const &event) {
1123 // Snapping will be on hold if we're moving the mouse at high speeds. When starting
1124 // drawing a new shape we really should snap though.
1125 _desktop->getNamedView()->snap_manager.snapprefs.setSnapPostponedGlobally(false);
1126 },
1127 [&] (CanvasEvent const &event) {}
1128 );
1129}
1130
1134bool ToolBase::start_root_handler(CanvasEvent const &event)
1135{
1136 if constexpr (DEBUG_EVENTS) {
1137 dump_event(event, "ToolBase::start_root_handler");
1138 }
1139
1140 if (!_uses_snap) {
1141 return tool_root_handler(event);
1142 }
1143
1144 _filterEventForSnapping(nullptr, event, DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER);
1145
1146 return tool_root_handler(event);
1147}
1148
1152bool ToolBase::tool_root_handler(CanvasEvent const &event)
1153{
1154 if constexpr (DEBUG_EVENTS) {
1155 dump_event(event, "ToolBase::tool_root_handler");
1156 }
1157
1158 // Just set the on buttons for now. later, behave as intended.
1159 set_on_buttons(event);
1160
1161 // refresh coordinates UI here while 'event' is still valid
1162 set_event_location(_desktop, event);
1163
1164 // Panning has priority over tool-specific event handling
1165 if (is_panning()) {
1166 return ToolBase::root_handler(event);
1167 } else {
1168 return root_handler(event);
1169 }
1170}
1171
1175bool ToolBase::start_item_handler(SPItem *item, CanvasEvent const &event)
1176{
1177 if (!_uses_snap) {
1178 return virtual_item_handler(item, event);
1179 }
1180
1181 _filterEventForSnapping(item, event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER);
1182
1183 return virtual_item_handler(item, event);
1184}
1185
1186bool ToolBase::virtual_item_handler(SPItem *item, CanvasEvent const &event)
1187{
1188 bool ret = false;
1189
1190 // Just set the on buttons for now. later, behave as intended.
1191 set_on_buttons(event);
1192
1193 // Panning has priority over tool-specific event handling
1194 if (is_panning()) {
1195 ret = ToolBase::item_handler(item, event);
1196 } else {
1197 ret = item_handler(item, event);
1198 }
1199
1200 if (!ret) {
1201 ret = tool_root_handler(event);
1202 } else {
1203 set_event_location(_desktop, event);
1204 }
1205
1206 return ret;
1207}
1208
1213{
1214 if (event.type() != EventType::MOTION) {
1215 return;
1216 }
1217
1218 auto const button_w = static_cast<MotionEvent const &>(event).pos;
1219 auto const button_dt = desktop->w2d(button_w);
1220 desktop->set_coordinate_status(button_dt);
1221}
1222
1223//-------------------------------------------------------------------
1227void ToolBase::menu_popup(CanvasEvent const &event, SPObject *obj)
1228{
1229 if (!obj) {
1230 if (event.type() == EventType::KEY_PRESS && !_desktop->getSelection()->isEmpty()) {
1231 obj = _desktop->getSelection()->items().front();
1232 } else if (event.type() == EventType::BUTTON_PRESS) {
1233 // Using the same function call used on left click in sp_select_context_item_handler() to get top of z-order
1234 // fixme: sp_canvas_arena should set the top z-order object as arena->active
1235 auto p = static_cast<ButtonPressEvent const &>(event).pos;
1236 obj = sp_event_context_find_item(_desktop, p, false, false);
1237 }
1238 }
1239
1240 auto const popup = [&] (std::optional<Geom::Point> const &pos) {
1241 // Get a list of items under the cursor, used for unhiding and unlocking.
1242 auto point_win = _desktop->point() * _desktop->d2w();
1243 auto items_under_cursor = _desktop->getItemsAtPoints({point_win}, true, false, 0, false);
1244 auto menu = Gtk::make_managed<ContextMenu>(_desktop, obj, items_under_cursor);
1245 _desktop->getDesktopWidget()->get_canvas_grid()->setPopover(menu);
1246 UI::popup_at(*menu, *_desktop->getCanvas(), pos);
1247 };
1248
1249 inspect_event(event,
1250 [&] (ButtonPressEvent const &event) {
1251 popup(event.orig_pos);
1252 },
1253 [&] (KeyPressEvent const &event) {
1254 popup(event.orig_pos);
1255 },
1256 [&] (CanvasEvent const &event) {}
1257 );
1258}
1259
1264 KeyEvent const &event, char const *ctrl_tip, char const *shift_tip,
1265 char const *alt_tip)
1266{
1267 auto const keyval = get_latin_keyval(event);
1268
1269 bool ctrl = ctrl_tip && (mod_ctrl(event) || keyval == GDK_KEY_Control_L || keyval == GDK_KEY_Control_R);
1270 bool shift = shift_tip && (mod_shift(event) || keyval == GDK_KEY_Shift_L || keyval == GDK_KEY_Shift_R);
1271 bool alt = alt_tip && (mod_alt(event) || keyval == GDK_KEY_Alt_L || keyval == GDK_KEY_Alt_R
1272 || keyval == GDK_KEY_Meta_L || keyval == GDK_KEY_Meta_R);
1273
1274 char *tip = g_strdup_printf("%s%s%s%s%s", ctrl ? ctrl_tip : "",
1275 ctrl && (shift || alt) ? "; " : "",
1276 shift ? shift_tip : "",
1277 (ctrl || shift) && alt ? "; " : "",
1278 alt ? alt_tip : "");
1279
1280 if (std::strlen(tip) > 0) {
1281 message_context->flash(INFORMATION_MESSAGE, tip);
1282 }
1283
1284 g_free(tip);
1285}
1286
1292{
1293 GdkKeymapKey* keys;
1294 gint n_keys;
1295
1296 latin_keys_group_valid = FALSE;
1297 latin_keys_groups.clear();
1298
1299 if (gdk_display_map_keyval(gdk_display_get_default(), GDK_KEY_a, &keys, &n_keys)) {
1300 for (int i = 0; i < n_keys; i++) {
1301 latin_keys_groups.insert(keys[i].group);
1302
1303 if (!latin_keys_group_valid || keys[i].group < latin_keys_group) {
1304 latin_keys_group = keys[i].group;
1306 }
1307 }
1308 g_free(keys);
1309 }
1310}
1311
1316{
1317 auto const keyboard = Gdk::Display::get_default()->get_default_seat()->get_keyboard();
1318 g_assert(keyboard);
1319 keyboard->signal_changed().connect(&update_latin_keys_group);
1321}
1322
1323unsigned get_latin_keyval_impl(unsigned const event_keyval, unsigned const event_keycode,
1324 GdkModifierType const event_state, unsigned const event_group,
1325 unsigned *consumed_modifiers)
1326{
1327 auto keyval = 0u;
1328 GdkModifierType modifiers;
1329
1330 auto group = latin_keys_group_valid ? latin_keys_group : event_group;
1331 if (latin_keys_groups.count(event_group)) {
1332 // Keyboard group is a latin layout, so just use it.
1333 group = event_group;
1334 }
1335 gdk_display_translate_key(gdk_display_get_default(),
1336 event_keycode,
1337 event_state,
1338 group,
1339 &keyval,
1340 nullptr,
1341 nullptr,
1342 &modifiers);
1343 if (consumed_modifiers) {
1344 *consumed_modifiers = modifiers;
1345 }
1346#ifndef __APPLE__
1347 // on macOS <option> key inserts special characters and below condition fires all the time
1348 if (keyval != event_keyval) {
1349 std::cerr << "get_latin_keyval: OH OH OH keyval did change! "
1350 << " keyval: " << keyval << " (" << (char)keyval << ")"
1351 << " event_keyval: " << event_keyval << "(" << (char)event_keyval << ")" << std::endl;
1352 }
1353#endif
1354
1355 return keyval;
1356}
1357
1364unsigned get_latin_keyval(GtkEventControllerKey const * const controller,
1365 unsigned const keyval, unsigned const keycode, GdkModifierType const state,
1366 unsigned *consumed_modifiers /*= nullptr*/)
1367{
1368 auto const group = gtk_event_controller_key_get_group(const_cast<GtkEventControllerKey *>(controller));
1369 return get_latin_keyval_impl(keyval, keycode, state, group, consumed_modifiers);
1370}
1371
1372unsigned get_latin_keyval(Gtk::EventControllerKey const &controller,
1373 unsigned keyval, unsigned keycode, Gdk::ModifierType state,
1374 unsigned *consumed_modifiers /*= nullptr*/)
1375{
1376 auto const group = controller.get_group();
1377 return get_latin_keyval_impl(keyval, keycode, static_cast<GdkModifierType>(state), group, consumed_modifiers);
1378}
1379
1380unsigned get_latin_keyval(KeyEvent const &event, unsigned *consumed_modifiers)
1381{
1382 return get_latin_keyval_impl(event.keyval, event.keycode,
1383 static_cast<GdkModifierType>(event.modifiers),
1384 event.group, consumed_modifiers);
1385}
1386
1394 bool select_under, bool into_groups)
1395{
1396 SPItem *item = nullptr;
1397
1398 if (select_under) {
1399 auto tmp = desktop->getSelection()->items();
1400 std::vector<SPItem *> vec(tmp.begin(), tmp.end());
1401 SPItem *selected_at_point = desktop->getItemFromListAtPointBottom(vec, p);
1402 item = desktop->getItemAtPoint(p, into_groups, selected_at_point);
1403 if (!item) { // we may have reached bottom, flip over to the top
1404 item = desktop->getItemAtPoint(p, into_groups, nullptr);
1405 }
1406 } else {
1407 item = desktop->getItemAtPoint(p, into_groups, nullptr);
1408 }
1409
1410 return item;
1411}
1412
1419{
1420 std::vector<SPItem*> temp;
1421 temp.push_back(item);
1422 SPItem *item_at_point = desktop->getItemFromListAtPointBottom(temp, p);
1423 return item_at_point;
1424}
1425
1430
1441void ToolBase::snap_delay_handler(gpointer item, gpointer item2, MotionEvent const &event, DelayedSnapEvent::Origin origin)
1442{
1443 static uint32_t prev_time;
1444 static std::optional<Geom::Point> prev_pos;
1445
1446 if (!_uses_snap || _dse_callback_in_process) {
1447 return;
1448 }
1449
1450 // Snapping occurs when dragging with the left mouse button down, or when hovering e.g. in the pen tool with left mouse button up
1451 bool const c1 = event.modifiers & GDK_BUTTON2_MASK; // We shouldn't hold back any events when other mouse buttons have been
1452 bool const c2 = event.modifiers & GDK_BUTTON3_MASK; // pressed, e.g. when scrolling with the middle mouse button; if we do then
1453 // Inkscape will get stuck in an unresponsive state
1454 bool const c3 = dynamic_cast<CalligraphicTool*>(this);
1455 // The snap delay will repeat the last motion event, which will lead to
1456 // erroneous points in the calligraphy context. And because we don't snap
1457 // in this context, we might just as well disable the snap delay all together
1458 bool const c4 = is_panning(); // Don't snap while panning
1459
1460 if (c1 || c2 || c3 || c4) {
1461 // Make sure that we don't send any pending snap events to a context if we know in advance
1462 // that we're not going to snap any way (e.g. while scrolling with middle mouse button)
1463 // Any motion event might affect the state of the context, leading to unexpected behavior
1464 discard_delayed_snap_event();
1465 } else if (getDesktop() && getDesktop()->getNamedView()->snap_manager.snapprefs.getSnapEnabledGlobally()) {
1466 // Snap when speed drops below e.g. 0.02 px/msec, or when no motion events have occurred for some period.
1467 // i.e. snap when we're at stand still. A speed threshold enforces snapping for tablets, which might never
1468 // be fully at stand still and might keep spitting out motion events.
1469 getDesktop()->getNamedView()->snap_manager.snapprefs.setSnapPostponedGlobally(true); // put snapping on hold
1470
1471 auto event_pos = event.pos;
1472 uint32_t event_t = event.time;
1473
1474 if (prev_pos) {
1475 auto dist = Geom::L2(event_pos - *prev_pos);
1476 uint32_t delta_t = event_t - prev_time;
1477 double speed = delta_t > 0 ? dist / delta_t : 1000;
1478 //std::cout << "Mouse speed = " << speed << " px/msec " << std::endl;
1479 if (speed > 0.02) { // Jitter threshold, might be needed for tablets
1480 // We're moving fast, so postpone any snapping until the next GDK_MOTION_NOTIFY event. We
1481 // will keep on postponing the snapping as long as the speed is high.
1482 // We must snap at some point in time though, so set a watchdog timer at some time from
1483 // now, just in case there's no future motion event that drops under the speed limit (when
1484 // stopping abruptly)
1485 _dse.emplace(this, item, item2, event, origin);
1486 _schedule_delayed_snap_event(); // watchdog is reset, i.e. pushed forward in time
1487 // If the watchdog expires before a new motion event is received, we will snap (as explained
1488 // above). This means however that when the timer is too short, we will always snap and that the
1489 // speed threshold is ineffective. In the extreme case the delay is set to zero, and snapping will
1490 // be immediate, as it used to be in the old days ;-).
1491 } else { // Speed is very low, so we're virtually at stand still
1492 // But if we're really standing still, then we should snap now. We could use some low-pass filtering,
1493 // otherwise snapping occurs for each jitter movement. For this filtering we'll leave the watchdog to expire,
1494 // snap, and set a new watchdog again.
1495 if (!_dse) { // no watchdog has been set
1496 // it might have already expired, so we'll set a new one; the snapping frequency will be limited this way
1497 _dse.emplace(this, item, item2, event, origin);
1498 _schedule_delayed_snap_event();
1499 } // else: watchdog has been set before and we'll wait for it to expire
1500 }
1501 } else {
1502 // This is the first GDK_MOTION_NOTIFY event, so postpone snapping and set the watchdog
1503 g_assert(!_dse);
1504 _dse.emplace(this, item, item2, event, origin);
1505 _schedule_delayed_snap_event();
1506 }
1507
1508 prev_pos = event_pos;
1509 prev_time = event_t;
1510 }
1511}
1512
1517void ToolBase::process_delayed_snap_event()
1518{
1519 // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated
1520
1521 _dse_timeout_conn.disconnect();
1522
1523 if (!_dse) {
1524 // This might occur when this method is called directly, i.e. not through the timer
1525 // E.g. on GDK_BUTTON_RELEASE in start_root_handler()
1526 return;
1527 }
1528
1529 auto dt = getDesktop();
1530 if (!dt) {
1531 _dse.reset();
1532 return;
1533 }
1534
1535 _dse_callback_in_process = true;
1536 dt->getNamedView()->snap_manager.snapprefs.setSnapPostponedGlobally(false);
1537
1538 // Depending on where the delayed snap event originated from, we will inject it back at its origin.
1539 // The switch below takes care of that and prepares the relevant parameters.
1540 switch (_dse->getOrigin()) {
1541 case DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER:
1542 tool_root_handler(_dse->getEvent());
1543 break;
1544 case DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER: {
1545 auto item = reinterpret_cast<SPItem*>(_dse->getItem());
1546 if (item) {
1547 virtual_item_handler(item, _dse->getEvent());
1548 }
1549 break;
1550 }
1551 case DelayedSnapEvent::KNOT_HANDLER: {
1552 auto knot = reinterpret_cast<SPKnot*>(_dse->getItem2());
1554 if (knot) {
1555 bool was_grabbed = knot->is_grabbed();
1556 knot->setFlag(SP_KNOT_GRABBED, true); // Must be grabbed for Inkscape::SelTrans::handleRequest() to pass
1557 knot->handler_request_position(_dse->getEvent());
1558 knot->setFlag(SP_KNOT_GRABBED, was_grabbed);
1559 }
1560 break;
1561 }
1562 case DelayedSnapEvent::CONTROL_POINT_HANDLER: {
1564 auto point = reinterpret_cast<ControlPoint*>(_dse->getItem2());
1565 if (point) {
1566 if (point->position().isFinite() && dt == point->_desktop) {
1567 point->_eventHandler(this, _dse->getEvent());
1568 } else {
1569 //workaround:
1570 //[Bug 781893] Crash after moving a Bezier node after Knot path effect?
1571 // --> at some time, some point with X = 0 and Y = nan (not a number) is created ...
1572 // even so, the desktop pointer is invalid and equal to 0xff
1573 g_warning("encountered non-finite point when evaluating snapping callback");
1574 }
1575 }
1576 break;
1577 }
1578 case DelayedSnapEvent::GUIDE_HANDLER: {
1579 auto guideline = reinterpret_cast<CanvasItemGuideLine*>(_dse->getItem());
1580 auto guide = reinterpret_cast<SPGuide*> (_dse->getItem2());
1581 if (guideline && guide) {
1582 sp_dt_guide_event(_dse->getEvent(), guideline, guide);
1583 }
1584 break;
1585 }
1586 case DelayedSnapEvent::GUIDE_HRULER:
1587 case DelayedSnapEvent::GUIDE_VRULER: {
1588 auto canvas_grid = reinterpret_cast<Widget::CanvasGrid*>(_dse->getItem());
1589 bool horiz = _dse->getOrigin() == DelayedSnapEvent::GUIDE_HRULER;
1590 canvas_grid->rulerMotion(_dse->getEvent(), horiz);
1591 break;
1592 }
1593 default:
1594 g_warning("Origin of snap-delay event has not been defined!");
1595 break;
1596 }
1597
1598 _dse_callback_in_process = false;
1599 _dse.reset();
1600}
1601
1605void ToolBase::discard_delayed_snap_event()
1606{
1607 _desktop->getNamedView()->snap_manager.snapprefs.setSnapPostponedGlobally(false);
1608 _dse.reset();
1609}
1610
1616void ToolBase::_schedule_delayed_snap_event()
1617{
1618 // Get timeout value in seconds.
1620 double value = prefs->getDoubleLimited("/options/snapdelay/value", 0, 0, 1000);
1621
1622 // If the timeout value is too large, we assume it comes from an old preferences file
1623 // where it used to be measured in milliseconds, and convert it appropriately.
1624 if (value > 1.0) {
1625 value /= 1000.0; // convert milliseconds to seconds
1626 }
1627
1628 _dse_timeout_conn = Glib::signal_timeout().connect([this] {
1629 process_delayed_snap_event();
1630 return false; // one-shot
1631 }, value * 1000.0);
1632}
1633
1634void ToolBase::set_last_active_tool(Glib::ustring last_tool) {
1635 _last_active_tool = std::move(last_tool);
1636}
1637
1638const Glib::ustring& ToolBase::get_last_active_tool() const {
1639 return _last_active_tool;
1640}
1641
1642} // namespace Tools
1643} // namespace UI
1644} // namespace Inkscape
1645
1646/*
1647 Local Variables:
1648 mode:c++
1649 c-file-style:"stroustrup"
1650 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1651 indent-tabs-mode:nil
1652 fill-column:99
1653 End:
1654 */
1655// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
Glib::ustring get_active_tool(InkscapeWindow *win)
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
Rewrite of code originally in desktop-widget.cpp.
Inkscape canvas widget.
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
CPoint midpoint() const
Get the point in the geometric center of the rectangle.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
IntPoint floor() const
Round coordinates downwards.
Definition point.h:206
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotate inverse() const
Definition transforms.h:209
Translation by a vector.
Definition transforms.h:115
This is the root class of the gradient dragging machinery.
static InkscapeApplication * instance()
Singleton instance.
A mask representing a subset of EventTypes.
Definition enums.h:38
SPGroup * currentLayer() const
Returns current top layer.
A convenience class for working with MessageStacks.
void flash(MessageType type, char const *message)
pushes a message onto the stack for a brief period of time without disturbing our "current" message
static Type which(Trigger trigger, int button_state)
Given a Trigger, find which modifier is active (category lookup)
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
Data type representing a typeless value of a preference.
Glib::ustring getEntryName() const
Get the last component of the preference's path.
bool getBool(bool def=false) const
Interpret the preference as a Boolean value.
static std::unique_ptr< PreferencesObserver > create(Glib::ustring path, std::function< void(const Preferences::Entry &new_value)> callback)
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
Entry const getEntry(Glib::ustring const &pref_path)
Retrieve a preference entry without specifying its type.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
static Rubberband * get(SPDesktop *desktop)
Geom::OptRect getRectangle() const
bool isStarted() const
Definition rubberband.h:48
void setChangePage(bool option)
Definition selection.h:257
void setChangeLayer(bool option)
Decide if the selection changing should change the layer and page selection too.
Definition selection.h:256
bool invoke_action(Gtk::AccelKey const &shortcut)
Trigger action from a shortcut.
static Shortcuts & getInstance(bool init=true)
Definition shortcuts.h:66
Draggable point, the workhorse of on-canvas editing.
static Glib::ustring virtual bool _eventHandler(Inkscape::UI::Tools::ToolBase *event_context, CanvasEvent const &event)
std::unique_ptr< KnotHolder > knotholder
std::unique_ptr< MotionEvent > _event
Definition tool-base.h:87
DelayedSnapEvent(ToolBase *tool, gpointer item, gpointer item2, MotionEvent const &event, DelayedSnapEvent::Origin origin)
Definition tool-base.cpp:91
Base class for Event processors.
Definition tool-base.h:107
Glib::RefPtr< Gdk::Cursor > get_cursor(Gtk::Widget &widget, std::string const &filename) const
Returns the Gdk Cursor for the given filename.
void set_cursor(std::string filename)
Sets the current cursor to the given filename.
void use_tool_cursor()
Uses the saved cursor, based on the saved filename.
SPGroup * currentLayer() const
std::string const & getPrefsPath() const
Definition tool-base.h:120
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
Glib::RefPtr< Gdk::Cursor > _cursor
Definition tool-base.h:141
void use_cursor(Glib::RefPtr< Gdk::Cursor > cursor)
Set the cursor to this specific one, don't remember it.
std::unique_ptr< Preferences::PreferencesObserver > pref_observer
Definition tool-base.h:133
virtual bool root_handler(CanvasEvent const &event)
ToolBase(SPDesktop *desktop, std::string &&prefs_path, std::string &&cursor_filename, bool uses_snap=true)
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
virtual void set(Preferences::Entry const &val)
Called by our pref_observer if a preference has been changed.
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
bool _keyboardMove(KeyEvent const &event, Geom::Point const &dir)
Moves the selected points along the supplied unit vector according to the modifier state of the suppl...
A Gtk::Grid widget that contains rulers, scrollbars, buttons, and, of course, the canvas.
Definition canvas-grid.h:70
void rulerMotion(MotionEvent const &event, bool horiz)
Geom::IntPoint get_dimensions() const
Definition canvas.cpp:1536
const Geom::Affine & get_affine() const
Definition canvas.h:86
const Geom::IntPoint & get_pos() const
Definition canvas.h:85
const Geom::Affine & get_geom_affine() const
Definition canvas.cpp:1595
To do: update description of desktop.
Definition desktop.h:149
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
double current_zoom() const
Definition desktop.h:335
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
bool waiting_cursor
Definition desktop.h:231
SPItem * getItemAtPoint(Geom::Point const &p, bool into_groups, SPItem *upto=nullptr) const
Definition desktop.cpp:352
SPItem * getItemFromListAtPointBottom(std::vector< SPItem * > const &list, Geom::Point const &p) const
Definition desktop.cpp:343
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Geom::Rotate const & current_rotation() const
Definition desktop.h:366
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::LayerManager & layerManager()
Definition desktop.h:287
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Base class for visual SVG elements.
Definition sp-item.h:109
Desktop-bound visual control object.
Definition knot.h:51
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
bool sp_dt_guide_event(Inkscape::CanvasEvent const &event, Inkscape::CanvasItemGuideLine *guide_item, SPGuide *guide)
std::optional< Color > sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill)
double sp_desktop_get_opacity_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill)
A class to hold:
Editable view implementation.
unsigned int guint32
static Glib::ustring const prefs_path
SPItem * item
@ SP_KNOT_GRABBED
Definition knot-enums.h:33
void check_if_knot_deleted(void *knot)
Definition knot-ptr.cpp:30
TODO: insert short description here.
Declarations for SPKnot: Desktop-bound visual control object.
void shift(T &a, T &b, T const &c)
Interface for locally managing a current status message.
double angle(std::vector< Point > const &A)
double atan2(Point const &p)
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Coord LInfty(Point const &p)
Type
This anonymous enum is used to provide a list of the Shifts.
Definition modifiers.h:56
static void set_event_location(SPDesktop *desktop, CanvasEvent const &event)
Shows coordinates on status bar.
void init_latin_keys_group()
Initialize Latin keys group handling.
static double accelerate_scroll(KeyEvent const &event, double acceleration)
Calculates and keeps track of scroll acceleration.
SPItem * sp_event_context_over_item(SPDesktop *desktop, SPItem *item, Geom::Point const &p)
Returns item if it is under point p in desktop, at any depth; otherwise returns NULL.
void sp_event_show_modifier_tip(MessageContext *message_context, KeyEvent const &event, char const *ctrl_tip, char const *shift_tip, char const *alt_tip)
Show tool context specific modifier tip.
SPItem * sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups)
Returns item at point p in desktop.
ShapeEditor * sp_event_context_get_shape_editor(ToolBase *tool)
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.
void sp_toggle_dropper(SPDesktop *dt)
Toggles current tool between active tool and dropper tool.
int gobble_key_events(unsigned keyval, unsigned mask)
Definition tool-base.h:243
static void update_latin_keys_group()
Try to determine the keys group of Latin layout.
void sp_event_context_read(ToolBase *tool, char const *key)
Calls virtual set() function of ToolBase.
unsigned get_latin_keyval_impl(unsigned const event_keyval, unsigned const event_keycode, GdkModifierType const event_state, unsigned const event_group, unsigned *consumed_modifiers)
static void sp_toggle_selector(SPDesktop *dt)
Toggles current tool between active tool and selector tool.
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
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.
Glib::RefPtr< Gdk::Cursor > load_svg_cursor(Gtk::Widget &widget, std::string const &file_name, std::optional< Colors::Color > maybe_fill, std::optional< Colors::Color > maybe_stroke)
Loads an SVG cursor from the specified file name.
bool mod_ctrl_only(unsigned modifiers)
bool mod_ctrl(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
bool mod_shift(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
void dump_event(CanvasEvent const &event, char const *prefix, bool merge=true)
Print an event to stdout.
Definition debug.h:29
@ INFORMATION_MESSAGE
Definition message.h:30
constexpr bool DEBUG_EVENTS
Whether event debug printing is enabled.
Definition debug.h:20
STL namespace.
New node tool with support for multiple path editing.
static cairo_user_data_key_t key
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
int num
Definition scribble.cpp:47
void sp_selection_item_next(SPDesktop *desktop)
void sp_selection_item_prev(SPDesktop *desktop)
Inkscape::ShapeEditor This is a container class which contains a knotholder for shapes.
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
SPGuide – a guideline.
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.
virtual EventType type() const =0
Return the dynamic type of the CanvasEvent.
A key has been pressed.
uint32_t keyval
The key that was pressed/released. (Matches gdkkeysyms.h.)
int group
The keyboard group.
uint16_t keycode
The raw code of the key that was pressed/released.
A key has been pressed.
Movement of the mouse pointer.
int delta
SPDesktop * desktop
static unsigned scroll_keyval
Definition tool-base.cpp:78
static double scroll_multiply
Definition tool-base.cpp:77
static Glib::ustring switch_dropper_to
Definition tool-base.cpp:73
static bool dropper_toggled
Definition tool-base.cpp:72
static int latin_keys_group
Definition tool-base.cpp:82
static guint32 scroll_event_time
Definition tool-base.cpp:76
static std::set< int > latin_keys_groups
Definition tool-base.cpp:83
static Glib::ustring switch_selector_to
Definition tool-base.cpp:69
static bool selector_toggled
Definition tool-base.cpp:68
static bool latin_keys_group_valid
Definition tool-base.cpp:81
Debug printing of event data.