From 1c6d5a1b7f53687908b6b4fd452f5e4fb9df2cd8 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 12 Apr 2020 18:03:08 +0200 Subject: [PATCH] Make widgets.TextBox work also when embedding. When embedding, `canvas.manager` may be None, but we may still have registered the default key_press_handler (see e.g. the embedding_in_tk_sgskip.py example), so we still need to disable the keymap rcParams. In order to avoid duplicating the logic as to whether use toolmanager-cleanup in two places (and avoid things going out of sync between begin_typing and stop_typing), register the cleanup actions in begin_typing. Example: ``` from matplotlib.backend_bases import key_press_handler from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT from matplotlib.backends.qt_compat import QtCore, QtWidgets from matplotlib.figure import Figure from matplotlib.widgets import TextBox class ApplicationWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() canvas = FigureCanvas(Figure(figsize=(5, 3))) canvas.setFocusPolicy(QtCore.Qt.StrongFocus) self.setCentralWidget(canvas) tb = NavigationToolbar2QT(canvas, self) self.addToolBar(tb) canvas.mpl_connect( "key_press_event", lambda event: key_press_handler(event, canvas, tb)) axbox = canvas.figure.add_axes([0.1, 0.05, 0.8, 0.075]) self._tb = TextBox(axbox, 'label') qapp = QtWidgets.QApplication([]) app = ApplicationWindow() app.show() qapp.exec_() ``` --- lib/matplotlib/widgets.py | 43 ++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 544f8d1f1c49..073f601c3908 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -842,37 +842,38 @@ def _notify_change_observers(self): def begin_typing(self, x): self.capturekeystrokes = True - # Check for toolmanager handling the keypress - if self.ax.figure.canvas.manager.key_press_handler_id is not None: - # Disable command keys so that the user can type without - # command keys causing figure to be saved, etc. - self._restore_keymap = ExitStack() + # Disable keypress shortcuts, which may otherwise cause the figure to + # be saved, closed, etc., until the user stops typing. The way to + # achieve this depends on whether toolmanager is in use. + stack = ExitStack() # Register cleanup actions when user stops typing. + self._on_stop_typing = stack.close + toolmanager = getattr( + self.ax.figure.canvas.manager, "toolmanager", None) + if toolmanager is not None: + # If using toolmanager, lock keypresses, and plan to release the + # lock when typing stops. + toolmanager.keypresslock(self) + stack.push(toolmanager.keypresslock.release, self) + else: + # If not using toolmanager, disable all keypress-related rcParams. # Avoid spurious warnings if keymaps are getting deprecated. with cbook._suppress_matplotlib_deprecation_warning(): - self._restore_keymap.enter_context( - mpl.rc_context({k: [] for k in mpl.rcParams - if k.startswith('keymap.')})) - else: - self.ax.figure.canvas.manager.toolmanager.keypresslock(self) + stack.enter_context(mpl.rc_context( + {k: [] for k in mpl.rcParams if k.startswith("keymap.")})) def stop_typing(self): - notifysubmit = False - # Because _notify_submit_users might throw an error in the user's code, - # we only want to call it once we've already done our cleanup. if self.capturekeystrokes: - # Check for toolmanager handling the keypress - if self.ax.figure.canvas.manager.key_press_handler_id is not None: - # since the user is no longer typing, - # reactivate the standard command keys - self._restore_keymap.close() - else: - toolmanager = self.ax.figure.canvas.manager.toolmanager - toolmanager.keypresslock.release(self) + self._on_stop_typing() + self._on_stop_typing = None notifysubmit = True + else: + notifysubmit = False self.capturekeystrokes = False self.cursor.set_visible(False) self.ax.figure.canvas.draw() if notifysubmit: + # Because _notify_submit_observers might throw an error in the + # user's code, only call it once we've already done our cleanup. self._notify_submit_observers() def position_cursor(self, x):