Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcd37b73

Browse files
committed
Stop relying on dead-reckoning mouse buttons for motion_notify_event.
Previously, for motion_notify_event, `event.attribute` was set bychecking the last button_press_event/button_release event. This isbrittle for the same reason as to why we introduced `event.modifiers`to improve over `event.key`, so introduce the more robust`event.buttons` (see detailed discussion in the attribute docstring).For a concrete example, consider e.g. from matplotlib import pyplot as plt from matplotlib.backends.qt_compat import QtWidgets def on_button_press(event): if event.button != 3: # Right-click. return menu = QtWidgets.QMenu() menu.addAction("Some menu action", lambda: None) menu.exec(event.guiEvent.globalPosition().toPoint()) fig = plt.figure() fig.canvas.mpl_connect("button_press_event", on_button_press) fig.add_subplot() plt.show()(connecting a contextual menu on right button click) where a right clickwhile having selected zoom mode on the toolbar starts a zoom mode thatstays on even after the mouse release (because the mouse release eventis received by the menu widget, not by the main canvas). This PR doesnot fix the issue, but will allow a followup fix (where themotion_notify_event associated with zoom mode will be able to firstcheck whether the button is indeed still pressed when the motionoccurs).Limitations, on macOS only (everything works on Linux and WindowsAFAICT):- tk only reports a single pressed button even if multiple buttons are pressed.- gtk4 spams the terminal with Gtk-WARNINGs: "Broken accounting of active state for widget ..." on right-clicks only; similar issues appear to have been reported a while ago to Gtk (https://gitlab.gnome.org/GNOME/gtk/-/issues/3356 and linked issues) but it's unclear whether any action was taken on their side.(Alternatively, some GUI toolkits have a "permissive" notion ofdrag events defined as mouse moves with a button pressed, which we coulduse as well to define event.button{,s} for motion_notify_event, bute.g. Qt attaches quite heavy semantics to drags which we probably don'twant to bother with.)
1 parentac1d009 commitcd37b73

File tree

10 files changed

+187
-44
lines changed

10 files changed

+187
-44
lines changed

