From 3f1047170dd27f2698943119791d780b763f916d Mon Sep 17 00:00:00 2001 From: PBS Date: Sat, 10 Jun 2023 12:40:18 +0900 Subject: [PATCH 1/4] Add callback-converter Add a compatibility aid that allows GTK signals to be connected directly to C++ member functions, without having to write wrapper callbacks full of reinterpret casts. This is useful when functions exist in GTK but not yet in GTKmm. --- src/util/CMakeLists.txt | 1 + src/util/callback-converter.h | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/util/callback-converter.h diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 7c76d101c1..73e2564012 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -23,6 +23,7 @@ set(util_SRC action-accel.h cached_map.h cast.h + callback-converter.h const_char_ptr.h document-fonts.h enums.h diff --git a/src/util/callback-converter.h b/src/util/callback-converter.h new file mode 100644 index 0000000000..9a0f34666b --- /dev/null +++ b/src/util/callback-converter.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Callback converter for interfacing with C APIs. + */ +/* + * Author: PBS + * Copyright (C) 2022 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_UTIL_CALLBACK_CONVERTER_H +#define INKSCAPE_UTIL_CALLBACK_CONVERTER_H + +#include + +namespace Inkscape::Util { +namespace detail { + +template +struct CallbackConverter +{ + template + struct Helper; + + template + struct Helper + { + static constexpr auto result = +[] (Args... args, void *data) -> Ret { + return (reinterpret_cast(data)->*Fp)(std::forward(args)...); + }; + }; + + static constexpr auto result = Helper::result; +}; + +} // namespace detail + +/** + * Given a member function, make_c_callback produces a pure function with an extra void* argument at the end, + * into which an object pointer can be passed. Calling the pure function then invokes the original + * function on this object. In other words + * + * make_c_callback<&X::f>(..., &x) + * + * is equivalent to + * + * x->f(...); + * + * This is useful for passing member functions as callbacks to C code. + * + * Note: Actually they're not completely equivalent in that some extra forwarding might go on. + * Specifically, if your member function takes a T (by value) then the resulting callback + * will also take a T by value (because make_c_callback always exactly preserves argument types). That means + * your T will have to be moved from the wrapping function's argument into the wrapped function's + * argument. This won't make much difference if you only use this with C-compatible types. + */ +template +constexpr auto make_c_callback = detail::CallbackConverter::result; + +/** + * A worse version of make_c_callback that also casts the result to a GCallback, losing even more type-safety. + * Commonly needed to interface with Glib and GTK. (See make_c_callback for more details.) + */ +template +inline auto make_g_callback = reinterpret_cast(make_c_callback); // inline instead of constexpr due to reinterpret_cast + +} // namespace Inkscape::Util + +#endif // INKSCAPE_UTIL_CALLBACK_CONVERTER_H -- GitLab From 62ffccd322a63c52310f7fe027c2d15c07d21d79 Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Sat, 19 Nov 2022 18:23:26 +1100 Subject: [PATCH 2/4] Port Canvas to GTK event controllers --- src/ui/CMakeLists.txt | 2 + src/ui/tools/tool-base.cpp | 11 + src/ui/widget/canvas.cpp | 511 ++++++++++++++------------ src/ui/widget/canvas.h | 43 ++- src/ui/widget/events/canvas-event.cpp | 363 ++++++++++++++++++ src/ui/widget/events/canvas-event.h | 299 +++++++++++++++ 6 files changed, 986 insertions(+), 243 deletions(-) create mode 100644 src/ui/widget/events/canvas-event.cpp create mode 100644 src/ui/widget/events/canvas-event.h diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index d4dfdef920..26802136f3 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -201,6 +201,7 @@ set(ui_SRC widget/dash-selector.cpp widget/entity-entry.cpp widget/entry.cpp + widget/events/canvas-event.cpp widget/export-lists.cpp widget/export-preview.cpp widget/filter-effect-chooser.cpp @@ -479,6 +480,7 @@ set(ui_SRC widget/dash-selector.h widget/entity-entry.h widget/entry.h + widget/events/canvas-event.h widget/export-lists.h widget/export-preview.h widget/fill-style.h diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index 59a6470e43..36cd2ecd7a 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -1336,6 +1336,17 @@ void ToolBase::menu_popup(GdkEvent *event, SPObject *obj) menu->attach_to_widget(*_desktop->getCanvas()); // So actions work! menu->show(); + // Fixme: Hack to get the current window so we can popup a menu. + // The entire menu infrastructure is gone in GTK4, so we'll be using a much saner + // API then anyway. + auto window = _desktop->getCanvas()->get_window()->gobj(); + if (event->type == GDK_BUTTON_PRESS) { + g_set_object(&event->button.window, window); + } else if (event->type == GDK_KEY_PRESS) { + g_set_object(&event->key.window, window); + } + gdk_event_set_device(event, gtk_get_current_event_device()); + switch (event->type) { case GDK_BUTTON_PRESS: case GDK_KEY_PRESS: diff --git a/src/ui/widget/canvas.cpp b/src/ui/widget/canvas.cpp index 172ff04a0f..c294c4d74e 100644 --- a/src/ui/widget/canvas.cpp +++ b/src/ui/widget/canvas.cpp @@ -21,7 +21,6 @@ #include <2geom/convex-hull.h> #include "canvas.h" -#include "canvas-grid.h" #include "color.h" // Background color #include "desktop.h" @@ -29,6 +28,7 @@ #include "preferences.h" #include "ui/util.h" #include "helper/geom.h" +#include "util/callback-converter.h" #include "canvas/prefs.h" #include "canvas/fragment.h" @@ -96,10 +96,6 @@ namespace { * Utilities */ -// GdkEvents can only be safely copied using gdk_event_copy. Since this function allocates, we need the following smart pointer to wrap the result. -struct GdkEventFreer {void operator()(GdkEvent *ev) const {gdk_event_free(ev);}}; -using GdkEventUniqPtr = std::unique_ptr; - // Copies a GdkEvent, returning the result as a smart pointer. auto make_unique_copy(GdkEvent const *ev) { return GdkEventUniqPtr(gdk_event_copy(ev)); } @@ -244,7 +240,9 @@ public: // Event handling. bool process_event(const GdkEvent*); - bool pick_current_item(const GdkEvent*); + void set_pick_event(bool is_leave, double x, double y, EventModifier state); + CanvasItem *find_item_at_coords(double x, double y); + bool repick(); bool emit_event(const GdkEvent*); void ensure_geometry_uptodate(); Inkscape::CanvasItem *pre_scroll_grabbed_item; @@ -320,6 +318,26 @@ Canvas::Canvas() Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK ); + scroll_controller = Glib::wrap(gtk_event_controller_scroll_new(Gtk::Widget::gobj(), GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES)); + g_signal_connect(scroll_controller->gobj(), "scroll", Util::make_g_callback<&Canvas::on_scroll>, this); + + click_gesture = Glib::wrap(gtk_gesture_multi_press_new(Gtk::Widget::gobj())); + gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(click_gesture->gobj()), 0); // for right click detection + drag_gesture = Glib::wrap(gtk_gesture_drag_new(Gtk::Widget::gobj())); + gtk_gesture_group(click_gesture->gobj(), drag_gesture->gobj()); + g_signal_connect(click_gesture->gobj(), "pressed", Util::make_g_callback<&Canvas::on_button_pressed>, this); + g_signal_connect(drag_gesture->gobj(), "drag-end", Util::make_g_callback<&Canvas::on_button_released>, this); + + key_controller = Glib::wrap(gtk_event_controller_key_new(Gtk::Widget::gobj())); + g_signal_connect(key_controller->gobj(), "focus-in", Util::make_g_callback<&Canvas::on_focus_in>, this); + g_signal_connect(key_controller->gobj(), "key-pressed", Util::make_g_callback<&Canvas::on_key_pressed>, this); + g_signal_connect(key_controller->gobj(), "key-released", Util::make_g_callback<&Canvas::on_key_released>, this); + + motion_controller = Glib::wrap(gtk_event_controller_motion_new(Gtk::Widget::gobj())); + g_signal_connect(motion_controller->gobj(), "motion", Util::make_g_callback<&Canvas::on_motion>, this); + g_signal_connect(motion_controller->gobj(), "enter", Util::make_g_callback<&Canvas::on_enter>, this); + g_signal_connect(motion_controller->gobj(), "leave", Util::make_g_callback<&Canvas::on_leave>, this); + // Updater d->updater = Updater::create(pref_to_updater(d->prefs.update_strategy)); d->updater->reset(); @@ -416,9 +434,9 @@ void CanvasPrivate::activate_graphics() void CanvasPrivate::activate() { // Event handling/item picking - q->_pick_event.type = GDK_LEAVE_NOTIFY; - q->_pick_event.crossing.x = 0; - q->_pick_event.crossing.y = 0; + q->_pick_x = 0; + q->_pick_y = 0; + q->_pick_is_leave = true; q->_in_repick = false; q->_left_grabbed_item = false; @@ -849,14 +867,9 @@ void CanvasPrivate::autoscroll_begin(Geom::IntPoint const &to) displacement -= dpos; if (last_mouse) { - GdkEventMotion event; - memset(&event, 0, sizeof(GdkEventMotion)); - event.type = GDK_MOTION_NOTIFY; - event.x = last_mouse->x(); - event.y = last_mouse->y(); - event.state = q->_state; ensure_geometry_uptodate(); - emit_event(reinterpret_cast(&event)); + MotionEvent event(GDK_CURRENT_TIME, last_mouse->x(), last_mouse->y(), q->_state); + emit_event(event.compatSynthesize().get()); } if (strain_zero && velocity.length() <= 0.1) { @@ -890,54 +903,51 @@ void Canvas::enable_autoscroll() /* * Event handling + * + * Internally, Canvas uses GTK events. With the following event controllers, we create + * a "fake" event and pass it to the canvas for processing. The aim is to slowly migrate + * away from GdkEvent and replace these events with our own internal hierarchy. */ -bool Canvas::on_scroll_event(GdkEventScroll *scroll_event) +/** + * Obtains active modifiers from the current event and stores them + * inside canvas. This is a placeholder until GTK 4, where we should use + * GtkEventController accessors directly. + */ +void Canvas::update_modifiers() { - return d->process_event(reinterpret_cast(scroll_event)); + GdkModifierType state; + if (gtk_get_current_event_state(&state)) { + _state = state; + } } -bool Canvas::on_button_press_event(GdkEventButton *button_event) +bool Canvas::on_scroll(GtkEventControllerScroll *controller, double dx, double dy) { - return on_button_event(button_event); -} + update_modifiers(); -bool Canvas::on_button_release_event(GdkEventButton *button_event) -{ - if (button_event->button == 1) { - d->autoscroll_end(); - } + EventModifier state(static_cast(_state)); + ScrollEvent event(gtk_get_current_event_time(), dx, dy, state); - return on_button_event(button_event); + return d->process_event(event.compatSynthesize().get()); } -// Unified handler for press and release events. -bool Canvas::on_button_event(GdkEventButton *button_event) +bool Canvas::on_button_pressed(GtkGestureMultiPress *controller, int n_press, double x, double y) { - // Sanity-check event type. - switch (button_event->type) { - case GDK_BUTTON_PRESS: - case GDK_2BUTTON_PRESS: - case GDK_3BUTTON_PRESS: - case GDK_BUTTON_RELEASE: - break; // Good - default: - std::cerr << "Canvas::on_button_event: illegal event type!" << std::endl; - return false; - } + update_modifiers(); // Drag the split view controller. if (_split_mode == Inkscape::SplitMode::SPLIT) { - auto cursor_position = Geom::IntPoint(button_event->x, button_event->y); - switch (button_event->type) { - case GDK_BUTTON_PRESS: + auto cursor_position = Geom::IntPoint(x, y); + switch (n_press) { + case 1: if (_hover_direction != Inkscape::SplitDirection::NONE) { _split_dragging = true; _split_drag_start = cursor_position; return true; } break; - case GDK_2BUTTON_PRESS: + case 2: if (_hover_direction != Inkscape::SplitDirection::NONE) { _split_direction = _hover_direction; _split_dragging = false; @@ -945,96 +955,123 @@ bool Canvas::on_button_event(GdkEventButton *button_event) return true; } break; - case GDK_BUTTON_RELEASE: - if (!_split_dragging) break; - _split_dragging = false; - - // Check if we are near the edge. If so, revert to normal mode. - if (cursor_position.x() < 5 || - cursor_position.y() < 5 || - cursor_position.x() > get_allocation().get_width() - 5 || - cursor_position.y() > get_allocation().get_height() - 5) - { - // Reset everything. - _split_frac = {0.5, 0.5}; - set_cursor(); - set_split_mode(Inkscape::SplitMode::NORMAL); - - // Update action (turn into utility function?). - auto window = dynamic_cast(get_toplevel()); - if (!window) { - std::cerr << "Canvas::on_motion_notify_event: window missing!" << std::endl; - return true; - } - - auto action = window->lookup_action("canvas-split-mode"); - if (!action) { - std::cerr << "Canvas::on_motion_notify_event: action 'canvas-split-mode' missing!" << std::endl; - return true; - } - - auto saction = Glib::RefPtr::cast_dynamic(action); - if (!saction) { - std::cerr << "Canvas::on_motion_notify_event: action 'canvas-split-mode' not SimpleAction!" << std::endl; - return true; - } - - saction->change_state(static_cast(Inkscape::SplitMode::NORMAL)); - } - - break; - default: break; } } - return d->process_event(reinterpret_cast(button_event)); + EventModifier state(static_cast(_state)); + int button = gtk_gesture_single_get_current_button(reinterpret_cast(controller)); + + ButtonPressEvent event(gtk_get_current_event_time(), static_cast(button), n_press, x, y, state); + + return d->process_event(event.compatSynthesize().get()); } -bool Canvas::on_enter_notify_event(GdkEventCrossing *crossing_event) +bool Canvas::on_button_released(GtkGestureDrag *controller, double offset_x, double offset_y) { - if (crossing_event->window != get_window()->gobj()) { - return false; + double x, y; + gtk_gesture_drag_get_start_point(controller, &x, &y); + x += offset_x; + y += offset_y; + + update_modifiers(); + + // Drag the split view controller. + if (_split_mode == Inkscape::SplitMode::SPLIT && _split_dragging) { + auto cursor_position = Geom::IntPoint(x, y); + + _split_dragging = false; + + // Check if we are near the edge. If so, revert to normal mode. + if (cursor_position.x() < 5 || + cursor_position.y() < 5 || + cursor_position.x() > get_allocation().get_width() - 5 || + cursor_position.y() > get_allocation().get_height() - 5) + { + // Reset everything. + _split_frac = {0.5, 0.5}; + set_cursor(); + set_split_mode(Inkscape::SplitMode::NORMAL); + + // Update action (turn into utility function?). + auto window = dynamic_cast(get_toplevel()); + if (!window) { + std::cerr << "Canvas::on_button_released: window missing!" << std::endl; + return true; + } + + auto action = window->lookup_action("canvas-split-mode"); + if (!action) { + std::cerr << "Canvas::on_button_released: action 'canvas-split-mode' missing!" << std::endl; + return true; + } + + auto saction = Glib::RefPtr::cast_dynamic(action); + if (!saction) { + std::cerr << "Canvas::on_button_released: action 'canvas-split-mode' not SimpleAction!" << std::endl; + return true; + } + + saction->change_state(static_cast(Inkscape::SplitMode::NORMAL)); + } } - return d->process_event(reinterpret_cast(crossing_event)); -} -bool Canvas::on_leave_notify_event(GdkEventCrossing *crossing_event) -{ - if (crossing_event->window != get_window()->gobj()) { - return false; + EventModifier state(static_cast(_state)); + int button = gtk_gesture_single_get_current_button(reinterpret_cast(controller)); + + if (button == 1) { + d->autoscroll_end(); } - d->last_mouse = {}; - return d->process_event(reinterpret_cast(crossing_event)); + + ButtonReleaseEvent event(gtk_get_current_event_time(), static_cast(button), x, y, state); + + return d->process_event(event.compatSynthesize().get()); } -bool Canvas::on_focus_in_event(GdkEventFocus *focus_event) +bool Canvas::on_focus_in(GtkEventControllerKey *controller) { + update_modifiers(); grab_focus(); return false; } -bool Canvas::on_key_press_event(GdkEventKey *key_event) +bool Canvas::on_key_pressed(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType *state) { - return d->process_event(reinterpret_cast(key_event)); + update_modifiers(); + + EventModifier modifiers(static_cast(_state)); + int group = gtk_event_controller_key_get_group(controller); + + KeyPressEvent event(gtk_get_current_event_time(), group, keycode, keyval, modifiers); + + return d->process_event(event.compatSynthesize().get()); } -bool Canvas::on_key_release_event(GdkEventKey *key_event) +bool Canvas::on_key_released(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType *state) { - return d->process_event(reinterpret_cast(key_event)); + update_modifiers(); + + EventModifier modifiers(static_cast(_state)); + int group = gtk_event_controller_key_get_group(controller); + + KeyReleaseEvent event(gtk_get_current_event_time(), group, keycode, keyval, modifiers); + + return d->process_event(event.compatSynthesize().get()); } -bool Canvas::on_motion_notify_event(GdkEventMotion *motion_event) +bool Canvas::on_motion(GtkEventControllerMotion *controller, double x, double y) { + update_modifiers(); + // Record the last mouse position. - d->last_mouse = Geom::IntPoint(motion_event->x, motion_event->y); + d->last_mouse = Geom::IntPoint(x, y); // Handle interactions with the split view controller. if (_split_mode == Inkscape::SplitMode::XRAY) { queue_draw(); } else if (_split_mode == Inkscape::SplitMode::SPLIT) { - auto cursor_position = Geom::IntPoint(motion_event->x, motion_event->y); + auto cursor_position = Geom::IntPoint(x, y); // Move controller. if (_split_dragging) { @@ -1095,14 +1132,45 @@ bool Canvas::on_motion_notify_event(GdkEventMotion *motion_event) } // Avoid embarrassing neverending autoscroll in case the button-released handler somehow doesn't fire. - if (!(motion_event->state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) { + if (!(_state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK))) { d->autoscroll_end(); } - return d->process_event(reinterpret_cast(motion_event)); + EventModifier modifiers(static_cast(_state)); + + MotionEvent event(gtk_get_current_event_time(), x, y, modifiers); + + return d->process_event(event.compatSynthesize().get()); } -// Unified handler for all events. +bool Canvas::on_enter(GtkEventControllerMotion *controller, double x, double y) +{ + update_modifiers(); + + EventModifier modifiers(static_cast(_state)); + + EnterEvent event(gtk_get_current_event_time(), x, y, modifiers); + + return d->process_event(event.compatSynthesize().get()); +} + +bool Canvas::on_leave(GtkEventControllerMotion *controller) +{ + d->last_mouse = {}; + update_modifiers(); + + EventModifier modifiers(static_cast(_state)); + + LeaveEvent event(gtk_get_current_event_time(), modifiers); + + return d->process_event(event.compatSynthesize().get()); +} + +/** + * Unified handler for all events. + * @param event Event to process + * @return True if event was handled + */ bool CanvasPrivate::process_event(const GdkEvent *event) { framecheck_whole_function(this) @@ -1141,8 +1209,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) bool retval = emit_event(event); // ...then repick. - q->_state = event->scroll.state; - pick_current_item(event); + repick(); return retval; } @@ -1154,8 +1221,8 @@ bool CanvasPrivate::process_event(const GdkEvent *event) pre_scroll_grabbed_item = nullptr; // Pick the current item as if the button were not pressed... - q->_state = event->button.state; - pick_current_item(event); + set_pick_event(false, event->button.x, event->button.y, EventModifier(static_cast(event->button.state))); + repick(); // ...then process the event. q->_state ^= calc_button_mask(); @@ -1167,31 +1234,31 @@ bool CanvasPrivate::process_event(const GdkEvent *event) pre_scroll_grabbed_item = nullptr; // Process the event as if the button were pressed... - q->_state = event->button.state; bool retval = emit_event(event); // ...then repick after the button has been released. auto event_copy = make_unique_copy(event); event_copy->button.state ^= calc_button_mask(); q->_state = event_copy->button.state; - pick_current_item(event_copy.get()); + set_pick_event(false, event_copy->button.x, event_copy->button.y, EventModifier(static_cast(event_copy->button.state))); + repick(); return retval; } case GDK_ENTER_NOTIFY: pre_scroll_grabbed_item = nullptr; - q->_state = event->crossing.state; - return pick_current_item(event); + set_pick_event(true, event->crossing.x, event->crossing.y, EventModifier(static_cast(event->crossing.state))); + return repick(); case GDK_LEAVE_NOTIFY: pre_scroll_grabbed_item = nullptr; - q->_state = event->crossing.state; // This is needed to remove alignment or distribution snap indicators. if (q->_desktop) { q->_desktop->snapindicator->remove_snaptarget(); } - return pick_current_item(event); + set_pick_event(false, event->crossing.x, event->crossing.y, EventModifier(static_cast(event->crossing.state))); + return repick(); case GDK_KEY_PRESS: case GDK_KEY_RELEASE: @@ -1199,8 +1266,8 @@ bool CanvasPrivate::process_event(const GdkEvent *event) case GDK_MOTION_NOTIFY: pre_scroll_grabbed_item = nullptr; - q->_state = event->motion.state; - pick_current_item(event); + set_pick_event(false, event->motion.x, event->motion.y, EventModifier(static_cast(event->motion.state))); + repick(); return emit_event(event); default: @@ -1208,121 +1275,95 @@ bool CanvasPrivate::process_event(const GdkEvent *event) } } -// This function is called by 'process_event' to manipulate the state variables relating -// to the current object under the mouse, for example, to generate enter and leave events. -// -// This routine reacts to events from the canvas. Its main purpose is to find the canvas item -// closest to the cursor where the event occurred and then send the event (sometimes modified) to -// that item. The event then bubbles up the canvas item tree until an object handles it. If the -// widget is redrawn, this routine may be called again for the same event. -// -// Canvas items register their interest by connecting to the "event" signal. -// Example in desktop.cpp: -// canvas_catchall->connect_event(sigc::bind(sigc::ptr_fun(sp_desktop_root_handler), this)); -bool CanvasPrivate::pick_current_item(const GdkEvent *event) +/** + * Saves the provided coordinates and modifiers to the canvas for use + * by CanvasPrivate::repick. + * + * @param is_leave True if the cursor has left the canvas, false otherwise + * @param x Horizontal position of cursor + * @param y Vertical position of cursor + * @param state Modifier keys and button state + */ +void CanvasPrivate::set_pick_event(bool is_leave, double x, double y, EventModifier state) +{ + // Save the event in the canvas. This is used to synthesize enter and + // leave events in case the current item changes. It is also used to + // re-pick the current item if the current one gets deleted. + + q->_pick_x = x; + q->_pick_y = y; + q->_pick_is_leave = is_leave; + q->_pick_state = state; + + // TODO: Remove this once we're sure repicking never happens + if (q->_in_repick) { + assert(false); + } +} + +/** + * Internal helper method to retrieve the canvas item under the given + * coordinates, taking into account the current render mode and other + * characteristics. + * + * @param x Horizontal position of cursor + * @param y Vertical position of cursor + * @return The current canvas item, or nullptr + */ +CanvasItem *CanvasPrivate::find_item_at_coords(double x, double y) +{ + // Look at where the cursor is to see if one should pick with outline mode. + bool outline = q->canvas_point_in_outline_zone({ x, y }); + + // Convert to world coordinates. + auto p = Geom::Point(x, y) + q->_pos; + if (stores.mode() == Stores::Mode::Decoupled) { + p *= q->_affine.inverse() * canvasitem_ctx->affine(); + } + + q->_drawing->getCanvasItemDrawing()->set_pick_outline(outline); + return canvasitem_ctx->root()->pick_item(p); +} + +/** + * This function is called by 'process_event' to manipulate the state variables relating + * to the current object under the mouse. Additionally, it will generate enter and leave events + * for canvas items if the current item changes. + * + * This routine reacts to events from the canvas. Its main purpose is to find the canvas item + * closest to the cursor where the event occurred and synthesise enter and leave events for the + * current and previously selected items. + * + * The coordinates and state used for picking is set by the CanvasPrivate::set_pick_event method + * and used here. This routine may be called any number of times for the same event. This is useful + * for cases in which re-picking is necessary but the cursor position has not changed (e.g. scrolling). + * + * @return True if the enter or leave event was handled + */ +bool CanvasPrivate::repick() { // Ensure requested geometry updates are performed first. ensure_geometry_uptodate(); - int button_down = 0; + bool button_down = false; if (!q->_all_enter_events) { // Only set true in connector-tool.cpp. // If a button is down, we'll perform enter and leave events on the // current item, but not enter on any other item. This is more or // less like X pointer grabbing for canvas items. - button_down = q->_state & (GDK_BUTTON1_MASK | - GDK_BUTTON2_MASK | - GDK_BUTTON3_MASK | - GDK_BUTTON4_MASK | - GDK_BUTTON5_MASK); - if (!button_down) q->_left_grabbed_item = false; - } - - // Save the event in the canvas. This is used to synthesize enter and - // leave events in case the current item changes. It is also used to - // re-pick the current item if the current one gets deleted. Also, - // synthesize an enter event. - if (event != &q->_pick_event) { - if (event->type == GDK_MOTION_NOTIFY || event->type == GDK_SCROLL || event->type == GDK_BUTTON_RELEASE) { - // Convert to GDK_ENTER_NOTIFY - - // These fields have the same offsets in all types of events. - q->_pick_event.crossing.type = GDK_ENTER_NOTIFY; - q->_pick_event.crossing.window = event->motion.window; - q->_pick_event.crossing.send_event = event->motion.send_event; - q->_pick_event.crossing.subwindow = nullptr; - q->_pick_event.crossing.x = event->motion.x; - q->_pick_event.crossing.y = event->motion.y; - q->_pick_event.crossing.mode = GDK_CROSSING_NORMAL; - q->_pick_event.crossing.detail = GDK_NOTIFY_NONLINEAR; - q->_pick_event.crossing.focus = false; - - // These fields don't have the same offsets in all types of events. - switch (event->type) - { - case GDK_MOTION_NOTIFY: - q->_pick_event.crossing.state = event->motion.state; - q->_pick_event.crossing.x_root = event->motion.x_root; - q->_pick_event.crossing.y_root = event->motion.y_root; - break; - case GDK_SCROLL: - q->_pick_event.crossing.state = event->scroll.state; - q->_pick_event.crossing.x_root = event->scroll.x_root; - q->_pick_event.crossing.y_root = event->scroll.y_root; - break; - case GDK_BUTTON_RELEASE: - q->_pick_event.crossing.state = event->button.state; - q->_pick_event.crossing.x_root = event->button.x_root; - q->_pick_event.crossing.y_root = event->button.y_root; - break; - default: - assert(false); - } - - } else { - q->_pick_event = *event; + button_down = q->_state & (GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | GDK_BUTTON4_MASK | GDK_BUTTON5_MASK); + if (!button_down) { + q->_left_grabbed_item = false; } } - if (q->_in_repick) { - // Don't do anything else if this is a recursive call. - return false; - } - // Find new item q->_current_canvas_item_new = nullptr; - if (q->_pick_event.type != GDK_LEAVE_NOTIFY && canvasitem_ctx->root()->is_visible()) { - // Leave notify means there is no current item. - // Find closest item. - double x = 0.0; - double y = 0.0; - - if (q->_pick_event.type == GDK_ENTER_NOTIFY) { - x = q->_pick_event.crossing.x; - y = q->_pick_event.crossing.y; - } else { - x = q->_pick_event.motion.x; - y = q->_pick_event.motion.y; - } - - // Look at where the cursor is to see if one should pick with outline mode. - bool outline = q->canvas_point_in_outline_zone({ x, y }); - - // Convert to world coordinates. - auto p = Geom::Point(x, y) + q->_pos; - if (stores.mode() == Stores::Mode::Decoupled) { - p *= q->_affine.inverse() * canvasitem_ctx->affine(); - } - - q->_drawing->getCanvasItemDrawing()->set_pick_outline(outline); - q->_current_canvas_item_new = canvasitem_ctx->root()->pick_item(p); - // if (q->_current_canvas_item_new) { - // std::cout << " PICKING: FOUND ITEM: " << q->_current_canvas_item_new->get_name() << std::endl; - // } else { - // std::cout << " PICKING: DID NOT FIND ITEM" << std::endl; - // } + // A 'leave' pick event means there is no new item (i.e. cursor has left the canvas) + if (!q->_pick_is_leave && canvasitem_ctx->root()->is_visible()) { + q->_current_canvas_item_new = find_item_at_coords(q->_pick_x, q->_pick_y); } if (q->_current_canvas_item_new == q->_current_canvas_item && !q->_left_grabbed_item) { @@ -1336,17 +1377,13 @@ bool CanvasPrivate::pick_current_item(const GdkEvent *event) q->_current_canvas_item != nullptr && !q->_left_grabbed_item ) { - GdkEvent new_event; - new_event = q->_pick_event; - new_event.type = GDK_LEAVE_NOTIFY; - new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; - new_event.crossing.subwindow = nullptr; + LeaveEvent leaveEvent(GDK_CURRENT_TIME, q->_pick_state); q->_in_repick = true; - retval = emit_event(&new_event); + retval = emit_event(leaveEvent.compatSynthesize().get()); q->_in_repick = false; } - if (q->_all_enter_events == false) { + if (!q->_all_enter_events) { // new_current_item may have been set to nullptr during the call to emitEvent() above. if (q->_current_canvas_item_new != q->_current_canvas_item && button_down) { q->_left_grabbed_item = true; @@ -1359,18 +1396,30 @@ bool CanvasPrivate::pick_current_item(const GdkEvent *event) q->_current_canvas_item = q->_current_canvas_item_new; if (q->_current_canvas_item != nullptr) { - GdkEvent new_event; - new_event = q->_pick_event; - new_event.type = GDK_ENTER_NOTIFY; - new_event.crossing.detail = GDK_NOTIFY_ANCESTOR; - new_event.crossing.subwindow = nullptr; - retval = emit_event(&new_event); + EnterEvent enterEvent(GDK_CURRENT_TIME, q->_pick_x, q->_pick_y, q->_pick_state); + q->_in_repick = true; + retval = emit_event(enterEvent.compatSynthesize().get()); + q->_in_repick = false; } return retval; } -// Fires an event at the canvas, after a little pre-processing. Returns true if handled. +/** + * Fires an event at the canvas, after a little pre-processing. The event then bubbles up the + * CanvasItem tree until an object or tool handles it. + * + * If the event is not handled, the method returns false and the event is passed back to GTK where + * it propagates back up the widget tree in the 'bubble' propagation phase. + * + * Canvas items register their interest by connecting to the "event" signal. + * + * Example in desktop.cpp: + * canvas_catchall->connect_event(sigc::bind(sigc::ptr_fun(sp_desktop_root_handler), this)); + * + * @param event Event to dispatch to the canvas + * @return Returns true if handled. + */ bool CanvasPrivate::emit_event(const GdkEvent *event) { // Handle grabbed items. diff --git a/src/ui/widget/canvas.h b/src/ui/widget/canvas.h index bc50848e68..dcc20ada8b 100644 --- a/src/ui/widget/canvas.h +++ b/src/ui/widget/canvas.h @@ -22,6 +22,7 @@ #include <2geom/int-rect.h> #include "display/rendermode.h" #include "optglarea.h" +#include "events/canvas-event.h" class SPDesktop; @@ -134,17 +135,29 @@ protected: void get_preferred_width_vfunc (int &minimum_width, int &natural_width ) const override; void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override; + // Event controllers + Glib::RefPtr scroll_controller, key_controller, motion_controller; + Glib::RefPtr click_gesture, drag_gesture; + // Event handlers - bool on_scroll_event (GdkEventScroll* ) override; - bool on_button_event (GdkEventButton* ); - bool on_button_press_event (GdkEventButton* ) override; - bool on_button_release_event(GdkEventButton* ) override; - bool on_enter_notify_event (GdkEventCrossing*) override; - bool on_leave_notify_event (GdkEventCrossing*) override; - bool on_focus_in_event (GdkEventFocus* ) override; - bool on_key_press_event (GdkEventKey* ) override; - bool on_key_release_event (GdkEventKey* ) override; - bool on_motion_notify_event (GdkEventMotion* ) override; + void update_modifiers(); + + // EventControllerScroll + bool on_scroll(GtkEventControllerScroll *controller, double dx, double dy); + + // GtkGestureMultiPress + bool on_button_pressed (GtkGestureMultiPress *controller, int n_press, double x, double y); + bool on_button_released(GtkGestureDrag *controller, double offset_x, double offset_y); + + // EventControllerMotion + bool on_motion(GtkEventControllerMotion *controller, double x, double y); + bool on_enter (GtkEventControllerMotion *controller, double x, double y); + bool on_leave (GtkEventControllerMotion *controller); + + // EventControllerKey + bool on_focus_in (GtkEventControllerKey *controller); + bool on_key_pressed (GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType *state); + bool on_key_released(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType *state); void on_realize() override; void on_unrealize() override; @@ -179,8 +192,14 @@ private: /* Internal state */ // Event handling/item picking - GdkEvent _pick_event; ///< Event used to find currently selected item. - bool _in_repick; ///< For tracking recursion of pick_current_item(). + // State used to find 'current' item + double _pick_x; + double _pick_y; + EventModifier _pick_state; // TODO: Merge with _state + bool _pick_is_leave; // TODO: Get rid of? + + // State for repick operation + bool _in_repick; ///< For tracking recursion of repick(). bool _left_grabbed_item; ///< ? bool _all_enter_events; ///< Keep all enter events. Only set true in connector-tool.cpp. bool _is_dragging; ///< Used in selection-chemistry to block undo/redo. diff --git a/src/ui/widget/events/canvas-event.cpp b/src/ui/widget/events/canvas-event.cpp new file mode 100644 index 0000000000..84f06d1c65 --- /dev/null +++ b/src/ui/widget/events/canvas-event.cpp @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Matthew Jakeman + * + * Copyright (C) 2022 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "canvas-event.h" +#include + +namespace Inkscape::UI::Widget { +namespace { + +void debug_print_mods(EventModifier mods) +{ + std::cout + << "Control: " << mods.hasFlag(EventModifierType::CONTROL) << " / " + << "Shift: " << mods.hasFlag(EventModifierType::SHIFT) << " / " + << "Alt: " << mods.hasFlag(EventModifierType::ALT) << " / " + << "Left Button: " << mods.hasFlag(EventModifierType::LEFT_BUTTON) << " / " + << "Middle Button: " << mods.hasFlag(EventModifierType::MIDDLE_BUTTON) << " / " + << "Right Button: " << mods.hasFlag(EventModifierType::RIGHT_BUTTON) << " / " + << std::endl; +} + +/* +void debug_print_mods(GdkModifierType mods) +{ + std::cout + << "Control: " << (mods & GDK_CONTROL_MASK) << " / " + << "Shift: " << (mods & GDK_SHIFT_MASK) << " / " + << "Alt: " << (mods & GDK_MOD1_MASK) << " / " + << "Left Button: " << (mods & GDK_BUTTON1_MASK) << " / " + << "Middle Button: " << (mods & GDK_BUTTON2_MASK) << " / " + << "Right Button: " << (mods & GDK_BUTTON3_MASK) << " / " + << std::endl; +} +*/ + +/** + * Translate GdkModifierType -> EventModifier which provides a nicer and more + * limited API for accessing modifiers. + * + * Note that not all modifiers are transferred. Only the following are + * considered: + * - Control + * - Shift + * - Alt + * + * The mouse buttons are also translated for compatibility but should not + * be used in new code: + * - Left Button + * - Middle Button + * - Right Button + * + * @param state Inkscape modifier structure + * @param gdk_state GDK modifier flags + */ +void translate_modifiers(EventModifier &state, GdkModifierType gdk_state) +{ + state.clear(); + + if (gdk_state & GDK_CONTROL_MASK) + state.setFlag(EventModifierType::CONTROL); + + if (gdk_state & GDK_SHIFT_MASK) + state.setFlag(EventModifierType::SHIFT); + + if (gdk_state & GDK_MOD1_MASK) + state.setFlag(EventModifierType::ALT); + + if (gdk_state & GDK_MOD2_MASK) + state.setFlag(EventModifierType::META); + + if (gdk_state & GDK_BUTTON1_MASK) + state.setFlag(EventModifierType::LEFT_BUTTON); + + if (gdk_state & GDK_BUTTON2_MASK) + state.setFlag(EventModifierType::MIDDLE_BUTTON); + + if (gdk_state & GDK_BUTTON3_MASK) + state.setFlag(EventModifierType::RIGHT_BUTTON); +} + +/** + * Translate EventModifier -> GdkModifierType for compatibility with some internal + * functions which still depend on GdkEvent directly. + * + * Note this function does not guarantee a perfect round trip. It only translates + * the modifier keys stored in EventModifier and not anything else. + * + * Do not use this in new code. + * + * @param state Destination GdkModifierType + * @param modifier Source EventModifier + */ +GdkModifierType untranslate_modifiers(EventModifier modifier) +{ + guint32 raw_state = 0; + + if (modifier.hasFlag(EventModifierType::CONTROL)) + raw_state |= GDK_CONTROL_MASK; + + if (modifier.hasFlag(EventModifierType::SHIFT)) + raw_state |= GDK_SHIFT_MASK; + + if (modifier.hasFlag(EventModifierType::ALT)) + raw_state |= GDK_MOD1_MASK; + + if (modifier.hasFlag(EventModifierType::META)) + raw_state |= GDK_MOD2_MASK; + + if (modifier.hasFlag(EventModifierType::LEFT_BUTTON)) + raw_state |= GDK_BUTTON1_MASK; + + if (modifier.hasFlag(EventModifierType::MIDDLE_BUTTON)) + raw_state |= GDK_BUTTON2_MASK; + + if (modifier.hasFlag(EventModifierType::RIGHT_BUTTON)) + raw_state |= GDK_BUTTON3_MASK; + + return static_cast(raw_state); +} + +// Todo: Remove in C++23. +template +auto to_underlying(T t) +{ + return static_cast>(t); +} + +} // namespace + +EventModifier::EventModifier(GdkModifierType state) +{ + // Round Trip Testing + // TODO: Move to unit test + /*auto original = static_cast(state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK|GDK_CONTROL_MASK)); + translate_modifiers(*this, state); + debug_print_mods(*this); + + auto compare = untranslate_modifiers(*this); + debug_print_mods(compare); + debug_print_mods(original); + debug_print_mods(state); + + if (static_cast(original) != static_cast(compare)) + std::cout << "WRONG" << std::endl;*/ + + translate_modifiers(*this, state); +} + +void EventModifier::setFlag(EventModifierType type) +{ + _flags |= to_underlying(type); +} + +void EventModifier::unsetFlag(EventModifierType type) +{ + _flags &= ~to_underlying(type); +} + +bool EventModifier::hasFlag(EventModifierType type) const +{ + return (_flags & to_underlying(type)) == to_underlying(type); +} + +void EventModifier::clear() +{ + _flags = 0; +} + +void EventModifier::debugPrintMods() +{ + debug_print_mods(*this); +} + +CanvasEvent::CanvasEvent(uint32_t time) + : _time(time) +{} + +ButtonEvent::ButtonEvent(uint32_t time, EventButton button, double x, double y, EventModifier state) + : CanvasEvent(time) + , _button(button) + , _x(x) + , _y(y) + , _state(state) +{} + +ButtonPressEvent::ButtonPressEvent(uint32_t time, EventButton button, int n_press, double x, double y, EventModifier state) + : ButtonEvent(time, button, x, y, state) + , _n_press(n_press) +{} + +GdkEventUniqPtr ButtonPressEvent::compatSynthesize() +{ + GdkEventType type; + switch (_n_press % 3) { + default: + case 1: type = GDK_BUTTON_PRESS; break; + case 2: type = GDK_2BUTTON_PRESS; break; + case 0: type = GDK_3BUTTON_PRESS; break; + } + + auto event = GdkEventUniqPtr(gdk_event_new(type)); + + auto button_event = reinterpret_cast(event.get()); + button_event->x = _x; + button_event->y = _y; + button_event->state = untranslate_modifiers(_state); + button_event->button = to_underlying(_button); + button_event->time = _time; + + return event; +} + +ButtonReleaseEvent::ButtonReleaseEvent(uint32_t time, EventButton button, double x, double y, EventModifier state) + : ButtonEvent(time, button, x, y, state) +{} + +GdkEventUniqPtr ButtonReleaseEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_BUTTON_RELEASE)); + + auto button_event = reinterpret_cast(event.get()); + button_event->x = _x; + button_event->y = _y; + button_event->state = untranslate_modifiers(_state); + button_event->button = to_underlying(_button); + button_event->time = _time; + + return event; +} + +PointerEvent::PointerEvent(uint32_t time, EventModifier state) + : CanvasEvent(time) + , _state(state) +{} + +MotionEvent::MotionEvent(uint32_t time, double x, double y, EventModifier state) + : PointerEvent(time, state) + , _x(x) + , _y(y) +{} + +GdkEventUniqPtr MotionEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_MOTION_NOTIFY)); + + auto motion_event = reinterpret_cast(event.get()); + motion_event->x = _x; + motion_event->y = _y; + motion_event->state = untranslate_modifiers(_state); + motion_event->time = _time; + + return event; +} + +EnterEvent::EnterEvent(uint32_t time, double x, double y, EventModifier state) + : PointerEvent(time, state) + , _x(x) + , _y(y) +{} + +GdkEventUniqPtr EnterEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_ENTER_NOTIFY)); + + auto crossing_event = reinterpret_cast(event.get()); + crossing_event->x = _x; + crossing_event->y = _y; + crossing_event->state = untranslate_modifiers(_state); + crossing_event->time = _time; + + return event; +} + +LeaveEvent::LeaveEvent(uint32_t time, EventModifier state) + : PointerEvent(time, state) +{} + +GdkEventUniqPtr LeaveEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_LEAVE_NOTIFY)); + + auto crossing_event = reinterpret_cast(event.get()); + crossing_event->x = 0.0f; // x not relevant + crossing_event->y = 0.0f; // y not relevant + crossing_event->state = untranslate_modifiers(_state); + crossing_event->time = _time; + + return event; +} + +KeyEvent::KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state) + : CanvasEvent(time) + , _group(group) + , _hardware_keycode(keycode) + , _keyval(keyval) + , _state(state) +{} + +KeyPressEvent::KeyPressEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state) + : KeyEvent(time, group, keycode, keyval, state) +{} + +GdkEventUniqPtr KeyPressEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_KEY_PRESS)); + + auto key_event = reinterpret_cast(event.get()); + key_event->keyval = _keyval; + key_event->hardware_keycode = _hardware_keycode; + key_event->state = untranslate_modifiers(_state); + key_event->group = _group; + key_event->time = _time; + + return event; +} + +KeyReleaseEvent::KeyReleaseEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state) + : KeyEvent(time, group, keycode, keyval, state) +{} + +GdkEventUniqPtr KeyReleaseEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_KEY_RELEASE)); + + auto key_event = reinterpret_cast(event.get()); + key_event->keyval = _keyval; + key_event->hardware_keycode = _hardware_keycode; + key_event->state = untranslate_modifiers(_state); + key_event->group = _group; + key_event->time = _time; + + return event; +} + +ScrollEvent::ScrollEvent(uint32_t time, double dx, double dy, EventModifier state) + : CanvasEvent(time) + , _dx(dx) + , _dy(dy) + , _state(state) +{} + +GdkEventUniqPtr ScrollEvent::compatSynthesize() +{ + auto event = GdkEventUniqPtr(gdk_event_new(GDK_SCROLL)); + + auto scroll_event = reinterpret_cast(event.get()); + + scroll_event->delta_x = _dx; + scroll_event->delta_y = _dy; + scroll_event->direction = GDK_SCROLL_SMOOTH; + scroll_event->state = untranslate_modifiers(_state); + scroll_event->time = _time; + + return event; +} + +} // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/events/canvas-event.h b/src/ui/widget/events/canvas-event.h new file mode 100644 index 0000000000..73d0142578 --- /dev/null +++ b/src/ui/widget/events/canvas-event.h @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Matthew Jakeman + * + * Copyright (C) 2022 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_UI_WIDGET_EVENTS_CANVAS_EVENT_H +#define INKSCAPE_UI_WIDGET_EVENTS_CANVAS_EVENT_H + +#include +#include +#include + +namespace Inkscape::UI::Widget { + +// Smart pointer for wrapping GdkEvents. +struct GdkEventFreer { void operator()(GdkEvent *ev) const { gdk_event_free(ev); } }; +using GdkEventUniqPtr = std::unique_ptr; + +/** + * An internal event hierarchy for Inkscape's Canvas. + * + * When events from GDK reach the Canvas widget, we translate them + * into a set of Inkscape specific events which can be emitted, suspended, + * modified, and re-fired as necessary. + * + * For now, each CanvasEvent maps directly to a GdkEvent, although this + * may change in the future (e.g. special casing support for touch). + * + * - GdkCrossingEvent -> CrossingEvent + * - GdkMotionEvent -> MotionEvent + * - GdkButtonEvent -> ButtonEvent + * - GdkKeyEvent -> KeyEvent + */ +enum class EventType +{ + ENTER, + LEAVE, + MOTION, + BUTTON_PRESS, + BUTTON_RELEASE, + KEY_PRESS, + KEY_RELEASE, + SCROLL +}; + +/** + * Types of modifier keys that can be pressed. + * + * The mouse buttons (Left/Middle/Right) are present for compatibility + * purposes only - avoid using these in new code. + * + * These values are arbitrary and should not be relied on in code. + */ +enum class EventModifierType +{ + CONTROL = 1 << 0, + SHIFT = 1 << 1, + ALT = 1 << 2, + META = 1 << 3, // Command on macOS + LEFT_BUTTON = 1 << 4, + MIDDLE_BUTTON = 1 << 5, + RIGHT_BUTTON = 1 << 6 +}; + +/** + * Represents the modifier key(s) relevant to this event. + * + * The value of _flags is arbitrary and should not be relied on + * in code. Use the accessors instead. + */ +class EventModifier +{ +public: + EventModifier() = default; + EventModifier(GdkModifierType state); + + void setFlag(EventModifierType type); + void unsetFlag(EventModifierType type); + bool hasFlag(EventModifierType type) const; + void clear(); + void debugPrintMods(); + +private: + uint8_t _flags; +}; + +/** + * Which mouse button the event pertains to. This is mapped to + * GDK's own representation of button numbers so you can + * directly cast e.g. `(EventButton) event->button`. + */ +enum class EventButton +{ + PRIMARY = 1, + MIDDLE = 2, + SECONDARY = 3 +}; + +/** + * Abstract base class for events + */ +class CanvasEvent +{ +public: + CanvasEvent(uint32_t time); + virtual ~CanvasEvent() {} + + virtual EventType getEventType() const = 0; + virtual GdkEventUniqPtr compatSynthesize() = 0; + + uint32_t getTime() const { return _time; } + +protected: + uint32_t _time; +}; + +/** + * Abstract event for mouse button (left/right/middle). May also + * be used for touch interactions. + */ +class ButtonEvent : public CanvasEvent +{ +public: + ButtonEvent(uint32_t time, EventButton button, double x, double y, EventModifier state); + + double getEventX() const { return _x; } + double getEventY() const { return _y; } + EventModifier getModifiers() const { return _state; } + EventButton getButton() const { return _button; } + +protected: + double _x; + double _y; + EventModifier _state; + EventButton _button; +}; + +/** + * A mouse button (left/right/middle) is pressed. May also + * be used for touch interactions. + */ +class ButtonPressEvent : public ButtonEvent +{ +public: + ButtonPressEvent(uint32_t time, EventButton button, int n_press, double x, double y, EventModifier state); + EventType getEventType() const override { return EventType::BUTTON_PRESS; } + GdkEventUniqPtr compatSynthesize() override; + + int getNumPress() { return _n_press; }; + +private: + int _n_press; +}; + +/** + * A mouse button (left/right/middle) is released. May also + * be used for touch interactions. + */ +class ButtonReleaseEvent : public ButtonEvent +{ +public: + ButtonReleaseEvent(uint32_t time, EventButton button, double x, double y, EventModifier state); + EventType getEventType() const override { return EventType::BUTTON_RELEASE; } + GdkEventUniqPtr compatSynthesize() override; +}; + +/** + * A key has been pressed. + */ +class KeyEvent : public CanvasEvent +{ +public: + KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state); + + uint8_t getGroup() const { return _group; } + uint16_t getHardwareKeycode() const { return _hardware_keycode; } + uint32_t getKeyval() const { return _keyval; } + EventModifier getModifiers() const { return _state; } + +protected: + uint8_t _group; + uint16_t _hardware_keycode; + uint32_t _keyval; + EventModifier _state; +}; + +/** + * A key has been pressed. + */ +class KeyPressEvent : public KeyEvent +{ +public: + KeyPressEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state); + EventType getEventType() const override { return EventType::KEY_PRESS; } + GdkEventUniqPtr compatSynthesize() override; +}; + +/** + * A key has been released. + */ +class KeyReleaseEvent : public KeyEvent +{ +public: + KeyReleaseEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state); + EventType getEventType() const override { return EventType::KEY_RELEASE; } + GdkEventUniqPtr compatSynthesize() override; +}; + +/** + * The pointer has moved, entered, or exited a widget or item. + */ +class PointerEvent : public CanvasEvent +{ +public: + PointerEvent(uint32_t time, EventModifier state); + + EventModifier getModifiers() const { return _state; } + +protected: + EventModifier _state; +}; + +/** + * Movement of the mouse pointer. May also be used + * for touch interactions. + */ +class MotionEvent : public PointerEvent +{ +public: + MotionEvent(uint32_t time, double x, double y, EventModifier state); + EventType getEventType() const override { return EventType::MOTION; } + GdkEventUniqPtr compatSynthesize() override; + + double getEventX() { return _x; } + double getEventY() { return _y; } + +private: + double _x; + double _y; +}; + +/** + * The pointer has entered a widget or item. + */ +class EnterEvent : public PointerEvent +{ +public: + EnterEvent(uint32_t time, double x, double y, EventModifier state); + EventType getEventType() const override { return EventType::ENTER; } + GdkEventUniqPtr compatSynthesize() override; + + double getEventX() { return _x; } + double getEventY() { return _y; } + +private: + double _x; + double _y; +}; + +/** + * The pointer has exited a widget or item. + * + * Note the coordinates will always be (0, 0) for this event. + */ +class LeaveEvent : public PointerEvent +{ +public: + LeaveEvent(uint32_t time, EventModifier state); + EventType getEventType() const override { return EventType::LEAVE; } + GdkEventUniqPtr compatSynthesize() override; +}; + +/** + * Scroll the item or widget by the provided amount + */ +class ScrollEvent : public CanvasEvent +{ +public: + ScrollEvent(uint32_t time, double dx, double dy, EventModifier state); + EventType getEventType() const override { return EventType::SCROLL; } + GdkEventUniqPtr compatSynthesize() override; + + EventModifier getModifiers() const { return _state; } + double getDeltaX() { return _dx; } + double getDeltaY() { return _dy; } + +private: + double _dx; + double _dy; + EventModifier _state; +}; + +} // namespace Inkscape::UI::Widget + +#endif // INKSCAPE_UI_WIDGET_EVENTS_CANVAS_EVENT_H -- GitLab From b931e4d749b5540a1a32c5d1d9bf8276e1714d18 Mon Sep 17 00:00:00 2001 From: PBS Date: Mon, 12 Jun 2023 13:12:51 +0900 Subject: [PATCH 3/4] Remove custom EventModifier class We need to pass through Gdk modifier flags exactly when the data in CanvasEvents is passed back to Gtk, which happens for example in the text tool. This removes the need for the custom EventModifier class, whose purpose was to store only the subset of modifiers that Inkscape uses. Fixes a text tool issue where the UI enters a glitchy state after typing on some versions of GTK. This commit should be squashed into the previous one. --- src/ui/widget/canvas.cpp | 45 +++--- src/ui/widget/canvas.h | 10 +- src/ui/widget/events/canvas-event.cpp | 198 +++----------------------- src/ui/widget/events/canvas-event.h | 93 ++++-------- 4 files changed, 68 insertions(+), 278 deletions(-) diff --git a/src/ui/widget/canvas.cpp b/src/ui/widget/canvas.cpp index c294c4d74e..a7f721571f 100644 --- a/src/ui/widget/canvas.cpp +++ b/src/ui/widget/canvas.cpp @@ -240,7 +240,7 @@ public: // Event handling. bool process_event(const GdkEvent*); - void set_pick_event(bool is_leave, double x, double y, EventModifier state); + void set_pick_event(bool is_leave, double x, double y, unsigned state); CanvasItem *find_item_at_coords(double x, double y); bool repick(); bool emit_event(const GdkEvent*); @@ -926,8 +926,7 @@ bool Canvas::on_scroll(GtkEventControllerScroll *controller, double dx, double d { update_modifiers(); - EventModifier state(static_cast(_state)); - ScrollEvent event(gtk_get_current_event_time(), dx, dy, state); + ScrollEvent event(gtk_get_current_event_time(), dx, dy, _state); return d->process_event(event.compatSynthesize().get()); } @@ -960,10 +959,9 @@ bool Canvas::on_button_pressed(GtkGestureMultiPress *controller, int n_press, do } } - EventModifier state(static_cast(_state)); int button = gtk_gesture_single_get_current_button(reinterpret_cast(controller)); - ButtonPressEvent event(gtk_get_current_event_time(), static_cast(button), n_press, x, y, state); + ButtonPressEvent event(gtk_get_current_event_time(), static_cast(button), n_press, x, y, _state); return d->process_event(event.compatSynthesize().get()); } @@ -1017,14 +1015,13 @@ bool Canvas::on_button_released(GtkGestureDrag *controller, double offset_x, dou } } - EventModifier state(static_cast(_state)); int button = gtk_gesture_single_get_current_button(reinterpret_cast(controller)); if (button == 1) { d->autoscroll_end(); } - ButtonReleaseEvent event(gtk_get_current_event_time(), static_cast(button), x, y, state); + ButtonReleaseEvent event(gtk_get_current_event_time(), static_cast(button), x, y, _state); return d->process_event(event.compatSynthesize().get()); } @@ -1036,26 +1033,24 @@ bool Canvas::on_focus_in(GtkEventControllerKey *controller) return false; } -bool Canvas::on_key_pressed(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType *state) +bool Canvas::on_key_pressed(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType*) { update_modifiers(); - EventModifier modifiers(static_cast(_state)); int group = gtk_event_controller_key_get_group(controller); - KeyPressEvent event(gtk_get_current_event_time(), group, keycode, keyval, modifiers); + KeyPressEvent event(gtk_get_current_event_time(), group, keycode, keyval, _state); return d->process_event(event.compatSynthesize().get()); } -bool Canvas::on_key_released(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType *state) +bool Canvas::on_key_released(GtkEventControllerKey *controller, unsigned keyval, unsigned keycode, GdkModifierType*) { update_modifiers(); - EventModifier modifiers(static_cast(_state)); int group = gtk_event_controller_key_get_group(controller); - KeyReleaseEvent event(gtk_get_current_event_time(), group, keycode, keyval, modifiers); + KeyReleaseEvent event(gtk_get_current_event_time(), group, keycode, keyval, _state); return d->process_event(event.compatSynthesize().get()); } @@ -1136,9 +1131,7 @@ bool Canvas::on_motion(GtkEventControllerMotion *controller, double x, double y) d->autoscroll_end(); } - EventModifier modifiers(static_cast(_state)); - - MotionEvent event(gtk_get_current_event_time(), x, y, modifiers); + MotionEvent event(gtk_get_current_event_time(), x, y, _state); return d->process_event(event.compatSynthesize().get()); } @@ -1147,9 +1140,7 @@ bool Canvas::on_enter(GtkEventControllerMotion *controller, double x, double y) { update_modifiers(); - EventModifier modifiers(static_cast(_state)); - - EnterEvent event(gtk_get_current_event_time(), x, y, modifiers); + EnterEvent event(gtk_get_current_event_time(), x, y, _state); return d->process_event(event.compatSynthesize().get()); } @@ -1159,9 +1150,7 @@ bool Canvas::on_leave(GtkEventControllerMotion *controller) d->last_mouse = {}; update_modifiers(); - EventModifier modifiers(static_cast(_state)); - - LeaveEvent event(gtk_get_current_event_time(), modifiers); + LeaveEvent event(gtk_get_current_event_time(), _state); return d->process_event(event.compatSynthesize().get()); } @@ -1221,7 +1210,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) pre_scroll_grabbed_item = nullptr; // Pick the current item as if the button were not pressed... - set_pick_event(false, event->button.x, event->button.y, EventModifier(static_cast(event->button.state))); + set_pick_event(false, event->button.x, event->button.y, event->button.state); repick(); // ...then process the event. @@ -1240,7 +1229,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) auto event_copy = make_unique_copy(event); event_copy->button.state ^= calc_button_mask(); q->_state = event_copy->button.state; - set_pick_event(false, event_copy->button.x, event_copy->button.y, EventModifier(static_cast(event_copy->button.state))); + set_pick_event(false, event_copy->button.x, event_copy->button.y, event_copy->button.state); repick(); return retval; @@ -1248,7 +1237,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) case GDK_ENTER_NOTIFY: pre_scroll_grabbed_item = nullptr; - set_pick_event(true, event->crossing.x, event->crossing.y, EventModifier(static_cast(event->crossing.state))); + set_pick_event(true, event->crossing.x, event->crossing.y, event->crossing.state); return repick(); case GDK_LEAVE_NOTIFY: @@ -1257,7 +1246,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) if (q->_desktop) { q->_desktop->snapindicator->remove_snaptarget(); } - set_pick_event(false, event->crossing.x, event->crossing.y, EventModifier(static_cast(event->crossing.state))); + set_pick_event(false, event->crossing.x, event->crossing.y, event->crossing.state); return repick(); case GDK_KEY_PRESS: @@ -1266,7 +1255,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) case GDK_MOTION_NOTIFY: pre_scroll_grabbed_item = nullptr; - set_pick_event(false, event->motion.x, event->motion.y, EventModifier(static_cast(event->motion.state))); + set_pick_event(false, event->motion.x, event->motion.y, event->motion.state); repick(); return emit_event(event); @@ -1284,7 +1273,7 @@ bool CanvasPrivate::process_event(const GdkEvent *event) * @param y Vertical position of cursor * @param state Modifier keys and button state */ -void CanvasPrivate::set_pick_event(bool is_leave, double x, double y, EventModifier state) +void CanvasPrivate::set_pick_event(bool is_leave, double x, double y, unsigned state) { // Save the event in the canvas. This is used to synthesize enter and // leave events in case the current item changes. It is also used to diff --git a/src/ui/widget/canvas.h b/src/ui/widget/canvas.h index dcc20ada8b..272b37e90a 100644 --- a/src/ui/widget/canvas.h +++ b/src/ui/widget/canvas.h @@ -193,17 +193,17 @@ private: // Event handling/item picking // State used to find 'current' item - double _pick_x; - double _pick_y; - EventModifier _pick_state; // TODO: Merge with _state - bool _pick_is_leave; // TODO: Get rid of? + double _pick_x; + double _pick_y; + unsigned _pick_state; // TODO: Merge with _state + bool _pick_is_leave; // TODO: Get rid of? // State for repick operation bool _in_repick; ///< For tracking recursion of repick(). bool _left_grabbed_item; ///< ? bool _all_enter_events; ///< Keep all enter events. Only set true in connector-tool.cpp. bool _is_dragging; ///< Used in selection-chemistry to block undo/redo. - int _state; ///< Last known modifier state (SHIFT, CTRL, etc.). + unsigned _state; ///< Last known modifier state (SHIFT, CTRL, etc.). Inkscape::CanvasItem *_current_canvas_item; ///< Item containing cursor, nullptr if none. Inkscape::CanvasItem *_current_canvas_item_new; ///< Item to become _current_item, nullptr if none. diff --git a/src/ui/widget/events/canvas-event.cpp b/src/ui/widget/events/canvas-event.cpp index 84f06d1c65..d90af7a60f 100644 --- a/src/ui/widget/events/canvas-event.cpp +++ b/src/ui/widget/events/canvas-event.cpp @@ -11,177 +11,19 @@ #include namespace Inkscape::UI::Widget { -namespace { - -void debug_print_mods(EventModifier mods) -{ - std::cout - << "Control: " << mods.hasFlag(EventModifierType::CONTROL) << " / " - << "Shift: " << mods.hasFlag(EventModifierType::SHIFT) << " / " - << "Alt: " << mods.hasFlag(EventModifierType::ALT) << " / " - << "Left Button: " << mods.hasFlag(EventModifierType::LEFT_BUTTON) << " / " - << "Middle Button: " << mods.hasFlag(EventModifierType::MIDDLE_BUTTON) << " / " - << "Right Button: " << mods.hasFlag(EventModifierType::RIGHT_BUTTON) << " / " - << std::endl; -} - -/* -void debug_print_mods(GdkModifierType mods) -{ - std::cout - << "Control: " << (mods & GDK_CONTROL_MASK) << " / " - << "Shift: " << (mods & GDK_SHIFT_MASK) << " / " - << "Alt: " << (mods & GDK_MOD1_MASK) << " / " - << "Left Button: " << (mods & GDK_BUTTON1_MASK) << " / " - << "Middle Button: " << (mods & GDK_BUTTON2_MASK) << " / " - << "Right Button: " << (mods & GDK_BUTTON3_MASK) << " / " - << std::endl; -} -*/ - -/** - * Translate GdkModifierType -> EventModifier which provides a nicer and more - * limited API for accessing modifiers. - * - * Note that not all modifiers are transferred. Only the following are - * considered: - * - Control - * - Shift - * - Alt - * - * The mouse buttons are also translated for compatibility but should not - * be used in new code: - * - Left Button - * - Middle Button - * - Right Button - * - * @param state Inkscape modifier structure - * @param gdk_state GDK modifier flags - */ -void translate_modifiers(EventModifier &state, GdkModifierType gdk_state) -{ - state.clear(); - - if (gdk_state & GDK_CONTROL_MASK) - state.setFlag(EventModifierType::CONTROL); - - if (gdk_state & GDK_SHIFT_MASK) - state.setFlag(EventModifierType::SHIFT); - - if (gdk_state & GDK_MOD1_MASK) - state.setFlag(EventModifierType::ALT); - - if (gdk_state & GDK_MOD2_MASK) - state.setFlag(EventModifierType::META); - - if (gdk_state & GDK_BUTTON1_MASK) - state.setFlag(EventModifierType::LEFT_BUTTON); - - if (gdk_state & GDK_BUTTON2_MASK) - state.setFlag(EventModifierType::MIDDLE_BUTTON); - - if (gdk_state & GDK_BUTTON3_MASK) - state.setFlag(EventModifierType::RIGHT_BUTTON); -} - -/** - * Translate EventModifier -> GdkModifierType for compatibility with some internal - * functions which still depend on GdkEvent directly. - * - * Note this function does not guarantee a perfect round trip. It only translates - * the modifier keys stored in EventModifier and not anything else. - * - * Do not use this in new code. - * - * @param state Destination GdkModifierType - * @param modifier Source EventModifier - */ -GdkModifierType untranslate_modifiers(EventModifier modifier) -{ - guint32 raw_state = 0; - - if (modifier.hasFlag(EventModifierType::CONTROL)) - raw_state |= GDK_CONTROL_MASK; - - if (modifier.hasFlag(EventModifierType::SHIFT)) - raw_state |= GDK_SHIFT_MASK; - - if (modifier.hasFlag(EventModifierType::ALT)) - raw_state |= GDK_MOD1_MASK; - - if (modifier.hasFlag(EventModifierType::META)) - raw_state |= GDK_MOD2_MASK; - - if (modifier.hasFlag(EventModifierType::LEFT_BUTTON)) - raw_state |= GDK_BUTTON1_MASK; - - if (modifier.hasFlag(EventModifierType::MIDDLE_BUTTON)) - raw_state |= GDK_BUTTON2_MASK; - - if (modifier.hasFlag(EventModifierType::RIGHT_BUTTON)) - raw_state |= GDK_BUTTON3_MASK; - - return static_cast(raw_state); -} // Todo: Remove in C++23. template -auto to_underlying(T t) +static auto to_underlying(T t) { return static_cast>(t); } -} // namespace - -EventModifier::EventModifier(GdkModifierType state) -{ - // Round Trip Testing - // TODO: Move to unit test - /*auto original = static_cast(state & (GDK_BUTTON1_MASK|GDK_BUTTON2_MASK|GDK_BUTTON3_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK|GDK_CONTROL_MASK)); - translate_modifiers(*this, state); - debug_print_mods(*this); - - auto compare = untranslate_modifiers(*this); - debug_print_mods(compare); - debug_print_mods(original); - debug_print_mods(state); - - if (static_cast(original) != static_cast(compare)) - std::cout << "WRONG" << std::endl;*/ - - translate_modifiers(*this, state); -} - -void EventModifier::setFlag(EventModifierType type) -{ - _flags |= to_underlying(type); -} - -void EventModifier::unsetFlag(EventModifierType type) -{ - _flags &= ~to_underlying(type); -} - -bool EventModifier::hasFlag(EventModifierType type) const -{ - return (_flags & to_underlying(type)) == to_underlying(type); -} - -void EventModifier::clear() -{ - _flags = 0; -} - -void EventModifier::debugPrintMods() -{ - debug_print_mods(*this); -} - CanvasEvent::CanvasEvent(uint32_t time) : _time(time) {} -ButtonEvent::ButtonEvent(uint32_t time, EventButton button, double x, double y, EventModifier state) +ButtonEvent::ButtonEvent(uint32_t time, EventButton button, double x, double y, unsigned state) : CanvasEvent(time) , _button(button) , _x(x) @@ -189,7 +31,7 @@ ButtonEvent::ButtonEvent(uint32_t time, EventButton button, double x, double y, , _state(state) {} -ButtonPressEvent::ButtonPressEvent(uint32_t time, EventButton button, int n_press, double x, double y, EventModifier state) +ButtonPressEvent::ButtonPressEvent(uint32_t time, EventButton button, int n_press, double x, double y, unsigned state) : ButtonEvent(time, button, x, y, state) , _n_press(n_press) {} @@ -209,14 +51,14 @@ GdkEventUniqPtr ButtonPressEvent::compatSynthesize() auto button_event = reinterpret_cast(event.get()); button_event->x = _x; button_event->y = _y; - button_event->state = untranslate_modifiers(_state); + button_event->state = _state; button_event->button = to_underlying(_button); button_event->time = _time; return event; } -ButtonReleaseEvent::ButtonReleaseEvent(uint32_t time, EventButton button, double x, double y, EventModifier state) +ButtonReleaseEvent::ButtonReleaseEvent(uint32_t time, EventButton button, double x, double y, unsigned state) : ButtonEvent(time, button, x, y, state) {} @@ -227,19 +69,19 @@ GdkEventUniqPtr ButtonReleaseEvent::compatSynthesize() auto button_event = reinterpret_cast(event.get()); button_event->x = _x; button_event->y = _y; - button_event->state = untranslate_modifiers(_state); + button_event->state = _state; button_event->button = to_underlying(_button); button_event->time = _time; return event; } -PointerEvent::PointerEvent(uint32_t time, EventModifier state) +PointerEvent::PointerEvent(uint32_t time, unsigned state) : CanvasEvent(time) , _state(state) {} -MotionEvent::MotionEvent(uint32_t time, double x, double y, EventModifier state) +MotionEvent::MotionEvent(uint32_t time, double x, double y, unsigned state) : PointerEvent(time, state) , _x(x) , _y(y) @@ -252,13 +94,13 @@ GdkEventUniqPtr MotionEvent::compatSynthesize() auto motion_event = reinterpret_cast(event.get()); motion_event->x = _x; motion_event->y = _y; - motion_event->state = untranslate_modifiers(_state); + motion_event->state = _state; motion_event->time = _time; return event; } -EnterEvent::EnterEvent(uint32_t time, double x, double y, EventModifier state) +EnterEvent::EnterEvent(uint32_t time, double x, double y, unsigned state) : PointerEvent(time, state) , _x(x) , _y(y) @@ -271,13 +113,13 @@ GdkEventUniqPtr EnterEvent::compatSynthesize() auto crossing_event = reinterpret_cast(event.get()); crossing_event->x = _x; crossing_event->y = _y; - crossing_event->state = untranslate_modifiers(_state); + crossing_event->state = _state; crossing_event->time = _time; return event; } -LeaveEvent::LeaveEvent(uint32_t time, EventModifier state) +LeaveEvent::LeaveEvent(uint32_t time, unsigned state) : PointerEvent(time, state) {} @@ -288,13 +130,13 @@ GdkEventUniqPtr LeaveEvent::compatSynthesize() auto crossing_event = reinterpret_cast(event.get()); crossing_event->x = 0.0f; // x not relevant crossing_event->y = 0.0f; // y not relevant - crossing_event->state = untranslate_modifiers(_state); + crossing_event->state = _state; crossing_event->time = _time; return event; } -KeyEvent::KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state) +KeyEvent::KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, unsigned state) : CanvasEvent(time) , _group(group) , _hardware_keycode(keycode) @@ -302,7 +144,7 @@ KeyEvent::KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyv , _state(state) {} -KeyPressEvent::KeyPressEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state) +KeyPressEvent::KeyPressEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, unsigned state) : KeyEvent(time, group, keycode, keyval, state) {} @@ -313,14 +155,14 @@ GdkEventUniqPtr KeyPressEvent::compatSynthesize() auto key_event = reinterpret_cast(event.get()); key_event->keyval = _keyval; key_event->hardware_keycode = _hardware_keycode; - key_event->state = untranslate_modifiers(_state); + key_event->state = _state; key_event->group = _group; key_event->time = _time; return event; } -KeyReleaseEvent::KeyReleaseEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state) +KeyReleaseEvent::KeyReleaseEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, unsigned state) : KeyEvent(time, group, keycode, keyval, state) {} @@ -331,14 +173,14 @@ GdkEventUniqPtr KeyReleaseEvent::compatSynthesize() auto key_event = reinterpret_cast(event.get()); key_event->keyval = _keyval; key_event->hardware_keycode = _hardware_keycode; - key_event->state = untranslate_modifiers(_state); + key_event->state = _state; key_event->group = _group; key_event->time = _time; return event; } -ScrollEvent::ScrollEvent(uint32_t time, double dx, double dy, EventModifier state) +ScrollEvent::ScrollEvent(uint32_t time, double dx, double dy, unsigned state) : CanvasEvent(time) , _dx(dx) , _dy(dy) @@ -354,7 +196,7 @@ GdkEventUniqPtr ScrollEvent::compatSynthesize() scroll_event->delta_x = _dx; scroll_event->delta_y = _dy; scroll_event->direction = GDK_SCROLL_SMOOTH; - scroll_event->state = untranslate_modifiers(_state); + scroll_event->state = _state; scroll_event->time = _time; return event; diff --git a/src/ui/widget/events/canvas-event.h b/src/ui/widget/events/canvas-event.h index 73d0142578..5d6f3fc4f5 100644 --- a/src/ui/widget/events/canvas-event.h +++ b/src/ui/widget/events/canvas-event.h @@ -47,47 +47,6 @@ enum class EventType SCROLL }; -/** - * Types of modifier keys that can be pressed. - * - * The mouse buttons (Left/Middle/Right) are present for compatibility - * purposes only - avoid using these in new code. - * - * These values are arbitrary and should not be relied on in code. - */ -enum class EventModifierType -{ - CONTROL = 1 << 0, - SHIFT = 1 << 1, - ALT = 1 << 2, - META = 1 << 3, // Command on macOS - LEFT_BUTTON = 1 << 4, - MIDDLE_BUTTON = 1 << 5, - RIGHT_BUTTON = 1 << 6 -}; - -/** - * Represents the modifier key(s) relevant to this event. - * - * The value of _flags is arbitrary and should not be relied on - * in code. Use the accessors instead. - */ -class EventModifier -{ -public: - EventModifier() = default; - EventModifier(GdkModifierType state); - - void setFlag(EventModifierType type); - void unsetFlag(EventModifierType type); - bool hasFlag(EventModifierType type) const; - void clear(); - void debugPrintMods(); - -private: - uint8_t _flags; -}; - /** * Which mouse button the event pertains to. This is mapped to * GDK's own representation of button numbers so you can @@ -125,17 +84,17 @@ protected: class ButtonEvent : public CanvasEvent { public: - ButtonEvent(uint32_t time, EventButton button, double x, double y, EventModifier state); + ButtonEvent(uint32_t time, EventButton button, double x, double y, unsigned state); double getEventX() const { return _x; } double getEventY() const { return _y; } - EventModifier getModifiers() const { return _state; } + unsigned getModifiers() const { return _state; } EventButton getButton() const { return _button; } protected: double _x; double _y; - EventModifier _state; + unsigned _state; EventButton _button; }; @@ -146,11 +105,11 @@ protected: class ButtonPressEvent : public ButtonEvent { public: - ButtonPressEvent(uint32_t time, EventButton button, int n_press, double x, double y, EventModifier state); + ButtonPressEvent(uint32_t time, EventButton button, int n_press, double x, double y, unsigned state); EventType getEventType() const override { return EventType::BUTTON_PRESS; } GdkEventUniqPtr compatSynthesize() override; - int getNumPress() { return _n_press; }; + int getNumPress() const { return _n_press; }; private: int _n_press; @@ -163,7 +122,7 @@ private: class ButtonReleaseEvent : public ButtonEvent { public: - ButtonReleaseEvent(uint32_t time, EventButton button, double x, double y, EventModifier state); + ButtonReleaseEvent(uint32_t time, EventButton button, double x, double y, unsigned state); EventType getEventType() const override { return EventType::BUTTON_RELEASE; } GdkEventUniqPtr compatSynthesize() override; }; @@ -174,18 +133,18 @@ public: class KeyEvent : public CanvasEvent { public: - KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state); + KeyEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, unsigned state); uint8_t getGroup() const { return _group; } uint16_t getHardwareKeycode() const { return _hardware_keycode; } uint32_t getKeyval() const { return _keyval; } - EventModifier getModifiers() const { return _state; } + unsigned getModifiers() const { return _state; } protected: uint8_t _group; uint16_t _hardware_keycode; uint32_t _keyval; - EventModifier _state; + unsigned _state; }; /** @@ -194,7 +153,7 @@ protected: class KeyPressEvent : public KeyEvent { public: - KeyPressEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state); + KeyPressEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, unsigned state); EventType getEventType() const override { return EventType::KEY_PRESS; } GdkEventUniqPtr compatSynthesize() override; }; @@ -205,7 +164,7 @@ public: class KeyReleaseEvent : public KeyEvent { public: - KeyReleaseEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, EventModifier state); + KeyReleaseEvent(uint32_t time, uint8_t group, uint16_t keycode, uint32_t keyval, unsigned state); EventType getEventType() const override { return EventType::KEY_RELEASE; } GdkEventUniqPtr compatSynthesize() override; }; @@ -216,12 +175,12 @@ public: class PointerEvent : public CanvasEvent { public: - PointerEvent(uint32_t time, EventModifier state); + PointerEvent(uint32_t time, unsigned state); - EventModifier getModifiers() const { return _state; } + unsigned getModifiers() const { return _state; } protected: - EventModifier _state; + unsigned _state; }; /** @@ -231,12 +190,12 @@ protected: class MotionEvent : public PointerEvent { public: - MotionEvent(uint32_t time, double x, double y, EventModifier state); + MotionEvent(uint32_t time, double x, double y, unsigned state); EventType getEventType() const override { return EventType::MOTION; } GdkEventUniqPtr compatSynthesize() override; - double getEventX() { return _x; } - double getEventY() { return _y; } + double getEventX() const { return _x; } + double getEventY() const { return _y; } private: double _x; @@ -249,12 +208,12 @@ private: class EnterEvent : public PointerEvent { public: - EnterEvent(uint32_t time, double x, double y, EventModifier state); + EnterEvent(uint32_t time, double x, double y, unsigned state); EventType getEventType() const override { return EventType::ENTER; } GdkEventUniqPtr compatSynthesize() override; - double getEventX() { return _x; } - double getEventY() { return _y; } + double getEventX() const { return _x; } + double getEventY() const { return _y; } private: double _x; @@ -269,7 +228,7 @@ private: class LeaveEvent : public PointerEvent { public: - LeaveEvent(uint32_t time, EventModifier state); + LeaveEvent(uint32_t time, unsigned state); EventType getEventType() const override { return EventType::LEAVE; } GdkEventUniqPtr compatSynthesize() override; }; @@ -280,18 +239,18 @@ public: class ScrollEvent : public CanvasEvent { public: - ScrollEvent(uint32_t time, double dx, double dy, EventModifier state); + ScrollEvent(uint32_t time, double dx, double dy, unsigned state); EventType getEventType() const override { return EventType::SCROLL; } GdkEventUniqPtr compatSynthesize() override; - EventModifier getModifiers() const { return _state; } - double getDeltaX() { return _dx; } - double getDeltaY() { return _dy; } + unsigned getModifiers() const { return _state; } + double getDeltaX() const { return _dx; } + double getDeltaY() const { return _dy; } private: double _dx; double _dy; - EventModifier _state; + unsigned _state; }; } // namespace Inkscape::UI::Widget -- GitLab From a77c9bbdbf2e30681dd71987859e4cd3b0770598 Mon Sep 17 00:00:00 2001 From: PBS Date: Wed, 14 Jun 2023 15:15:44 +0900 Subject: [PATCH 4/4] Fix text tool and double clicking issues from events rework * Store window and device in events before passing them to the input method for filtering. Fixes text entry with custom input methods. * Refactor text tool to handle the fact clicks no longer happen before double clicks. Fixes double/triple click to select text. * Similarly fix double click handling in control points. Fixes double click to insert node on path. * Remove dead code from KnotPropertiesDialog. --- src/desktop.cpp | 3 +- src/text-editing.cpp | 65 ++++++++++++----------- src/ui/dialog/knot-properties.cpp | 21 -------- src/ui/dialog/knot-properties.h | 25 ++++----- src/ui/tool/control-point.cpp | 8 +-- src/ui/tools/text-tool.cpp | 88 +++++++++++++++---------------- 6 files changed, 93 insertions(+), 117 deletions(-) diff --git a/src/desktop.cpp b/src/desktop.cpp index 22f195b647..1e76f0e6b4 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -211,7 +211,8 @@ SPDesktop::init (SPNamedView *nv, Inkscape::UI::Widget::Canvas *acanvas, SPDeskt canvas_group_sketch->set_pickable(false); // Temporary items are not pickable! canvas_group_temp->set_pickable(false); // Temporary items are not pickable! - // The root should never emit events. The "catchall" should get it! (CHECK) + // The root should never emit events. The "catchall" should get it! + // But somehow there are still exceptions, e.g. Ctrl+scroll to zoom. canvas_item_root->connect_event(sigc::bind(sigc::ptr_fun(sp_desktop_root_handler), this)); canvas_catchall->connect_event(sigc::bind(sigc::ptr_fun(sp_desktop_root_handler), this)); diff --git a/src/text-editing.cpp b/src/text-editing.cpp index 375e4ef182..727dbb3044 100644 --- a/src/text-editing.cpp +++ b/src/text-editing.cpp @@ -45,59 +45,64 @@ static void move_child_nodes(Inkscape::XML::Node *from_repr, Inkscape::XML::Node static bool tidy_xml_tree_recursively(SPObject *root, bool has_text_decoration); -Inkscape::Text::Layout const * te_get_layout (SPItem const *item) +Inkscape::Text::Layout const *te_get_layout(SPItem const *item) { - if (is(item)) { - return &(cast(item)->layout); - } else if (is(item)) { - return &(cast(item)->layout); + if (auto text = cast(item)) { + return &text->layout; + } else if (auto flow = cast(item)) { + return &flow->layout; + } else { + return nullptr; } - return nullptr; } -static void te_update_layout_now (SPItem *item) +static void te_update_layout_now(SPItem *item) { - if (is(item)) - cast(item)->rebuildLayout(); - else if (is(item)) - cast(item)->rebuildLayout(); + if (auto text = cast(item)) { + text->rebuildLayout(); + } else if (auto flow = cast(item)) { + flow->rebuildLayout(); + } else { + return; + } item->updateRepr(); } void te_update_layout_now_recursive(SPItem *item) { - if (is(item)) { - std::vector item_list = cast(item)->item_list(); - for(auto list_item : item_list){ - te_update_layout_now_recursive(list_item); + if (auto group = cast(item)) { + for (auto childitem : group->item_list()) { + te_update_layout_now_recursive(childitem); } - } else if (is(item)) - cast(item)->rebuildLayout(); - else if (is(item)) - cast(item)->rebuildLayout(); + } else if (auto text = cast(item)) { + text->rebuildLayout(); + } else if (auto flow = cast(item)) { + flow->rebuildLayout(); + } else { + return; + } item->updateRepr(); } bool sp_te_output_is_empty(SPItem const *item) { - Inkscape::Text::Layout const *layout = te_get_layout(item); + auto layout = te_get_layout(item); return layout->begin() == layout->end(); } bool sp_te_input_is_empty(SPObject const *item) { - bool empty = true; - if (is(item)) { - empty = cast(item)->string.empty(); - } else { - for (auto& child: item->children) { - if (!sp_te_input_is_empty(&child)) { - empty = false; - break; - } + if (auto str = cast(item)) { + return str->string.empty(); + } + + for (auto &child : item->children) { + if (!sp_te_input_is_empty(&child)) { + return false; } } - return empty; + + return true; } Inkscape::Text::Layout::iterator diff --git a/src/ui/dialog/knot-properties.cpp b/src/ui/dialog/knot-properties.cpp index 048cd33c4f..74eef23467 100644 --- a/src/ui/dialog/knot-properties.cpp +++ b/src/ui/dialog/knot-properties.cpp @@ -137,27 +137,6 @@ KnotPropertiesDialog::_close() ); } -bool KnotPropertiesDialog::_handleKeyEvent(GdkEventKey * /*event*/) -{ - - /*switch (get_latin_keyval(event)) { - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: { - _apply(); - return true; - } - break; - }*/ - return false; -} - -void KnotPropertiesDialog::_handleButtonEvent(GdkEventButton* event) -{ - if ( (event->type == GDK_2BUTTON_PRESS) && (event->button == 1) ) { - _apply(); - } -} - void KnotPropertiesDialog::_setKnotPoint(Geom::Point knotpoint, Glib::ustring const unit_name) { _unit_name = unit_name; diff --git a/src/ui/dialog/knot-properties.h b/src/ui/dialog/knot-properties.h index 18f674500d..b475f8b677 100644 --- a/src/ui/dialog/knot-properties.h +++ b/src/ui/dialog/knot-properties.h @@ -29,9 +29,12 @@ namespace Dialogs { // Used in Measure tool to set ends of "ruler" (via Shift-click)." -class KnotPropertiesDialog : public Gtk::Dialog { - public: - KnotPropertiesDialog(); +class KnotPropertiesDialog : public Gtk::Dialog +{ +public: + KnotPropertiesDialog(); + KnotPropertiesDialog(KnotPropertiesDialog const &) = delete; + KnotPropertiesDialog &operator=(KnotPropertiesDialog const &) = delete; ~KnotPropertiesDialog() override; Glib::ustring getName() const { return "LayerPropertiesDialog"; } @@ -66,23 +69,15 @@ protected: void _close(); void _setKnotPoint(Geom::Point knotpoint, Glib::ustring const unit_name); - void _prepareLabelRenderer(Gtk::TreeModel::const_iterator const &row); - bool _handleKeyEvent(GdkEventKey *event); - void _handleButtonEvent(GdkEventButton* event); friend class Inkscape::UI::Tools::MeasureTool; - -private: - KnotPropertiesDialog(KnotPropertiesDialog const &); // no copy - KnotPropertiesDialog &operator=(KnotPropertiesDialog const &); // no assign }; -} // namespace -} // namespace -} // namespace +} // namespace Dialogs +} // namespace UI +} // namespace Inkscape - -#endif //INKSCAPE_DIALOG_LAYER_PROPERTIES_H +#endif // INKSCAPE_DIALOG_KNOT_PROPERTIES_H /* Local Variables: diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp index 1c40dfb54e..be30c4c494 100644 --- a/src/ui/tool/control-point.cpp +++ b/src/ui/tool/control-point.cpp @@ -231,7 +231,8 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G switch(event->type) { case GDK_BUTTON_PRESS: - next_release_doubleclick = 0; + case GDK_2BUTTON_PRESS: + next_release_doubleclick = event->type == GDK_2BUTTON_PRESS ? event->button.button : 0; if (event->button.button == 1 && !event_context->is_space_panning()) { // 1st mouse button click. internally, start dragging, but do not emit signals // or change position until drag tolerance is exceeded. @@ -246,11 +247,6 @@ bool ControlPoint::_eventHandler(Inkscape::UI::Tools::ToolBase *event_context, G return true; } return _event_grab; - - case GDK_2BUTTON_PRESS: - // store the button number for next release - next_release_doubleclick = event->button.button; - return true; case GDK_MOTION_NOTIFY: if (_event_grab && ! event_context->is_space_panning()) { diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp index 9aaffa7d0c..b89b2b8046 100644 --- a/src/ui/tools/text-tool.cpp +++ b/src/ui/tools/text-tool.cpp @@ -215,85 +215,75 @@ void TextTool::deleteSelected() DocumentUndo::done(_desktop->getDocument(), _("Delete text"), INKSCAPE_ICON("draw-text")); } -bool TextTool::item_handler(SPItem* item, GdkEvent* event) { - SPItem *item_ungrouped; - - gint ret = FALSE; +bool TextTool::item_handler(SPItem *item, GdkEvent *event) +{ sp_text_context_validate_cursor_iterators(this); - Inkscape::Text::Layout::iterator old_start = this->text_sel_start; switch (event->type) { case GDK_BUTTON_PRESS: if (event->button.button == 1) { - // this var allow too much lees subbselection queries - // reducing it to cursor iteracion, mouseup and down - // find out clicked item, disregarding groups - item_ungrouped = _desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); + auto item_ungrouped = _desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), true); if (is(item_ungrouped) || is(item_ungrouped)) { + auto old_start = text_sel_start; _desktop->getSelection()->set(item_ungrouped); - if (this->text) { - // find out click point in document coordinates - Geom::Point p = _desktop->w2d(Geom::Point(event->button.x, event->button.y)); - // set the cursor closest to that point + if (text) { + // Find click point in document coordinates. + auto p = _desktop->w2d(Geom::Point(event->button.x, event->button.y)); + // Set the cursor closest to that point. if (event->button.state & GDK_SHIFT_MASK) { - this->text_sel_start = old_start; - this->text_sel_end = sp_te_get_position_by_coords(this->text, p); + text_sel_start = old_start; + text_sel_end = sp_te_get_position_by_coords(text, p); } else { - this->text_sel_start = this->text_sel_end = sp_te_get_position_by_coords(this->text, p); + text_sel_start = text_sel_end = sp_te_get_position_by_coords(text, p); } - // update display + // Update display. sp_text_context_update_cursor(this); sp_text_context_update_text_selection(this); - this->dragging = 1; + dragging = 1; } - ret = TRUE; + return true; } } break; case GDK_2BUTTON_PRESS: - if (event->button.button == 1 && this->text && this->dragging) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (layout) { - if (!layout->isStartOfWord(this->text_sel_start)) - this->text_sel_start.prevStartOfWord(); - if (!layout->isEndOfWord(this->text_sel_end)) - this->text_sel_end.nextEndOfWord(); + if (event->button.button == 1 && text) { + if (auto layout = te_get_layout(text)) { + if (!layout->isStartOfWord(text_sel_start)) { + text_sel_start.prevStartOfWord(); + } + if (!layout->isEndOfWord(text_sel_end)) { + text_sel_end.nextEndOfWord(); + } sp_text_context_update_cursor(this); sp_text_context_update_text_selection(this); - this->dragging = 2; - ret = TRUE; + dragging = 2; + return true; } } break; case GDK_3BUTTON_PRESS: - if (event->button.button == 1 && this->text && this->dragging) { - this->text_sel_start.thisStartOfLine(); - this->text_sel_end.thisEndOfLine(); + if (event->button.button == 1 && text) { + text_sel_start.thisStartOfLine(); + text_sel_end.thisEndOfLine(); sp_text_context_update_cursor(this); sp_text_context_update_text_selection(this); - this->dragging = 3; - ret = TRUE; + dragging = 3; + return true; } break; case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && this->dragging) { - this->dragging = 0; - this->discard_delayed_snap_event(); - ret = TRUE; + if (event->button.button == 1 && dragging) { + dragging = 0; + discard_delayed_snap_event(); _desktop->emit_text_cursor_moved(this, this); + return true; } break; - case GDK_MOTION_NOTIFY: - break; default: break; } - if (!ret) { - ret = ToolBase::item_handler(item, event); - } - - return ret; + return ToolBase::item_handler(item, event); } static void sp_text_context_setup_text(TextTool *tc) @@ -654,6 +644,11 @@ bool TextTool::root_handler(GdkEvent* event) { bool preedit_activation = (MOD__CTRL(event) && MOD__SHIFT(event) && !MOD__ALT(event)) && (group0_keyval == GDK_KEY_U || group0_keyval == GDK_KEY_u); + // Fixme: Hack needed to get custom input methods to work properly. + // In GTK4 we should be using gtk_im_context_filter_key() instead. + g_set_object(&event->key.window, _desktop->getCanvas()->get_window()->gobj()); + gdk_event_set_device(event, gtk_get_current_event_device()); + if (this->unimode || !this->imc || preedit_activation || !gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { // IM did not consume the key, or we're in unimode @@ -1218,6 +1213,11 @@ bool TextTool::root_handler(GdkEvent* event) { } case GDK_KEY_RELEASE: + // Fixme: Hack needed to get custom input methods to work properly. + // In GTK4 we should be using gtk_im_context_filter_key() instead. + g_set_object(&event->key.window, _desktop->getCanvas()->get_window()->gobj()); + gdk_event_set_device(event, gtk_get_current_event_device()); + if (!this->unimode && this->imc && gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { return TRUE; } -- GitLab