8000 Refactor Pass 2. Refactored Gcf out of all backend code. · matplotlib/matplotlib@1f7e9f4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1f7e9f4

Browse files
committed
Refactor Pass 2. Refactored Gcf out of all backend code.
1 parent 00a2252 commit 1f7e9f4

File tree

8 files changed

+285
-141
lines changed

8 files changed

+285
-141
lines changed

lib/matplotlib/_pylab_helpers.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import gc
1010
import atexit
1111

12+
from matplotlib import is_interactive
1213

1314
def error_msg(msg):
1415
print(msg, file=sys.stderr)
@@ -35,6 +36,16 @@ class Gcf(object):
3536
_activeQue = []
3637
figs = {}
3738

39+
@classmethod
40+
def add_figure_manager(cls, manager):
41+
cls.figs[manager.num] = manager
42+
try: # TODO remove once all backends converted to use the new manager.
43+
manager.mpl_connect('window_destroy_event', cls.destroy_cbk)
44+
except:
45+
pass
46+
47+
cls.set_active(manager)
48+
3849
@classmethod
3950
def get_fig_manager(cls, num):
4051
"""
@@ -46,6 +57,49 @@ def get_fig_manager(cls, num):
4657
cls.set_active(manager)
4758
return manager
4859

60+
@classmethod
61+
def show_all(cls, block=None):
62+
"""
63+
Show all figures. If *block* is not None, then
64+
it is a boolean that overrides all other factors
65+
determining whether show blocks by calling mainloop().
66+
The other factors are:
67+
it does not block if run inside ipython's "%pylab" mode
68+
it does not block in interactive mode.
69+
"""
70+
managers = cls.get_all_fig_managers()
71+
if not managers:
72+
return
73+
74+
for manager in managers:
75+
manager.show()
76+
77+
if block is not None:
78+
if block:
79+
manager.mainloop()
80+
return
81+
82+
from matplotlib import pyplot
83+
try:
84+
ipython_pylab = not pyplot.show._needmain
85+
# IPython versions >= 0.10 tack the _needmain
86+
# attribute onto pyplot.show, and always set
87+
# it to False, when in %pylab mode.
88+
ipython_pylab = ipython_pylab and 6D40 get_backend() != 'WebAgg'
89+
# TODO: The above is a hack to get the WebAgg backend
90+
# working with ipython's `%pylab` mode until proper
91+
# integration is implemented.
92+
except AttributeError:
93+
ipython_pylab = False
94+
95+
# Leave the following as a separate step in case we
96+
# want to control this behavior with an rcParam.
97+
if ipython_pylab:
98+
block = False
99+
100+
if not is_interactive() or get_backend() == 'WebAgg':
101+
manager.mainloop()
102+
49103
@classmethod
50104
def destroy(cls, num):
51105
"""
@@ -134,7 +188,9 @@ def set_active(cls, manager):
134188
if m != manager:
135189
cls._activeQue.append(m)
136190
cls._activeQue.append(manager)
137-
cls.figs[manager.num] = manager
138191

192+
@classmethod
193+
def destroy_cbk(cls, event):
194+
cls.destroy(event.figure_manager.num)
139195

140196
atexit.register(Gcf.destroy_all)

lib/matplotlib/backend_bases.py

Lines changed: 33 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import io
4141

4242
import numpy as np
43-
import matplotlib # temporary )assuming we refactor where marked below)
4443
import matplotlib.cbook as cbook
4544
import matplotlib.colors as colors
4645
import matplotlib.transforms as transforms
@@ -133,6 +132,33 @@ def get_registered_canvas_class(format):
133132
return backend_class
134133

135134

