Skip to content

Commit

Permalink
Simplify pan/zoom toggling.
Browse files Browse the repository at this point in the history
- Store current active tool just in the .mode attribute, rather than
  both in .mode and ._active; use a "StrEnum" (like an IntEnum, but for
  strs...) for backcompat.
- Connect a single handler which will dispatch to the correct
  sub-handler depending on the active tool, rather than having to
  disconnect and reconnect handlers every time a tool is changed.
  • Loading branch information
anntzer committed Apr 14, 2020
1 parent f6b8891 commit 2d41a72
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 64 deletions.
102 changes: 43 additions & 59 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"""

from contextlib import contextmanager
from enum import IntEnum
from enum import Enum, IntEnum
import functools
import importlib
import io
Expand Down Expand Up @@ -2651,6 +2651,15 @@ def set_window_title(self, title):
cursors = tools.cursors


class _Mode(str, Enum):
NONE = ""
PAN = "pan/zoom"
ZOOM = "zoom rect"

def __str__(self):
return self.value


class NavigationToolbar2:
"""
Base class for the navigation cursor, version 2
Expand Down Expand Up @@ -2715,19 +2724,20 @@ def __init__(self, canvas):
canvas.toolbar = self
self._nav_stack = cbook.Stack()
self._xypress = None # location and axis info at the time of the press
self._idPress = None
self._idRelease = None
self._active = None
# This cursor will be set after the initial draw.
self._lastCursor = cursors.POINTER
self._init_toolbar()
self._id_press = self.canvas.mpl_connect(
'button_press_event', self._zoom_pan_handler)
self._id_release = self.canvas.mpl_connect(
'button_release_event', self._zoom_pan_handler)
self._id_drag = self.canvas.mpl_connect(
'motion_notify_event', self.mouse_move)
self._zoom_info = None

self._button_pressed = None # determined by button pressed at start

self.mode = '' # a mode string for the status bar
self.mode = _Mode.NONE # a mode string for the status bar
self.set_history_buttons()

def set_message(self, s):
Expand Down Expand Up @@ -2805,17 +2815,17 @@ def _update_cursor(self, event):
"""
Update the cursor after a mouse move event or a tool (de)activation.
"""
if not event.inaxes or not self._active:
if not event.inaxes or not self.mode:
if self._lastCursor != cursors.POINTER:
self.set_cursor(cursors.POINTER)
self._lastCursor = cursors.POINTER
else:
if (self._active == 'ZOOM'
if (self.mode == _Mode.ZOOM
and self._lastCursor != cursors.SELECT_REGION):
self.set_cursor(cursors.SELECT_REGION)
self._lastCursor = cursors.SELECT_REGION
elif (self._active == 'PAN' and
self._lastCursor != cursors.MOVE):
elif (self.mode == _Mode.PAN
and self._lastCursor != cursors.MOVE):
self.set_cursor(cursors.MOVE)
self._lastCursor = cursors.MOVE

Expand Down Expand Up @@ -2870,40 +2880,32 @@ def mouse_move(self, event):
else:
self.set_message(self.mode)

def _zoom_pan_handler(self, event):
if self.mode == _Mode.PAN:
if event.name == "button_press_event":
self.press_pan(event)
elif event.name == "button_release_event":
self.release_pan(event)
if self.mode == _Mode.ZOOM:
if event.name == "button_press_event":
self.press_zoom(event)
elif event.name == "button_release_event":
self.release_zoom(event)

def pan(self, *args):
"""
Activate the pan/zoom tool.
Toggle the pan/zoom tool.
Pan with left button, zoom with right.
"""
# set the pointer icon and button press funcs to the
# appropriate callbacks

if self._active == 'PAN':
self._active = None
if self.mode == _Mode.PAN:
self.mode = _Mode.NONE
self.canvas.widgetlock.release(self)
else:
self._active = 'PAN'
if self._idPress is not None:
self._idPress = self.canvas.mpl_disconnect(self._idPress)
self.mode = ''

if self._idRelease is not None:
self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
self.mode = ''

if self._active:
self._idPress = self.canvas.mpl_connect(
'button_press_event', self.press_pan)
self._idRelease = self.canvas.mpl_connect(
'button_release_event', self.release_pan)
self.mode = 'pan/zoom'
self.mode = _Mode.PAN
self.canvas.widgetlock(self)
else:
self.canvas.widgetlock.release(self)

for a in self.canvas.figure.get_axes():
a.set_navigate_mode(self._active)

a.set_navigate_mode(self.mode)
self.set_message(self.mode)

def press(self, event):
Expand Down Expand Up @@ -3101,33 +3103,15 @@ def update(self):
self.set_history_buttons()

def zoom(self, *args):
"""Activate zoom to rect mode."""
if self._active == 'ZOOM':
self._active = None
"""Toggle zoom to rect mode."""
if self.mode == _Mode.ZOOM:
self.mode = _Mode.NONE
self.canvas.widgetlock.release(self)
else:
self._active = 'ZOOM'

if self._idPress is not None:
self._idPress = self.canvas.mpl_disconnect(self._idPress)
self.mode = ''

if self._idRelease is not None:
self._idRelease = self.canvas.mpl_disconnect(self._idRelease)
self.mode = ''

if self._active:
self._idPress = self.canvas.mpl_connect('button_press_event',
self.press_zoom)
self._idRelease = self.canvas.mpl_connect('button_release_event',
self.release_zoom)
self.mode = 'zoom rect'
self.mode = _Mode.ZOOM
self.canvas.widgetlock(self)
else:
self.canvas.widgetlock.release(self)

for a in self.canvas.figure.get_axes():
a.set_navigate_mode(self._active)

a.set_navigate_mode(self.mode)
self.set_message(self.mode)

def set_history_buttons(self):
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ def _update_buttons_checked(self):
button = self._gtk_ids.get(name)
if button:
with button.handler_block(button._signal_handler):
button.set_active(self._active == active)
button.set_active(self.mode.name == active)

def pan(self, *args):
super().pan(*args)
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/backends/backend_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,9 +745,9 @@ def edit_parameters(self):
def _update_buttons_checked(self):
# sync button checkstates to match active mode
if 'pan' in self._actions:
self._actions['pan'].setChecked(self._active == 'PAN')
self._actions['pan'].setChecked(self.mode.name == 'PAN')
if 'zoom' in self._actions:
self._actions['zoom'].setChecked(self._active == 'ZOOM')
self._actions['zoom'].setChecked(self.mode.name == 'ZOOM')

def pan(self, *args):
super().pan(*args)
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/backends/backend_wx.py
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,7 @@ def set_cursor(self, cursor):
self.canvas.Update()

def press(self, event):
if self._active == 'ZOOM':
if self.mode.name == 'ZOOM':
if not self.retinaFix:
self.wxoverlay = wx.Overlay()
else:
Expand All @@ -1221,7 +1221,7 @@ def press(self, event):
self.zoomAxes = event.inaxes

def release(self, event):
if self._active == 'ZOOM':
if self.mode.name == 'ZOOM':
# When the mouse is released we reset the overlay and it
# restores the former content to the window.
if not self.retinaFix:
Expand Down

0 comments on commit 2d41a72

Please sign in to comment.