Skip to content

Commit 30dd65f

Browse files
committed
Move show() to somewhere naturally inheritable.
It's actually not clear whether to move it to FigureCanvas or FigureManager. FigureCanvas already has start_event_loop (cf. start_main_loop), but pyplot_show/start_main_loop is more a global/pyplot-only concept, so perhaps it belongs to FigureManager instead? OTOH, being on canvas_class makes it easier for switch_backend to access it (it doesn't need to go through `canvas_class.manager_class.pyplot_show`, which is relevant considering that some designs of inheritable backends didn't even have a manager_class attribute).
1 parent 231d1c8 commit 30dd65f

11 files changed

+168
-90
lines changed

lib/matplotlib/backend_bases.py

+49-3
Original file line numberDiff line numberDiff line change
@@ -2840,6 +2840,48 @@ def create_with_canvas(cls, canvas_class, figure, num):
28402840
"""
28412841
return cls(canvas_class(figure), num)
28422842

2843+
@classmethod
2844+
def start_main_loop(cls):
2845+
"""
2846+
Start the main event loop.
2847+
2848+
Interactive backends need to reimplement this method or
2849+
`~.FigureManagerBase.pyplot_show`.
2850+
"""
2851+
2852+
@classmethod
2853+
def pyplot_show(cls, *, block=None):
2854+
"""
2855+
Show all figures. This method is the implementation of `.pyplot.show`.
2856+
2857+
Interactive backends need to reimplement this method or
2858+
`~.FigureManagerBase.start_main_loop`.
2859+
2860+
Parameters
2861+
----------
2862+
block : bool, optional
2863+
Whether to block by calling ``start_main_loop``. The default,
2864+
None, means to block if we are neither in IPython's ``%pylab`` mode
2865+
nor in ``interactive`` mode.
2866+
"""
2867+
managers = Gcf.get_all_fig_managers()
2868+
if not managers:
2869+
return
2870+
for manager in managers:
2871+
try:
2872+
manager.show() # Emits a warning for non-interactive backend.
2873+
except NonGuiException as exc:
2874+
_api.warn_external(str(exc))
2875+
if block is None:
2876+
# Hack: Are we in IPython's %pylab mode? In pylab mode, IPython
2877+
# (>= 0.10) tacks a _needmain attribute onto pyplot.show (always
2878+
# set to False).
2879+
ipython_pylab = hasattr(
2880+
getattr(sys.modules.get("pyplot"), "show"), "_needmain")
2881+
block = not ipython_pylab and not is_interactive()
2882+
if block:
2883+
cls.start_main_loop()
2884+
28432885
def show(self):
28442886
"""
28452887
For GUI backends, show the figure window and redraw.
@@ -3518,7 +3560,11 @@ def new_figure_manager_given_figure(cls, num, figure):
35183560

35193561
@classmethod
35203562
def draw_if_interactive(cls):
3521-
if cls.mainloop is not None and is_interactive():
3563+
manager_class = cls.FigureCanvas.manager_class
3564+
backend_is_interactive = (
3565+
manager_class.start_main_loop != FigureManagerBase.start_main_loop
3566+
or manager_class.pyplot_show != FigureManagerBase.pyplot_show)
3567+
if backend_is_interactive and is_interactive():
35223568
manager = Gcf.get_active()
35233569
if manager:
35243570
manager.canvas.draw_idle()
@@ -3546,8 +3592,8 @@ def show(cls, *, block=None):
35463592
# Hack: Are we in IPython's %pylab mode? In pylab mode, IPython
35473593
# (>= 0.10) tacks a _needmain attribute onto pyplot.show (always
35483594
# set to False).
3549-
from matplotlib import pyplot
3550-
ipython_pylab = hasattr(pyplot.show, "_needmain")
3595+
ipython_pylab = hasattr(
3596+
getattr(sys.modules.get("pyplot"), "show"), "_needmain")
35513597
block = not ipython_pylab and not is_interactive()
35523598
if block:
35533599
cls.mainloop()

