Skip to content

Commit 200380e

Browse files
authored
Merge pull request #17111 from anntzer/init_toolbar
Deprecate NavigationToolbar2._init_toolbar.
2 parents 4f42c68 + 89a5f9c commit 200380e

File tree

10 files changed

+146
-140
lines changed

10 files changed

+146
-140
lines changed

doc/api/api_changes_3.3/deprecations.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,16 @@ The ``Fil``, ``Fill``, ``Filll``, ``NegFil``, ``NegFill``, ``NegFilll``, and
371371
``SsGlue`` classes in the :mod:`matplotlib.mathtext` module are deprecated.
372372
As an alternative, directly construct glue instances with ``Glue("fil")``, etc.
373373

374+
NavigationToolbar2._init_toolbar
375+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
376+
Overriding this method to initialize third-party toolbars is deprecated.
377+
Instead, the toolbar should be initialized in the ``__init__`` method of the
378+
subclass (which should call the base-class' ``__init__`` as appropriate). To
379+
keep back-compatibility with earlier versions of Matplotlib (which *required*
380+
``_init_toolbar`` to be overridden), a fully empty implementation (``def
381+
_init_toolbar(self): pass``) may be kept and will not trigger the deprecation
382+
warning.
383+
374384
NavigationToolbar2QT.parent and .basedir
375385
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
376386
These attributes are deprecated. In order to access the parent window, use

lib/matplotlib/backend_bases.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2674,11 +2674,11 @@ def __str__(self):
26742674

26752675
class NavigationToolbar2:
26762676
"""
2677-
Base class for the navigation cursor, version 2
2677+
Base class for the navigation cursor, version 2.
26782678
2679-
backends must implement a canvas that handles connections for
2679+
Backends must implement a canvas that handles connections for
26802680
'button_press_event' and 'button_release_event'. See
2681-
:meth:`FigureCanvasBase.mpl_connect` for more information
2681+
:meth:`FigureCanvasBase.mpl_connect` for more information.
26822682
26832683
They must also define
26842684
@@ -2688,9 +2688,6 @@ class NavigationToolbar2:
26882688
:meth:`set_cursor`
26892689
if you want the pointer icon to change
26902690
2691-
:meth:`_init_toolbar`
2692-
create your toolbar widget
2693-
26942691
:meth:`draw_rubberband` (optional)
26952692
draw the zoom to rect "rubberband" rectangle
26962693
@@ -2701,6 +2698,12 @@ class NavigationToolbar2:
27012698
you can change the history back / forward buttons to
27022699
indicate disabled / enabled state.
27032700
2701+
and override ``__init__`` to set up the toolbar -- without forgetting to
2702+
call the base-class init. Typically, ``__init__`` needs to set up toolbar
2703+
buttons connected to the `home`, `back`, `forward`, `pan`, `zoom`, and
2704+
`save_figure` methods and using standard icons in the "images" subdirectory
2705+
of the data path.
2706+
27042707
That's it, we'll do the rest!
27052708
"""
27062709

@@ -2730,7 +2733,15 @@ def __init__(self, canvas):
27302733
self._xypress = None # location and axis info at the time of the press
27312734
# This cursor will be set after the initial draw.
27322735
self._lastCursor = cursors.POINTER
2733-
self._init_toolbar()
2736+
2737+
init = cbook._deprecate_method_override(
2738+
__class__._init_toolbar, self, allow_empty=True, since="3.3",
2739+
addendum="Please fully initialize the toolbar in your subclass' "
2740+
"__init__; a fully empty _init_toolbar implementation may be kept "
2741+
"for compatibility with earlier versions of Matplotlib.")
2742+
if init:
2743+
init()
2744+
27342745
self._id_press = self.canvas.mpl_connect(
27352746
'button_press_event', self._zoom_pan_handler)
27362747
self._id_release = self.canvas.mpl_connect(
@@ -2793,6 +2804,7 @@ def home(self, *args):
27932804
self.set_history_buttons()
27942805
self._update_view()
27952806

2807+
@cbook.deprecated("3.3", alternative="__init__")
27962808
def _init_toolbar(self):
27972809
"""
27982810
This is where you actually build the GUI widgets (called by

lib/matplotlib/backends/_backend_tk.py

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,29 @@ def __init__(self, canvas, window, *, pack_toolbar=True):
491491
# Avoid using self.window (prefer self.canvas.get_tk_widget().master),
492492
# so that Tool implementations can reuse the methods.
493493
self.window = window
494+
495+
tk.Frame.__init__(self, master=window, borderwidth=2,
496+
width=int(canvas.figure.bbox.width), height=50)
497+
498+
self._buttons = {}
499+
for text, tooltip_text, image_file, callback in self.toolitems:
500+
if text is None:
501+
# Add a spacer; return value is unused.
502+
self._Spacer()
503+
else:
504+
self._buttons[text] = button = self._Button(
505+
text,
506+
str(cbook._get_data_path(f"images/{image_file}.gif")),
507+
toggle=callback in ["zoom", "pan"],
508+
command=getattr(self, callback),
509+
)
510+
if tooltip_text is not None:
511+
ToolTip.createToolTip(button, tooltip_text)
512+
513+
self.message = tk.StringVar(master=self)
514+
self._message_label = tk.Label(master=self, textvariable=self.message)
515+
self._message_label.pack(side=tk.RIGHT)
516+
494517
NavigationToolbar2.__init__(self, canvas)
495518
if pack_toolbar:
496519
self.pack(side=tk.BOTTOM, fill=tk.X)
@@ -547,51 +570,6 @@ def _Spacer(self):
547570
s.pack(side=tk.LEFT, padx=5)
548571
return s
549572

550-
def _init_toolbar(self):
551-
xmin, xmax = self.canvas.figure.bbox.intervalx
552-
height, width = 50, xmax-xmin
553-
tk.Frame.__init__(self, master=self.window,
554-
width=int(width), height=int(height),
555-
borderwidth=2)
556-
557-
self.update() # Make axes menu
558-
559-
self._buttons = {}
560-
for text, tooltip_text, image_file, callback in self.toolitems:
561-
if text is None:
562-
# Add a spacer; return value is unused.
563-
self._Spacer()
564-
else:
565-
self._buttons[text] = button = self._Button(
566-
text,
567-
str(cbook._get_data_path(f"images/{image_file}.gif")),
568-
toggle=callback in ["zoom", "pan"],
569-
command=getattr(self, callback),
570-
)
571-
if tooltip_text is not None:
572-
ToolTip.createToolTip(button, tooltip_text)
573-
574-
self.message = tk.StringVar(master=self)
575-
self._message_label = tk.Label(master=self, textvariable=self.message)
576-
self._message_label.pack(side=tk.RIGHT)
577-
578-
def _update_buttons_checked(self):
579-
for name, mode in [("Pan", "PAN"), ("Zoom", "ZOOM")]:
580-
button = self._buttons.get(name)
581-
if button:
582-
if self.mode.name == mode and not button.var.get():
583-
button.select()
584-
elif self.mode.name != mode and button.var.get():
585-
button.deselect()
586-
587-
def pan(self, *args):
588-
super().pan(*args)
589-
self._update_buttons_checked()
590-
591-
def zoom(self, *args):
592-
super().zoom(*args)
593-
self._update_buttons_checked()
594-
595573
def configure_subplots(self):
596574
toolfig = Figure(figsize=(6, 3))
597575
window = tk.Toplevel()

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -451,43 +451,7 @@ class NavigationToolbar2GTK3(NavigationToolbar2, Gtk.Toolbar):
451451
def __init__(self, canvas, window):
452452
self.win = window
453453
GObject.GObject.__init__(self)
454-
NavigationToolbar2.__init__(self, canvas)
455-
456-
@cbook.deprecated("3.3")
457-
@property
458-
def ctx(self):
459-
return self.canvas.get_property("window").cairo_create()
460-
461-
def set_message(self, s):
462-
self.message.set_label(s)
463-
464-
def set_cursor(self, cursor):
465-
self.canvas.get_property("window").set_cursor(cursord[cursor])
466-
Gtk.main_iteration()
467-
468-
def draw_rubberband(self, event, x0, y0, x1, y1):
469-
# adapted from
470-
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744
471-
ctx = self.canvas.get_property("window").cairo_create()
472-
473-
# todo: instead of redrawing the entire figure, copy the part of
474-
# the figure that was covered by the previous rubberband rectangle
475-
self.canvas.draw()
476454

477-
height = self.canvas.figure.bbox.height
478-
y1 = height - y1
479-
y0 = height - y0
480-
w = abs(x1 - x0)
481-
h = abs(y1 - y0)
482-
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
483-
484-
ctx.new_path()
485-
ctx.set_line_width(0.5)
486-
ctx.rectangle(*rect)
487-
ctx.set_source_rgb(0, 0, 0)
488-
ctx.stroke()
489-
490-
def _init_toolbar(self):
491455
self.set_style(Gtk.ToolbarStyle.ICONS)
492456

493457
self._gtk_ids = {}
@@ -521,6 +485,42 @@ def _init_toolbar(self):
521485

522486
self.show_all()
523487

488+
NavigationToolbar2.__init__(self, canvas)
489+
490+
@cbook.deprecated("3.3")
491+
@property
492+
def ctx(self):
493+
return self.canvas.get_property("window").cairo_create()
494+
495+
def set_message(self, s):
496+
self.message.set_label(s)
497+
498+
def set_cursor(self, cursor):
499+
self.canvas.get_property("window").set_cursor(cursord[cursor])
500+
Gtk.main_iteration()
501+
502+
def draw_rubberband(self, event, x0, y0, x1, y1):
503+
# adapted from
504+
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189744
505+
self.ctx = self.canvas.get_property("window").cairo_create()
506+
507+
# todo: instead of redrawing the entire figure, copy the part of
508+
# the figure that was covered by the previous rubberband rectangle
509+
self.canvas.draw()
510+
511+
height = self.canvas.figure.bbox.height
512+
y1 = height - y1
513+
y0 = height - y0
514+
w = abs(x1 - x0)
515+
h = abs(y1 - y0)
516+
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
517+
518+
self.ctx.new_path()
519+
self.ctx.set_line_width(0.5)
520+
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
521+
self.ctx.set_source_rgb(0, 0, 0)
522+
self.ctx.stroke()
523+
524524
def _update_buttons_checked(self):
525525
for name, active in [("Pan", "PAN"), ("Zoom", "ZOOM")]:
526526
button = self._gtk_ids.get(name)

lib/matplotlib/backends/backend_macosx.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,10 @@ def close(self):
110110
class NavigationToolbar2Mac(_macosx.NavigationToolbar2, NavigationToolbar2):
111111

112112
def __init__(self, canvas):
113-
NavigationToolbar2.__init__(self, canvas)
114-
115-
def _init_toolbar(self):
113+
self.canvas = canvas # Needed by the _macosx __init__.
116114
_macosx.NavigationToolbar2.__init__(
117115
self, str(cbook._get_data_path('images')))
116+
NavigationToolbar2.__init__(self, canvas)
118117

119118
def draw_rubberband(self, event, x0, y0, x1, y1):
120119
self.canvas.set_rubberband(int(x0), int(y0), int(x1), int(y1))

lib/matplotlib/backends/backend_qt5.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -639,36 +639,12 @@ class NavigationToolbar2QT(NavigationToolbar2, QtWidgets.QToolBar):
639639

640640
def __init__(self, canvas, parent, coordinates=True):
641641
"""coordinates: should we show the coordinates on the right?"""
642+
QtWidgets.QToolBar.__init__(self, parent)
643+
642644
self._parent = parent
643645
self.coordinates = coordinates
644646
self._actions = {} # mapping of toolitem method names to QActions.
645-
QtWidgets.QToolBar.__init__(self, parent)
646-
NavigationToolbar2.__init__(self, canvas)
647-
648-
@cbook.deprecated("3.3", alternative="self.canvas.parent()")
649-
@property
650-
def parent(self):
651-
return self._parent
652-
653-
@cbook.deprecated(
654-
"3.3", alternative="os.path.join(mpl.get_data_path(), 'images')")
655-
@property
656-
def basedir(self):
657-
return str(cbook._get_data_path('images'))
658-
659-
def _icon(self, name, color=None):
660-
if is_pyqt5():
661-
name = name.replace('.png', '_large.png')
662-
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
663-
qt_compat._setDevicePixelRatio(pm, self.canvas._dpi_ratio)
664-
if color is not None:
665-
mask = pm.createMaskFromColor(QtGui.QColor('black'),
666-
QtCore.Qt.MaskOutColor)
667-
pm.fill(color)
668-
pm.setMask(mask)
669-
return QtGui.QIcon(pm)
670647

671-
def _init_toolbar(self):
672648
background_color = self.palette().color(self.backgroundRole())
673649
foreground_color = self.palette().color(self.foregroundRole())
674650
icon_color = (foreground_color
@@ -699,6 +675,31 @@ def _init_toolbar(self):
699675
labelAction = self.addWidget(self.locLabel)
700676
labelAction.setVisible(True)
701677

678+
NavigationToolbar2.__init__(self, canvas)
679+
680+
@cbook.deprecated("3.3", alternative="self.canvas.parent()")
681+
@property
682+
def parent(self):
683+
return self._parent
684+
685+
@cbook.deprecated(
686+
"3.3", alternative="os.path.join(mpl.get_data_path(), 'images')")
687+
@property
688+
def basedir(self):
689+
return str(cbook._get_data_path('images'))
690+
691+
def _icon(self, name, color=None):
692+
if is_pyqt5():
693+
name = name.replace('.png', '_large.png')
694+
pm = QtGui.QPixmap(str(cbook._get_data_path('images', name)))
695+
qt_compat._setDevicePixelRatio(pm, qt_compat._devicePixelRatio(self))
696+
if color is not None:
697+
mask = pm.createMaskFromColor(QtGui.QColor('black'),
698+
QtCore.Qt.MaskOutColor)
699+
pm.fill(color)
700+
pm.setMask(mask)
701+
return QtGui.QIcon(pm)
702+
702703
def edit_parameters(self):
703704
axes = self.canvas.figure.get_axes()
704705
if not axes:

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,10 @@ class NavigationToolbar2WebAgg(backend_bases.NavigationToolbar2):
367367
if name_of_method in _ALLOWED_TOOL_ITEMS
368368
]
369369

370-
def _init_toolbar(self):
370+
def __init__(self, canvas):
371371
self.message = ''
372372
self.cursor = 0
373+
super().__init__(canvas)
373374

374375
def set_message(self, message):
375376
if message != self.message:

lib/matplotlib/backends/backend_wx.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,22 +1104,6 @@ def _set_frame_icon(frame):
11041104
class NavigationToolbar2Wx(NavigationToolbar2, wx.ToolBar):
11051105
def __init__(self, canvas):
11061106
wx.ToolBar.__init__(self, canvas.GetParent(), -1)
1107-
NavigationToolbar2.__init__(self, canvas)
1108-
self._idle = True
1109-
self.prevZoomRect = None
1110-
# for now, use alternate zoom-rectangle drawing on all
1111-
# Macs. N.B. In future versions of wx it may be possible to
1112-
# detect Retina displays with window.GetContentScaleFactor()
1113-
# and/or dc.GetContentScaleFactor()
1114-
self.retinaFix = 'wxMac' in wx.PlatformInfo
1115-
1116-
def get_canvas(self, frame, fig):
1117-
return type(self.canvas)(frame, -1, fig)
1118-
1119-
def _init_toolbar(self):
1120-
_log.debug("%s - _init_toolbar", type(self))
1121-
1122-
self._parent = self.canvas.GetParent()
11231107

11241108
self.wx_ids = {}
11251109
for text, tooltip_text, image_file, callback in self.toolitems:
@@ -1140,6 +1124,18 @@ def _init_toolbar(self):
11401124

11411125
self.Realize()
11421126

1127+
NavigationToolbar2.__init__(self, canvas)
1128+
self._idle = True
1129+
self.prevZoomRect = None
1130+
# for now, use alternate zoom-rectangle drawing on all
1131+
# Macs. N.B. In future versions of wx it may be possible to
1132+
# detect Retina displays with window.GetContentScaleFactor()
1133+
# and/or dc.GetContentScaleFactor()
1134+
self.retinaFix = 'wxMac' in wx.PlatformInfo
1135+
1136+
def get_canvas(self, frame, fig):
1137+
return type(self.canvas)(frame, -1, fig)
1138+
11431139
def zoom(self, *args):
11441140
self.ToggleTool(self.wx_ids['Pan'], False)
11451141
NavigationToolbar2.zoom(self, *args)

0 commit comments

Comments
 (0)