Show More
@@ -0,0 +1,206 b'' | |||||
|
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 | #----------------------------------------------------------------------------- | |||
|
11 | ||||
|
12 | ||||
|
13 | #----------------------------------------------------------------------------- | |||
|
14 | # Imports | |||
|
15 | #----------------------------------------------------------------------------- | |||
|
16 | ||||
|
17 | import sys | |||
|
18 | ||||
|
19 | # System library imports. | |||
|
20 | import zmq | |||
|
21 | ||||
|
22 | # Local imports. | |||
|
23 | from IPython.utils import io | |||
|
24 | ||||
|
25 | #------------------------------------------------------------------------------ | |||
|
26 | # Eventloops for integrating the Kernel into different GUIs | |||
|
27 | #------------------------------------------------------------------------------ | |||
|
28 | ||||
|
29 | def loop_qt4(kernel): | |||
|
30 | """Start a kernel with PyQt4 event loop integration.""" | |||
|
31 | ||||
|
32 | from IPython.external.qt_for_kernel import QtCore | |||
|
33 | from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 | |||
|
34 | ||||
|
35 | kernel.app = get_app_qt4([" "]) | |||
|
36 | kernel.app.setQuitOnLastWindowClosed(False) | |||
|
37 | kernel.timer = QtCore.QTimer() | |||
|
38 | kernel.timer.timeout.connect(kernel.do_one_iteration) | |||
|
39 | # Units for the timer are in milliseconds | |||
|
40 | kernel.timer.start(1000*kernel._poll_interval) | |||
|
41 | start_event_loop_qt4(kernel.app) | |||
|
42 | ||||
|
43 | ||||
|
44 | def loop_wx(kernel): | |||
|
45 | """Start a kernel with wx event loop support.""" | |||
|
46 | ||||
|
47 | import wx | |||
|
48 | from IPython.lib.guisupport import start_event_loop_wx | |||
|
49 | ||||
|
50 | doi = kernel.do_one_iteration | |||
|
51 | # Wx uses milliseconds | |||
|
52 | poll_interval = int(1000*kernel._poll_interval) | |||
|
53 | ||||
|
54 | # We have to put the wx.Timer in a wx.Frame for it to fire properly. | |||
|
55 | # We make the Frame hidden when we create it in the main app below. | |||
|
56 | class TimerFrame(wx.Frame): | |||
|
57 | def __init__(self, func): | |||
|
58 | wx.Frame.__init__(self, None, -1) | |||
|
59 | self.timer = wx.Timer(self) | |||
|
60 | # Units for the timer are in milliseconds | |||
|
61 | self.timer.Start(poll_interval) | |||
|
62 | self.Bind(wx.EVT_TIMER, self.on_timer) | |||
|
63 | self.func = func | |||
|
64 | ||||
|
65 | def on_timer(self, event): | |||
|
66 | self.func() | |||
|
67 | ||||
|
68 | # We need a custom wx.App to create our Frame subclass that has the | |||
|
69 | # wx.Timer to drive the ZMQ event loop. | |||
|
70 | class IPWxApp(wx.App): | |||
|
71 | def OnInit(self): | |||
|
72 | self.frame = TimerFrame(doi) | |||
|
73 | self.frame.Show(False) | |||
|
74 | return True | |||
|
75 | ||||
|
76 | # The redirect=False here makes sure that wx doesn't replace | |||
|
77 | # sys.stdout/stderr with its own classes. | |||
|
78 | kernel.app = IPWxApp(redirect=False) | |||
|
79 | start_event_loop_wx(kernel.app) | |||
|
80 | ||||
|
81 | ||||
|
82 | def loop_tk(kernel): | |||
|
83 | """Start a kernel with the Tk event loop.""" | |||
|
84 | ||||
|
85 | import Tkinter | |||
|
86 | doi = kernel.do_one_iteration | |||
|
87 | # Tk uses milliseconds | |||
|
88 | poll_interval = int(1000*kernel._poll_interval) | |||
|
89 | # For Tkinter, we create a Tk object and call its withdraw method. | |||
|
90 | class Timer(object): | |||
|
91 | def __init__(self, func): | |||
|
92 | self.app = Tkinter.Tk() | |||
|
93 | self.app.withdraw() | |||
|
94 | self.func = func | |||
|
95 | ||||
|
96 | def on_timer(self): | |||
|
97 | self.func() | |||
|
98 | self.app.after(poll_interval, self.on_timer) | |||
|
99 | ||||
|
100 | def start(self): | |||
|
101 | self.on_timer() # Call it once to get things going. | |||
|
102 | self.app.mainloop() | |||
|
103 | ||||
|
104 | kernel.timer = Timer(doi) | |||
|
105 | kernel.timer.start() | |||
|
106 | ||||
|
107 | ||||
|
108 | def loop_gtk(kernel): | |||
|
109 | """Start the kernel, coordinating with the GTK event loop""" | |||
|
110 | from .gui.gtkembed import GTKEmbed | |||
|
111 | ||||
|
112 | gtk_kernel = GTKEmbed(kernel) | |||
|
113 | gtk_kernel.start() | |||
|
114 | ||||
|
115 | ||||
|
116 | def loop_cocoa(kernel): | |||
|
117 | """Start the kernel, coordinating with the Cocoa CFRunLoop event loop | |||
|
118 | via the matplotlib MacOSX backend. | |||
|
119 | """ | |||
|
120 | import matplotlib | |||
|
121 | if matplotlib.__version__ < '1.1.0': | |||
|
122 | kernel.log.warn( | |||
|
123 | "MacOSX backend in matplotlib %s doesn't have a Timer, " | |||
|
124 | "falling back on Tk for CFRunLoop integration. Note that " | |||
|
125 | "even this won't work if Tk is linked against X11 instead of " | |||
|
126 | "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, " | |||
|
127 | "you must use matplotlib >= 1.1.0, or a native libtk." | |||
|
128 | ) | |||
|
129 | return loop_tk(kernel) | |||
|
130 | ||||
|
131 | from matplotlib.backends.backend_macosx import TimerMac, show | |||
|
132 | ||||
|
133 | # scale interval for sec->ms | |||
|
134 | poll_interval = int(1000*kernel._poll_interval) | |||
|
135 | ||||
|
136 | real_excepthook = sys.excepthook | |||
|
137 | def handle_int(etype, value, tb): | |||
|
138 | """don't let KeyboardInterrupts look like crashes""" | |||
|
139 | if etype is KeyboardInterrupt: | |||
|
140 | io.raw_print("KeyboardInterrupt caught in CFRunLoop") | |||
|
141 | else: | |||
|
142 | real_excepthook(etype, value, tb) | |||
|
143 | ||||
|
144 | # add doi() as a Timer to the CFRunLoop | |||
|
145 | def doi(): | |||
|
146 | # restore excepthook during IPython code | |||
|
147 | sys.excepthook = real_excepthook | |||
|
148 | kernel.do_one_iteration() | |||
|
149 | # and back: | |||
|
150 | sys.excepthook = handle_int | |||
|
151 | ||||
|
152 | t = TimerMac(poll_interval) | |||
|
153 | t.add_callback(doi) | |||
|
154 | t.start() | |||
|
155 | ||||
|
156 | # but still need a Poller for when there are no active windows, | |||
|
157 | # during which time mainloop() returns immediately | |||
|
158 | poller = zmq.Poller() | |||
|
159 | poller.register(kernel.shell_socket, zmq.POLLIN) | |||
|
160 | ||||
|
161 | while True: | |||
|
162 | try: | |||
|
163 | # double nested try/except, to properly catch KeyboardInterrupt | |||
|
164 | # due to pyzmq Issue #130 | |||
|
165 | try: | |||
|
166 | # don't let interrupts during mainloop invoke crash_handler: | |||
|
167 | sys.excepthook = handle_int | |||
|
168 | show.mainloop() | |||
|
169 | sys.excepthook = real_excepthook | |||
|
170 | # use poller if mainloop returned (no windows) | |||
|
171 | # scale by extra factor of 10, since it's a real poll | |||
|
172 | poller.poll(10*poll_interval) | |||
|
173 | kernel.do_one_iteration() | |||
|
174 | except: | |||
|
175 | raise | |||
|
176 | except KeyboardInterrupt: | |||
|
177 | # Ctrl-C shouldn't crash the kernel | |||
|
178 | io.raw_print("KeyboardInterrupt caught in kernel") | |||
|
179 | finally: | |||
|
180 | # ensure excepthook is restored | |||
|
181 | sys.excepthook = real_excepthook | |||
|
182 | ||||
|
183 | # mapping of keys to loop functions | |||
|
184 | loop_map = { | |||
|
185 | 'qt' : loop_qt4, | |||
|
186 | 'qt4': loop_qt4, | |||
|
187 | 'inline': None, | |||
|
188 | 'osx': loop_cocoa, | |||
|
189 | 'wx' : loop_wx, | |||
|
190 | 'tk' : loop_tk, | |||
|
191 | 'gtk': loop_gtk, | |||
|
192 | None : None, | |||
|
193 | } | |||
|
194 | ||||
|
195 | ||||
|
196 | def enable_gui(gui, kernel=None): | |||
|
197 | """Enable integration with a given GUI""" | |||
|
198 | if kernel is None: | |||
|
199 | from .ipkernel import IPKernelApp | |||
|
200 | kernel = IPKernelApp.instance().kernel | |||
|
201 | if gui not in loop_map: | |||
|
202 | raise ValueError("GUI %r not supported" % gui) | |||
|
203 | loop = loop_map[gui] | |||
|
204 | if kernel.eventloop is not None and kernel.eventloop is not loop: | |||
|
205 | raise RuntimeError("Cannot activate multiple GUI eventloops") | |||
|
206 | kernel.eventloop = loop |
General Comments 0
You need to be logged in to leave comments.
Login now