diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 173558b534ac..a666f131475b 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2717,6 +2717,9 @@ class FigureManagerBase: figure.canvas.manager.button_press_handler_id) """ + _toolbar2_class = None + _toolmanager_toolbar_class = None + def __init__(self, canvas, num): self.canvas = canvas canvas.manager = self # store a pointer to parent @@ -2734,7 +2737,19 @@ def __init__(self, canvas, num): self.toolmanager = (ToolManager(canvas.figure) if mpl.rcParams['toolbar'] == 'toolmanager' else None) - self.toolbar = None + if (mpl.rcParams["toolbar"] == "toolbar2" + and self._toolbar2_class): + self.toolbar = self._toolbar2_class(self.canvas) + elif (mpl.rcParams["toolbar"] == "toolmanager" + and self._toolmanager_toolbar_class): + self.toolbar = self._toolmanager_toolbar_class(self.toolmanager) + else: + self.toolbar = None + + if self.toolmanager: + tools.add_tools_to_manager(self.toolmanager) + if self.toolbar: + tools.add_tools_to_container(self.toolbar) @self.canvas.figure.add_axobserver def notify_axes_change(fig): diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index cf2083e33534..0e5f2e8be7c2 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -410,14 +410,8 @@ def __init__(self, canvas, num, window): self.window.withdraw() # packing toolbar first, because if space is getting low, last packed # widget is getting shrunk first (-> the canvas) - self.toolbar = self._get_toolbar() self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1) - if self.toolmanager: - backend_tools.add_tools_to_manager(self.toolmanager) - if self.toolbar: - backend_tools.add_tools_to_container(self.toolbar) - # If the window has per-monitor DPI awareness, then setup a Tk variable # to store the DPI, which will be updated by the C code, and the trace # will handle it on the Python side. @@ -430,15 +424,6 @@ def __init__(self, canvas, num, window): self._shown = False - def _get_toolbar(self): - if mpl.rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2Tk(self.canvas) - elif mpl.rcParams['toolbar'] == 'toolmanager': - toolbar = ToolbarTk(self.toolmanager) - else: - toolbar = None - return toolbar - def _update_window_dpi(self, *args): newdpi = self._window_dpi.get() self.window.call('tk', 'scaling', newdpi / 72) @@ -898,6 +883,8 @@ def trigger(self, *args): Toolbar = ToolbarTk +FigureManagerTk._toolbar2_class = NavigationToolbar2Tk +FigureManagerTk._toolmanager_toolbar_class = ToolbarTk @_Backend.export diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 6f48fcf633ba..60073a2d8b1e 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -331,13 +331,6 @@ def __init__(self, canvas, num): # calculate size for window w, h = self.canvas.get_width_height() - self.toolbar = self._get_toolbar() - - if self.toolmanager: - backend_tools.add_tools_to_manager(self.toolmanager) - if self.toolbar: - backend_tools.add_tools_to_container(self.toolbar) - if self.toolbar is not None: self.toolbar.show() self.vbox.pack_end(self.toolbar, False, False, 0) @@ -737,6 +730,8 @@ def error_msg_gtk(msg, parent=None): FigureCanvasGTK3, _backend_gtk.ConfigureSubplotsGTK) backend_tools._register_tool_class( FigureCanvasGTK3, _backend_gtk.RubberbandGTK) +FigureManagerGTK3._toolbar2_class = NavigationToolbar2GTK3 +FigureManagerGTK3._toolmanager_toolbar_class = ToolbarGTK3 @_BackendGTK.export diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py index 1c5f4d16f0a5..aeb4a7a0dffa 100644 --- a/lib/matplotlib/backends/backend_gtk4.py +++ b/lib/matplotlib/backends/backend_gtk4.py @@ -279,13 +279,6 @@ def __init__(self, canvas, num): # calculate size for window w, h = self.canvas.get_width_height() - self.toolbar = self._get_toolbar() - - if self.toolmanager: - backend_tools.add_tools_to_manager(self.toolmanager) - if self.toolbar: - backend_tools.add_tools_to_container(self.toolbar) - if self.toolbar is not None: sw = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER) sw.set_child(self.toolbar) @@ -335,17 +328,6 @@ def full_screen_toggle(self): else: self.window.unfullscreen() - def _get_toolbar(self): - # must be inited after the window, drawingArea and figure - # attrs are set - if mpl.rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2GTK4(self.canvas) - elif mpl.rcParams['toolbar'] == 'toolmanager': - toolbar = ToolbarGTK4(self.toolmanager) - else: - toolbar = None - return toolbar - def get_window_title(self): return self.window.get_title() @@ -673,6 +655,8 @@ def trigger(self, *args, **kwargs): backend_tools._register_tool_class( FigureCanvasGTK4, _backend_gtk.RubberbandGTK) Toolbar = ToolbarGTK4 +FigureManagerGTK4._toolbar2_class = NavigationToolbar2GTK4 +FigureManagerGTK4._toolmanager_toolbar_class = ToolbarGTK4 @_BackendGTK.export diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 69ef4dae3737..7f8185284fca 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -62,30 +62,6 @@ def resize(self, width, height): self.draw_idle() -class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): - """ - Wrap everything up into a window for the pylab interface - """ - def __init__(self, canvas, num): - _macosx.FigureManager.__init__(self, canvas) - icon_path = str(cbook._get_data_path('images/matplotlib.pdf')) - _macosx.FigureManager.set_icon(icon_path) - FigureManagerBase.__init__(self, canvas, num) - if mpl.rcParams['toolbar'] == 'toolbar2': - self.toolbar = NavigationToolbar2Mac(canvas) - else: - self.toolbar = None - if self.toolbar is not None: - self.toolbar.update() - - if mpl.is_interactive(): - self.show() - self.canvas.draw_idle() - - def close(self): - Gcf.destroy(self) - - class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2): def __init__(self, canvas): @@ -123,6 +99,24 @@ def set_message(self, message): _macosx.NavigationToolbar2.set_message(self, message.encode('utf-8')) +class FigureManagerMac(_macosx.FigureManager, FigureManagerBase): + _toolbar2_class = NavigationToolbar2Mac + + def __init__(self, canvas, num): + _macosx.FigureManager.__init__(self, canvas) + icon_path = str(cbook._get_data_path('images/matplotlib.pdf')) + _macosx.FigureManager.set_icon(icon_path) + FigureManagerBase.__init__(self, canvas, num) + if self.toolbar is not None: + self.toolbar.update() + if mpl.is_interactive(): + self.show() + self.canvas.draw_idle() + + def close(self): + Gcf.destroy(self) + + @_Backend.export class _BackendMac(_Backend): FigureCanvas = FigureCanvasMac diff --git a/lib/matplotlib/backends/backend_nbagg.py b/lib/matplotlib/backends/backend_nbagg.py index c9df2e8dbad7..abd2206e27d2 100644 --- a/lib/matplotlib/backends/backend_nbagg.py +++ b/lib/matplotlib/backends/backend_nbagg.py @@ -72,7 +72,7 @@ class NavigationIPy(NavigationToolbar2WebAgg): class FigureManagerNbAgg(FigureManagerWebAgg): - ToolbarCls = NavigationIPy + _toolbar2_class = ToolbarCls = NavigationIPy def __init__(self, canvas, num): self._shown = False diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py index a8a12c76c80a..66b6698affbf 100644 --- a/lib/matplotlib/backends/backend_qt.py +++ b/lib/matplotlib/backends/backend_qt.py @@ -529,13 +529,6 @@ def __init__(self, canvas, num): self.window._destroying = False - self.toolbar = self._get_toolbar(self.canvas, self.window) - - if self.toolmanager: - backend_tools.add_tools_to_manager(self.toolmanager) - if self.toolbar: - backend_tools.add_tools_to_container(self.toolbar) - if self.toolbar: self.window.addToolBar(self.toolbar) tbs_height = self.toolbar.sizeHint().height() @@ -582,17 +575,6 @@ def _widgetclosed(self): # Gcf can get destroyed before the Gcf.destroy # line is run, leading to a useless AttributeError. - def _get_toolbar(self, canvas, parent): - # must be inited after the window, drawingArea and figure - # attrs are set - if mpl.rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2QT(canvas) - elif mpl.rcParams['toolbar'] == 'toolmanager': - toolbar = ToolbarQt(self.toolmanager) - else: - toolbar = None - return toolbar - def resize(self, width, height): # The Qt methods return sizes in 'virtual' pixels so we do need to # rescale from physical to logical pixels. @@ -1021,6 +1003,10 @@ def trigger(self, *args, **kwargs): qApp.clipboard().setPixmap(pixmap) +FigureManagerQT._toolbar2_class = NavigationToolbar2QT +FigureManagerQT._toolmanager_toolbar_class = ToolbarQt + + @_Backend.export class _BackendQT(_Backend): FigureCanvas = FigureCanvasQT diff --git a/lib/matplotlib/backends/backend_webagg_core.py b/lib/matplotlib/backends/backend_webagg_core.py index 8e1997887912..8b5f1ba479d0 100644 --- a/lib/matplotlib/backends/backend_webagg_core.py +++ b/lib/matplotlib/backends/backend_webagg_core.py @@ -453,20 +453,15 @@ def set_history_buttons(self): class FigureManagerWebAgg(backend_bases.FigureManagerBase): - ToolbarCls = NavigationToolbar2WebAgg + _toolbar2_class = ToolbarCls = NavigationToolbar2WebAgg def __init__(self, canvas, num): self.web_sockets = set() super().__init__(canvas, num) - self.toolbar = self._get_toolbar(canvas) def show(self): pass - def _get_toolbar(self, canvas): - toolbar = self.ToolbarCls(canvas) - return toolbar - def resize(self, w, h, forward=True): self._send_event( 'resize', diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 7153887c80fa..b37af1809b05 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -901,13 +901,9 @@ def __init__(self, num, fig, *, canvas_class=None): self.figmgr = FigureManagerWx(self.canvas, num, self) - self.toolbar = self._get_toolbar() - if self.figmgr.toolmanager: - backend_tools.add_tools_to_manager(self.figmgr.toolmanager) - if self.toolbar: - backend_tools.add_tools_to_container(self.toolbar) - if self.toolbar is not None: - self.SetToolBar(self.toolbar) + toolbar = self.canvas.manager.toolbar + if toolbar is not None: + self.SetToolBar(toolbar) # On Windows, canvas sizing must occur after toolbar addition; # otherwise the toolbar further resizes the canvas. @@ -920,18 +916,8 @@ def __init__(self, num, fig, *, canvas_class=None): self.Bind(wx.EVT_CLOSE, self._on_close) - @property - def toolmanager(self): - return self.figmgr.toolmanager - - def _get_toolbar(self): - if mpl.rcParams['toolbar'] == 'toolbar2': - toolbar = NavigationToolbar2Wx(self.canvas) - elif mpl.rcParams['toolbar'] == 'toolmanager': - toolbar = ToolbarWx(self.toolmanager) - else: - toolbar = None - return toolbar + toolmanager = property(lambda self: self.figmgr.toolmanager) + toolbar = property(lambda self: self.GetToolBar()) # backcompat @_api.deprecated( "3.6", alternative="the canvas_class constructor parameter") @@ -956,7 +942,7 @@ def _on_close(self, event): def Destroy(self, *args, **kwargs): try: - self.canvas.mpl_disconnect(self.toolbar._id_drag) + self.canvas.mpl_disconnect(self.canvas.manager.toolbar._id_drag) # Rationale for line above: see issue 2941338. except AttributeError: pass # classic toolbar lacks the attribute @@ -965,8 +951,8 @@ def Destroy(self, *args, **kwargs): # MPLBACKEND=wxagg python -c 'from pylab import *; plot()'. if self and not self.IsBeingDeleted(): super().Destroy(*args, **kwargs) - # self.toolbar.Destroy() should not be necessary if the close event - # is allowed to propagate. + # toolbar.Destroy() should not be necessary if the close event is + # allowed to propagate. return True @@ -988,20 +974,7 @@ class FigureManagerWx(FigureManagerBase): def __init__(self, canvas, num, frame): _log.debug("%s - __init__()", type(self)) self.frame = self.window = frame - self._initializing = True super().__init__(canvas, num) - self._initializing = False - - @property - def toolbar(self): - return self.frame.GetToolBar() - - @toolbar.setter - def toolbar(self, value): - # Never allow this, except that base class inits this to None before - # the frame is set up. - if not self._initializing: - raise AttributeError("can't set attribute") def show(self): # docstring inherited @@ -1373,6 +1346,10 @@ def trigger(self, *args, **kwargs): wx.TheClipboard.Close() +FigureManagerWx._toolbar2_class = NavigationToolbar2Wx +FigureManagerWx._toolmanager_toolbar_class = ToolbarWx + + @_Backend.export class _BackendWx(_Backend): FigureCanvas = FigureCanvasWx diff --git a/lib/matplotlib/tests/test_backend_bases.py b/lib/matplotlib/tests/test_backend_bases.py index 4abaf1a78ed5..78833a8a7e29 100644 --- a/lib/matplotlib/tests/test_backend_bases.py +++ b/lib/matplotlib/tests/test_backend_bases.py @@ -4,8 +4,6 @@ from matplotlib.backend_bases import ( FigureCanvasBase, LocationEvent, MouseButton, MouseEvent, NavigationToolbar2, RendererBase) -from matplotlib.backend_tools import (ToolZoom, ToolPan, RubberbandBase, - ToolViewsPositions, _views_positions) from matplotlib.figure import Figure import matplotlib.pyplot as plt import matplotlib.transforms as transforms @@ -250,14 +248,6 @@ def test_toolbar_zoompan(): plt.rcParams['toolbar'] = 'toolmanager' ax = plt.gca() assert ax.get_navigate_mode() is None - ax.figure.canvas.manager.toolmanager.add_tool(name="zoom", - tool=ToolZoom) - ax.figure.canvas.manager.toolmanager.add_tool(name="pan", - tool=ToolPan) - ax.figure.canvas.manager.toolmanager.add_tool(name=_views_positions, - tool=ToolViewsPositions) - ax.figure.canvas.manager.toolmanager.add_tool(name='rubberband', - tool=RubberbandBase) ax.figure.canvas.manager.toolmanager.trigger_tool('zoom') assert ax.get_navigate_mode() == "ZOOM" ax.figure.canvas.manager.toolmanager.trigger_tool('pan')