Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 24524c4

Browse files
authored
Merge pull request #12760 from anntzer/deduplicate-tools
MNT: Deduplicate implementation of per-backend Tools.
2 parents 38371f1 + d64890b commit 24524c4

File tree

5 files changed

+46
-164
lines changed

5 files changed

+46
-164
lines changed

lib/matplotlib/backend_tools.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import re
1515
import time
1616
import logging
17+
from types import SimpleNamespace
1718
from weakref import WeakKeyDictionary
1819

1920
import numpy as np
@@ -102,6 +103,15 @@ def canvas(self):
102103
def toolmanager(self):
103104
return self._toolmanager
104105

106+
def _make_classic_style_pseudo_toolbar(self):
107+
"""
108+
Return a placeholder object with a single `canvas` attribute.
109+
110+
This is useful to reuse the implementations of tools already provided
111+
by the classic Toolbars.
112+
"""
113+
return SimpleNamespace(canvas=self.canvas)
114+
105115
def set_figure(self, figure):
106116
"""
107117
Assign a figure to the tool

lib/matplotlib/backends/_backend_tk.py

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ class NavigationToolbar2Tk(NavigationToolbar2, tk.Frame):
584584
"""
585585
def __init__(self, canvas, window):
586586
self.canvas = canvas
587+
# Avoid using self.window (prefer self.canvas.manager.window), so that
588+
# Tool implementations can reuse the methods.
587589
self.window = window
588590
NavigationToolbar2.__init__(self, canvas)
589591

@@ -602,16 +604,15 @@ def draw_rubberband(self, event, x0, y0, x1, y1):
602604
self.canvas._tkcanvas.delete(self.lastrect)
603605
self.lastrect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1)
604606

605-
#self.canvas.draw()
606-
607607
def release(self, event):
608608
if hasattr(self, "lastrect"):
609609
self.canvas._tkcanvas.delete(self.lastrect)
610610
del self.lastrect
611611

612612
def set_cursor(self, cursor):
613-
self.window.configure(cursor=cursord[cursor])
614-
self.window.update_idletasks()
613+
window = self.canvas.manager.window
614+
window.configure(cursor=cursord[cursor])
615+
window.update_idletasks()
615616

