Deduplicate implementation of per-backend Tools. by anntzer · Pull Request #12760 · matplotlib/matplotlib · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/matplotlib/backend_tools.py
60 changes: 10 additions & 50 deletions lib/matplotlib/backends/_backend_tk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -765,9 +766,6 @@ def hidetip(self):


class RubberbandTk(backend_tools.RubberbandBase):
def __init__(self, *args, **kwargs):
backend_tools.RubberbandBase.__init__(self, *args, **kwargs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that RubberbandBase does not have an __init__. So technically, this change is possible.

Generally, is it wise not to call super().__init__(). The parent class may later add an __init__() method.

Here in particular, while RubberbandBase does not have __init__(), its parent ToolBase has, which does not get called also with the existing code. That doesn't look right to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will get called even after deleting this code block: when instantiating a RubberbandTk, Python will call RubberbandTk.__init__; because RubberbandTk does not define an __init__, it is inherited by looking up in the base classes; ultimately this resolves to ToolBase.init`.
In other words, the deleted code block does not do anything.


def draw_rubberband(self, x0, y0, x1, y1):
height = self.figure.canvas.figure.bbox.height
y0 = height - y0
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
37 changes: 8 additions & 29 deletions lib/matplotlib/backends/backend_gtk3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
59 changes: 12 additions & 47 deletions lib/matplotlib/backends/backend_qt5.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_()

Expand All @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
44 changes: 6 additions & 38 deletions lib/matplotlib/backends/backend_wx.py