From dd8cc8973f3561ab73a66389068082077f37a73e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 20 Nov 2025 20:35:33 +0100 Subject: [PATCH] Implemented a solution for issue #2214 - The mouse release event's button mask reflects the keyboard modifiers at the time the button was released - Same for click event - Selection tool and partial selection uses the buttons from the release event - New bitmap mask constants are available to extract the keyboard modifiers and mouse buttons from the button mask. - Documentation was enhanced --- src/edt/edt/edtPartialService.cc | 4 +- src/laybasic/laybasic/gsiDeclLayAdded.cc | 4 ++ src/laybasic/laybasic/gsiDeclLayPlugin.cc | 76 +++++++++++++++-------- src/laybasic/laybasic/laySelector.cc | 8 +-- src/laybasic/laybasic/laySelector.h | 1 - src/laybasic/laybasic/layViewObject.cc | 22 ++++--- src/laybasic/laybasic/layViewObject.h | 14 +++-- 7 files changed, 81 insertions(+), 48 deletions(-) diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index c1d80a2bb..b056b5692 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -2315,8 +2315,8 @@ PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons, if (ui ()->mouse_event_viewport ().contains (p)) { lay::Editable::SelectionMode mode = lay::Editable::Replace; - bool shift = ((m_buttons & lay::ShiftButton) != 0); - bool ctrl = ((m_buttons & lay::ControlButton) != 0); + bool shift = ((buttons & lay::ShiftButton) != 0); + bool ctrl = ((buttons & lay::ControlButton) != 0); if (shift && ctrl) { mode = lay::Editable::Invert; } else if (shift) { diff --git a/src/laybasic/laybasic/gsiDeclLayAdded.cc b/src/laybasic/laybasic/gsiDeclLayAdded.cc index 32cb1c54d..13f0b4f88 100644 --- a/src/laybasic/laybasic/gsiDeclLayAdded.cc +++ b/src/laybasic/laybasic/gsiDeclLayAdded.cc @@ -84,11 +84,15 @@ class ButtonStateNamespace { }; static int const_ShiftButton() { return (int) lay::ShiftButton; } static int const_ControlButton() { return (int) lay::ControlButton; } static int const_AltButton() { return (int) lay::AltButton; } +static int const_ModifierMask() { return (int) lay::ModifierMask; } static int const_LeftButton() { return (int) lay::LeftButton; } static int const_MidButton() { return (int) lay::MidButton; } static int const_RightButton() { return (int) lay::RightButton; } +static int const_MouseButtonMask() { return (int) lay::MouseButtonMask; } Class decl_ButtonState ("lay", "ButtonState", + method ("ModifierMask", &const_ModifierMask, "@brief A bit mask that selects all keyboard modifiers in the button state mask\nThis constant has been introduced in version 0.30.6.") + + method ("MouseButtonMask", &const_MouseButtonMask, "@brief A bit mask that selects all mouse buttons in the button state mask\nThis constant has been introduced in version 0.30.6.") + method ("ShiftKey", &const_ShiftButton, "@brief Indicates that the Shift key is pressed\nThis constant is combined with other constants within \\ButtonState") + method ("ControlKey", &const_ControlButton, "@brief Indicates that the Control key is pressed\nThis constant is combined with other constants within \\ButtonState") + method ("AltKey", &const_AltButton, "@brief Indicates that the Alt key is pressed\nThis constant is combined with other constants within \\ButtonState") + diff --git a/src/laybasic/laybasic/gsiDeclLayPlugin.cc b/src/laybasic/laybasic/gsiDeclLayPlugin.cc index 28d5245b3..ef3d696dc 100644 --- a/src/laybasic/laybasic/gsiDeclLayPlugin.cc +++ b/src/laybasic/laybasic/gsiDeclLayPlugin.cc @@ -125,58 +125,76 @@ static void drag_cancel_impl (lay::EditorServiceBase *p) Class decl_PluginBase ("lay", "PluginBase", gsi::method_ext ("tracking_position", &tracking_position_impl, - "@brief Gets the tracking position (base class implementation)" + "@brief Gets the tracking position (base class implementation)\n" + "See \\Plugin#tracking_position for details." ) + gsi::method_ext ("has_tracking_position", &has_tracking_position_impl, - "@brief Gets a value indicating whether the plugin provides a tracking position (base class implementation)" + "@brief Gets a value indicating whether the plugin provides a tracking position (base class implementation)\n" + "See \\Plugin#has_tracking_position for details." ) + gsi::method_ext ("menu_activated", &menu_activated_impl, gsi::arg ("symbol"), - "@brief Gets called when a custom menu item is selected (base class implementation)" + "@brief Gets called when a custom menu item is selected (base class implementation)\n" + "See \\Plugin#menu_activated for details." ) + gsi::method_ext ("configure", &configure_impl, gsi::arg ("name"), gsi::arg ("value"), - "@brief Sends configuration requests to the plugin (base class implementation)" + "@brief Sends configuration requests to the plugin (base class implementation)\n" + "See \\Plugin#configure for details." ) + gsi::method_ext ("config_finalize", &config_finalize_impl, - "@brief Sends the post-configuration request to the plugin (base class implementation)" + "@brief Sends the post-configuration request to the plugin (base class implementation)\n" + "See \\Plugin#config_finalize for details." ) + gsi::method_ext ("key_event", &key_event_impl, gsi::arg ("key"), gsi::arg ("buttons"), - "@brief Handles the key pressed event (base class implementation)" + "@brief Handles the key pressed event (base class implementation)\n" + "See \\Plugin#key_event for details." ) + gsi::method_ext ("mouse_button_pressed_event", &mouse_press_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button pressed event (base class implementation)" + "@brief Handles the mouse button pressed event (base class implementation)\n" + "See \\Plugin#mouse_button_pressed_event for details." ) + gsi::method_ext ("mouse_click_event", &mouse_click_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button click event after the button has been released (base class implementation)" + "@brief Handles the mouse button click event after the button has been released (base class implementation)\n" + "See \\Plugin#mouse_click_event for details." ) + gsi::method_ext ("mouse_double_click_event", &mouse_double_click_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button double-click event (base class implementation)" + "@brief Handles the mouse button double-click event (base class implementation)\n" + "See \\Plugin#mouse_double_click_event for details." ) + gsi::method_ext ("leave_event", &leave_event_impl, gsi::arg ("prio"), - "@brief Handles the leave event (base class implementation)" + "@brief Handles the leave event (base class implementation)\n" + "See \\Plugin#leave_event for details." ) + gsi::method_ext ("enter_event", &enter_event_impl, gsi::arg ("prio"), - "@brief Handles the enter event (base class implementation)" + "@brief Handles the enter event (base class implementation)\n" + "See \\Plugin#enter_event for details." ) + gsi::method_ext ("mouse_moved_event", &mouse_move_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse move event (base class implementation)" + "@brief Handles the mouse move event (base class implementation)\n" + "See \\Plugin#mouse_moved_event for details." ) + gsi::method_ext ("mouse_button_released_event", &mouse_release_event_impl, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse button release event (base class implementation)" + "@brief Handles the mouse button release event (base class implementation)\n" + "See \\Plugin#mouse_button_released_event for details." ) + gsi::method_ext ("wheel_event", &wheel_event_impl, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), - "@brief Handles the mouse wheel event (base class implementation)" + "@brief Handles the mouse wheel event (base class implementation)\n" + "See \\Plugin#wheel_event for details." ) + gsi::method_ext ("activated", &activated_impl, - "@brief Gets called when the plugin is activated (base class implementation)" + "@brief Gets called when the plugin is activated (base class implementation)\n" + "See \\Plugin#activated for details." ) + gsi::method_ext ("deactivated", &deactivated_impl, - "@brief Gets called when the plugin is deactivated and another plugin is activated (base class implementation)" + "@brief Gets called when the plugin is deactivated and another plugin is activated (base class implementation)\n" + "See \\Plugin#deactivated for details." ) + gsi::method_ext ("drag_cancel", &drag_cancel_impl, - "@brief This method is called when some mouse dragging operation should be cancelled (base class implementation)" + "@brief This method is called when some mouse dragging operation should be cancelled (base class implementation)\n" + "See \\Plugin#drag_cancel for details." ) + gsi::method_ext ("update", &update_impl, - "@brief Gets called when the view has changed (base class implementation)" + "@brief Gets called when the view has changed (base class implementation)\n" + "See \\Plugin#update for details." ), "@brief The plugin base class\n" "\n" @@ -735,36 +753,44 @@ Class decl_Plugin (decl_PluginBase, "lay", "Plugin", ) + callback ("mouse_click_event", &gsi::PluginImpl::mouse_click_event_noref, &gsi::PluginImpl::f_mouse_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button click event (after the button has been released)\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been released without moving it.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse button has been released without moving it.\n" + "A mouse click is not defined by duration, but by releasing a button without moving the mouse after the button was pressed. " + "As a consequence, a \\mouse_button_pressed_event is always issued at the beginning, but it is not followed by a \\mouse_button_released_event.\n" + "Instead, the 'mouse_click_event' is issued.\n" + "\n" + "Starting with version 0.30.6, the button mask reflects the keyboard modifiers at the moment the mouse was released. Before, the keyboard modifiers were " + "captured at the moment when the mouse was pressed." ) + callback ("mouse_double_click_event", &gsi::PluginImpl::mouse_double_click_event_noref, &gsi::PluginImpl::f_mouse_double_click_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button double-click event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button has been double-clicked.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse button has been double-clicked.\n" ) + callback ("leave_event", &gsi::PluginImpl::leave_event, &gsi::PluginImpl::f_leave_event, gsi::arg ("prio"), "@brief Handles the leave event (mouse leaves canvas area of view)\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse leaves the canvas area.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse leaves the canvas area.\n" "This method does not have a position nor button flags.\n" ) + callback ("enter_event", &gsi::PluginImpl::enter_event, &gsi::PluginImpl::f_enter_event, gsi::arg ("prio"), "@brief Handles the enter event (mouse enters canvas area of view)\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse enters the canvas area.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse enters the canvas area.\n" "This method does not have a position nor button flags.\n" ) + callback ("mouse_moved_event", &gsi::PluginImpl::mouse_move_event_noref, &gsi::PluginImpl::f_mouse_move_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse move event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse is moved in the canvas area.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse is moved in the canvas area.\n" "\n" "The mouse move event is important for a number of background jobs, such as coordinate display in the status bar.\n" "Hence, you should not consume the event - i.e. you should return 'false' from this method.\n" ) + callback ("mouse_button_released_event", &gsi::PluginImpl::mouse_release_event_noref, &gsi::PluginImpl::f_mouse_release_event, gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse button release event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse button is released.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse button is released.\n" + "Starting with version 0.30.6, the button mask reflects the keyboard modifiers at the moment the mouse was released. Before, the keyboard modifiers were " + "captured at the moment when the mouse was pressed." ) + callback ("wheel_event", &gsi::PluginImpl::wheel_event_noref, &gsi::PluginImpl::f_wheel_event, gsi::arg ("delta"), gsi::arg ("horizontal"), gsi::arg ("p"), gsi::arg ("buttons"), gsi::arg ("prio"), "@brief Handles the mouse wheel event\n" - "The behaviour of this callback is the same than for \\mouse_press_event, except that it is called when the mouse wheel is rotated.\n" + "The behaviour of this callback is the same than for \\mouse_button_pressed_event, except that it is called when the mouse wheel is rotated.\n" "Additional parameters for this event are 'delta' (the rotation angle in units of 1/8th degree) and 'horizontal' which is true when the horizontal wheel was rotated and " "false if the vertical wheel was rotated.\n" ) + diff --git a/src/laybasic/laybasic/laySelector.cc b/src/laybasic/laybasic/laySelector.cc index 8a86a6048..b5e7cf4cb 100644 --- a/src/laybasic/laybasic/laySelector.cc +++ b/src/laybasic/laybasic/laySelector.cc @@ -47,7 +47,6 @@ SelectionService::SelectionService (lay::LayoutViewBase *view) : mp_view (view), mp_box (0), m_color (0), - m_buttons (0), m_hover (false), m_hover_wait (false), m_mouse_in_window (false) @@ -204,7 +203,6 @@ SelectionService::mouse_press_event (const db::DPoint &p, unsigned int buttons, if ((buttons & lay::LeftButton) != 0) { mp_view->stop_redraw (); // TODO: how to restart if selection is aborted? - m_buttons = buttons; begin (p); return true; } @@ -263,7 +261,7 @@ SelectionService::mouse_click_event (const db::DPoint &p, unsigned int buttons, } bool -SelectionService::mouse_release_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, bool prio) +SelectionService::mouse_release_event (const db::DPoint & /*p*/, unsigned int buttons, bool prio) { hover_reset (); @@ -274,8 +272,8 @@ SelectionService::mouse_release_event (const db::DPoint & /*p*/, unsigned int /* if (mp_view) { lay::Editable::SelectionMode mode = lay::Editable::Replace; - bool shift = ((m_buttons & lay::ShiftButton) != 0); - bool ctrl = ((m_buttons & lay::ControlButton) != 0); + bool shift = ((buttons & lay::ShiftButton) != 0); + bool ctrl = ((buttons & lay::ControlButton) != 0); if (shift && ctrl) { mode = lay::Editable::Invert; } else if (shift) { diff --git a/src/laybasic/laybasic/laySelector.h b/src/laybasic/laybasic/laySelector.h index d4f29dbd3..0d9eee884 100644 --- a/src/laybasic/laybasic/laySelector.h +++ b/src/laybasic/laybasic/laySelector.h @@ -91,7 +91,6 @@ private: lay::LayoutViewBase *mp_view; lay::RubberBox *mp_box; unsigned int m_color; - unsigned int m_buttons; bool m_hover; bool m_hover_wait; db::DPoint m_hover_point; diff --git a/src/laybasic/laybasic/layViewObject.cc b/src/laybasic/laybasic/layViewObject.cc index 6efa9f881..488ff5fbb 100644 --- a/src/laybasic/laybasic/layViewObject.cc +++ b/src/laybasic/laybasic/layViewObject.cc @@ -907,7 +907,7 @@ ViewObjectUI::send_mouse_double_clicked_event (const db::DPoint &pt, unsigned in } void -ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int /*buttons*/) +ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int buttons) { try { @@ -916,23 +916,27 @@ ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int /*but bool done = false; + // Qt does not include the released button in the mask, so we take the mouse buttons that we stored + // on "press", but use the current modifiers (issue #2214) + unsigned int effective_buttons = (m_mouse_buttons & lay::MouseButtonMask) | (buttons & lay::ModifierMask); + m_mouse_pos = pt; db::DPoint p = pixel_to_um (m_mouse_pos); auto grabbed = m_grabbed; for (auto g = grabbed.begin (); !done && g != grabbed.end (); ++g) { if (m_mouse_pressed_state) { - done = (*g)->enabled () && (*g)->mouse_click_event (p, m_mouse_buttons, true); + done = (*g)->enabled () && (*g)->mouse_click_event (p, effective_buttons, true); } else { - done = (*g)->enabled () && (*g)->mouse_release_event (p, m_mouse_buttons, true); + done = (*g)->enabled () && (*g)->mouse_release_event (p, effective_buttons, true); } } if (! done && mp_active_service && mp_active_service->enabled ()) { if (m_mouse_pressed_state) { - done = mp_active_service->mouse_click_event (p, m_mouse_buttons, true); + done = mp_active_service->mouse_click_event (p, effective_buttons, true); } else { - done = mp_active_service->mouse_release_event (p, m_mouse_buttons, true); + done = mp_active_service->mouse_release_event (p, effective_buttons, true); } } @@ -942,9 +946,9 @@ ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int /*but ++next; if ((*svc)->enabled ()) { if (m_mouse_pressed_state) { - done = (*svc)->mouse_click_event (p, m_mouse_buttons, false); + done = (*svc)->mouse_click_event (p, effective_buttons, false); } else { - done = (*svc)->mouse_release_event (p, m_mouse_buttons, false); + done = (*svc)->mouse_release_event (p, effective_buttons, false); } } svc = next; @@ -952,9 +956,9 @@ ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int /*but if (! done) { if (m_mouse_pressed_state) { - mouse_click_event (p, m_mouse_buttons); + mouse_click_event (p, effective_buttons); } else { - mouse_release_event (p, m_mouse_buttons); + mouse_release_event (p, effective_buttons); } } diff --git a/src/laybasic/laybasic/layViewObject.h b/src/laybasic/laybasic/layViewObject.h index a175e5c5d..6ef9b691e 100644 --- a/src/laybasic/laybasic/layViewObject.h +++ b/src/laybasic/laybasic/layViewObject.h @@ -548,12 +548,14 @@ private: * @brief Describes the button state (supposed to be ored) */ enum ButtonState { - ShiftButton = 1, - ControlButton = 2, - AltButton = 4, - LeftButton = 8, - MidButton = 16, - RightButton = 32 + ShiftButton = 0x01, + ControlButton = 0x02, + AltButton = 0x04, + ModifierMask = 0x07, // all keyboard modifiers + LeftButton = 0x08, + MidButton = 0x10, + RightButton = 0x20, + MouseButtonMask = 0x38 // all mouse buttons }; /**