135+
class MainLoopBase(object):
136+
"""This gets used as a key maintaining the event loop.
137+
Backends should only need to override begin and end.
138+
It should not matter if this gets used as a singleton or not due to
139+
clever magic.
140+
"""
141+
_instance_count = {}
142+
def __init__(self):
143+
MainLoopBase._instance_count.setdefault(self.__class__, 0)
144+
MainLoopBase._instance_count[self.__class__] += 1
145+
146+
def begin(self):
147+
pass
148+
149+
def end(self):
150+
pass
151+
152+
def __call__(self):
153+
self.begin()
154+
155+
def __del__(self):
156+
MainLoopBase._instance_count[self.__class__] -= 1
157+
if (MainLoopBase._instance_count[self.__class__] <= 0 and
158+
not is_interactive()):
159+
self.end()
160+
161+
136162
class ShowBase(object):
137163
"""
138164
Simple base class to generate a show() callable in backends.
@@ -2460,7 +2486,10 @@ def key_press_handler(event, canvas, toolbar=None):
24602486

24612487
# quit the figure (defaut key 'ctrl+w')
24622488
if event.key in quit_keys:
2463-
Gcf.destroy_fig(canvas.figure)
2489+
if isinstance(canvas.manager.mainloop, MainLoopBase): # If new no Gcf.
2490+
canvas.manager._destroy('window_destroy_event')
2491+
else:
2492+
Gcf.destroy_fig(canvas.figure)
24642493

24652494
if toolbar is not None:
24662495
# home or reset mnemonic (default key 'h', 'home' and 'r')
@@ -2540,15 +2569,9 @@ def __init__(self, name, window):
25402569
self.name = name
25412570
self.window = window
25422571

2543-
class WindowBase(object):
2572+
class WindowBase(cbook.EventListener):
25442573
def __init__(self, title):
2545-
self._callbacks = cbook.CallbackRegistry()
2546-
2547-
def mpl_connect(self, s, func):
2548-
return self._callbacks.connect(s, func)
2549-
2550-
def mpl_disconnect(self, cid):
2551-
return self._callbacks.disconnect(cid)
2574+
cbook.EventListener.__init__(self)
25522575

25532576
def show(self):
25542577
"""
@@ -2589,112 +2612,12 @@ def add_element_to_window(self, element, expand, fill, padding, from_start=False
25892612
"""
25902613
pass
25912614

2592-
def terminate_backend(self):
2593-
"""Method to terminate the usage of the backend
2594-
"""
2595-
# TODO refactor me out on second pass
2596-
pass
2597-
25982615
def destroy_event(self, *args):
25992616
s = 'window_destroy_event'
26002617
event = WindowEvent(s, self)
26012618
self._callbacks.process(s, event)
26022619

26032620

2604-
class FigureManager(object):
2605-
def __init__(self, canvas, num, classes):
2606-
self._classes = classes
2607-
self.canvas = canvas
2608-
canvas.manager = self
2609-
self.num = num
2610-
2611-
self.key_press_handler_id = self.canvas.mpl_connect('key_press_event',
2612-
self.key_press)
2613-
2614-
self.window = classes['Window']('Figure %d' % num)
2615-
self.window.mpl_connect('window_destroy_event', self._destroy)
2616-
2617-
w = int(self.canvas.figure.bbox.width)
2618-
h = int(self.canvas.figure.bbox.height)
2619-
2620-
self.window.add_element_to_window(self.canvas, True, True, 0, True)
2621-
2622-
self.toolbar = self._get_toolbar(canvas)
2623-
if self.toolbar is not None:
2624-
h += self.window.add_element_to_window(self.toolbar, False, False, 0)
2625-
2626-
self.window.set_default_size(w,h)
2627-
2628-
# Refactor this? If so, delete import matplotlib from above.
2629-
if matplotlib.is_interactive():
2630-
self.window.show()
2631-
2632-
def notify_axes_change(fig):
2633-
'this will be called whenever the current axes is changed'
2634-
if self.toolbar is not None: self.toolbar.update()
2635-
self.canvas.figure.add_axobserver(notify_axes_change)
2636-
2637-
self.canvas.grab_focus()
2638-
2639-
def key_press(self, event):
2640-
"""
2641-
Implement the default mpl key bindings defined at
2642-
:ref:`key-event-handling`
2643-
"""
2644-
key_press_handler(event, self.canvas, self.canvas.toolbar)
2645-
2646-
def _destroy(self, event):
2647-
Gcf.destroy(self.num) # TODO refactor me out of here on second pass!
2648-
2649-
def destroy(self, *args):
2650-
self.window.destroy()
2651-
self.canvas.destroy()
2652-
if self.toolbar:
2653-
self.toolbar.destroy()
2654-
2655-
# TODO refactor out on second pass
2656-
if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive():
2657-
self.window.terminate_backend()
2658-
2659-
def show(self):
2660-
self.window.show()
2661-
2662-
def full_screen_toggle(self):
2663-
self._full_screen_flag = not self._full_screen_flag
2664-
self.window.set_fullscreen(self._full_screen_flag)
2665-
2666-
def resize(self, w, h):
2667-
self.window.resize(w,h)
2668-
2669-
def get_window_title(self):
2670-
"""
2671-
Get the title text of the window containing the figure.
2672-
Return None for non-GUI backends (e.g., a PS backend).
2673-
"""
2674-
return self.window.get_window_title()
2675-
2676-
def set_window_title(self, title):
2677-
"""
2678-
Set the title text of the window containing the figure. Note that
2679-
this has no effect for non-GUI backends (e.g., a PS backend).
2680-
"""
2681-
self.window.set_window_title(title)
2682-
2683-
def show_popup(self, msg):
2684-
"""
2685-
Display message in a popup -- GUI only
2686-
"""
2687-
pass
2688-
2689-
def _get_toolbar(self, canvas):
2690-
# must be inited after the window, drawingArea and figure
2691-
# attrs are set
2692-
if rcParams['toolbar'] == 'toolbar2':
2693-
toolbar = self._classes['Toolbar2'](canvas, self.window)
2694-
else:
2695-
toolbar = None
2696-
return toolbar
2697-
26982621
class FigureManagerBase(object):
26992622
"""
27002623
Helper class for pyplot mode, wraps everything up into a neat bundle

0 commit comments

Comments
 (0)
0