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