Merge pull request #2225 from KLayout/feature/issue-2214

Implemented a solution for issue #2214
This commit is contained in:
Matthias Köfferlein 2025-12-07 22:27:08 +01:00 committed by GitHub
commit b8ff75d6a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 81 additions and 48 deletions

View File

@ -2315,8 +2315,8 @@ PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons,
if (ui ()->mouse_event_viewport ().contains (p)) { if (ui ()->mouse_event_viewport ().contains (p)) {
lay::Editable::SelectionMode mode = lay::Editable::Replace; lay::Editable::SelectionMode mode = lay::Editable::Replace;
bool shift = ((m_buttons & lay::ShiftButton) != 0); bool shift = ((buttons & lay::ShiftButton) != 0);
bool ctrl = ((m_buttons & lay::ControlButton) != 0); bool ctrl = ((buttons & lay::ControlButton) != 0);
if (shift && ctrl) { if (shift && ctrl) {
mode = lay::Editable::Invert; mode = lay::Editable::Invert;
} else if (shift) { } else if (shift) {

View File

@ -84,11 +84,15 @@ class ButtonStateNamespace { };
static int const_ShiftButton() { return (int) lay::ShiftButton; } static int const_ShiftButton() { return (int) lay::ShiftButton; }
static int const_ControlButton() { return (int) lay::ControlButton; } static int const_ControlButton() { return (int) lay::ControlButton; }
static int const_AltButton() { return (int) lay::AltButton; } 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_LeftButton() { return (int) lay::LeftButton; }
static int const_MidButton() { return (int) lay::MidButton; } static int const_MidButton() { return (int) lay::MidButton; }
static int const_RightButton() { return (int) lay::RightButton; } static int const_RightButton() { return (int) lay::RightButton; }
static int const_MouseButtonMask() { return (int) lay::MouseButtonMask; }
Class<gsi::ButtonStateNamespace> decl_ButtonState ("lay", "ButtonState", Class<gsi::ButtonStateNamespace> 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 ("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 ("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") + method ("AltKey", &const_AltButton, "@brief Indicates that the Alt key is pressed\nThis constant is combined with other constants within \\ButtonState") +

View File

@ -125,58 +125,76 @@ static void drag_cancel_impl (lay::EditorServiceBase *p)
Class<lay::EditorServiceBase> decl_PluginBase ("lay", "PluginBase", Class<lay::EditorServiceBase> decl_PluginBase ("lay", "PluginBase",
gsi::method_ext ("tracking_position", &tracking_position_impl, 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, 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"), 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"), 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, 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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"), 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, 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, 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, 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, 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" "@brief The plugin base class\n"
"\n" "\n"
@ -735,36 +753,44 @@ Class<gsi::PluginImpl> 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"), 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" "@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"), 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" "@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"), 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" "@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" "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"), 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" "@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" "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"), 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" "@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" "\n"
"The mouse move event is important for a number of background jobs, such as coordinate display in the status bar.\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" "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"), 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" "@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"), 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" "@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 " "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" "false if the vertical wheel was rotated.\n"
) + ) +

View File

@ -47,7 +47,6 @@ SelectionService::SelectionService (lay::LayoutViewBase *view) :
mp_view (view), mp_view (view),
mp_box (0), mp_box (0),
m_color (0), m_color (0),
m_buttons (0),
m_hover (false), m_hover (false),
m_hover_wait (false), m_hover_wait (false),
m_mouse_in_window (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) { if ((buttons & lay::LeftButton) != 0) {
mp_view->stop_redraw (); // TODO: how to restart if selection is aborted? mp_view->stop_redraw (); // TODO: how to restart if selection is aborted?
m_buttons = buttons;
begin (p); begin (p);
return true; return true;
} }
@ -263,7 +261,7 @@ SelectionService::mouse_click_event (const db::DPoint &p, unsigned int buttons,
} }
bool 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 (); hover_reset ();
@ -274,8 +272,8 @@ SelectionService::mouse_release_event (const db::DPoint & /*p*/, unsigned int /*
if (mp_view) { if (mp_view) {
lay::Editable::SelectionMode mode = lay::Editable::Replace; lay::Editable::SelectionMode mode = lay::Editable::Replace;
bool shift = ((m_buttons & lay::ShiftButton) != 0); bool shift = ((buttons & lay::ShiftButton) != 0);
bool ctrl = ((m_buttons & lay::ControlButton) != 0); bool ctrl = ((buttons & lay::ControlButton) != 0);
if (shift && ctrl) { if (shift && ctrl) {
mode = lay::Editable::Invert; mode = lay::Editable::Invert;
} else if (shift) { } else if (shift) {

View File

@ -91,7 +91,6 @@ private:
lay::LayoutViewBase *mp_view; lay::LayoutViewBase *mp_view;
lay::RubberBox *mp_box; lay::RubberBox *mp_box;
unsigned int m_color; unsigned int m_color;
unsigned int m_buttons;
bool m_hover; bool m_hover;
bool m_hover_wait; bool m_hover_wait;
db::DPoint m_hover_point; db::DPoint m_hover_point;

View File

@ -907,7 +907,7 @@ ViewObjectUI::send_mouse_double_clicked_event (const db::DPoint &pt, unsigned in
} }
void 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 { try {
@ -916,23 +916,27 @@ ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int /*but
bool done = false; 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; m_mouse_pos = pt;
db::DPoint p = pixel_to_um (m_mouse_pos); db::DPoint p = pixel_to_um (m_mouse_pos);
auto grabbed = m_grabbed; auto grabbed = m_grabbed;
for (auto g = grabbed.begin (); !done && g != grabbed.end (); ++g) { for (auto g = grabbed.begin (); !done && g != grabbed.end (); ++g) {
if (m_mouse_pressed_state) { 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 { } 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 (! done && mp_active_service && mp_active_service->enabled ()) {
if (m_mouse_pressed_state) { 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 { } 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; ++next;
if ((*svc)->enabled ()) { if ((*svc)->enabled ()) {
if (m_mouse_pressed_state) { 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 { } else {
done = (*svc)->mouse_release_event (p, m_mouse_buttons, false); done = (*svc)->mouse_release_event (p, effective_buttons, false);
} }
} }
svc = next; svc = next;
@ -952,9 +956,9 @@ ViewObjectUI::send_mouse_release_event (const db::DPoint &pt, unsigned int /*but
if (! done) { if (! done) {
if (m_mouse_pressed_state) { if (m_mouse_pressed_state) {
mouse_click_event (p, m_mouse_buttons); mouse_click_event (p, effective_buttons);
} else { } else {
mouse_release_event (p, m_mouse_buttons); mouse_release_event (p, effective_buttons);
} }
} }

View File

@ -548,12 +548,14 @@ private:
* @brief Describes the button state (supposed to be ored) * @brief Describes the button state (supposed to be ored)
*/ */
enum ButtonState { enum ButtonState {
ShiftButton = 1, ShiftButton = 0x01,
ControlButton = 2, ControlButton = 0x02,
AltButton = 4, AltButton = 0x04,
LeftButton = 8, ModifierMask = 0x07, // all keyboard modifiers
MidButton = 16, LeftButton = 0x08,
RightButton = 32 MidButton = 0x10,
RightButton = 0x20,
MouseButtonMask = 0x38 // all mouse buttons
}; };
/** /**