2525 The base class for the Toolbar class of each interactive backend.
2626"""
2727
28+ from collections import namedtuple
2829from contextlib import contextmanager , suppress
2930from enum import Enum , IntEnum
3031import functools
@@ -2871,7 +2872,6 @@ def __init__(self, canvas):
28712872 self .canvas = canvas
28722873 canvas .toolbar = self
28732874 self ._nav_stack = cbook .Stack ()
2874- self ._xypress = None # location and axis info at the time of the press
28752875 # This cursor will be set after the initial draw.
28762876 self ._lastCursor = cursors .POINTER
28772877
@@ -2889,10 +2889,9 @@ def __init__(self, canvas):
28892889 'button_release_event' , self ._zoom_pan_handler )
28902890 self ._id_drag = self .canvas .mpl_connect (
28912891 'motion_notify_event' , self .mouse_move )
2892+ self ._pan_info = None
28922893 self ._zoom_info = None
28932894
2894- self ._button_pressed = None # determined by button pressed at start
2895-
28962895 self .mode = _Mode .NONE # a mode string for the status bar
28972896 self .set_history_buttons ()
28982897
@@ -3074,26 +3073,25 @@ def pan(self, *args):
30743073 a .set_navigate_mode (self .mode ._navigate_mode )
30753074 self .set_message (self .mode )
30763075
3076+ _PanInfo = namedtuple ("_PanInfo" , "button axes cid" )
3077+
30773078 def press_pan (self , event ):
30783079 """Callback for mouse button press in pan/zoom mode."""
3079- if event .button in [1 , 3 ]:
3080- self ._button_pressed = event .button
3081- else :
3082- self ._button_pressed = None
3080+ if (event .button not in [MouseButton .LEFT , MouseButton .RIGHT ]
3081+ or event .x is None or event .y is None ):
3082+ return
3083+ axes = [a for a in self .canvas .figure .get_axes ()
3084+ if a .in_axes (event ) and a .get_navigate () and a .can_pan ()]
3085+ if not axes :
30833086 return
30843087 if self ._nav_stack () is None :
3085- # set the home button to this view
3086- self .push_current ()
3087- x , y = event .x , event .y
3088- self ._xypress = []
3089- for i , a in enumerate (self .canvas .figure .get_axes ()):
3090- if (x is not None and y is not None and a .in_axes (event ) and
3091- a .get_navigate () and a .can_pan ()):
3092- a .start_pan (x , y , event .button )
3093- self ._xypress .append ((a , i ))
3094- self .canvas .mpl_disconnect (self ._id_drag )
3095- self ._id_drag = self .canvas .mpl_connect (
3096- 'motion_notify_event' , self .drag_pan )
3088+ self .push_current () # set the home button to this view
3089+ for ax in axes :
3090+ ax .start_pan (event .x , event .y , event .button )
3091+ self .canvas .mpl_disconnect (self ._id_drag )
3092+ id_drag = self .canvas .mpl_connect ("motion_notify_event" , self .drag_pan )
3093+ self ._pan_info = self ._PanInfo (
3094+ button = event .button , axes = axes , cid = id_drag )
30973095 press = cbook ._deprecate_method_override (
30983096 __class__ .press , self , since = "3.3" , message = "Calling an "
30993097 "overridden press() at pan start is deprecated since %(since)s "
@@ -3103,34 +3101,30 @@ def press_pan(self, event):
31033101
31043102 def drag_pan (self , event ):
31053103 """Callback for dragging in pan/zoom mode."""
3106- for a , ind in self ._xypress :
3107- #safer to use the recorded button at the press than current button:
3108- #multiple button can get pressed during motion.. .
3109- a .drag_pan (self ._button_pressed , event .key , event .x , event .y )
3104+ for ax in self ._pan_info . axes :
3105+ # Using the recorded button at the press is safer than the current
3106+ # button, as multiple buttons can get pressed during motion.
3107+ ax .drag_pan (self ._pan_info . button , event .key , event .x , event .y )
31103108 self .canvas .draw_idle ()
31113109
31123110 def release_pan (self , event ):
31133111 """Callback for mouse button release in pan/zoom mode."""
3114-
3115- if self ._button_pressed is None :
3112+ if self ._pan_info is None :
31163113 return
3117- self .canvas .mpl_disconnect (self ._id_drag )
3114+ self .canvas .mpl_disconnect (self ._pan_info . cid )
31183115 self ._id_drag = self .canvas .mpl_connect (
31193116 'motion_notify_event' , self .mouse_move )
3120- for a , ind in self ._xypress :
3121- a .end_pan ()
3122- if not self ._xypress :
3123- return
3124- self ._xypress = []
3125- self ._button_pressed = None
3126- self .push_current ()
3117+ for ax in self ._pan_info .axes :
3118+ ax .end_pan ()
31273119 release = cbook ._deprecate_method_override (
31283120 __class__ .press , self , since = "3.3" , message = "Calling an "
31293121 "overridden release() at pan stop is deprecated since %(since)s "
31303122 "and will be removed %(removal)s; override release_pan() instead." )
31313123 if release is not None :
31323124 release (event )
31333125 self ._draw ()
3126+ self ._pan_info = None
3127+ self .push_current ()
31343128
31353129 def zoom (self , * args ):
31363130 """Toggle zoom to rect mode."""
@@ -3144,11 +3138,12 @@ def zoom(self, *args):
31443138 a .set_navigate_mode (self .mode ._navigate_mode )
31453139 self .set_message (self .mode )
31463140
3141+ _ZoomInfo = namedtuple ("_ZoomInfo" , "direction start_xy axes cid" )
3142+
31473143 def press_zoom (self , event ):
31483144 """Callback for mouse button press in zoom to rect mode."""
3149- if event .button not in [1 , 3 ]:
3150- return
3151- if event .x is None or event .y is None :
3145+ if (event .button not in [MouseButton .LEFT , MouseButton .RIGHT ]
3146+ or event .x is None or event .y is None ):
31523147 return
31533148 axes = [a for a in self .canvas .figure .get_axes ()
31543149 if a .in_axes (event ) and a .get_navigate () and a .can_zoom ()]
@@ -3158,12 +3153,9 @@ def press_zoom(self, event):
31583153 self .push_current () # set the home button to this view
31593154 id_zoom = self .canvas .mpl_connect (
31603155 "motion_notify_event" , self .drag_zoom )
3161- self ._zoom_info = {
3162- "direction" : "in" if event .button == 1 else "out" ,
3163- "start_xy" : (event .x , event .y ),
3164- "axes" : axes ,
3165- "cid" : id_zoom ,
3166- }
3156+ self ._zoom_info = self ._ZoomInfo (
3157+ direction = "in" if event .button == 1 else "out" ,
3158+ start_xy = (event .x , event .y ), axes = axes , cid = id_zoom )
31673159 press = cbook ._deprecate_method_override (
31683160 __class__ .press , self , since = "3.3" , message = "Calling an "
31693161 "overridden press() at zoom start is deprecated since %(since)s "
@@ -3173,8 +3165,8 @@ def press_zoom(self, event):
31733165
31743166 def drag_zoom (self , event ):
31753167 """Callback for dragging in zoom mode."""
3176- start_xy = self ._zoom_info [ " start_xy" ]
3177- ax = self ._zoom_info [ " axes" ] [0 ]
3168+ start_xy = self ._zoom_info . start_xy
3169+ ax = self ._zoom_info . axes [0 ]
31783170 (x1 , y1 ), (x2 , y2 ) = np .clip (
31793171 [start_xy , [event .x , event .y ]], ax .bbox .min , ax .bbox .max )
31803172 if event .key == "x" :
@@ -3190,44 +3182,40 @@ def release_zoom(self, event):
31903182
31913183 # We don't check the event button here, so that zooms can be cancelled
31923184 # by (pressing and) releasing another mouse button.
3193- self .canvas .mpl_disconnect (self ._zoom_info [ " cid" ] )
3185+ self .canvas .mpl_disconnect (self ._zoom_info . cid )
31943186 self .remove_rubberband ()
31953187
3196- start_x , start_y = self ._zoom_info ["start_xy" ]
3197-
3198- for i , ax in enumerate (self ._zoom_info ["axes" ]):
3199- x , y = event .x , event .y
3200- # ignore singular clicks - 5 pixels is a threshold
3201- # allows the user to "cancel" a zoom action
3202- # by zooming by less than 5 pixels
3203- if ((abs (x - start_x ) < 5 and event .key != "y" ) or
3204- (abs (y - start_y ) < 5 and event .key != "x" )):
3205- self ._xypress = None
3206- release = cbook ._deprecate_method_override (
3207- __class__ .press , self , since = "3.3" , message = "Calling an "
3208- "overridden release() at zoom stop is deprecated since "
3209- "%(since)s and will be removed %(removal)s; override "
3210- "release_zoom() instead." )
3211- if release is not None :
3212- release (event )
3213- self ._draw ()
3214- return
3188+ start_x , start_y = self ._zoom_info .start_xy
3189+ # Ignore single clicks: 5 pixels is a threshold that allows the user to
3190+ # "cancel" a zoom action by zooming by less than 5 pixels.
3191+ if ((abs (event .x - start_x ) < 5 and event .key != "y" )
3192+ or (abs (event .y - start_y ) < 5 and event .key != "x" )):
3193+ self ._draw ()
3194+ self ._zoom_info = None
3195+ release = cbook ._deprecate_method_override (
3196+ __class__ .press , self , since = "3.3" , message = "Calling an "
3197+ "overridden release() at zoom stop is deprecated since "
3198+ "%(since)s and will be removed %(removal)s; override "
3199+ "release_zoom() instead." )
3200+ if release is not None :
3201+ release (event )
3202+ return
32153203
3204+ for i , ax in enumerate (self ._zoom_info .axes ):
32163205 # Detect whether this axes is twinned with an earlier axes in the
32173206 # list of zoomed axes, to avoid double zooming.
32183207 twinx = any (ax .get_shared_x_axes ().joined (ax , prev )
3219- for prev in self ._zoom_info [ " axes" ] [:i ])
3208+ for prev in self ._zoom_info . axes [:i ])
32203209 twiny = any (ax .get_shared_y_axes ().joined (ax , prev )
3221- for prev in self ._zoom_info ["axes" ][:i ])
3222-
3210+ for prev in self ._zoom_info .axes [:i ])
32233211 ax ._set_view_from_bbox (
3224- (start_x , start_y , x , y ), self . _zoom_info [ "direction" ] ,
3225- event .key , twinx , twiny )
3212+ (start_x , start_y , event . x , event . y ) ,
3213+ self . _zoom_info . direction , event .key , twinx , twiny )
32263214
32273215 self ._draw ()
32283216 self ._zoom_info = None
3229-
32303217 self .push_current ()
3218+
32313219 release = cbook ._deprecate_method_override (
32323220 __class__ .release , self , since = "3.3" , message = "Calling an "
32333221 "overridden release() at zoom stop is deprecated since %(since)s "
0 commit comments