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( 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..1cf6d05f33c9 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 = getattr(frame, "toolmanager", None) + self.toolbar = frame.GetToolBar() def show(self): self.frame.Show() @@ -1328,6 +1349,20 @@ def _load_bitmap(filename): return bmp +def _set_frame_icon(frame): + # set 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): """ wxPython does not permit a menu to be incorporated directly into a toolbar. @@ -1515,6 +1550,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) @@ -1675,7 +1711,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 +1726,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) + + 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 +1961,7 @@ def remove_rubberband(self, dc=None): backend_tools.ToolSaveFigure = SaveFigureWx +backend_tools.ToolConfigureSubplots = ConfigureSubplotsWx backend_tools.ToolSetCursor = SetCursorWx backend_tools.ToolRubberband = RubberbandWx
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: