Skip to content

Commit 5f1d623

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 36685eb commit 5f1d623

11 files changed

+163
-87
lines changed

lib/matplotlib/backend_bases.py

+45-1
Original file line numberDiff line numberDiff line change
@@ -2822,6 +2822,46 @@ def create_with_canvas(cls, canvas_class, figure, num):
28222822
"""
28232823
return cls(canvas_class(figure), num)
28242824

2825+
@classmethod
2826+
def start_main_loop(cls):
2827+
"""
2828+
Start the main event loop.
2829+
2830+
Interactive backends need to reimplement this method or
2831+
`~.FigureManagerBase.pyplot_show`.
2832+
"""
2833+
2834+
@classmethod
2835+
def pyplot_show(cls, *, block=None):
2836+
"""
2837+
Show all figures. This method is the implementation of `.pyplot.show`.
2838+
2839+
Interactive backends need to reimplement this method or
2840+
`~.FigureManagerBase.start_main_loop`.
2841+
2842+
Parameters
2843+
----------
2844+
block : bool, optional
2845+
Whether to block by calling ``start_main_loop``. The default,
2846+
None, means to block if we are neither in IPython's ``%pylab`` mode
2847+
nor in ``interactive`` mode.
2848+
"""
2849+
managers = Gcf.get_all_fig_managers()
2850+
if not managers:
2851+
return
2852+
for manager in managers:
2853+
try:
2854+
manager.show() # Emits a warning for non-interactive backend.
2855+
except NonGuiException as exc:
2856+
_api.warn_external(str(exc))
2857+
if block is None:
2858+
# Hack: Are we in IPython's pylab mode?
2859+
from matplotlib import pyplot
2860+
ipython_pylab = hasattr(pyplot.show, "_needmain")
2861+
block = not ipython_pylab and not is_interactive()
2862+
if block:
2863+
cls.start_main_loop()
2864+
28252865
def show(self):
28262866
"""
28272867
For GUI backends, show the figure window and redraw.
@@ -3500,7 +3540,11 @@ def new_figure_manager_given_figure(cls, num, figure):
35003540

35013541
@classmethod
35023542
def draw_if_interactive(cls):
3503-
if cls.mainloop is not None and is_interactive():
3543+
manager_class = cls.FigureCanvas.manager_class
3544+
backend_is_interactive = (
3545+
manager_class.start_main_loop != FigureManagerBase.start_main_loop
3546+
or manager_class.pyplot_show != FigureManagerBase.pyplot_show)
3547+
if backend_is_interactive and is_interactive():
35043548
manager = Gcf.get_active()
35053549
if manager:
35063550
manager.canvas.draw_idle()

lib/matplotlib/backends/_backend_gtk.py

+26-19
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
@@ -118,6 +119,10 @@ def _on_timer(self):
118119
return False
119120

120121

