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
@@ -2863,7 +2864,6 @@ def __init__(self, canvas):
28632864 self .canvas = canvas
28642865 canvas .toolbar = self
28652866 self ._nav_stack = cbook .Stack ()
2866- self ._xypress = None # location and axis info at the time of the press
28672867 # This cursor will be set after the initial draw.
28682868 self ._lastCursor = cursors .POINTER
28692869
@@ -2881,10 +2881,9 @@ def __init__(self, canvas):
28812881 'button_release_event' , self ._zoom_pan_handler )
28822882 self ._id_drag = self .canvas .mpl_connect (
28832883 'motion_notify_event' , self .mouse_move )
2884+ self ._pan_info = None
28842885 self ._zoom_info = None
28852886
2886- self ._button_pressed = None # determined by button pressed at start
2887-
28882887 self .mode = _Mode .NONE # a mode string for the status bar
28892888 self .set_history_buttons ()
28902889
@@ -3066,26 +3065,25 @@ def pan(self, *args):
30663065 a .set_navigate_mode (self .mode ._navigate_mode )
30673066 self .set_message (self .mode )
30683067
3068+ _PanInfo = namedtuple ("_PanInfo" , "button axes cid" )
3069+
30693070 def press_pan (self , event ):
30703071 """Callback for mouse button press in pan/zoom mode."""
3071- if event .button in [1 , 3 ]:
3072- self ._button_pressed = event .button
3073- else :
3074- self ._button_pressed = None
3072+ if (event .button not in [MouseButton .LEFT , MouseButton .RIGHT ]
3073+ or event .x is None or event .y is None ):
3074+ return
3075+ axes = [a for a in self .canvas .figure .get_axes ()
3076+ if a .in_axes (event ) and a .get_navigate () and a .can_pan ()]
3077+ if not axes :
30753078 return
30763079 if self ._nav_stack () is None :
3077- # set the home button to this view
3078- self .push_current ()
3079- x , y = event .x , event .y
3080- self ._xypress = []
3081- for i , a in enumerate (self .canvas .figure .get_axes ()):
3082- if (x is not None and y is not None and a .in_axes (event ) and
3083- a .get_navigate () and a .can_pan ()):
3084- a .start_pan (x , y , event .button )
3085- self ._xypress .append ((a , i ))
3086- self .canvas .mpl_disconnect (self ._id_drag )
3087- self ._id_drag = self .canvas .mpl_connect (
3088- 'motion_notify_event' , self .drag_pan )
3080+ self .push_current () # set the home button to this view
3081+ for ax in axes :
3082+ ax .start_pan (event .x , event .y , event .button )
3083+ self .canvas .mpl_disconnect (self ._id_drag )
3084+ id_drag = self .canvas .mpl_connect ("motion_notify_event" , self .drag_pan )
3085+ self ._pan_info = self ._PanInfo (
3086+ button = event .button , axes = axes , cid = id_drag )
30893087 press = cbook ._deprecate_method_override (
30903088 __class__ .press , self , since = "3.3" , message = "Calling an "
30913089 "overridden press() at pan start is deprecated since %(since)s "
@@ -3095,34 +3093,30 @@ def press_pan(self, event):
30953093
30963094 def drag_pan (self , event ):
30973095 """Callback for dragging in pan/zoom mode."""
3098- for a , ind in self ._xypress :
3099- #safer to use the recorded button at the press than current button:
3100- #multiple button can get pressed during motion.. .
3101- a .drag_pan (self ._button_pressed , event .key , event .x , event .y )
3096+ for ax in self ._pan_info . axes :
3097+ # Using the recorded button at the press is safer than the current
3098+ # button, as multiple buttons can get pressed during motion.
3099+ ax .drag_pan (self ._pan_info . button , event .key , event .x , event .y )
31023100 self .canvas .draw_idle ()
31033101
31043102 def release_pan (self , event ):
31053103 """Callback for mouse button release in pan/zoom mode."""
3106-
3107- if self ._button_pressed is None :
3104+ if self ._pan_info is None :
31083105 return
3109- self .canvas .mpl_disconnect (self ._id_drag )
3106+ self .canvas .mpl_disconnect (self ._pan_info . cid )
31103107 self ._id_drag = self .canvas .mpl_connect (
31113108 'motion_notify_event' , self .mouse_move )
3112- for a , ind in self ._xypress :
3113- a .end_pan ()
3114- if not self ._xypress :
3115- return
3116- self ._xypress = []
3117- self ._button_pressed = None
3118- self .push_current ()
3109+ for ax in self ._pan_info .axes :
3110+ ax .end_pan ()
31193111 release = cbook ._deprecate_method_override (
31203112 __class__ .press , self , since = "3.3" , message = "Calling an "
31213113 "overridden release() at pan stop is deprecated since %(since)s "
31223114 "and will be removed %(removal)s; override release_pan() instead." )
31233115 if release is not None :
31243116 release (event )
31253117 self ._draw ()
3118+ self ._pan_info = None
3119+ self .push_current ()
31263120
31273121 def zoom (self , * args ):
31283122 """Toggle zoom to rect mode."""
@@ -3136,11 +3130,12 @@ def zoom(self, *args):
31363130 a .set_navigate_mode (self .mode ._navigate_mode )
31373131 self .set_message (self .mode )
31383132
3133+ _ZoomInfo = namedtuple ("_ZoomInfo" , "direction start_xy axes cid" )
3134+
31393135 def press_zoom (self , event ):
31403136 """Callback for mouse button press in zoom to rect mode."""
3141- if event .button not in [1 , 3 ]:
3142- return
3143- if event .x is None or event .y is None :
3137+ if (event .button not in [MouseButton .LEFT , MouseButton .RIGHT ]
3138+ or event .x is None or event .y is None ):
31443139 return
31453140 axes = [a for a in self .canvas .figure .get_axes ()
31463141 if a .in_axes (event ) and a .get_navigate () and a .can_zoom ()]
@@ -3150,12 +3145,9 @@ def press_zoom(self, event):
31503145 self .push_current () # set the home button to this view
31513146 id_zoom = self .canvas .mpl_connect (
31523147 "motion_notify_event" , self .drag_zoom )
3153- self ._zoom_info = {
3154- "direction" : "in" if event .button == 1 else "out" ,
3155- "start_xy" : (event .x , event .y ),
3156- "axes" : axes ,
3157- "cid" : id_zoom ,
3158- }
3148+ self ._zoom_info = self ._ZoomInfo (
3149+ direction = "in" if event .button == 1 else "out" ,
3150+ start_xy = (event .x , event .y ), axes = axes , cid = id_zoom )
31593151 press = cbook ._deprecate_method_override (
31603152 __class__ .press , self , since = "3.3" , message = "Calling an "
31613153 "overridden press() at zoom start is deprecated since %(since)s "
@@ -3165,8 +3157,8 @@ def press_zoom(self, event):
31653157
31663158 def drag_zoom (self , event ):
31673159 """Callback for dragging in zoom mode."""
3168- start_xy = self ._zoom_info [ " start_xy" ]
3169- ax = self ._zoom_info [ " axes" ] [0 ]
3160+ start_xy = self ._zoom_info . start_xy
3161+ ax = self ._zoom_info . axes [0 ]
31703162 (x1 , y1 ), (x2 , y2 ) = np .clip (
31713163 [start_xy , [event .x , event .y ]], ax .bbox .min , ax .bbox .max )
31723164 if event .key == "x" :
@@ -3182,44 +3174,40 @@ def release_zoom(self, event):
31823174
31833175 # We don't check the event button here, so that zooms can be cancelled
31843176 # by (pressing and) releasing another mouse button.
3185- self .canvas .mpl_disconnect (self ._zoom_info [ " cid" ] )
3177+ self .canvas .mpl_disconnect (self ._zoom_info . cid )
31863178 self .remove_rubberband ()
31873179
3188- start_x , start_y = self ._zoom_info ["start_xy" ]
3189-
3190- for i , ax in enumerate (self ._zoom_info ["axes" ]):
3191- x , y = event .x , event .y
3192- # ignore singular clicks - 5 pixels is a threshold
3193- # allows the user to "cancel" a zoom action
3194- # by zooming by less than 5 pixels
3195- if ((abs (x - start_x ) < 5 and event .key != "y" ) or
3196- (abs (y - start_y ) < 5 and event .key != "x" )):
3197- self ._xypress = None
3198- release = cbook ._deprecate_method_override (
3199- __class__ .press , self , since = "3.3" , message = "Calling an "
3200- "overridden release() at zoom stop is deprecated since "
3201- "%(since)s and will be removed %(removal)s; override "
3202- "release_zoom() instead." )
3203- if release is not None :
3204- release (event )
3205- self ._draw ()
3206- return
3180+ start_x , start_y = self ._zoom_info .start_xy
3181+ # Ignore single clicks: 5 pixels is a threshold that allows the user to
3182+ # "cancel" a zoom action by zooming by less than 5 pixels.
3183+ if ((abs (event .x - start_x ) < 5 and event .key != "y" )
3184+ or (abs (event .y - start_y ) < 5 and event .key != "x" )):
3185+ self ._draw ()
3186+ self ._zoom_info = None
3187+ release = cbook ._deprecate_method_override (
3188+ __class__ .press , self , since = "3.3" , message = "Calling an "
3189+ "overridden release() at zoom stop is deprecated since "
3190+ "%(since)s and will be removed %(removal)s; override "
3191+ "release_zoom() instead." )
3192+ if release is not None :
3193+ release (event )
3194+ return
32073195
3196+ for i , ax in enumerate (self ._zoom_info .axes ):
32083197 # Detect whether this axes is twinned with an earlier axes in the
32093198 # list of zoomed axes, to avoid double zooming.
32103199 twinx = any (ax .get_shared_x_axes ().joined (ax , prev )
3211- for prev in self ._zoom_info [ " axes" ] [:i ])
3200+ for prev in self ._zoom_info . axes [:i ])
32123201 twiny = any (ax .get_shared_y_axes ().joined (ax , prev )
3213- for prev in self ._zoom_info ["axes" ][:i ])
3214-
3202+ for prev in self ._zoom_info .axes [:i ])
32153203 ax ._set_view_from_bbox (
3216- (start_x , start_y , x , y ), self . _zoom_info [ "direction" ] ,
3217- event .key , twinx , twiny )
3204+ (start_x , start_y , event . x , event . y ) ,
3205+ self . _zoom_info . direction , event .key , twinx , twiny )
32183206
32193207 self ._draw ()
32203208 self ._zoom_info = None
3221-
32223209 self .push_current ()
3210+
32233211 release = cbook ._deprecate_method_override (
32243212 __class__ .release , self , since = "3.3" , message = "Calling an "
32253213 "overridden release() at zoom stop is deprecated since %(since)s "
0 commit comments