lib/matplotlib/backends/_backend_gtk.py

+26-20
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from matplotlib import _api, backend_tools, cbook
1010
from matplotlib._pylab_helpers import Gcf
1111
from matplotlib.backend_bases import (
12-
_Backend, FigureManagerBase, NavigationToolbar2, TimerBase)
12+
_Backend, FigureCanvasBase, FigureManagerBase, NavigationToolbar2,
13+
TimerBase)
1314
from matplotlib.backend_tools import Cursors
1415

1516
import gi
@@ -113,6 +114,10 @@ def _on_timer(self):
113114
return False
114115

115116

117+
class _FigureCanvasGTK(FigureCanvasBase):
118+
_timer_cls = TimerGTK
119+
120+
116121
class _FigureManagerGTK(FigureManagerBase):
117122
"""
118123
Attributes
@@ -192,6 +197,25 @@ def destroy(self, *args):
192197
self.window.destroy()
193198
self.canvas.destroy()
194199

200+
@classmethod
201+
def start_main_loop(cls):
202+
global _application
203+
if _application is None:
204+
return
205+
206+
try:
207+
_application.run() # Quits when all added windows close.
208+
except KeyboardInterrupt:
209+
# Ensure all windows can process their close event from
210+
# _shutdown_application.
211+
context = GLib.MainContext.default()
212+
while context.pending():
213+
context.iteration(True)
214+
raise
215+
finally:
216+
# Running after quit is undefined, so create a new one next time.
217+
_application = None
218+
195219
def show(self):
196220
# show the figure window
197221
self.window.show()
@@ -305,22 +329,4 @@ class _BackendGTK(_Backend):
305329
Gtk.get_minor_version(),
306330
Gtk.get_micro_version(),
307331
)
308-
309-
@staticmethod
310-
def mainloop():
311-
global _application
312-
if _application is None:
313-
return
314-
315-
try:
316-
_application.run() # Quits when all added windows close.
317-
except KeyboardInterrupt:
318-
# Ensure all windows can process their close event from
319-
# _shutdown_application.
320-
context = GLib.MainContext.default()
321-
while context.pending():
322-
context.iteration(True)
323-
raise
324-
finally:
325-
# Running after quit is undefined, so create a new one next time.
326-
_application = None
332+
mainloop = _FigureManagerGTK.start_main_loop

lib/matplotlib/backends/_backend_tk.py

+16-14
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,20 @@ def create_with_canvas(cls, canvas_class, figure, num):
484484
canvas.draw_idle()
485485
return manager
486486

487+
@classmethod
488+
def start_main_loop(cls):
489+
managers = Gcf.get_all_fig_managers()
490+
if managers:
491+
first_manager = managers[0]
492+
manager_class = type(first_manager)
493+
if manager_class._owns_mainloop:
494+
return
495+
manager_class._owns_mainloop = True
496+
try:
497+
first_manager.window.mainloop()
498+
finally:
499+
manager_class._owns_mainloop = False
500+
487501
def _update_window_dpi(self, *args):
488502
newdpi = self._window_dpi.get()
489503
self.window.call('tk', 'scaling', newdpi / 72)
@@ -1018,18 +1032,6 @@ def trigger(self, *args):
10181032
@_Backend.export
10191033
class _BackendTk(_Backend):
10201034
backend_version = tk.TkVersion
1035+
FigureCanvas = FigureCanvasTk
10211036
FigureManager = FigureManagerTk
1022-
1023-
@staticmethod
1024-
def mainloop():
1025-
managers = Gcf.get_all_fig_managers()
1026-
if managers:
1027-
first_manager = managers[0]
1028-
manager_class = type(first_manager)
1029-
if manager_class._owns_mainloop:
1030-
return
1031-
manager_class._owns_mainloop = True
1032-
try:
1033-
first_manager.window.mainloop()
1034-
finally:
1035-
manager_class._owns_mainloop = False
1037+
mainloop = FigureManagerTk.start_main_loop

lib/matplotlib/backends/backend_gtk3.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import matplotlib as mpl
88
from matplotlib import _api, backend_tools, cbook
99
from matplotlib.backend_bases import (
10-
FigureCanvasBase, ToolContainerBase,
11-
CloseEvent, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
10+
ToolContainerBase, CloseEvent, KeyEvent, LocationEvent, MouseEvent,
11+
ResizeEvent)
1212

1313
try:
1414
import gi
@@ -26,8 +26,8 @@
2626

2727
from gi.repository import Gio, GLib, GObject, Gtk, Gdk
2828
from . import _backend_gtk
29-
from ._backend_gtk import (
30-
_BackendGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
29+
from ._backend_gtk import ( # noqa: F401 # pylint: disable=W0611
30+
_BackendGTK, _FigureCanvasGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
3131
TimerGTK as TimerGTK3,
3232
)
3333

@@ -52,9 +52,8 @@ def _mpl_to_gtk_cursor(mpl_cursor):
5252
_backend_gtk.mpl_to_gtk_cursor_name(mpl_cursor))
5353

5454

55-
class FigureCanvasGTK3(FigureCanvasBase, Gtk.DrawingArea):
55+
class FigureCanvasGTK3(_FigureCanvasGTK, Gtk.DrawingArea):
5656
required_interactive_framework = "gtk3"
57-
_timer_cls = TimerGTK3
5857
manager_class = _api.classproperty(lambda cls: FigureManagerGTK3)
5958
# Setting this as a static constant prevents
6059
# this resulting expression from leaking

lib/matplotlib/backends/backend_gtk4.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import matplotlib as mpl
66
from matplotlib import _api, backend_tools, cbook
77
from matplotlib.backend_bases import (
8-
FigureCanvasBase, ToolContainerBase,
9-
KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
8+
ToolContainerBase, KeyEvent, LocationEvent, MouseEvent, ResizeEvent)
109

1110
try:
1211
import gi
@@ -24,16 +23,15 @@
2423

2524
from gi.repository import Gio, GLib, Gtk, Gdk, GdkPixbuf
2625
from . import _backend_gtk
27-
from ._backend_gtk import (
28-
_BackendGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
26+
from ._backend_gtk import ( # noqa: F401 # pylint: disable=W0611
27+
_BackendGTK, _FigureCanvasGTK, _FigureManagerGTK, _NavigationToolbar2GTK,
2928
TimerGTK as TimerGTK4,
3029
)
3130

3231

33-
class FigureCanvasGTK4(FigureCanvasBase, Gtk.DrawingArea):
32+
class FigureCanvasGTK4(_FigureCanvasGTK, Gtk.DrawingArea):
3433
required_interactive_framework = "gtk4"
3534
supports_blit = False
36-
_timer_cls = TimerGTK4
3735
manager_class = _api.classproperty(lambda cls: FigureManagerGTK4)
3836
_context_is_scaled = False
3937

lib/matplotlib/backends/backend_macosx.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ def _close_button_pressed(self):
165165
def close(self):
166166
return self._close_button_pressed()
167167

168+
@classmethod
169+
def start_main_loop(cls):
170+
_macosx.show()
171+
168172
def show(self):
169173
if not self._shown:
170174
self._show()
@@ -177,7 +181,4 @@ def show(self):
177181
class _BackendMac(_Backend):
178182
FigureCanvas = FigureCanvasMac
179183
FigureManager = FigureManagerMac
180-
181-
@staticmethod
182-
def mainloop():
183-
_macosx.show()
184+
mainloop = FigureManagerMac.start_main_loop

lib/matplotlib/backends/backend_qt.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,13 @@ def resize(self, width, height):
583583
self.canvas.resize(width, height)
584584
self.window.resize(width + extra_width, height + extra_height)
585585

586+
@classmethod
587+
def start_main_loop(cls):
588+
qapp = QtWidgets.QApplication.instance()
589+
if qapp:
590+
with _maybe_allow_interrupt(qapp):
591+
qt_compat._exec(qapp)
592+
586593
def show(self):
587594
self.window.show()
588595
if mpl.rcParams['figure.raise_window']:
@@ -1007,9 +1014,4 @@ class _BackendQT(_Backend):
10071014
backend_version = __version__
10081015
FigureCanvas = FigureCanvasQT
10091016
FigureManager = FigureManagerQT
1010-
1011-
@staticmethod
1012-
def mainloop():
1013-
qapp = QtWidgets.QApplication.instance()
1014-
with _maybe_allow_interrupt(qapp):
1015-
qt_compat._exec(qapp)
1017+
mainloop = FigureManagerQT.start_main_loop

lib/matplotlib/backends/backend_webagg.py

+18-18
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,24 @@ def run(self):
5353
class FigureManagerWebAgg(core.FigureManagerWebAgg):
5454
_toolbar2_class = core.NavigationToolbar2WebAgg
5555

56+
@classmethod
57+
def pyplot_show(cls, *, block=None):
58+
WebAggApplication.initialize()
59+
60+
url = "http://{address}:{port}{prefix}".format(
61+
address=WebAggApplication.address,
62+
port=WebAggApplication.port,
63+
prefix=WebAggApplication.url_prefix)
64+
65+
if mpl.rcParams['webagg.open_in_browser']:
66+
import webbrowser
67+
if not webbrowser.open(url):
68+
print("To view figure, visit {0}".format(url))
69+
else:
70+
print("To view figure, visit {0}".format(url))
71+
72+
WebAggApplication.start()
73+
5674

5775
class FigureCanvasWebAgg(core.FigureCanvasWebAggCore):
5876
manager_class = FigureManagerWebAgg
@@ -307,21 +325,3 @@ def ipython_inline_display(figure):
307325
class _BackendWebAgg(_Backend):
308326
FigureCanvas = FigureCanvasWebAgg
309327
FigureManager = FigureManagerWebAgg
310-
311-
@staticmethod
312-
def show(*, block=None):
313-
WebAggApplication.initialize()
314-
315-
url = "http://{address}:{port}{prefix}".format(
316-
address=WebAggApplication.address,
317-
port=WebAggApplication.port,
318-
prefix=WebAggApplication.url_prefix)
319-
320-
if mpl.rcParams['webagg.open_in_browser']:
321-
import webbrowser
322-
if not webbrowser.open(url):
323-
print("To view figure, visit {0}".format(url))
324-
else:
325-
print("To view figure, visit {0}".format(url))
326-
327-
WebAggApplication.start()

lib/matplotlib/backends/backend_wx.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,13 @@ def create_with_canvas(cls, canvas_class, figure, num):
999999
figure.canvas.draw_idle()
10001000
return manager
10011001

1002+
@classmethod
1003+
def start_main_loop(cls):
1004+
if not wx.App.IsMainLoopRunning():
1005+
wxapp = wx.GetApp()
1006+
if wxapp is not None:
1007+
wxapp.MainLoop()
1008+
10021009
def show(self):
10031010
# docstring inherited
10041011
self.frame.Show()
@@ -1365,10 +1372,4 @@ def trigger(self, *args, **kwargs):
13651372
class _BackendWx(_Backend):
13661373
FigureCanvas = FigureCanvasWx
13671374
FigureManager = FigureManagerWx
1368-
1369-
@staticmethod
1370-
def mainloop():
1371-
if not wx.App.IsMainLoopRunning():
1372-
wxapp = wx.GetApp()
1373-
if wxapp is not None:
1374-
wxapp.MainLoop()
1375+
mainloop = FigureManagerWx.start_main_loop

0 commit comments

Comments
 (0)