122+
class _FigureCanvasGTK(FigureCanvasBase):
123+
_timer_cls = TimerGTK
124+
125+
121126
class _FigureManagerGTK(FigureManagerBase):
122127
"""
123128
Attributes
@@ -197,6 +202,25 @@ def destroy(self, *args):
197202
self.window.destroy()
198203
self.canvas.destroy()
199204

205+
@classmethod
206+
def start_main_loop(cls):
207+
global _application
208+
if _application is None:
209+
return
210+
211+
try:
212+
_application.run() # Quits when all added windows close.
213+
except KeyboardInterrupt:
214+
# Ensure all windows can process their close event from
215+
# _shutdown_application.
216+
context = GLib.MainContext.default()
217+
while context.pending():
218+
context.iteration(True)
219+
raise
220+
finally:
221+
# Running after quit is undefined, so create a new one next time.
222+
_application = None
223+
200224
def show(self):
201225
# show the figure window
202226
self.window.show()
@@ -305,21 +329,4 @@ def trigger(self, *args):
305329

306330

307331
class _BackendGTK(_Backend):
308-
@staticmethod
309-
def mainloop():
310-
global _application
311-
if _application is None:
312-
return
313-
314-
try:
315-
_application.run() # Quits when all added windows close.
316-
except KeyboardInterrupt:
317-
# Ensure all windows can process their close event from
318-
# _shutdown_application.
319-
context = GLib.MainContext.default()
320-
while context.pending():
321-
context.iteration(True)
322-
raise
323-
finally:
324-
# Running after quit is undefined, so create a new one next time.
325-
_application = None
332+
mainloop = _FigureManagerGTK.start_main_loop

lib/matplotlib/backends/_backend_tk.py

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

489+
@classmethod
490+
def start_main_loop(cls):
491+
managers = Gcf.get_all_fig_managers()
492+
if managers:
493+
first_manager = managers[0]
494+
manager_class = type(first_manager)
495+
if manager_class._owns_mainloop:
496+
return
497+
manager_class._owns_mainloop = True
498+
try:
499+
first_manager.window.mainloop()
500+
finally:
501+
manager_class._owns_mainloop = False
502+
489503
def _update_window_dpi(self, *args):
490504
newdpi = self._window_dpi.get()
491505
self.window.call('tk', 'scaling', newdpi / 72)
@@ -1010,18 +1024,6 @@ def trigger(self, *args):
10101024

10111025
@_Backend.export
10121026
class _BackendTk(_Backend):
1027+
FigureCanvas = FigureCanvasTk
10131028
FigureManager = FigureManagerTk
1014-
1015-
@staticmethod
1016-
def mainloop():
1017-
managers = Gcf.get_all_fig_managers()
1018-
if managers:
1019-
first_manager = managers[0]
1020-
manager_class = type(first_manager)
1021-
if manager_class._owns_mainloop:
1022-
return
1023-
manager_class._owns_mainloop = True
1024-
try:
1025-
first_manager.window.mainloop()
1026-
finally:
1027-
manager_class._owns_mainloop = False
1029+
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
from ._backend_gtk import backend_version # noqa: F401 # pylint: disable=W0611
@@ -53,9 +53,8 @@ def _mpl_to_gtk_cursor(mpl_cursor):
5353
_backend_gtk.mpl_to_gtk_cursor_name(mpl_cursor))
5454

5555

56-
class FigureCanvasGTK3(FigureCanvasBase, Gtk.DrawingArea):
56+
class FigureCanvasGTK3(_FigureCanvasGTK, Gtk.DrawingArea):
5757
required_interactive_framework = "gtk3"
58-
_timer_cls = TimerGTK3
5958
manager_class = _api.classproperty(lambda cls: FigureManagerGTK3)
6059
# Setting this as a static constant prevents
6160
# 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,17 +23,16 @@
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
from ._backend_gtk import backend_version # noqa: F401 # pylint: disable=W0611
3231

3332

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

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,12 @@ def resize(self, width, height):
585585
self.canvas.resize(width, height)
586586
self.window.resize(width + extra_width, height + extra_height)
587587

588+
@classmethod
589+
def start_main_loop(cls):
590+
qapp = QtWidgets.QApplication.instance()
591+
with _maybe_allow_interrupt(qapp):
592+
qt_compat._exec(qapp)
593+
588594
def show(self):
589595
self.window.show()
590596
if mpl.rcParams['figure.raise_window']:
@@ -1008,9 +1014,4 @@ def trigger(self, *args, **kwargs):
10081014
class _BackendQT(_Backend):
10091015
FigureCanvas = FigureCanvasQT
10101016
FigureManager = FigureManagerQT
1011-
1012-
@staticmethod
1013-
def mainloop():
1014-
qapp = QtWidgets.QApplication.instance()
1015-
with _maybe_allow_interrupt(qapp):
1016-
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
@@ -997,6 +997,13 @@ def create_with_canvas(cls, canvas_class, figure, num):
997997
figure.canvas.draw_idle()
998998
return manager
999999

1000+
@classmethod
1001+
def start_main_loop(cls):
1002+
if not wx.App.IsMainLoopRunning():
1003+
wxapp = wx.GetApp()
1004+
if wxapp is not None:
1005+
wxapp.MainLoop()
1006+
10001007
def show(self):
10011008
# docstring inherited
10021009
self.frame.Show()
@@ -1363,10 +1370,4 @@ def trigger(self, *args, **kwargs):
13631370
class _BackendWx(_Backend):
13641371
FigureCanvas = FigureCanvasWx
13651372
FigureManager = FigureManagerWx
1366-
1367-
@staticmethod
1368-
def mainloop():
1369-
if not wx.App.IsMainLoopRunning():
1370-
wxapp = wx.GetApp()
1371-
if wxapp is not None:
1372-
wxapp.MainLoop()
1373+
mainloop = FigureManagerWx.start_main_loop

0 commit comments

Comments
 (0)