Skip to content

Commit ea20300

Browse files
committed
Unify toolbar init across backends.
... by instantiating classes specified by _toolbar2_class or _toolmanager_toolbar_class. (Third-parties can still manually instantiate their toolbars the old-fashioned way.) Note that this would read a bit nicer if the toolbar classes were defined before the manager classes, as this would allow _toolbar2_class and _toolmanager_toolbar_class to be set using normal class attributes, but moving large chunks of code around did not seem worth it. I only did so for the macosx backend, where the moved code is very little. This also moves the canonical storage of the toolbar on wx from the frame to the manager (keeping a proxy for backcompat), for consistency with the other backends.
1 parent 76012ae commit ea20300

9 files changed

+58
-125
lines changed

lib/matplotlib/backend_bases.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -2717,6 +2717,9 @@ class FigureManagerBase:
27172717
figure.canvas.manager.button_press_handler_id)
27182718
"""
27192719

2720+
_toolbar2_class = None
2721+
_toolmanager_toolbar_class = None
2722+
27202723
def __init__(self, canvas, num):
27212724
self.canvas = canvas
27222725
canvas.manager = self # store a pointer to parent
@@ -2734,7 +2737,19 @@ def __init__(self, canvas, num):
27342737
self.toolmanager = (ToolManager(canvas.figure)
27352738
if mpl.rcParams['toolbar'] == 'toolmanager'
27362739
else None)
2737-
self.toolbar = None
2740+
if (mpl.rcParams["toolbar"] == "toolbar2"
2741+
and self._toolbar2_class):
2742+
self.toolbar = self._toolbar2_class(self.canvas)
2743+
elif (mpl.rcParams["toolbar"] == "toolmanager"
2744+
and self._toolmanager_toolbar_class):
2745+
self.toolbar = self._toolmanager_toolbar_class(self.toolmanager)
2746+
else:
2747+
self.toolbar = None
2748+
2749+
if self.toolmanager:
2750+
tools.add_tools_to_manager(self.toolmanager)
2751+
if self.toolbar:
2752+
tools.add_tools_to_container(self.toolbar)
27382753

27392754
@self.canvas.figure.add_axobserver
27402755
def notify_axes_change(fig):

lib/matplotlib/backends/_backend_tk.py

+2-15
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,8 @@ def __init__(self, canvas, num, window):
410410
self.window.withdraw()
411411
# packing toolbar first, because if space is getting low, last packed
412412
# widget is getting shrunk first (-> the canvas)
413-
self.toolbar = self._get_toolbar()
414413
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
415414

416-
if self.toolmanager:
417-
backend_tools.add_tools_to_manager(self.toolmanager)
418-
if self.toolbar:
419-
backend_tools.add_tools_to_container(self.toolbar)
420-
421415
# If the window has per-monitor DPI awareness, then setup a Tk variable
422416
# to store the DPI, which will be updated by the C code, and the trace
423417
# will handle it on the Python side.
@@ -430,15 +424,6 @@ def __init__(self, canvas, num, window):
430424

431425
self._shown = False
432426

433-
def _get_toolbar(self):
434-
if mpl.rcParams['toolbar'] == 'toolbar2':
435-
toolbar = NavigationToolbar2Tk(self.canvas)
436-
elif mpl.rcParams['toolbar'] == 'toolmanager':
437-
toolbar = ToolbarTk(self.toolmanager)
438-
else:
439-
toolbar = None
440-
return toolbar
441-
442427
def _update_window_dpi(self, *args):
443428
newdpi = self._window_dpi.get()
444429
self.window.call('tk', 'scaling', newdpi / 72)
@@ -898,6 +883,8 @@ def trigger(self, *args):
898883

899884

900885
Toolbar = ToolbarTk
886+
FigureManagerTk._toolbar2_class = NavigationToolbar2Tk
887+
FigureManagerTk._toolmanager_toolbar_class = ToolbarTk
901888

902889

903890
@_Backend.export

lib/matplotlib/backends/backend_gtk3.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -331,13 +331,6 @@ def __init__(self, canvas, num):
331331
# calculate size for window
332332
w, h = self.canvas.get_width_height()
333333

334-
self.toolbar = self._get_toolbar()
335-
336-
if self.toolmanager:
337-
backend_tools.add_tools_to_manager(self.toolmanager)
338-
if self.toolbar:
339-
backend_tools.add_tools_to_container(self.toolbar)
340-
341334
if self.toolbar is not None:
342335
self.toolbar.show()
343336
self.vbox.pack_end(self.toolbar, False, False, 0)
@@ -737,6 +730,8 @@ def error_msg_gtk(msg, parent=None):
737730
FigureCanvasGTK3, _backend_gtk.ConfigureSubplotsGTK)
738731
backend_tools._register_tool_class(
739732
FigureCanvasGTK3, _backend_gtk.RubberbandGTK)
733+
FigureManagerGTK3._toolbar2_class = NavigationToolbar2GTK3
734+
FigureManagerGTK3._toolmanager_toolbar_class = ToolbarGTK3
740735

741736

742737
@_BackendGTK.export

lib/matplotlib/backends/backend_gtk4.py

+2-18
Original file line numberDiff line numberDiff line change
@@ -279,13 +279,6 @@ def __init__(self, canvas, num):
279279
# calculate size for window
280280
w, h = self.canvas.get_width_height()
281281

282-
self.toolbar = self._get_toolbar()
283-
284-
if self.toolmanager:
285-
backend_tools.add_tools_to_manager(self.toolmanager)
286-
if self.toolbar:
287-
backend_tools.add_tools_to_container(self.toolbar)
288-
289282
if self.toolbar is not None:
290283
sw = Gtk.ScrolledWindow(vscrollbar_policy=Gtk.PolicyType.NEVER)
291284
sw.set_child(self.toolbar)
@@ -335,17 +328,6 @@ def full_screen_toggle(self):
335328
else:
336329
self.window.unfullscreen()
337330

338-
def _get_toolbar(self):
339-
# must be inited after the window, drawingArea and figure
340-
# attrs are set
341-
if mpl.rcParams['toolbar'] == 'toolbar2':
342-
toolbar = NavigationToolbar2GTK4(self.canvas)
343-
elif mpl.rcParams['toolbar'] == 'toolmanager':
344-
toolbar = ToolbarGTK4(self.toolmanager)
345-
else:
346-
toolbar = None
347-
return toolbar
348-
349331
def get_window_title(self):
350332
return self.window.get_title()
351333

@@ -673,6 +655,8 @@ def trigger(self, *args, **kwargs):
673655
backend_tools._register_tool_class(
674656
FigureCanvasGTK4, _backend_gtk.RubberbandGTK)
675657
Toolbar = ToolbarGTK4
658+
FigureManagerGTK4._toolbar2_class = NavigationToolbar2GTK4
659+
FigureManagerGTK4._toolmanager_toolbar_class = ToolbarGTK4
676660

677661

678662
@_BackendGTK.export

lib/matplotlib/backends/backend_macosx.py

+18-24
Original file line numberDiff line numberDiff line change
@@ -62,30 +62,6 @@ def resize(self, width, height):
6262
self.draw_idle()
6363

6464

65-
class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
66-
"""
67-
Wrap everything up into a window for the pylab interface
68-
"""
69-
def __init__(self, canvas, num):
70-
_macosx.FigureManager.__init__(self, canvas)
71-
icon_path = str(cbook._get_data_path('images/matplotlib.pdf'))
72-
_macosx.FigureManager.set_icon(icon_path)
73-
FigureManagerBase.__init__(self, canvas, num)
74-
if mpl.rcParams['toolbar'] == 'toolbar2':
75-
self.toolbar = NavigationToolbar2Mac(canvas)
76-
else:
77-
self.toolbar = None
78-
if self.toolbar is not None:
79-
self.toolbar.update()
80-
81-
if mpl.is_interactive():
82-
self.show()
83-
self.canvas.draw_idle()
84-
85-
def close(self):
86-
Gcf.destroy(self)
87-
88-
8965
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
9066

9167
def __init__(self, canvas):
@@ -123,6 +99,24 @@ def set_message(self, message):
12399
_macosx.NavigationToolbar2.set_message(self, message.encode('utf-8'))
124100

125101

102+
class FigureManagerMac(_macosx.FigureManager, FigureManagerBase):
103+
_toolbar2_class = NavigationToolbar2Mac
104+
105+
def __init__(self, canvas, num):
106+
_macosx.FigureManager.__init__(self, canvas)
107+
icon_path = str(cbook._get_data_path('images/matplotlib.pdf'))
108+
_macosx.FigureManager.set_icon(icon_path)
109+
FigureManagerBase.__init__(self, canvas, num)
110+
if self.toolbar is not None:
111+
self.toolbar.update()
112+
if mpl.is_interactive():
113+
self.show()
114+
self.canvas.draw_idle()
115+
116+
def close(self):
117+
Gcf.destroy(self)
118+
119+
126120
@_Backend.export
127121
class _BackendMac(_Backend):
128122
FigureCanvas = FigureCanvasMac

lib/matplotlib/backends/backend_nbagg.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class NavigationIPy(NavigationToolbar2WebAgg):
7272

7373

7474
class FigureManagerNbAgg(FigureManagerWebAgg):
75-
ToolbarCls = NavigationIPy
75+
_toolbar2_class = ToolbarCls = NavigationIPy
7676

7777
def __init__(self, canvas, num):
7878
self._shown = False

lib/matplotlib/backends/backend_qt.py

+4-18
Original file line numberDiff line numberDiff line change
@@ -529,13 +529,6 @@ def __init__(self, canvas, num):
529529

530530
self.window._destroying = False
531531

532-
self.toolbar = self._get_toolbar(self.canvas, self.window)
533-
534-
if self.toolmanager:
535-
backend_tools.add_tools_to_manager(self.toolmanager)
536-
if self.toolbar:
537-
backend_tools.add_tools_to_container(self.toolbar)
538-
539532
if self.toolbar:
540533
self.window.addToolBar(self.toolbar)
541534
tbs_height = self.toolbar.sizeHint().height()
@@ -582,17 +575,6 @@ def _widgetclosed(self):
582575
# Gcf can get destroyed before the Gcf.destroy
583576
# line is run, leading to a useless AttributeError.
584577

585-
def _get_toolbar(self, canvas, parent):
586-
# must be inited after the window, drawingArea and figure
587-
# attrs are set
588-
if mpl.rcParams['toolbar'] == 'toolbar2':
589-
toolbar = NavigationToolbar2QT(canvas)
590-
elif mpl.rcParams['toolbar'] == 'toolmanager':
591-
toolbar = ToolbarQt(self.toolmanager)
592-
else:
593-
toolbar = None
594-
return toolbar
595-
596578
def resize(self, width, height):
597579
# The Qt methods return sizes in 'virtual' pixels so we do need to
598580
# rescale from physical to logical pixels.
@@ -1021,6 +1003,10 @@ def trigger(self, *args, **kwargs):
10211003
qApp.clipboard().setPixmap(pixmap)
10221004

10231005

1006+
FigureManagerQT._toolbar2_class = NavigationToolbar2QT
1007+
FigureManagerQT._toolmanager_toolbar_class = ToolbarQt
1008+
1009+
10241010
@_Backend.export
10251011
class _BackendQT(_Backend):
10261012
FigureCanvas = FigureCanvasQT

lib/matplotlib/backends/backend_webagg_core.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -453,20 +453,15 @@ def set_history_buttons(self):
453453

454454

455455
class FigureManagerWebAgg(backend_bases.FigureManagerBase):
456-
ToolbarCls = NavigationToolbar2WebAgg
456+
_toolbar2_class = ToolbarCls = NavigationToolbar2WebAgg
457457

458458
def __init__(self, canvas, num):
459459
self.web_sockets = set()
460460
super().__init__(canvas, num)
461-
self.toolbar = self._get_toolbar(canvas)
462461

463462
def show(self):
464463
pass
465464

466-
def _get_toolbar(self, canvas):
467-
toolbar = self.ToolbarCls(canvas)
468-
return toolbar
469-
470465
def resize(self, w, h, forward=True):
471466
self._send_event(
472467
'resize',

lib/matplotlib/backends/backend_wx.py

+12-35
Original file line numberDiff line numberDiff line change
@@ -901,13 +901,9 @@ def __init__(self, num, fig, *, canvas_class=None):
901901

902902
self.figmgr = FigureManagerWx(self.canvas, num, self)
903903

904-
self.toolbar = self._get_toolbar()
905-
if self.figmgr.toolmanager:
906-
backend_tools.add_tools_to_manager(self.figmgr.toolmanager)
907-
if self.toolbar:
908-
backend_tools.add_tools_to_container(self.toolbar)
909-
if self.toolbar is not None:
910-
self.SetToolBar(self.toolbar)
904+
toolbar = self.canvas.manager.toolbar
905+
if toolbar is not None:
906+
self.SetToolBar(toolbar)
911907

912908
# On Windows, canvas sizing must occur after toolbar addition;
913909
# otherwise the toolbar further resizes the canvas.
@@ -920,18 +916,8 @@ def __init__(self, num, fig, *, canvas_class=None):
920916

921917
self.Bind(wx.EVT_CLOSE, self._on_close)
922918

923-
@property
924-
def toolmanager(self):
925-
return self.figmgr.toolmanager
926-
927-
def _get_toolbar(self):
928-
if mpl.rcParams['toolbar'] == 'toolbar2':
929-
toolbar = NavigationToolbar2Wx(self.canvas)
930-
elif mpl.rcParams['toolbar'] == 'toolmanager':
931-
toolbar = ToolbarWx(self.toolmanager)
932-
else:
933-
toolbar = None
934-
return toolbar
919+
toolmanager = property(lambda self: self.figmgr.toolmanager)
920+
toolbar = property(lambda self: self.GetToolBar()) # backcompat
935921

936922
@_api.deprecated(
937923
"3.6", alternative="the canvas_class constructor parameter")
@@ -956,7 +942,7 @@ def _on_close(self, event):
956942

957943
def Destroy(self, *args, **kwargs):
958944
try:
959-
self.canvas.mpl_disconnect(self.toolbar._id_drag)
945+
self.canvas.mpl_disconnect(self.canvas.manager.toolbar._id_drag)
960946
# Rationale for line above: see issue 2941338.
961947
except AttributeError:
962948
pass # classic toolbar lacks the attribute
@@ -965,8 +951,8 @@ def Destroy(self, *args, **kwargs):
965951
# MPLBACKEND=wxagg python -c 'from pylab import *; plot()'.
966952
if self and not self.IsBeingDeleted():
967953
super().Destroy(*args, **kwargs)
968-
# self.toolbar.Destroy() should not be necessary if the close event
969-
# is allowed to propagate.
954+
# toolbar.Destroy() should not be necessary if the close event is
955+
# allowed to propagate.
970956
return True
971957

972958

@@ -988,20 +974,7 @@ class FigureManagerWx(FigureManagerBase):
988974
def __init__(self, canvas, num, frame):
989975
_log.debug("%s - __init__()", type(self))
990976
self.frame = self.window = frame
991-
self._initializing = True
992977
super().__init__(canvas, num)
993-
self._initializing = False
994-
995-
@property
996-
def toolbar(self):
997-
return self.frame.GetToolBar()
998-
999-
@toolbar.setter
1000-
def toolbar(self, value):
1001-
# Never allow this, except that base class inits this to None before
1002-
# the frame is set up.
1003-
if not self._initializing:
1004-
raise AttributeError("can't set attribute")
1005978

1006979
def show(self):
1007980
# docstring inherited
@@ -1373,6 +1346,10 @@ def trigger(self, *args, **kwargs):
13731346
wx.TheClipboard.Close()
13741347

13751348

1349+
FigureManagerWx._toolbar2_class = NavigationToolbar2Wx
1350+
FigureManagerWx._toolmanager_toolbar_class = ToolbarWx
1351+
1352+
13761353
@_Backend.export
13771354
class _BackendWx(_Backend):
13781355
FigureCanvas = FigureCanvasWx

0 commit comments

Comments
 (0)