Show More
@@ -1,381 +1,368 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | """Pylab (matplotlib) support utilities. | |
|
3 | ||
|
4 | Authors | |
|
5 | ------- | |
|
6 | ||
|
7 | * Fernando Perez. | |
|
8 | * Brian Granger | |
|
9 | """ | |
|
2 | """Pylab (matplotlib) support utilities.""" | |
|
10 | 3 | from __future__ import print_function |
|
11 | 4 | |
|
12 | #----------------------------------------------------------------------------- | |
|
13 | # Copyright (C) 2009 The IPython Development Team | |
|
14 | # | |
|
15 | # Distributed under the terms of the BSD License. The full license is in | |
|
16 | # the file COPYING, distributed as part of this software. | |
|
17 | #----------------------------------------------------------------------------- | |
|
18 | ||
|
19 | #----------------------------------------------------------------------------- | |
|
20 | # Imports | |
|
21 | #----------------------------------------------------------------------------- | |
|
5 | # Copyright (c) IPython Development Team. | |
|
6 | # Distributed under the terms of the Modified BSD License. | |
|
22 | 7 | |
|
23 | 8 | from io import BytesIO |
|
24 | 9 | |
|
25 | 10 | from IPython.core.display import _pngxy |
|
26 | 11 | from IPython.utils.decorators import flag_calls |
|
27 | 12 | from IPython.utils import py3compat |
|
28 | 13 | |
|
29 | 14 | # If user specifies a GUI, that dictates the backend, otherwise we read the |
|
30 | 15 | # user's mpl default from the mpl rc structure |
|
31 | 16 | backends = {'tk': 'TkAgg', |
|
32 | 17 | 'gtk': 'GTKAgg', |
|
33 | 18 | 'gtk3': 'GTK3Agg', |
|
34 | 19 | 'wx': 'WXAgg', |
|
35 | 20 | 'qt': 'Qt4Agg', # qt3 not supported |
|
36 | 21 | 'qt4': 'Qt4Agg', |
|
22 | 'qt5': 'Qt5Agg', | |
|
37 | 23 | 'osx': 'MacOSX', |
|
24 | 'nbagg': 'nbAgg', | |
|
38 | 25 | 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'} |
|
39 | 26 | |
|
40 | 27 | # We also need a reverse backends2guis mapping that will properly choose which |
|
41 | 28 | # GUI support to activate based on the desired matplotlib backend. For the |
|
42 | 29 | # most part it's just a reverse of the above dict, but we also need to add a |
|
43 | 30 | # few others that map to the same GUI manually: |
|
44 | 31 | backend2gui = dict(zip(backends.values(), backends.keys())) |
|
45 | 32 | # Our tests expect backend2gui to just return 'qt' |
|
46 | 33 | backend2gui['Qt4Agg'] = 'qt' |
|
47 | 34 | # In the reverse mapping, there are a few extra valid matplotlib backends that |
|
48 | 35 | # map to the same GUI support |
|
49 | 36 | backend2gui['GTK'] = backend2gui['GTKCairo'] = 'gtk' |
|
50 | 37 | backend2gui['GTK3Cairo'] = 'gtk3' |
|
51 | 38 | backend2gui['WX'] = 'wx' |
|
52 | 39 | backend2gui['CocoaAgg'] = 'osx' |
|
53 | 40 | |
|
54 | 41 | #----------------------------------------------------------------------------- |
|
55 | 42 | # Matplotlib utilities |
|
56 | 43 | #----------------------------------------------------------------------------- |
|
57 | 44 | |
|
58 | 45 | |
|
59 | 46 | def getfigs(*fig_nums): |
|
60 | 47 | """Get a list of matplotlib figures by figure numbers. |
|
61 | 48 | |
|
62 | 49 | If no arguments are given, all available figures are returned. If the |
|
63 | 50 | argument list contains references to invalid figures, a warning is printed |
|
64 | 51 | but the function continues pasting further figures. |
|
65 | 52 | |
|
66 | 53 | Parameters |
|
67 | 54 | ---------- |
|
68 | 55 | figs : tuple |
|
69 | 56 | A tuple of ints giving the figure numbers of the figures to return. |
|
70 | 57 | """ |
|
71 | 58 | from matplotlib._pylab_helpers import Gcf |
|
72 | 59 | if not fig_nums: |
|
73 | 60 | fig_managers = Gcf.get_all_fig_managers() |
|
74 | 61 | return [fm.canvas.figure for fm in fig_managers] |
|
75 | 62 | else: |
|
76 | 63 | figs = [] |
|
77 | 64 | for num in fig_nums: |
|
78 | 65 | f = Gcf.figs.get(num) |
|
79 | 66 | if f is None: |
|
80 | 67 | print('Warning: figure %s not available.' % num) |
|
81 | 68 | else: |
|
82 | 69 | figs.append(f.canvas.figure) |
|
83 | 70 | return figs |
|
84 | 71 | |
|
85 | 72 | |
|
86 | 73 | def figsize(sizex, sizey): |
|
87 | 74 | """Set the default figure size to be [sizex, sizey]. |
|
88 | 75 | |
|
89 | 76 | This is just an easy to remember, convenience wrapper that sets:: |
|
90 | 77 | |
|
91 | 78 | matplotlib.rcParams['figure.figsize'] = [sizex, sizey] |
|
92 | 79 | """ |
|
93 | 80 | import matplotlib |
|
94 | 81 | matplotlib.rcParams['figure.figsize'] = [sizex, sizey] |
|
95 | 82 | |
|
96 | 83 | |
|
97 | 84 | def print_figure(fig, fmt='png', bbox_inches='tight', **kwargs): |
|
98 | 85 | """Print a figure to an image, and return the resulting file data |
|
99 | 86 | |
|
100 | 87 | Returned data will be bytes unless ``fmt='svg'``, |
|
101 | 88 | in which case it will be unicode. |
|
102 | 89 | |
|
103 | 90 | Any keyword args are passed to fig.canvas.print_figure, |
|
104 | 91 | such as ``quality`` or ``bbox_inches``. |
|
105 | 92 | """ |
|
106 | 93 | from matplotlib import rcParams |
|
107 | 94 | # When there's an empty figure, we shouldn't return anything, otherwise we |
|
108 | 95 | # get big blank areas in the qt console. |
|
109 | 96 | if not fig.axes and not fig.lines: |
|
110 | 97 | return |
|
111 | 98 | |
|
112 | 99 | dpi = rcParams['savefig.dpi'] |
|
113 | 100 | if fmt == 'retina': |
|
114 | 101 | dpi = dpi * 2 |
|
115 | 102 | fmt = 'png' |
|
116 | 103 | |
|
117 | 104 | # build keyword args |
|
118 | 105 | kw = dict( |
|
119 | 106 | format=fmt, |
|
120 | 107 | facecolor=fig.get_facecolor(), |
|
121 | 108 | edgecolor=fig.get_edgecolor(), |
|
122 | 109 | dpi=dpi, |
|
123 | 110 | bbox_inches=bbox_inches, |
|
124 | 111 | ) |
|
125 | 112 | # **kwargs get higher priority |
|
126 | 113 | kw.update(kwargs) |
|
127 | 114 | |
|
128 | 115 | bytes_io = BytesIO() |
|
129 | 116 | fig.canvas.print_figure(bytes_io, **kw) |
|
130 | 117 | data = bytes_io.getvalue() |
|
131 | 118 | if fmt == 'svg': |
|
132 | 119 | data = data.decode('utf-8') |
|
133 | 120 | return data |
|
134 | 121 | |
|
135 | 122 | def retina_figure(fig, **kwargs): |
|
136 | 123 | """format a figure as a pixel-doubled (retina) PNG""" |
|
137 | 124 | pngdata = print_figure(fig, fmt='retina', **kwargs) |
|
138 | 125 | w, h = _pngxy(pngdata) |
|
139 | 126 | metadata = dict(width=w//2, height=h//2) |
|
140 | 127 | return pngdata, metadata |
|
141 | 128 | |
|
142 | 129 | # We need a little factory function here to create the closure where |
|
143 | 130 | # safe_execfile can live. |
|
144 | 131 | def mpl_runner(safe_execfile): |
|
145 | 132 | """Factory to return a matplotlib-enabled runner for %run. |
|
146 | 133 | |
|
147 | 134 | Parameters |
|
148 | 135 | ---------- |
|
149 | 136 | safe_execfile : function |
|
150 | 137 | This must be a function with the same interface as the |
|
151 | 138 | :meth:`safe_execfile` method of IPython. |
|
152 | 139 | |
|
153 | 140 | Returns |
|
154 | 141 | ------- |
|
155 | 142 | A function suitable for use as the ``runner`` argument of the %run magic |
|
156 | 143 | function. |
|
157 | 144 | """ |
|
158 | 145 | |
|
159 | 146 | def mpl_execfile(fname,*where,**kw): |
|
160 | 147 | """matplotlib-aware wrapper around safe_execfile. |
|
161 | 148 | |
|
162 | 149 | Its interface is identical to that of the :func:`execfile` builtin. |
|
163 | 150 | |
|
164 | 151 | This is ultimately a call to execfile(), but wrapped in safeties to |
|
165 | 152 | properly handle interactive rendering.""" |
|
166 | 153 | |
|
167 | 154 | import matplotlib |
|
168 | 155 | import matplotlib.pylab as pylab |
|
169 | 156 | |
|
170 | 157 | #print '*** Matplotlib runner ***' # dbg |
|
171 | 158 | # turn off rendering until end of script |
|
172 | 159 | is_interactive = matplotlib.rcParams['interactive'] |
|
173 | 160 | matplotlib.interactive(False) |
|
174 | 161 | safe_execfile(fname,*where,**kw) |
|
175 | 162 | matplotlib.interactive(is_interactive) |
|
176 | 163 | # make rendering call now, if the user tried to do it |
|
177 | 164 | if pylab.draw_if_interactive.called: |
|
178 | 165 | pylab.draw() |
|
179 | 166 | pylab.draw_if_interactive.called = False |
|
180 | 167 | |
|
181 | 168 | return mpl_execfile |
|
182 | 169 | |
|
183 | 170 | |
|
184 | 171 | def select_figure_formats(shell, formats, **kwargs): |
|
185 | 172 | """Select figure formats for the inline backend. |
|
186 | 173 | |
|
187 | 174 | Parameters |
|
188 | 175 | ========== |
|
189 | 176 | shell : InteractiveShell |
|
190 | 177 | The main IPython instance. |
|
191 | 178 | formats : str or set |
|
192 | 179 | One or a set of figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'. |
|
193 | 180 | **kwargs : any |
|
194 | 181 | Extra keyword arguments to be passed to fig.canvas.print_figure. |
|
195 | 182 | """ |
|
196 | 183 | from matplotlib.figure import Figure |
|
197 | 184 | from IPython.kernel.zmq.pylab import backend_inline |
|
198 | 185 | |
|
199 | 186 | svg_formatter = shell.display_formatter.formatters['image/svg+xml'] |
|
200 | 187 | png_formatter = shell.display_formatter.formatters['image/png'] |
|
201 | 188 | jpg_formatter = shell.display_formatter.formatters['image/jpeg'] |
|
202 | 189 | pdf_formatter = shell.display_formatter.formatters['application/pdf'] |
|
203 | 190 | |
|
204 | 191 | if isinstance(formats, py3compat.string_types): |
|
205 | 192 | formats = {formats} |
|
206 | 193 | # cast in case of list / tuple |
|
207 | 194 | formats = set(formats) |
|
208 | 195 | |
|
209 | 196 | [ f.pop(Figure, None) for f in shell.display_formatter.formatters.values() ] |
|
210 | 197 | |
|
211 | 198 | supported = {'png', 'png2x', 'retina', 'jpg', 'jpeg', 'svg', 'pdf'} |
|
212 | 199 | bad = formats.difference(supported) |
|
213 | 200 | if bad: |
|
214 | 201 | bs = "%s" % ','.join([repr(f) for f in bad]) |
|
215 | 202 | gs = "%s" % ','.join([repr(f) for f in supported]) |
|
216 | 203 | raise ValueError("supported formats are: %s not %s" % (gs, bs)) |
|
217 | 204 | |
|
218 | 205 | if 'png' in formats: |
|
219 | 206 | png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs)) |
|
220 | 207 | if 'retina' in formats or 'png2x' in formats: |
|
221 | 208 | png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs)) |
|
222 | 209 | if 'jpg' in formats or 'jpeg' in formats: |
|
223 | 210 | jpg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'jpg', **kwargs)) |
|
224 | 211 | if 'svg' in formats: |
|
225 | 212 | svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg', **kwargs)) |
|
226 | 213 | if 'pdf' in formats: |
|
227 | 214 | pdf_formatter.for_type(Figure, lambda fig: print_figure(fig, 'pdf', **kwargs)) |
|
228 | 215 | |
|
229 | 216 | #----------------------------------------------------------------------------- |
|
230 | 217 | # Code for initializing matplotlib and importing pylab |
|
231 | 218 | #----------------------------------------------------------------------------- |
|
232 | 219 | |
|
233 | 220 | |
|
234 | 221 | def find_gui_and_backend(gui=None, gui_select=None): |
|
235 | 222 | """Given a gui string return the gui and mpl backend. |
|
236 | 223 | |
|
237 | 224 | Parameters |
|
238 | 225 | ---------- |
|
239 | 226 | gui : str |
|
240 | 227 | Can be one of ('tk','gtk','wx','qt','qt4','inline'). |
|
241 | 228 | gui_select : str |
|
242 | 229 | Can be one of ('tk','gtk','wx','qt','qt4','inline'). |
|
243 | 230 | This is any gui already selected by the shell. |
|
244 | 231 | |
|
245 | 232 | Returns |
|
246 | 233 | ------- |
|
247 | 234 | A tuple of (gui, backend) where backend is one of ('TkAgg','GTKAgg', |
|
248 | 235 | 'WXAgg','Qt4Agg','module://IPython.kernel.zmq.pylab.backend_inline'). |
|
249 | 236 | """ |
|
250 | 237 | |
|
251 | 238 | import matplotlib |
|
252 | 239 | |
|
253 | 240 | if gui and gui != 'auto': |
|
254 | 241 | # select backend based on requested gui |
|
255 | 242 | backend = backends[gui] |
|
256 | 243 | else: |
|
257 | 244 | # We need to read the backend from the original data structure, *not* |
|
258 | 245 | # from mpl.rcParams, since a prior invocation of %matplotlib may have |
|
259 | 246 | # overwritten that. |
|
260 | 247 | # WARNING: this assumes matplotlib 1.1 or newer!! |
|
261 | 248 | backend = matplotlib.rcParamsOrig['backend'] |
|
262 | 249 | # In this case, we need to find what the appropriate gui selection call |
|
263 | 250 | # should be for IPython, so we can activate inputhook accordingly |
|
264 | 251 | gui = backend2gui.get(backend, None) |
|
265 | 252 | |
|
266 | 253 | # If we have already had a gui active, we need it and inline are the |
|
267 | 254 | # ones allowed. |
|
268 | 255 | if gui_select and gui != gui_select: |
|
269 | 256 | gui = gui_select |
|
270 | 257 | backend = backends[gui] |
|
271 | 258 | |
|
272 | 259 | return gui, backend |
|
273 | 260 | |
|
274 | 261 | |
|
275 | 262 | def activate_matplotlib(backend): |
|
276 | 263 | """Activate the given backend and set interactive to True.""" |
|
277 | 264 | |
|
278 | 265 | import matplotlib |
|
279 | 266 | matplotlib.interactive(True) |
|
280 | 267 | |
|
281 | 268 | # Matplotlib had a bug where even switch_backend could not force |
|
282 | 269 | # the rcParam to update. This needs to be set *before* the module |
|
283 | 270 | # magic of switch_backend(). |
|
284 | 271 | matplotlib.rcParams['backend'] = backend |
|
285 | 272 | |
|
286 | 273 | import matplotlib.pyplot |
|
287 | 274 | matplotlib.pyplot.switch_backend(backend) |
|
288 | 275 | |
|
289 | 276 | # This must be imported last in the matplotlib series, after |
|
290 | 277 | # backend/interactivity choices have been made |
|
291 | 278 | import matplotlib.pylab as pylab |
|
292 | 279 | |
|
293 | 280 | pylab.show._needmain = False |
|
294 | 281 | # We need to detect at runtime whether show() is called by the user. |
|
295 | 282 | # For this, we wrap it into a decorator which adds a 'called' flag. |
|
296 | 283 | pylab.draw_if_interactive = flag_calls(pylab.draw_if_interactive) |
|
297 | 284 | |
|
298 | 285 | |
|
299 | 286 | def import_pylab(user_ns, import_all=True): |
|
300 | 287 | """Populate the namespace with pylab-related values. |
|
301 | 288 | |
|
302 | 289 | Imports matplotlib, pylab, numpy, and everything from pylab and numpy. |
|
303 | 290 | |
|
304 | 291 | Also imports a few names from IPython (figsize, display, getfigs) |
|
305 | 292 | |
|
306 | 293 | """ |
|
307 | 294 | |
|
308 | 295 | # Import numpy as np/pyplot as plt are conventions we're trying to |
|
309 | 296 | # somewhat standardize on. Making them available to users by default |
|
310 | 297 | # will greatly help this. |
|
311 | 298 | s = ("import numpy\n" |
|
312 | 299 | "import matplotlib\n" |
|
313 | 300 | "from matplotlib import pylab, mlab, pyplot\n" |
|
314 | 301 | "np = numpy\n" |
|
315 | 302 | "plt = pyplot\n" |
|
316 | 303 | ) |
|
317 | 304 | exec(s, user_ns) |
|
318 | 305 | |
|
319 | 306 | if import_all: |
|
320 | 307 | s = ("from matplotlib.pylab import *\n" |
|
321 | 308 | "from numpy import *\n") |
|
322 | 309 | exec(s, user_ns) |
|
323 | 310 | |
|
324 | 311 | # IPython symbols to add |
|
325 | 312 | user_ns['figsize'] = figsize |
|
326 | 313 | from IPython.core.display import display |
|
327 | 314 | # Add display and getfigs to the user's namespace |
|
328 | 315 | user_ns['display'] = display |
|
329 | 316 | user_ns['getfigs'] = getfigs |
|
330 | 317 | |
|
331 | 318 | |
|
332 | 319 | def configure_inline_support(shell, backend): |
|
333 | 320 | """Configure an IPython shell object for matplotlib use. |
|
334 | 321 | |
|
335 | 322 | Parameters |
|
336 | 323 | ---------- |
|
337 | 324 | shell : InteractiveShell instance |
|
338 | 325 | |
|
339 | 326 | backend : matplotlib backend |
|
340 | 327 | """ |
|
341 | 328 | # If using our svg payload backend, register the post-execution |
|
342 | 329 | # function that will pick up the results for display. This can only be |
|
343 | 330 | # done with access to the real shell object. |
|
344 | 331 | |
|
345 | 332 | # Note: if we can't load the inline backend, then there's no point |
|
346 | 333 | # continuing (such as in terminal-only shells in environments without |
|
347 | 334 | # zeromq available). |
|
348 | 335 | try: |
|
349 | 336 | from IPython.kernel.zmq.pylab.backend_inline import InlineBackend |
|
350 | 337 | except ImportError: |
|
351 | 338 | return |
|
352 | 339 | from matplotlib import pyplot |
|
353 | 340 | |
|
354 | 341 | cfg = InlineBackend.instance(parent=shell) |
|
355 | 342 | cfg.shell = shell |
|
356 | 343 | if cfg not in shell.configurables: |
|
357 | 344 | shell.configurables.append(cfg) |
|
358 | 345 | |
|
359 | 346 | if backend == backends['inline']: |
|
360 | 347 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures |
|
361 | 348 | shell.events.register('post_execute', flush_figures) |
|
362 | 349 | |
|
363 | 350 | # Save rcParams that will be overwrittern |
|
364 | 351 | shell._saved_rcParams = dict() |
|
365 | 352 | for k in cfg.rc: |
|
366 | 353 | shell._saved_rcParams[k] = pyplot.rcParams[k] |
|
367 | 354 | # load inline_rc |
|
368 | 355 | pyplot.rcParams.update(cfg.rc) |
|
369 | 356 | else: |
|
370 | 357 | from IPython.kernel.zmq.pylab.backend_inline import flush_figures |
|
371 | 358 | try: |
|
372 | 359 | shell.events.unregister('post_execute', flush_figures) |
|
373 | 360 | except ValueError: |
|
374 | 361 | pass |
|
375 | 362 | if hasattr(shell, '_saved_rcParams'): |
|
376 | 363 | pyplot.rcParams.update(shell._saved_rcParams) |
|
377 | 364 | del shell._saved_rcParams |
|
378 | 365 | |
|
379 | 366 | # Setup the default figure format |
|
380 | 367 | select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs) |
|
381 | 368 |
@@ -1,285 +1,277 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | """Event loop integration for the ZeroMQ-based kernels. | |
|
3 | """ | |
|
4 | ||
|
5 | #----------------------------------------------------------------------------- | |
|
6 | # Copyright (C) 2011 The IPython Development Team | |
|
7 | ||
|
8 | # Distributed under the terms of the BSD License. The full license is in | |
|
9 | # the file COPYING, distributed as part of this software. | |
|
10 | #----------------------------------------------------------------------------- | |
|
2 | """Event loop integration for the ZeroMQ-based kernels.""" | |
|
11 | 3 | |
|
4 | # Copyright (c) IPython Development Team. | |
|
5 | # Distributed under the terms of the Modified BSD License. | |
|
12 | 6 | |
|
13 | #----------------------------------------------------------------------------- | |
|
14 | # Imports | |
|
15 | #----------------------------------------------------------------------------- | |
|
16 | ||
|
7 | import os | |
|
17 | 8 | import sys |
|
18 | 9 | |
|
19 | # System library imports | |
|
20 | 10 | import zmq |
|
21 | 11 | |
|
22 | # Local imports | |
|
23 | 12 | from IPython.config.application import Application |
|
24 | 13 | from IPython.utils import io |
|
25 | 14 | |
|
26 | 15 | |
|
27 | #------------------------------------------------------------------------------ | |
|
28 | # Eventloops for integrating the Kernel into different GUIs | |
|
29 | #------------------------------------------------------------------------------ | |
|
30 | ||
|
31 | 16 | def _on_os_x_10_9(): |
|
32 | 17 | import platform |
|
33 | 18 | from distutils.version import LooseVersion as V |
|
34 | 19 | return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') |
|
35 | 20 | |
|
36 | 21 | def _notify_stream_qt(kernel, stream): |
|
37 | 22 | |
|
38 | 23 | from IPython.external.qt_for_kernel import QtCore |
|
39 | 24 | |
|
40 | 25 | if _on_os_x_10_9() and kernel._darwin_app_nap: |
|
41 | 26 | from IPython.external.appnope import nope_scope as context |
|
42 | 27 | else: |
|
43 | 28 | from IPython.core.interactiveshell import NoOpContext as context |
|
44 | 29 | |
|
45 | 30 | def process_stream_events(): |
|
46 | 31 | while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN: |
|
47 | 32 | with context(): |
|
48 | 33 | kernel.do_one_iteration() |
|
49 | 34 | |
|
50 | 35 | fd = stream.getsockopt(zmq.FD) |
|
51 | 36 | notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) |
|
52 | 37 | notifier.activated.connect(process_stream_events) |
|
53 | 38 | |
|
54 | 39 | # mapping of keys to loop functions |
|
55 | 40 | loop_map = { |
|
56 | 41 | 'inline': None, |
|
42 | 'nbagg': None, | |
|
57 | 43 | None : None, |
|
58 | 44 | } |
|
59 | 45 | |
|
60 | 46 | def register_integration(*toolkitnames): |
|
61 | 47 | """Decorator to register an event loop to integrate with the IPython kernel |
|
62 | 48 | |
|
63 | 49 | The decorator takes names to register the event loop as for the %gui magic. |
|
64 | 50 | You can provide alternative names for the same toolkit. |
|
65 | 51 | |
|
66 | 52 | The decorated function should take a single argument, the IPython kernel |
|
67 | 53 | instance, arrange for the event loop to call ``kernel.do_one_iteration()`` |
|
68 | 54 | at least every ``kernel._poll_interval`` seconds, and start the event loop. |
|
69 | 55 | |
|
70 | 56 | :mod:`IPython.kernel.zmq.eventloops` provides and registers such functions |
|
71 | 57 | for a few common event loops. |
|
72 | 58 | """ |
|
73 | 59 | def decorator(func): |
|
74 | 60 | for name in toolkitnames: |
|
75 | 61 | loop_map[name] = func |
|
76 | 62 | return func |
|
77 | 63 | |
|
78 | 64 | return decorator |
|
79 | 65 | |
|
80 | 66 | |
|
81 | 67 | @register_integration('qt', 'qt4') |
|
82 | 68 | def loop_qt4(kernel): |
|
83 | 69 | """Start a kernel with PyQt4 event loop integration.""" |
|
84 | 70 | |
|
85 | 71 | from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 |
|
86 | 72 | |
|
87 | 73 | kernel.app = get_app_qt4([" "]) |
|
88 | 74 | kernel.app.setQuitOnLastWindowClosed(False) |
|
89 | 75 | |
|
90 | 76 | for s in kernel.shell_streams: |
|
91 | 77 | _notify_stream_qt(kernel, s) |
|
92 | 78 | |
|
93 | 79 | start_event_loop_qt4(kernel.app) |
|
94 | 80 | |
|
81 | @register_integration('qt5') | |
|
82 | def loop_qt5(kernel): | |
|
83 | """Start a kernel with PyQt5 event loop integration.""" | |
|
84 | os.environ['QT_API'] = 'pyqt5' | |
|
85 | return loop_qt4(kernel) | |
|
86 | ||
|
95 | 87 | |
|
96 | 88 | @register_integration('wx') |
|
97 | 89 | def loop_wx(kernel): |
|
98 | 90 | """Start a kernel with wx event loop support.""" |
|
99 | 91 | |
|
100 | 92 | import wx |
|
101 | 93 | from IPython.lib.guisupport import start_event_loop_wx |
|
102 | 94 | |
|
103 | 95 | if _on_os_x_10_9() and kernel._darwin_app_nap: |
|
104 | 96 | # we don't hook up App Nap contexts for Wx, |
|
105 | 97 | # just disable it outright. |
|
106 | 98 | from IPython.external.appnope import nope |
|
107 | 99 | nope() |
|
108 | 100 | |
|
109 | 101 | doi = kernel.do_one_iteration |
|
110 | 102 | # Wx uses milliseconds |
|
111 | 103 | poll_interval = int(1000*kernel._poll_interval) |
|
112 | 104 | |
|
113 | 105 | # We have to put the wx.Timer in a wx.Frame for it to fire properly. |
|
114 | 106 | # We make the Frame hidden when we create it in the main app below. |
|
115 | 107 | class TimerFrame(wx.Frame): |
|
116 | 108 | def __init__(self, func): |
|
117 | 109 | wx.Frame.__init__(self, None, -1) |
|
118 | 110 | self.timer = wx.Timer(self) |
|
119 | 111 | # Units for the timer are in milliseconds |
|
120 | 112 | self.timer.Start(poll_interval) |
|
121 | 113 | self.Bind(wx.EVT_TIMER, self.on_timer) |
|
122 | 114 | self.func = func |
|
123 | 115 | |
|
124 | 116 | def on_timer(self, event): |
|
125 | 117 | self.func() |
|
126 | 118 | |
|
127 | 119 | # We need a custom wx.App to create our Frame subclass that has the |
|
128 | 120 | # wx.Timer to drive the ZMQ event loop. |
|
129 | 121 | class IPWxApp(wx.App): |
|
130 | 122 | def OnInit(self): |
|
131 | 123 | self.frame = TimerFrame(doi) |
|
132 | 124 | self.frame.Show(False) |
|
133 | 125 | return True |
|
134 | 126 | |
|
135 | 127 | # The redirect=False here makes sure that wx doesn't replace |
|
136 | 128 | # sys.stdout/stderr with its own classes. |
|
137 | 129 | kernel.app = IPWxApp(redirect=False) |
|
138 | 130 | |
|
139 | 131 | # The import of wx on Linux sets the handler for signal.SIGINT |
|
140 | 132 | # to 0. This is a bug in wx or gtk. We fix by just setting it |
|
141 | 133 | # back to the Python default. |
|
142 | 134 | import signal |
|
143 | 135 | if not callable(signal.getsignal(signal.SIGINT)): |
|
144 | 136 | signal.signal(signal.SIGINT, signal.default_int_handler) |
|
145 | 137 | |
|
146 | 138 | start_event_loop_wx(kernel.app) |
|
147 | 139 | |
|
148 | 140 | |
|
149 | 141 | @register_integration('tk') |
|
150 | 142 | def loop_tk(kernel): |
|
151 | 143 | """Start a kernel with the Tk event loop.""" |
|
152 | 144 | |
|
153 | 145 | try: |
|
154 | 146 | from tkinter import Tk # Py 3 |
|
155 | 147 | except ImportError: |
|
156 | 148 | from Tkinter import Tk # Py 2 |
|
157 | 149 | doi = kernel.do_one_iteration |
|
158 | 150 | # Tk uses milliseconds |
|
159 | 151 | poll_interval = int(1000*kernel._poll_interval) |
|
160 | 152 | # For Tkinter, we create a Tk object and call its withdraw method. |
|
161 | 153 | class Timer(object): |
|
162 | 154 | def __init__(self, func): |
|
163 | 155 | self.app = Tk() |
|
164 | 156 | self.app.withdraw() |
|
165 | 157 | self.func = func |
|
166 | 158 | |
|
167 | 159 | def on_timer(self): |
|
168 | 160 | self.func() |
|
169 | 161 | self.app.after(poll_interval, self.on_timer) |
|
170 | 162 | |
|
171 | 163 | def start(self): |
|
172 | 164 | self.on_timer() # Call it once to get things going. |
|
173 | 165 | self.app.mainloop() |
|
174 | 166 | |
|
175 | 167 | kernel.timer = Timer(doi) |
|
176 | 168 | kernel.timer.start() |
|
177 | 169 | |
|
178 | 170 | |
|
179 | 171 | @register_integration('gtk') |
|
180 | 172 | def loop_gtk(kernel): |
|
181 | 173 | """Start the kernel, coordinating with the GTK event loop""" |
|
182 | 174 | from .gui.gtkembed import GTKEmbed |
|
183 | 175 | |
|
184 | 176 | gtk_kernel = GTKEmbed(kernel) |
|
185 | 177 | gtk_kernel.start() |
|
186 | 178 | |
|
187 | 179 | |
|
188 | 180 | @register_integration('gtk3') |
|
189 | 181 | def loop_gtk3(kernel): |
|
190 | 182 | """Start the kernel, coordinating with the GTK event loop""" |
|
191 | 183 | from .gui.gtk3embed import GTKEmbed |
|
192 | 184 | |
|
193 | 185 | gtk_kernel = GTKEmbed(kernel) |
|
194 | 186 | gtk_kernel.start() |
|
195 | 187 | |
|
196 | 188 | |
|
197 | 189 | @register_integration('osx') |
|
198 | 190 | def loop_cocoa(kernel): |
|
199 | 191 | """Start the kernel, coordinating with the Cocoa CFRunLoop event loop |
|
200 | 192 | via the matplotlib MacOSX backend. |
|
201 | 193 | """ |
|
202 | 194 | import matplotlib |
|
203 | 195 | if matplotlib.__version__ < '1.1.0': |
|
204 | 196 | kernel.log.warn( |
|
205 | 197 | "MacOSX backend in matplotlib %s doesn't have a Timer, " |
|
206 | 198 | "falling back on Tk for CFRunLoop integration. Note that " |
|
207 | 199 | "even this won't work if Tk is linked against X11 instead of " |
|
208 | 200 | "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, " |
|
209 | 201 | "you must use matplotlib >= 1.1.0, or a native libtk." |
|
210 | 202 | ) |
|
211 | 203 | return loop_tk(kernel) |
|
212 | 204 | |
|
213 | 205 | from matplotlib.backends.backend_macosx import TimerMac, show |
|
214 | 206 | |
|
215 | 207 | # scale interval for sec->ms |
|
216 | 208 | poll_interval = int(1000*kernel._poll_interval) |
|
217 | 209 | |
|
218 | 210 | real_excepthook = sys.excepthook |
|
219 | 211 | def handle_int(etype, value, tb): |
|
220 | 212 | """don't let KeyboardInterrupts look like crashes""" |
|
221 | 213 | if etype is KeyboardInterrupt: |
|
222 | 214 | io.raw_print("KeyboardInterrupt caught in CFRunLoop") |
|
223 | 215 | else: |
|
224 | 216 | real_excepthook(etype, value, tb) |
|
225 | 217 | |
|
226 | 218 | # add doi() as a Timer to the CFRunLoop |
|
227 | 219 | def doi(): |
|
228 | 220 | # restore excepthook during IPython code |
|
229 | 221 | sys.excepthook = real_excepthook |
|
230 | 222 | kernel.do_one_iteration() |
|
231 | 223 | # and back: |
|
232 | 224 | sys.excepthook = handle_int |
|
233 | 225 | |
|
234 | 226 | t = TimerMac(poll_interval) |
|
235 | 227 | t.add_callback(doi) |
|
236 | 228 | t.start() |
|
237 | 229 | |
|
238 | 230 | # but still need a Poller for when there are no active windows, |
|
239 | 231 | # during which time mainloop() returns immediately |
|
240 | 232 | poller = zmq.Poller() |
|
241 | 233 | if kernel.control_stream: |
|
242 | 234 | poller.register(kernel.control_stream.socket, zmq.POLLIN) |
|
243 | 235 | for stream in kernel.shell_streams: |
|
244 | 236 | poller.register(stream.socket, zmq.POLLIN) |
|
245 | 237 | |
|
246 | 238 | while True: |
|
247 | 239 | try: |
|
248 | 240 | # double nested try/except, to properly catch KeyboardInterrupt |
|
249 | 241 | # due to pyzmq Issue #130 |
|
250 | 242 | try: |
|
251 | 243 | # don't let interrupts during mainloop invoke crash_handler: |
|
252 | 244 | sys.excepthook = handle_int |
|
253 | 245 | show.mainloop() |
|
254 | 246 | sys.excepthook = real_excepthook |
|
255 | 247 | # use poller if mainloop returned (no windows) |
|
256 | 248 | # scale by extra factor of 10, since it's a real poll |
|
257 | 249 | poller.poll(10*poll_interval) |
|
258 | 250 | kernel.do_one_iteration() |
|
259 | 251 | except: |
|
260 | 252 | raise |
|
261 | 253 | except KeyboardInterrupt: |
|
262 | 254 | # Ctrl-C shouldn't crash the kernel |
|
263 | 255 | io.raw_print("KeyboardInterrupt caught in kernel") |
|
264 | 256 | finally: |
|
265 | 257 | # ensure excepthook is restored |
|
266 | 258 | sys.excepthook = real_excepthook |
|
267 | 259 | |
|
268 | 260 | |
|
269 | 261 | |
|
270 | 262 | def enable_gui(gui, kernel=None): |
|
271 | 263 | """Enable integration with a given GUI""" |
|
272 | 264 | if gui not in loop_map: |
|
273 | 265 | e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys()) |
|
274 | 266 | raise ValueError(e) |
|
275 | 267 | if kernel is None: |
|
276 | 268 | if Application.initialized(): |
|
277 | 269 | kernel = getattr(Application.instance(), 'kernel', None) |
|
278 | 270 | if kernel is None: |
|
279 | 271 | raise RuntimeError("You didn't specify a kernel," |
|
280 | 272 | " and no IPython Application with a kernel appears to be running." |
|
281 | 273 | ) |
|
282 | 274 | loop = loop_map[gui] |
|
283 | 275 | if loop and kernel.eventloop is not None and kernel.eventloop is not loop: |
|
284 | 276 | raise RuntimeError("Cannot activate multiple GUI eventloops") |
|
285 | 277 | kernel.eventloop = loop |
General Comments 0
You need to be logged in to leave comments.
Login now