616617
def _Button(self, text, file, command, extension='.gif'):
617618
img_file = os.path.join(
@@ -684,7 +685,7 @@ def save_figure(self, *args):
684685
initialdir = os.path.expanduser(rcParams['savefig.directory'])
685686
initialfile = self.canvas.get_default_filename()
686687
fname = tkinter.filedialog.asksaveasfilename(
687-
master=self.window,
688+
master=self.canvas.manager.window,
688689
title='Save the figure',
689690
filetypes=tk_filetypes,
690691
defaultextension=defaultextension,
@@ -765,9 +766,6 @@ def hidetip(self):
765766

766767

767768
class RubberbandTk(backend_tools.RubberbandBase):
768-
def __init__(self, *args, **kwargs):
769-
backend_tools.RubberbandBase.__init__(self, *args, **kwargs)
770-
771769
def draw_rubberband(self, x0, y0, x1, y1):
772770
height = self.figure.canvas.figure.bbox.height
773771
y0 = height - y0
@@ -785,7 +783,8 @@ def remove_rubberband(self):
785783

786784
class SetCursorTk(backend_tools.SetCursorBase):
787785
def set_cursor(self, cursor):
788-
self.figure.canvas.manager.window.configure(cursor=cursord[cursor])
786+
NavigationToolbar2Tk.set_cursor(
787+
self._make_classic_style_pseudo_toolbar(), cursor)
789788

790789

791790
class ToolbarTk(ToolContainerBase, tk.Frame):
@@ -885,47 +884,8 @@ def set_message(self, s):
885884

886885
class SaveFigureTk(backend_tools.SaveFigureBase):
887886
def trigger(self, *args):
888-
filetypes = self.figure.canvas.get_supported_filetypes().copy()
889-
default_filetype = self.figure.canvas.get_default_filetype()
890-
891-
# Tk doesn't provide a way to choose a default filetype,
892-
# so we just have to put it first
893-
default_filetype_name = filetypes.pop(default_filetype)
894-
sorted_filetypes = ([(default_filetype, default_filetype_name)]
895-
+ sorted(filetypes.items()))
896-
tk_filetypes = [(name, '*.%s' % ext) for ext, name in sorted_filetypes]
897-
898-
# adding a default extension seems to break the
899-
# asksaveasfilename dialog when you choose various save types
900-
# from the dropdown. Passing in the empty string seems to
901-
# work - JDH!
902-
# defaultextension = self.figure.canvas.get_default_filetype()
903-
defaultextension = ''
904-
initialdir = os.path.expanduser(rcParams['savefig.directory'])
905-
initialfile = self.figure.canvas.get_default_filename()
906-
fname = tkinter.filedialog.asksaveasfilename(
907-
master=self.figure.canvas.manager.window,
908-
title='Save the figure',
909-
filetypes=tk_filetypes,
910-
defaultextension=defaultextension,
911-
initialdir=initialdir,
912-
initialfile=initialfile,
913-
)
914-
915-
if fname == "" or fname == ():
916-
return
917-
else:
918-
if initialdir == '':
919-
# explicitly missing key or empty str signals to use cwd
920-
rcParams['savefig.directory'] = initialdir
921-
else:
922-
# save dir for next time
923-
rcParams['savefig.directory'] = os.path.dirname(str(fname))
924-
try:
925-
# This method will handle the delegation to the correct type
926-
self.figure.savefig(fname)
927-
except Exception as e:
928-
tkinter.messagebox.showerror("Error saving file", str(e))
887+
NavigationToolbar2Tk.save_figure(
888+
self._make_classic_style_pseudo_toolbar())
929889

930890

931891
class ConfigureSubplotsTk(backend_tools.ConfigureSubplotsBase):

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -692,34 +692,6 @@ def get_filename_from_user(self):
692692
return None, self.ext
693693

694694

695-
class RubberbandGTK3(backend_tools.RubberbandBase):
696-
def __init__(self, *args, **kwargs):
697-
backend_tools.RubberbandBase.__init__(self, *args, **kwargs)
698-
self.ctx = None
699-
700-
def draw_rubberband(self, x0, y0, x1, y1):
701-
# 'adapted from http://aspn.activestate.com/ASPN/Cookbook/Python/
702-
# Recipe/189744'
703-
self.ctx = self.figure.canvas.get_property("window").cairo_create()
704-
705-
# todo: instead of redrawing the entire figure, copy the part of
706-
# the figure that was covered by the previous rubberband rectangle
707-
self.figure.canvas.draw()
708-
709-
height = self.figure.bbox.height
710-
y1 = height - y1
711-
y0 = height - y0
712-
w = abs(x1 - x0)
713-
h = abs(y1 - y0)
714-
rect = [int(val) for val in (min(x0, x1), min(y0, y1), w, h)]
715-
716-
self.ctx.new_path()
717-
self.ctx.set_line_width(0.5)
718-
self.ctx.rectangle(rect[0], rect[1], rect[2], rect[3])
719-
self.ctx.set_source_rgb(0, 0, 0)
720-
self.ctx.stroke()
721-
722-
723695
class ToolbarGTK3(ToolContainerBase, Gtk.Box):
724696
_icon_extension = '.png'
725697

@@ -809,6 +781,12 @@ def set_message(self, s):
809781
self.push(self._context, s)
810782

811783

784+
class RubberbandGTK3(backend_tools.RubberbandBase):
785+
def draw_rubberband(self, x0, y0, x1, y1):
786+
NavigationToolbar2GTK3.draw_rubberband(
787+
self._make_classic_style_pseudo_toolbar(), None, x0, y0, x1, y1)
788+
789+
812790
class SaveFigureGTK3(backend_tools.SaveFigureBase):
813791

814792
@cbook.deprecated("3.1")
@@ -832,7 +810,8 @@ class PseudoToolbar:
832810

833811
class SetCursorGTK3(backend_tools.SetCursorBase):
834812
def set_cursor(self, cursor):
835-
self.figure.canvas.get_property("window").set_cursor(cursord[cursor])
813+
NavigationToolbar2GTK3.set_cursor(
814+
self._make_classic_style_pseudo_toolbar(), cursor)
836815

837816

838817
class ConfigureSubplotsGTK3(backend_tools.ConfigureSubplotsBase, Gtk.Window):

lib/matplotlib/backends/backend_qt5.py

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ def remove_rubberband(self):
806806
def configure_subplots(self):
807807
image = os.path.join(matplotlib.rcParams['datapath'],
808808
'images', 'matplotlib.png')
809-
dia = SubplotToolQt(self.canvas.figure, self.parent)
809+
dia = SubplotToolQt(self.canvas.figure, self.canvas.parent())
810810
dia.setWindowIcon(QtGui.QIcon(image))
811811
dia.exec_()
812812

@@ -828,7 +828,7 @@ def save_figure(self, *args):
828828
filters.append(filter)
829829
filters = ';;'.join(filters)
830830

831-
fname, filter = _getSaveFileName(self.parent,
831+
fname, filter = _getSaveFileName(self.canvas.parent(),
832832
"Choose a filename to save to",
833833
start, filters, selectedFilter)
834834
if fname:
@@ -994,65 +994,30 @@ def set_message(self, s):
994994

995995
class ConfigureSubplotsQt(backend_tools.ConfigureSubplotsBase):
996996
def trigger(self, *args):
997-
image = os.path.join(matplotlib.rcParams['datapath'],
998-
'images', 'matplotlib.png')
999-
parent = self.canvas.manager.window
1000-
dia = SubplotToolQt(self.figure, parent)
1001-
dia.setWindowIcon(QtGui.QIcon(image))
1002-
dia.exec_()
997+
NavigationToolbar2QT.configure_subplots(
998+
self._make_classic_style_pseudo_toolbar())
1003999

10041000

10051001
class SaveFigureQt(backend_tools.SaveFigureBase):
10061002
def trigger(self, *args):
1007-
filetypes = self.canvas.get_supported_filetypes_grouped()
1008-
sorted_filetypes = sorted(filetypes.items())
1009-
default_filetype = self.canvas.get_default_filetype()
1010-
1011-
startpath = os.path.expanduser(
1012-
matplotlib.rcParams['savefig.directory'])
1013-
start = os.path.join(startpath, self.canvas.get_default_filename())
1014-
filters = []
1015-
selectedFilter = None
1016-
for name, exts in sorted_filetypes:
1017-
exts_list = " ".join(['*.%s' % ext for ext in exts])
1018-
filter = '%s (%s)' % (name, exts_list)
1019-
if default_filetype in exts:
1020-
selectedFilter = filter
1021-
filters.append(filter)
1022-
filters = ';;'.join(filters)
1023-
1024-
parent = self.canvas.manager.window
1025-
fname, filter = _getSaveFileName(parent,
1026-
"Choose a filename to save to",
1027-
start, filters, selectedFilter)
1028-
if fname:
1029-
# Save dir for next time, unless empty str (i.e., use cwd).
1030-
if startpath != "":
1031-
matplotlib.rcParams['savefig.directory'] = (
1032-
os.path.dirname(fname))
1033-
try:
1034-
self.canvas.figure.savefig(fname)
1035-
except Exception as e:
1036-
QtWidgets.QMessageBox.critical(
1037-
self, "Error saving file", str(e),
1038-
QtWidgets.QMessageBox.Ok, QtWidgets.QMessageBox.NoButton)
1003+
NavigationToolbar2QT.save_figure(
1004+
self._make_classic_style_pseudo_toolbar())
10391005

10401006

10411007
class SetCursorQt(backend_tools.SetCursorBase):
10421008
def set_cursor(self, cursor):
1043-
self.canvas.setCursor(cursord[cursor])
1009+
NavigationToolbar2QT.set_cursor(
1010+
self._make_classic_style_pseudo_toolbar(), cursor)
10441011

10451012

10461013
class RubberbandQt(backend_tools.RubberbandBase):
10471014
def draw_rubberband(self, x0, y0, x1, y1):
1048-
height = self.canvas.figure.bbox.height
1049-
y1 = height - y1
1050-
y0 = height - y0
1051-
rect = [int(val) for val in (x0, y0, x1 - x0, y1 - y0)]
1052-
self.canvas.drawRectangle(rect)
1015+
NavigationToolbar2QT.draw_rubberband(
1016+
self._make_classic_style_pseudo_toolbar(), None, x0, y0, x1, y1)
10531017

10541018
def remove_rubberband(self):
1055-
self.canvas.drawRectangle(None)
1019+
NavigationToolbar2QT.remove_rubberband(
1020+
self._make_classic_style_pseudo_toolbar())
10561021

10571022

10581023
class HelpQt(backend_tools.ToolHelpBase):

lib/matplotlib/backends/backend_wx.py

Lines changed: 6 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1445,8 +1445,8 @@ def save_figure(self, *args):
14451445
# Fetch the required filename and file type.
14461446
filetypes, exts, filter_index = self.canvas._get_imagesave_wildcards()
14471447
default_file = self.canvas.get_default_filename()
1448-
dlg = wx.FileDialog(self._parent, "Save to file", "", default_file,
1449-
filetypes,
1448+
dlg = wx.FileDialog(self.canvas.GetParent(),
1449+
"Save to file", "", default_file, filetypes,
14501450
wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
14511451
dlg.SetFilterIndex(filter_index)
14521452
if dlg.ShowModal() == wx.ID_OK:
@@ -1692,46 +1692,14 @@ def get_canvas(self, frame, fig):
16921692

16931693
class SaveFigureWx(backend_tools.SaveFigureBase):
16941694
def trigger(self, *args):
1695-
# Fetch the required filename and file type.
1696-
filetypes, exts, filter_index = self.canvas._get_imagesave_wildcards()
1697-
default_dir = os.path.expanduser(
1698-
matplotlib.rcParams['savefig.directory'])
1699-
default_file = self.canvas.get_default_filename()
1700-
dlg = wx.FileDialog(self.canvas.GetTopLevelParent(), "Save to file",
1701-
default_dir, default_file, filetypes,
1702-
wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
1703-
dlg.SetFilterIndex(filter_index)
1704-
if dlg.ShowModal() != wx.ID_OK:
1705-
return
1706-
1707-
dirname = dlg.GetDirectory()
1708-
filename = dlg.GetFilename()
1709-
DEBUG_MSG('Save file dir:%s name:%s' % (dirname, filename), 3, self)
1710-
format = exts[dlg.GetFilterIndex()]
1711-
basename, ext = os.path.splitext(filename)
1712-
if ext.startswith('.'):
1713-
ext = ext[1:]
1714-
if ext in ('svg', 'pdf', 'ps', 'eps', 'png') and format != ext:
1715-
# looks like they forgot to set the image type drop
1716-
# down, going with the extension.
1717-
_log.warning('extension %s did not match the selected '
1718-
'image type %s; going with %s',
1719-
ext, format, ext)
1720-
format = ext
1721-
if default_dir != "":
1722-
matplotlib.rcParams['savefig.directory'] = dirname
1723-
try:
1724-
self.canvas.figure.savefig(
1725-
os.path.join(dirname, filename), format=format)
1726-
except Exception as e:
1727-
error_msg_wx(str(e))
1695+
NavigationToolbar2Wx.save_figure(
1696+
self._make_classic_style_pseudo_toolbar())
17281697

17291698

17301699
class SetCursorWx(backend_tools.SetCursorBase):
17311700
def set_cursor(self, cursor):
1732-
cursor = wx.Cursor(cursord[cursor])
1733-
self.canvas.SetCursor(cursor)
1734-
self.canvas.Update()
1701+
NavigationToolbar2Wx.set_cursor(
1702+
self._make_classic_style_pseudo_toolbar(), cursor)
17351703

17361704

17371705
if 'wxMac' not in wx.PlatformInfo:

0 commit comments

Comments
 (0)