‎lib/matplotlib/backend_bases.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1324,6 +1324,28 @@ class MouseEvent(LocationEvent):
13241324
If this is unset, *name* is "scroll_event", and *step* is nonzero, then
13251325
this will be set to "up" or "down" depending on the sign of *step*.
13261326
1327+
buttons : None or frozenset
1328+
For 'motion_notify_event', the mouse buttons currently being pressed
1329+
(a set of zero or more MouseButtons);
1330+
for other events, None.
1331+
1332+
.. note::
1333+
For 'motion_notify_event', this attribute is more accurate than
1334+
the ``button`` (singular) attribute, which is obtained from the last
1335+
'button_press_event' or 'button_release_event' that occurred within
1336+
the canvas (and thus 1. be wrong if the last change in mouse state
1337+
occurred when the canvas did not have focus, and 2. cannot report
1338+
when multiple buttons are pressed).
1339+
1340+
This attribute is not set for 'button_press_event' and
1341+
'button_release_event' because GUI toolkits are inconsistent as to
1342+
whether they report the button state *before* or *after* the
1343+
press/release occurred.
1344+
1345+
.. warning::
1346+
On macOS, the Tk backends only report a single button even if
1347+
multiple buttons are pressed.
1348+
13271349
key : None or str
13281350
The key pressed when the mouse event triggered, e.g. 'shift'.
13291351
See `KeyEvent`.
@@ -1356,7 +1378,8 @@ def on_press(event):
13561378
"""
13571379

13581380
def__init__(self,name,canvas,x,y,button=None,key=None,
1359-
step=0,dblclick=False,guiEvent=None,*,modifiers=None):
1381+
step=0,dblclick=False,guiEvent=None,*,
1382+
buttons=None,modifiers=None):
13601383
super().__init__(
13611384
name,canvas,x,y,guiEvent=guiEvent,modifiers=modifiers)
13621385
ifbuttoninMouseButton.__members__.values():
@@ -1367,6 +1390,16 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
13671390
elifstep<0:
13681391
button="down"
13691392
self.button=button
1393+
ifname=="motion_notify_event":
1394+
self.buttons=frozenset(buttonsifbuttonsisnotNoneelse [])
1395+
else:
1396+
# We don't support 'buttons' for button_press/release_event because
1397+
# toolkits are inconsistent as to whether they report the state
1398+
# before or after the event.
1399+
ifbuttons:
1400+
raiseValueError(
1401+
"'buttons' is only supported for 'motion_notify_event'")
1402+
self.buttons=None
13701403
self.key=key
13711404
self.step=step
13721405
self.dblclick=dblclick

‎lib/matplotlib/backend_bases.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ class MouseEvent(LocationEvent):
258258
dblclick:bool= ...,
259259
guiEvent:Any|None= ...,
260260
*,
261+
buttons:Iterable[MouseButton]|None= ...,
261262
modifiers:Iterable[str]|None= ...,
262263
)->None: ...
263264

‎lib/matplotlib/backends/_backend_tk.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
frommatplotlibimport_api,backend_tools,cbook,_c_internal_utils
2020
frommatplotlib.backend_basesimport (
2121
_Backend,FigureCanvasBase,FigureManagerBase,NavigationToolbar2,
22-
TimerBase,ToolContainerBase,cursors,_Mode,
22+
TimerBase,ToolContainerBase,cursors,_Mode,MouseButton,
2323
CloseEvent,KeyEvent,LocationEvent,MouseEvent,ResizeEvent)
2424
frommatplotlib._pylab_helpersimportGcf
2525
from .import_tkagg
@@ -296,6 +296,7 @@ def _event_mpl_coords(self, event):
296296
defmotion_notify_event(self,event):
297297
MouseEvent("motion_notify_event",self,
298298
*self._event_mpl_coords(event),
299+
buttons=self._mpl_buttons(event),
299300
modifiers=self._mpl_modifiers(event),
300301
guiEvent=event)._process()
301302

@@ -357,13 +358,33 @@ def scroll_event_windows(self, event):
357358
x,y,step=step,modifiers=self._mpl_modifiers(event),
358359
guiEvent=event)._process()
359360

361+
@staticmethod
362+
def_mpl_buttons(event):# See _mpl_modifiers.
363+
# NOTE: This fails to report multiclicks on macOS; only one button is
364+
# reported (multiclicks work correctly on Linux & Windows).
365+
modifiers= [
366+
# macOS appears to swap right and middle (look for "Swap buttons
367+
# 2/3" in tk/macosx/tkMacOSXMouseEvent.c).
368+
(MouseButton.LEFT,1<<8),
369+
(MouseButton.RIGHT,1<<9),
370+
(MouseButton.MIDDLE,1<<10),
371+
(MouseButton.BACK,1<<11),
372+
(MouseButton.FORWARD,1<<12),
373+
]ifsys.platform=="darwin"else [
374+
(MouseButton.LEFT,1<<8),
375+
(MouseButton.MIDDLE,1<<9),
376+
(MouseButton.RIGHT,1<<10),
377+
(MouseButton.BACK,1<<11),
378+
(MouseButton.FORWARD,1<<12),
379+
]
380+
# State *before* press/release.
381+
return [nameforname,maskinmodifiersifevent.state&mask]
382+
360383
@staticmethod
361384
def_mpl_modifiers(event,*,exclude=None):
362-
# add modifier keys to the key string. Bit details originate from
363-
# http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
364-
# BIT_SHIFT = 0x001; BIT_CAPSLOCK = 0x002; BIT_CONTROL = 0x004;
365-
# BIT_LEFT_ALT = 0x008; BIT_NUMLOCK = 0x010; BIT_RIGHT_ALT = 0x080;
366-
# BIT_MB_1 = 0x100; BIT_MB_2 = 0x200; BIT_MB_3 = 0x400;
385+
# Add modifier keys to the key string. Bit values are inferred from
386+
# the implementation of tkinter.Event.__repr__ (1, 2, 4, 8, ... =
387+
# Shift, Lock, Control, Mod1, ..., Mod5, Button1, ..., Button5)
367388
# In general, the modifier key is excluded from the modifier flag,
368389
# however this is not the case on "darwin", so double check that
369390
# we aren't adding repeat modifier flags to a modifier key.

‎lib/matplotlib/backends/backend_gtk3.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
importmatplotlibasmpl
77
frommatplotlibimport_api,backend_tools,cbook
88
frommatplotlib.backend_basesimport (
9-
ToolContainerBase,CloseEvent,KeyEvent,LocationEvent,MouseEvent,
10-
ResizeEvent)
9+
ToolContainerBase,MouseButton,
10+
CloseEvent,KeyEvent,LocationEvent,MouseEvent,ResizeEvent)
1111

1212
try:
1313
importgi
@@ -156,6 +156,7 @@ def key_release_event(self, widget, event):
156156

157157
defmotion_notify_event(self,widget,event):
158158
MouseEvent("motion_notify_event",self,*self._mpl_coords(event),
159+
buttons=self._mpl_buttons(event.state),
159160
modifiers=self._mpl_modifiers(event.state),
160161
guiEvent=event)._process()
161162
returnFalse# finish event propagation?
@@ -182,6 +183,18 @@ def size_allocate(self, widget, allocation):
182183
ResizeEvent("resize_event",self)._process()
183184
self.draw_idle()
184185

186+
@staticmethod
187+
def_mpl_buttons(event_state):
188+
modifiers= [
189+
(MouseButton.LEFT,Gdk.ModifierType.BUTTON1_MASK),
190+
(MouseButton.MIDDLE,Gdk.ModifierType.BUTTON2_MASK),
191+
(MouseButton.RIGHT,Gdk.ModifierType.BUTTON3_MASK),
192+
(MouseButton.BACK,Gdk.ModifierType.BUTTON4_MASK),
193+
(MouseButton.FORWARD,Gdk.ModifierType.BUTTON5_MASK),
194+
]
195+
# State *before* press/release.
196+
return [nameforname,maskinmodifiersifevent_state&mask]
197+
185198
@staticmethod
186199
def_mpl_modifiers(event_state,*,exclude=None):
187200
modifiers= [

‎lib/matplotlib/backends/backend_gtk4.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
importmatplotlibasmpl
66
frommatplotlibimport_api,backend_tools,cbook
77
frommatplotlib.backend_basesimport (
8-
ToolContainerBase,KeyEvent,LocationEvent,MouseEvent,ResizeEvent,
9-
CloseEvent)
8+
ToolContainerBase,MouseButton,
9+
KeyEvent,LocationEvent,MouseEvent,ResizeEvent,CloseEvent)
1010

1111
try:
1212
importgi
@@ -155,6 +155,7 @@ def key_release_event(self, controller, keyval, keycode, state):
155155
defmotion_notify_event(self,controller,x,y):
156156
MouseEvent(
157157
"motion_notify_event",self,*self._mpl_coords((x,y)),
158+
buttons=self._mpl_buttons(controller),
158159
modifiers=self._mpl_modifiers(controller),
159160
guiEvent=controller.get_current_event(),
160161
)._process()
@@ -182,6 +183,26 @@ def resize_event(self, area, width, height):
182183
ResizeEvent("resize_event",self)._process()
183184
self.draw_idle()
184185

186+
def_mpl_buttons(self,controller):
187+
# NOTE: This spews "Broken accounting of active state" warnings on
188+
# right click on macOS.
189+
surface=self.get_native().get_surface()
190+
is_over,x,y,event_state=surface.get_device_position(
191+
self.get_display().get_default_seat().get_pointer())
192+
# NOTE: alternatively we could use
193+
# event_state = controller.get_current_event_state()
194+
# but for button_press/button_release this would report the state
195+
# *prior* to the event rather than after it; the above reports the
196+
# state *after* it.
197+
mod_table= [
198+
(MouseButton.LEFT,Gdk.ModifierType.BUTTON1_MASK),
199+
(MouseButton.MIDDLE,Gdk.ModifierType.BUTTON2_MASK),
200+
(MouseButton.RIGHT,Gdk.ModifierType.BUTTON3_MASK),
201+
(MouseButton.BACK,Gdk.ModifierType.BUTTON4_MASK),
202+
(MouseButton.FORWARD,Gdk.ModifierType.BUTTON5_MASK),
203+
]
204+
return {nameforname,maskinmod_tableifevent_state&mask}
205+
185206
def_mpl_modifiers(self,controller=None):
186207
ifcontrollerisNone:
187208
surface=self.get_native().get_surface()

‎lib/matplotlib/backends/backend_qt.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ def mouseMoveEvent(self, event):
329329
return
330330
MouseEvent("motion_notify_event",self,
331331
*self.mouseEventCoords(event),
332+
buttons=self._mpl_buttons(event.buttons()),
332333
modifiers=self._mpl_modifiers(),
333334
guiEvent=event)._process()
334335

@@ -396,6 +397,13 @@ def sizeHint(self):
396397
defminimumSizeHint(self):
397398
returnQtCore.QSize(10,10)
398399

400+
@staticmethod
401+
def_mpl_buttons(buttons):
402+
buttons=_to_int(buttons)
403+
# State *after* press/release.
404+
return {buttonformask,buttoninFigureCanvasQT.buttond.items()
405+
if_to_int(mask)&buttons}
406+
399407
@staticmethod
400408
def_mpl_modifiers(modifiers=None,*,exclude=None):
401409
ifmodifiersisNone:

‎lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
frommatplotlibimport_api,backend_bases,backend_tools
2323
frommatplotlib.backendsimportbackend_agg
2424
frommatplotlib.backend_basesimport (
25-
_Backend,KeyEvent,LocationEvent,MouseEvent,ResizeEvent)
25+
_Backend,MouseButton,KeyEvent,LocationEvent,MouseEvent,ResizeEvent)
2626

2727
_log=logging.getLogger(__name__)
2828

@@ -283,10 +283,17 @@ def _handle_mouse(self, event):
283283
y=event['y']
284284
y=self.get_renderer().height-y
285285
self._last_mouse_xy=x,y
286-
# JavaScript button numbers and Matplotlib button numbers are off by 1.
287-
button=event['button']+1
288-
289286
e_type=event['type']
287+
button=event['button']+1# JS numbers off by 1 compared to mpl.
288+
buttons= {# JS ordering different compared to mpl.
289+
buttonforbutton,maskin [
290+
(MouseButton.LEFT,1),
291+
(MouseButton.RIGHT,2),
292+
(MouseButton.MIDDLE,4),
293+
(MouseButton.BACK,8),
294+
(MouseButton.FORWARD,16),
295+
]ifevent['buttons']&mask# State *after* press/release.
296+
}
290297
modifiers=event['modifiers']
291298
guiEvent=event.get('guiEvent')
292299
ife_typein ['button_press','button_release']:
@@ -300,10 +307,12 @@ def _handle_mouse(self, event):
300307
modifiers=modifiers,guiEvent=guiEvent)._process()
301308
elife_type=='motion_notify':
302309
MouseEvent(e_type+'_event',self,x,y,
303-
modifiers=modifiers,guiEvent=guiEvent)._process()
310+
buttons=buttons,modifiers=modifiers,guiEvent=guiEvent,
311+
)._process()
304312
elife_typein ['figure_enter','figure_leave']:
305313
LocationEvent(e_type+'_event',self,x,y,
306314
modifiers=modifiers,guiEvent=guiEvent)._process()
315+
307316
handle_button_press=handle_button_release=handle_dblclick= \
308317
handle_figure_enter=handle_figure_leave=handle_motion_notify= \
309318
handle_scroll=_handle_mouse

‎lib/matplotlib/backends/backend_wx.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,22 @@ def _on_size(self, event):
685685
ResizeEvent("resize_event",self)._process()
686686
self.draw_idle()
687687

688+
@staticmethod
689+
def_mpl_buttons():
690+
state=wx.GetMouseState()
691+
# NOTE: Alternatively, we could use event.LeftIsDown() / etc. but this
692+
# fails to report multiclick drags on macOS (other OSes have not been
693+
# verified).
694+
mod_table= [
695+
(MouseButton.LEFT,state.LeftIsDown()),
696+
(MouseButton.RIGHT,state.RightIsDown()),
697+
(MouseButton.MIDDLE,state.MiddleIsDown()),
698+
(MouseButton.BACK,state.Aux1IsDown()),
699+
(MouseButton.FORWARD,state.Aux2IsDown()),
700+
]
701+
# State *after* press/release.
702+
return {buttonforbutton,flaginmod_tableifflag}
703+
688704
@staticmethod
689705
def_mpl_modifiers(event=None,*,exclude=None):
690706
mod_table= [
@@ -794,9 +810,8 @@ def _on_mouse_button(self, event):
794810
MouseEvent("button_press_event",self,x,y,button,
795811
modifiers=modifiers,guiEvent=event)._process()
796812
elifevent.ButtonDClick():
797-
MouseEvent("button_press_event",self,x,y,button,
798-
dblclick=True,modifiers=modifiers,
799-
guiEvent=event)._process()
813+
MouseEvent("button_press_event",self,x,y,button,dblclick=True,
814+
modifiers=modifiers,guiEvent=event)._process()
800815
elifevent.ButtonUp():
801816
MouseEvent("button_release_event",self,x,y,button,
802817
modifiers=modifiers,guiEvent=event)._process()
@@ -826,6 +841,7 @@ def _on_motion(self, event):
826841
event.Skip()
827842
MouseEvent("motion_notify_event",self,
828843
*self._mpl_coords(event),
844+
buttons=self._mpl_buttons(),
829845
modifiers=self._mpl_modifiers(event),
830846
guiEvent=event)._process()
831847

‎lib/matplotlib/backends/web_backend/js/mpl.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ mpl.figure.prototype.mouse_event = function (event, name) {
644644
y:y,
645645
button:event.button,
646646
step:event.step,
647+
buttons:event.buttons,
647648
modifiers:getModifiers(event),
648649
guiEvent:simpleKeys(event),
649650
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp