From 30f8b77993b544f2f4433cb2fa8cbd9a4a6772aa Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Wed, 21 Mar 2018 03:54:38 +0100 Subject: [PATCH 1/5] first implementation of Toolmanager for the Wx backends --- .../user_interfaces/toolmanager_sgskip.py | 1 + lib/matplotlib/backends/backend_wx.py | 156 ++++++++++++++++-- 2 files changed, 147 insertions(+), 10 deletions(-) diff --git a/examples/user_interfaces/toolmanager_sgskip.py b/examples/user_interfaces/toolmanager_sgskip.py index 6ae0fc3d88e9..9820e12744c5 100644 --- a/examples/user_interfaces/toolmanager_sgskip.py +++ b/examples/user_interfaces/toolmanager_sgskip.py @@ -19,6 +19,7 @@ matplotlib.use('GTK3Cairo') # matplotlib.use('TkAgg') # matplotlib.use('QT5Agg') +# matplotlib.use('WxAgg') matplotlib.rcParams['toolbar'] = 'toolmanager' import matplotlib.pyplot as plt from matplotlib.backend_tools import ToolBase, ToolToggleBase diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index dfbf23340492..2ee7eb9d1deb 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -18,7 +18,8 @@ import matplotlib from matplotlib.backend_bases import ( _Backend, FigureCanvasBase, FigureManagerBase, GraphicsContextBase, - NavigationToolbar2, RendererBase, TimerBase, cursors) + NavigationToolbar2, RendererBase, TimerBase, cursors, ToolContainerBase, + StatusbarBase) from matplotlib.backend_bases import _has_pil from matplotlib._pylab_helpers import Gcf @@ -27,6 +28,7 @@ from matplotlib.path import Path from matplotlib.transforms import Affine2D from matplotlib.widgets import SubplotTool +from matplotlib.backend_managers import ToolManager from matplotlib import cbook, rcParams, backend_tools import wx @@ -1158,9 +1160,8 @@ def __init__(self, num, fig): # Frame will be sized later by the Fit method DEBUG_MSG("__init__()", 1, self) self.num = num + _set_frame_icon(self) - statbar = StatusBarWx(self) - self.SetStatusBar(statbar) self.canvas = self.get_canvas(fig) self.canvas.SetInitialSize(wx.Size(fig.bbox.width, fig.bbox.height)) self.canvas.SetFocus() @@ -1169,7 +1170,18 @@ def __init__(self, num, fig): # By adding toolbar in sizer, we are able to put it at the bottom # of the frame - so appearance is closer to GTK version - self.toolbar = self._get_toolbar(statbar) + self.toolmanager = self._get_toolmanager() + if self.toolmanager: + self.statusbar = StatusbarWx(self, self.toolmanager) + else: + self.statusbar = StatusBarWx(self) + self.SetStatusBar(self.statusbar) + self.toolbar = self._get_toolbar(self.statusbar) + + if self.toolmanager: + backend_tools.add_tools_to_manager(self.toolmanager) + if self.toolbar: + backend_tools.add_tools_to_container(self.toolbar) if self.toolbar is not None: self.toolbar.Realize() @@ -1203,10 +1215,19 @@ def _get_toolbar(self, statbar): if rcParams['toolbar'] == 'toolbar2': toolbar = NavigationToolbar2Wx(self.canvas) toolbar.set_status_bar(statbar) + elif matplotlib.rcParams['toolbar'] == 'toolmanager': + toolbar = ToolbarWx(self.toolmanager, self) else: toolbar = None return toolbar + def _get_toolmanager(self): + if matplotlib.rcParams['toolbar'] == 'toolmanager': + toolmanager = ToolManager(self.canvas.figure) + else: + toolmanager = None + return toolmanager + def get_canvas(self, fig): return FigureCanvasWx(self, -1, fig) @@ -1263,8 +1284,8 @@ def __init__(self, canvas, num, frame): self.frame = frame self.window = frame - self.tb = frame.GetToolBar() - self.toolbar = self.tb # consistent with other backends + self.toolmanager = frame.toolmanager + self.toolbar = frame.GetToolBar() def show(self): self.frame.Show() @@ -1328,6 +1349,22 @@ def _load_bitmap(filename): return bmp +_FRAME_ICON = None +def _set_frame_icon(frame): + # set frame icon + global _FRAME_ICON + if not _FRAME_ICON: + _FRAME_ICON = bundle = wx.IconBundle() + for image in ('matplotlib.png', 'matplotlib_large.png'): + image = os.path.join(matplotlib.rcParams['datapath'], 'images', + image) + if not os.path.exists(image): continue + icon = wx.Icon(_load_bitmap(image)) + if not icon.IsOk(): return + bundle.AddIcon(icon) + frame.SetIcons(_FRAME_ICON) + + class MenuButtonWx(wx.Button): """ wxPython does not permit a menu to be incorporated directly into a toolbar. @@ -1675,7 +1712,7 @@ class StatusBarWx(wx.StatusBar): convenience. """ - def __init__(self, parent): + def __init__(self, parent, *args, **kwargs): wx.StatusBar.__init__(self, parent, -1) self.SetFieldsCount(2) self.SetStatusText("None", 1) @@ -1690,9 +1727,107 @@ def set_function(self, string): # tools for matplotlib.backend_managers.ToolManager: -# for now only SaveFigure, SetCursor and Rubberband are implemented -# once a ToolbarWx is implemented, also FigureManagerWx needs to be -# modified, similar to pull request #9934 + +class ToolbarWx(ToolContainerBase, wx.ToolBar): + def __init__(self, toolmanager, parent, style=wx.TB_HORIZONTAL): + ToolContainerBase.__init__(self, toolmanager) + wx.ToolBar.__init__(self, parent, -1, style=style) + self._toolitems = {} + self._groups = {} + self._last = None + + def add_toolitem( + self, name, group, position, image_file, description, toggle): + + before, group = self._add_to_group(group, name, position) + idx = self.GetToolPos(before.Id) + if image_file: + bmp = _load_bitmap(image_file) + kind = wx.ITEM_NORMAL if not toggle else wx.ITEM_CHECK + tool = self.InsertTool(idx, -1, name, bmp, wx.NullBitmap, kind, + description or "") + else: + size = (self.GetTextExtent(name)[0]+10,-1) + if toggle: + control = wx.ToggleButton(self, -1, name, size=size) + else: + control = wx.Button(self, -1, name, size=size) + tool = self.InsertControl(idx, control, label=name) + self.Realize() + + def handler(event): + self.trigger_tool(name) + + if image_file: + self.Bind(wx.EVT_TOOL, handler, tool) + else: + control.Bind(wx.EVT_LEFT_DOWN, handler) + + self._last = tool + self._toolitems.setdefault(name, []) + group.insert(position, tool) + self._toolitems[name].append((tool, handler)) + + def _add_to_group(self, group, name, position): + gr = self._groups.get(group, []) + if not gr: + sep = self.AddSeparator() + gr.append(sep) + before = gr[position] + self._groups[group] = gr + return before, gr + + def toggle_toolitem(self, name, toggled): + if name not in self._toolitems: + return + for tool, handler in self._toolitems[name]: + if not tool.IsControl(): + self.ToggleTool(tool.Id, toggled) + else: + tool.GetControl().SetValue(toggled) + self.Refresh() + def remove_toolitem(self, name): + for tool, handler in self._toolitems[name]: + self.DeleteTool(tool.Id) + del self._toolitems[name] + + +class StatusbarWx(StatusbarBase, wx.StatusBar): + """for use with ToolManager""" + def __init__(self, parent, *args, **kwargs): + StatusbarBase.__init__(self, *args, **kwargs) + wx.StatusBar.__init__(self, parent, -1) + self.SetFieldsCount(1) + self.SetStatusText("") + + def set_message(self, s): + self.SetStatusText(s) + + +class ConfigureSubplotsWx(backend_tools.ConfigureSubplotsBase): + def trigger(self, *args): + self.configure_subplots() + + def configure_subplots(self): + frame = wx.Frame(None, -1, "Configure subplots") + _set_frame_icon(frame) + frame.toolmanager = None + + toolfig = Figure((6, 3)) + canvas = self.get_canvas(frame, toolfig) + + # Now put all into a sizer + sizer = wx.BoxSizer(wx.VERTICAL) + # This way of adding to sizer allows resizing + sizer.Add(canvas, 1, wx.LEFT | wx.TOP | wx.GROW) + frame.SetSizer(sizer) + frame.Fit() + tool = SubplotTool(self.canvas.figure, toolfig) + frame.Show() + + def get_canvas(self, frame, fig): + return type(self.canvas)(frame, -1, fig) + class SaveFigureWx(backend_tools.SaveFigureBase): def trigger(self, *args): @@ -1827,6 +1962,7 @@ def remove_rubberband(self, dc=None): backend_tools.ToolSaveFigure = SaveFigureWx +backend_tools.ToolConfigureSubplots = ConfigureSubplotsWx backend_tools.ToolSetCursor = SetCursorWx backend_tools.ToolRubberband = RubberbandWx From 5bd1d761d08908e28d6dca33f5f7ee9e2c367cbd Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Wed, 21 Mar 2018 04:08:00 +0100 Subject: [PATCH 2/5] fix for non-toolmanager use of Wx backends; set frame icon for subplot tool --- lib/matplotlib/backends/backend_wx.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 2ee7eb9d1deb..e6e022092317 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1284,7 +1284,7 @@ def __init__(self, canvas, num, frame): self.frame = frame self.window = frame - self.toolmanager = frame.toolmanager + self.toolmanager = getattr(frame, "toolmanager", None) self.toolbar = frame.GetToolBar() def show(self): @@ -1552,6 +1552,7 @@ def pan(self, *args): def configure_subplots(self, evt): global FigureManager # placates pyflakes: created by @_Backend.export frame = wx.Frame(None, -1, "Configure subplots") + _set_frame_icon(frame) toolfig = Figure((6, 3)) canvas = self.get_canvas(frame, toolfig) @@ -1811,7 +1812,6 @@ def trigger(self, *args): def configure_subplots(self): frame = wx.Frame(None, -1, "Configure subplots") _set_frame_icon(frame) - frame.toolmanager = None toolfig = Figure((6, 3)) canvas = self.get_canvas(frame, toolfig) From 9844acc11e8a5dd6fe3f5f362aae55971128a3a3 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Wed, 21 Mar 2018 10:54:36 +0100 Subject: [PATCH 3/5] fix PEP8 warnings --- lib/matplotlib/backends/backend_wx.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index e6e022092317..68b4f66e8802 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1350,6 +1350,8 @@ def _load_bitmap(filename): _FRAME_ICON = None + + def _set_frame_icon(frame): # set frame icon global _FRAME_ICON @@ -1358,9 +1360,11 @@ def _set_frame_icon(frame): for image in ('matplotlib.png', 'matplotlib_large.png'): image = os.path.join(matplotlib.rcParams['datapath'], 'images', image) - if not os.path.exists(image): continue + if not os.path.exists(image): + continue icon = wx.Icon(_load_bitmap(image)) - if not icon.IsOk(): return + if not icon.IsOk(): + return bundle.AddIcon(icon) frame.SetIcons(_FRAME_ICON) @@ -1748,7 +1752,7 @@ def add_toolitem( tool = self.InsertTool(idx, -1, name, bmp, wx.NullBitmap, kind, description or "") else: - size = (self.GetTextExtent(name)[0]+10,-1) + size = (self.GetTextExtent(name)[0]+10, -1) if toggle: control = wx.ToggleButton(self, -1, name, size=size) else: @@ -1787,6 +1791,7 @@ def toggle_toolitem(self, name, toggled): else: tool.GetControl().SetValue(toggled) self.Refresh() + def remove_toolitem(self, name): for tool, handler in self._toolitems[name]: self.DeleteTool(tool.Id) From 01a83de610b5326474a3fa412efd3f4d8a04d9db Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 26 Mar 2018 19:16:38 +0200 Subject: [PATCH 4/5] don't store icon as global; re-load instead --- lib/matplotlib/backends/backend_wx.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/backends/backend_wx.py b/lib/matplotlib/backends/backend_wx.py index 68b4f66e8802..1cf6d05f33c9 100644 --- a/lib/matplotlib/backends/backend_wx.py +++ b/lib/matplotlib/backends/backend_wx.py @@ -1349,24 +1349,18 @@ def _load_bitmap(filename): return bmp -_FRAME_ICON = None - - def _set_frame_icon(frame): # set frame icon - global _FRAME_ICON - if not _FRAME_ICON: - _FRAME_ICON = bundle = wx.IconBundle() - for image in ('matplotlib.png', 'matplotlib_large.png'): - image = os.path.join(matplotlib.rcParams['datapath'], 'images', - image) - if not os.path.exists(image): - continue - icon = wx.Icon(_load_bitmap(image)) - if not icon.IsOk(): - return - bundle.AddIcon(icon) - frame.SetIcons(_FRAME_ICON) + bundle = wx.IconBundle() + for image in ('matplotlib.png', 'matplotlib_large.png'): + image = os.path.join(matplotlib.rcParams['datapath'], 'images', image) + if not os.path.exists(image): + continue + icon = wx.Icon(_load_bitmap(image)) + if not icon.IsOk(): + return + bundle.AddIcon(icon) + frame.SetIcons(bundle) class MenuButtonWx(wx.Button): From 71043a6e8720e20ea11785e6fbc5b3ee195667f7 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Mon, 23 Apr 2018 20:22:38 +0200 Subject: [PATCH 5/5] add StatusBar to MyWx to avoid metaclass error message --- doc/sphinxext/mock_gui_toolkits.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/sphinxext/mock_gui_toolkits.py b/doc/sphinxext/mock_gui_toolkits.py index bb378e77382c..0cb9f93635b4 100644 --- a/doc/sphinxext/mock_gui_toolkits.py +++ b/doc/sphinxext/mock_gui_toolkits.py @@ -118,6 +118,9 @@ class ToolBar(object): class Frame(object): pass + class StatusBar(object): + pass + def setup(app): sys.modules.update(