10000 Deduplicate implementation of per-backend Tools. by anntzer · Pull Request #12760 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Deduplicate implementation of per-backend Tools. #12760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import re
import time
import logging
from types import SimpleNamespace
from weakref import WeakKeyDictionary

import numpy as np
Expand Down Expand Up @@ -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
Expand Down
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
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
0