diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index 17dcf516ac05..8f63d68a471f 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -14,6 +14,7 @@ import re import time import logging +from types import SimpleNamespace from weakref import WeakKeyDictionary import numpy as np @@ -102,6 +103,15 @@ def canvas(self): def toolmanager(self): return self._toolmanager + def _make_classic_style_pseudo_toolbar(self): + """ + Return a placeholder object with a single `canvas` attribute. + + This is useful to reuse the implementations of tools already provided + by the classic Toolbars. + """ + return SimpleNamespace(canvas=self.canvas) + def set_figure(self, figure): """ Assign a figure to the tool diff --git a/lib/matplotlib/backends/_backend_tk.py b/lib/matplotlib/backends/_backend_tk.py index 3635de2b4af3..6ed99935ecb3 100644 --- a/lib/matplotlib/backends/_backend_tk.py +++ b/lib/matplotlib/backends/_backend_tk.py @@ -584,6 +584,8 @@ class NavigationToolbar2Tk(NavigationToolbar2, tk.Frame): """ def __init__(self, canvas, window): self.canvas = canvas + # Avoid using self.window (prefer self.canvas.manager.window), so that + # Tool implementations can reuse the methods. self.window = window NavigationToolbar2.__init__(self, canvas) @@ -602,16 +604,15 @@ def draw_rubberband(self, event, x0, y0, x1, y1): self.canvas._tkcanvas.delete(self.lastrect) self.lastrect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1) - #self.canvas.draw() - def release(self, event): if hasattr(self, "lastrect"): self.canvas._tkcanvas.delete(self.lastrect) del self.lastrect def set_cursor(self, cursor): - self.window.configure(cursor=cursord[cursor]) - self.window.update_idletasks() + window = self.canvas.manager.window + window.configure(cursor=cursord[cursor]) + window.update_idletasks() def _Button(self, text, file, command, extension='.gif'): img_file = os.path.join( @@ -684,7 +685,7 @@ def save_figure(self, *args): initialdir = os.path.expanduser(rcParams['savefig.directory']) initialfile = self.canvas.get_default_filename() fname = tkinter.filedialog.asksaveasfilename( - master=self.window, + master=self.canvas.manager.window, title='Save the figure', filetypes=tk_filetypes, defaultextension=defaultextension, @@ -765,9 +766,6 @@ def hidetip(self): class RubberbandTk(backend_tools.RubberbandBase): - def __init__(self, *args, **kwargs): - backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - def draw_rubberband(self, x0, y0, x1, y1): height = self.figure.canvas.figure.bbox.height y0 = height - y0 @@ -785,7 +783,8 @@ def remove_rubberband(self): class SetCursorTk(backend_tools.SetCursorBase): def set_cursor(self, cursor): - self.figure.canvas.manager.window.configure(cursor=cursord[cursor]) + NavigationToolbar2Tk.set_cursor( + self._make_classic_style_pseudo_toolbar(), cursor) class ToolbarTk(ToolContainerBase, tk.Frame): @@ -885,47 +884,8 @@ def set_message(self, s): class SaveFigureTk(backend_tools.SaveFigureBase): def trigger(self, *args): - filetypes = self.figure.canvas.get_supported_filetypes().copy() - default_filetype = self.figure.canvas.get_default_filetype() - - # Tk doesn't provide a way to choose a default filetype, - # so we just have to put it first - default_filetype_name = filetypes.pop(default_filetype) - sorted_filetypes = ([(default_filetype, default_filetype_name)] - + sorted(filetypes.items())) - tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes] - - # adding a default extension seems to break the - # asksaveasfilename dialog when you choose various save types - # from the dropdown. Passing in the empty string seems to - # work - JDH! - # defaultextension = self.figure.canvas.get_default_filetype() - defaultextension = '' - initialdir = os.path.expanduser(rcParams['savefig.directory']) - initialfile = self.figure.canvas.get_default_filename() - fname = tkinter.filedialog.asksaveasfilename( - master=self.figure.canvas.manager.window, - title='Save the figure', - filetypes=tk_filetypes, - defaultextension=defaultextension, - initialdir=initialdir, - initialfile=initialfile, - ) - - if fname == "" or fname == (): - return - else: - if initialdir == '': - # explicitly missing key or empty str signals to use cwd - rcParams['savefig.directory'] = initialdir - else: - # save dir for next time - rcParams['savefig.directory'] = os.path.dirname(str(fname)) - try: - # This method will handle the delegation to the correct type - self.figure.savefig(fname) - except Exception as e: - tkinter.messagebox.showerror("Error saving file", str(e)) + NavigationToolbar2Tk.save_figure( + self._make_classic_style_pseudo_toolbar()) class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase): diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py index 7b2ac7d9eead..42067167a90d 100644 --- a/lib/matplotlib/backends/backend_gtk3.py +++ b/lib/matplotlib/backends/backend_gtk3.py @@ -658,34 +658,6 @@ def get_filename_from_user(self): return None, self.ext -class RubberbandGTK3(backend_tools.RubberbandBase): - def __init__(self, *args, **kwargs): - backend_tools.RubberbandBase.__init__(self, *args, **kwargs) - self.ctx = None - - def draw_rubberband(self, x0, y0, x1, y1): - # 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/ - # Recipe/189744' - self.ctx = self.figure.canvas.get_property("window").cairo_create() - - # todo: instead of redrawing the entire figure, copy the part of - # the figure that was covered by the previous rubberband rectangle - self.figure.canvas.draw() - - height = self.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - w = abs(x1 - x0) - h = abs(y1 - y0) - rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)] - - self.ctx.new_path() - self.ctx.set_line_width(0.5) - self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3]) - self.ctx.set_source_rgb(0, 0, 0) - self.ctx.stroke() - - class ToolbarGTK3(ToolContainerBase, Gtk.Box): _icon_extension = '.png' @@ -775,6 +747,12 @@ def set_message(self, s): self.push(self._context, s) +class RubberbandGTK3(backend_tools.RubberbandBase): + def draw_rubberband(self, x0, y0, x1, y1): + NavigationToolbar2GTK3.draw_rubberband( + self._make_classic_style_pseudo_toolbar(), None, x0, y0, x1, y1) + + class SaveFigureGTK3(backend_tools.SaveFigureBase): def get_filechooser(self): @@ -807,7 +785,8 @@ def trigger(self, *args, **kwargs): class SetCursorGTK3(backend_tools.SetCursorBase): def set_cursor(self, cursor): - self.figure.canvas.get_property("window").set_cursor(cursord[cursor]) + NavigationToolbar2GTK3.set_cursor( + self._make_classic_style_pseudo_toolbar(), cursor) class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window): diff --git a/lib/matplotlib/backends/backend_qt5.py b/lib/matplotlib/backends/backend_qt5.py index 7a2718760ac3..dde666e608cd 100644 --- a/lib/matplotlib/backends/backend_qt5.py +++ b/lib/matplotlib/backends/backend_qt5.py @@ -806,7 +806,7 @@ def remove_rubberband(self): def configure_subplots(self): image = os.path.join(matplotlib.rcParams['datapath'], 'images', 'matplotlib.png') - dia = SubplotToolQt(self.canvas.figure, self.parent) + dia = SubplotToolQt(self.canvas.figure, self.canvas.parent()) dia.setWindowIcon(QtGui.QIcon(image)) dia.exec_() @@ -828,7 +828,7 @@ def save_figure(self, *args): filters.append(filter) filters = ';;'.join(filters) - fname, filter = _getSaveFileName(self.parent, + fname, filter = _getSaveFileName(self.canvas.parent(), "Choose a filename to save to", start, filters, selectedFilter) if fname: @@ -994,65 +994,30 @@ def set_message(self, s): class ConfigureSubplotsQt(backend_tools.ConfigureSubplotsBase): def trigger(self, *args): - image = os.path.join(matplotlib.rcParams['datapath'], - 'images', 'matplotlib.png') - parent = self.canvas.manager.window - dia = SubplotToolQt(self.figure, parent) - dia.setWindowIcon(QtGui.QIcon(image)) - dia.exec_() + NavigationToolbar2QT.configure_subplots( + self._make_classic_style_pseudo_toolbar()) class SaveFigureQt(backend_tools.SaveFigureBase): def trigger(self, *args): - filetypes = self.canvas.get_supported_filetypes_grouped() - sorted_filetypes = sorted(filetypes.items()) - default_filetype = self.canvas.get_default_filetype() - - startpath = os.path.expanduser( - matplotlib.rcParams['savefig.directory']) - start = os.path.join(startpath, self.canvas.get_default_filename()) - filters = [] - selectedFilter = None - for name, exts in sorted_filetypes: - exts_list = " ".join(['*.%s' % ext for ext in exts]) - filter = '%s (%s)' % (name, exts_list) - if default_filetype in exts: - selectedFilter = filter - filters.append(filter) - filters = ';;'.join(filters) - - parent = self.canvas.manager.window - fname, filter = _getSaveFileName(parent, - "Choose a filename to save to", - start, filters, selectedFilter) - if fname: - # Save dir for next time, unless empty str (i.e., use cwd). - if startpath != "": - matplotlib.rcParams['savefig.directory'] = ( - os.path.dirname(fname)) - try: - self.canvas.figure.savefig(fname) - except Exception as e: - QtWidgets.QMessageBox.critical( - self, "Error saving file", str(e), - QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton) + NavigationToolbar2QT.save_figure( + self._make_classic_style_pseudo_toolbar()) class SetCursorQt(backend_tools.SetCursorBase): def set_cursor(self, cursor): - self.canvas.setCursor(cursord[cursor]) + NavigationToolbar2QT.set_cursor( + self._make_classic_style_pseudo_toolbar(), cursor) class RubberbandQt(backend_tools.RubberbandBase): def draw_rubberband(self, x0, y0, x1, y1): - height = self.canvas.figure.bbox.height - y1 = height - y1 - y0 = height - y0 - rect = [int(val) for val in (x0, y0, x1 - x0, y1 - y0)] - self.canvas.drawRectangle(rect) + NavigationToolbar2QT.draw_rubberband( + self._make_classic_style_pseudo_toolbar(), None, x0, y0, x1, y1) def remove_rubberband(self): - self.canvas.drawRectangle(None) + NavigationToolbar2QT.remove_rubberband( + self._make_classic_style_pseudo_toolbar()) class HelpQt(backend_tools.ToolHelpBase): diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 1a86b28ad98b..22d96f4a71cd 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1445,8 +1445,8 @@ def save_figure(self, *args): # Fetch the required filename and file type. filetypes, exts, filter_index = self.canvas._get_imagesave_wildcards() default_file = self.canvas.get_default_filename() - dlg = wx.FileDialog(self._parent, "Save to file", "", default_file, - filetypes, + dlg = wx.FileDialog(self.canvas.GetParent(), + "Save to file", "", default_file, filetypes, wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) dlg.SetFilterIndex(filter_index) if dlg.ShowModal() == wx.ID_OK: @@ -1692,46 +1692,14 @@ def get_canvas(self, frame, fig): class SaveFigureWx(backend_tools.SaveFigureBase): def trigger(self, *args): - # Fetch the required filename and file type. - filetypes, exts, filter_index = self.canvas._get_imagesave_wildcards() - default_dir = os.path.expanduser( - matplotlib.rcParams['savefig.directory']) - default_file = self.canvas.get_default_filename() - dlg = wx.FileDialog(self.canvas.GetTopLevelParent(), "Save to file", - default_dir, default_file, filetypes, - wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - dlg.SetFilterIndex(filter_index) - if dlg.ShowModal() != wx.ID_OK: - return - - dirname = dlg.GetDirectory() - filename = dlg.GetFilename() - DEBUG_MSG('Save file dir:%s name:%s' % (dirname, filename), 3, self) - format = exts[dlg.GetFilterIndex()] - basename, ext = os.path.splitext(filename) - if ext.startswith('.'): - ext = ext[1:] - if ext in ('svg', 'pdf', 'ps', 'eps', 'png') and format != ext: - # looks like they forgot to set the image type drop - # down, going with the extension. - _log.warning('extension %s did not match the selected ' - 'image type %s; going with %s', - ext, format, ext) - format = ext - if default_dir != "": - matplotlib.rcParams['savefig.directory'] = dirname - try: - self.canvas.figure.savefig( - os.path.join(dirname, filename), format=format) - except Exception as e: - error_msg_wx(str(e)) + NavigationToolbar2Wx.save_figure( + self._make_classic_style_pseudo_toolbar()) class SetCursorWx(backend_tools.SetCursorBase): def set_cursor(self, cursor): - cursor = wx.Cursor(cursord[cursor]) - self.canvas.SetCursor(cursor) - self.canvas.Update() + NavigationToolbar2Wx.set_cursor( + self._make_classic_style_pseudo_toolbar(), cursor) if 'wxMac' not in wx.PlatformInfo: