##// END OF EJS Templates
* Moved KernelManager attribute management code in FrontendWidget into a mixin class usable in any Qt frontend. Registering handlers for message types is now trivial....
epatters -
Show More
@@ -0,0 +1,85 b''
1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 """
3
4 class BaseFrontendMixin(object):
5 """ A mix-in class for implementing Qt frontends.
6
7 To handle messages of a particular type, frontends need only define an
8 appropriate handler method. For example, to handle 'stream' messaged, define
9 a '_handle_stream(msg)' method.
10 """
11
12 #---------------------------------------------------------------------------
13 # 'BaseFrontendMixin' concrete interface
14 #---------------------------------------------------------------------------
15
16 def _get_kernel_manager(self):
17 """ Returns the current kernel manager.
18 """
19 return self._kernel_manager
20
21 def _set_kernel_manager(self, kernel_manager):
22 """ Disconnect from the current kernel manager (if any) and set a new
23 kernel manager.
24 """
25 # Disconnect the old kernel manager, if necessary.
26 old_manager = self._kernel_manager
27 if old_manager is not None:
28 old_manager.started_channels.disconnect(self._started_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
30
31 # Disconnect the old kernel manager's channels.
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.connect(self._dispatch)
35
36 # Handle the case where the old kernel manager is still listening.
37 if old_manager.channels_running:
38 self._stopped_channels()
39
40 # Set the new kernel manager.
41 self._kernel_manager = kernel_manager
42 if kernel_manager is None:
43 return
44
45 # Connect the new kernel manager.
46 kernel_manager.started_channels.connect(self._started_channels)
47 kernel_manager.stopped_channels.connect(self._stopped_channels)
48
49 # Connect the new kernel manager's channels.
50 kernel_manager.sub_channel.message_received.connect(self._dispatch)
51 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
52 kernel_manager.rep_channel.message_received.connect(self._dispatch)
53
54 # Handle the case where the kernel manager started channels before
55 # we connected.
56 if kernel_manager.channels_running:
57 self._started_channels()
58
59 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
60
61 #---------------------------------------------------------------------------
62 # 'BaseFrontendMixin' abstract interface
63 #---------------------------------------------------------------------------
64
65 def _started_channels(self):
66 """ Called when the KernelManager channels have started listening or
67 when the frontend is assigned an already listening KernelManager.
68 """
69
70 def _stopped_channels(self):
71 """ Called when the KernelManager channels have stopped listening or
72 when a listening KernelManager is removed from the frontend.
73 """
74
75 #---------------------------------------------------------------------------
76 # Private interface
77 #---------------------------------------------------------------------------
78
79 def _dispatch(self, msg):
80 """ Call the frontend handler associated with
81 """
82 msg_type = msg['msg_type']
83 handler = getattr(self, '_handle_' + msg_type, None)
84 if handler:
85 handler(msg)
@@ -1,376 +1,340 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from pygments.lexers import PythonLexer
6 from pygments.lexers import PythonLexer
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8 import zmq
8 import zmq
9
9
10 # Local imports
10 # Local imports
11 from IPython.core.inputsplitter import InputSplitter
11 from IPython.core.inputsplitter import InputSplitter
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from call_tip_widget import CallTipWidget
13 from call_tip_widget import CallTipWidget
13 from completion_lexer import CompletionLexer
14 from completion_lexer import CompletionLexer
14 from console_widget import HistoryConsoleWidget
15 from console_widget import HistoryConsoleWidget
15 from pygments_highlighter import PygmentsHighlighter
16 from pygments_highlighter import PygmentsHighlighter
16
17
17
18
18 class FrontendHighlighter(PygmentsHighlighter):
19 class FrontendHighlighter(PygmentsHighlighter):
19 """ A PygmentsHighlighter that can be turned on and off and that ignores
20 """ A PygmentsHighlighter that can be turned on and off and that ignores
20 prompts.
21 prompts.
21 """
22 """
22
23
23 def __init__(self, frontend):
24 def __init__(self, frontend):
24 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 super(FrontendHighlighter, self).__init__(frontend._control.document())
25 self._current_offset = 0
26 self._current_offset = 0
26 self._frontend = frontend
27 self._frontend = frontend
27 self.highlighting_on = False
28 self.highlighting_on = False
28
29
29 def highlightBlock(self, qstring):
30 def highlightBlock(self, qstring):
30 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """
32 """
32 if not self.highlighting_on:
33 if not self.highlighting_on:
33 return
34 return
34
35
35 # The input to this function is unicode string that may contain
36 # The input to this function is unicode string that may contain
36 # paragraph break characters, non-breaking spaces, etc. Here we acquire
37 # paragraph break characters, non-breaking spaces, etc. Here we acquire
37 # the string as plain text so we can compare it.
38 # the string as plain text so we can compare it.
38 current_block = self.currentBlock()
39 current_block = self.currentBlock()
39 string = self._frontend._get_block_plain_text(current_block)
40 string = self._frontend._get_block_plain_text(current_block)
40
41
41 # Decide whether to check for the regular or continuation prompt.
42 # Decide whether to check for the regular or continuation prompt.
42 if current_block.contains(self._frontend._prompt_pos):
43 if current_block.contains(self._frontend._prompt_pos):
43 prompt = self._frontend._prompt
44 prompt = self._frontend._prompt
44 else:
45 else:
45 prompt = self._frontend._continuation_prompt
46 prompt = self._frontend._continuation_prompt
46
47
47 # Don't highlight the part of the string that contains the prompt.
48 # Don't highlight the part of the string that contains the prompt.
48 if string.startswith(prompt):
49 if string.startswith(prompt):
49 self._current_offset = len(prompt)
50 self._current_offset = len(prompt)
50 qstring.remove(0, len(prompt))
51 qstring.remove(0, len(prompt))
51 else:
52 else:
52 self._current_offset = 0
53 self._current_offset = 0
53
54
54 PygmentsHighlighter.highlightBlock(self, qstring)
55 PygmentsHighlighter.highlightBlock(self, qstring)
55
56
56 def setFormat(self, start, count, format):
57 def setFormat(self, start, count, format):
57 """ Reimplemented to highlight selectively.
58 """ Reimplemented to highlight selectively.
58 """
59 """
59 start += self._current_offset
60 start += self._current_offset
60 PygmentsHighlighter.setFormat(self, start, count, format)
61 PygmentsHighlighter.setFormat(self, start, count, format)
61
62
62
63
63 class FrontendWidget(HistoryConsoleWidget):
64 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
64 """ A Qt frontend for a generic Python kernel.
65 """ A Qt frontend for a generic Python kernel.
65 """
66 """
66
67
67 # Emitted when an 'execute_reply' is received from the kernel.
68 # Emitted when an 'execute_reply' is received from the kernel.
68 executed = QtCore.pyqtSignal(object)
69 executed = QtCore.pyqtSignal(object)
69
70
70 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
71 # 'object' interface
72 # 'object' interface
72 #---------------------------------------------------------------------------
73 #---------------------------------------------------------------------------
73
74
74 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
75 super(FrontendWidget, self).__init__(*args, **kw)
76 super(FrontendWidget, self).__init__(*args, **kw)
76
77
77 # FrontendWidget protected variables.
78 # FrontendWidget protected variables.
78 self._call_tip_widget = CallTipWidget(self._control)
79 self._call_tip_widget = CallTipWidget(self._control)
79 self._completion_lexer = CompletionLexer(PythonLexer())
80 self._completion_lexer = CompletionLexer(PythonLexer())
80 self._hidden = True
81 self._hidden = True
81 self._highlighter = FrontendHighlighter(self)
82 self._highlighter = FrontendHighlighter(self)
82 self._input_splitter = InputSplitter(input_mode='replace')
83 self._input_splitter = InputSplitter(input_mode='replace')
83 self._kernel_manager = None
84 self._kernel_manager = None
84
85
85 # Configure the ConsoleWidget.
86 # Configure the ConsoleWidget.
86 self.tab_width = 4
87 self.tab_width = 4
87 self._set_continuation_prompt('... ')
88 self._set_continuation_prompt('... ')
88
89
89 # Connect signal handlers.
90 # Connect signal handlers.
90 document = self._control.document()
91 document = self._control.document()
91 document.contentsChange.connect(self._document_contents_change)
92 document.contentsChange.connect(self._document_contents_change)
92
93
93 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
94 # 'ConsoleWidget' abstract interface
95 # 'ConsoleWidget' abstract interface
95 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
96
97
97 def _is_complete(self, source, interactive):
98 def _is_complete(self, source, interactive):
98 """ Returns whether 'source' can be completely processed and a new
99 """ Returns whether 'source' can be completely processed and a new
99 prompt created. When triggered by an Enter/Return key press,
100 prompt created. When triggered by an Enter/Return key press,
100 'interactive' is True; otherwise, it is False.
101 'interactive' is True; otherwise, it is False.
101 """
102 """
102 complete = self._input_splitter.push(source.expandtabs(4))
103 complete = self._input_splitter.push(source.expandtabs(4))
103 if interactive:
104 if interactive:
104 complete = not self._input_splitter.push_accepts_more()
105 complete = not self._input_splitter.push_accepts_more()
105 return complete
106 return complete
106
107
107 def _execute(self, source, hidden):
108 def _execute(self, source, hidden):
108 """ Execute 'source'. If 'hidden', do not show any output.
109 """ Execute 'source'. If 'hidden', do not show any output.
109 """
110 """
110 self.kernel_manager.xreq_channel.execute(source)
111 self.kernel_manager.xreq_channel.execute(source)
111 self._hidden = hidden
112 self._hidden = hidden
112
113
113 def _execute_interrupt(self):
114 def _execute_interrupt(self):
114 """ Attempts to stop execution. Returns whether this method has an
115 """ Attempts to stop execution. Returns whether this method has an
115 implementation.
116 implementation.
116 """
117 """
117 self._interrupt_kernel()
118 self._interrupt_kernel()
118 return True
119 return True
119
120
120 def _prompt_started_hook(self):
121 def _prompt_started_hook(self):
121 """ Called immediately after a new prompt is displayed.
122 """ Called immediately after a new prompt is displayed.
122 """
123 """
123 if not self._reading:
124 if not self._reading:
124 self._highlighter.highlighting_on = True
125 self._highlighter.highlighting_on = True
125
126
126 # Auto-indent if this is a continuation prompt.
127 # Auto-indent if this is a continuation prompt.
127 if self._get_prompt_cursor().blockNumber() != \
128 if self._get_prompt_cursor().blockNumber() != \
128 self._get_end_cursor().blockNumber():
129 self._get_end_cursor().blockNumber():
129 spaces = self._input_splitter.indent_spaces
130 spaces = self._input_splitter.indent_spaces
130 self._append_plain_text('\t' * (spaces / self.tab_width))
131 self._append_plain_text('\t' * (spaces / self.tab_width))
131 self._append_plain_text(' ' * (spaces % self.tab_width))
132 self._append_plain_text(' ' * (spaces % self.tab_width))
132
133
133 def _prompt_finished_hook(self):
134 def _prompt_finished_hook(self):
134 """ Called immediately after a prompt is finished, i.e. when some input
135 """ Called immediately after a prompt is finished, i.e. when some input
135 will be processed and a new prompt displayed.
136 will be processed and a new prompt displayed.
136 """
137 """
137 if not self._reading:
138 if not self._reading:
138 self._highlighter.highlighting_on = False
139 self._highlighter.highlighting_on = False
139
140
140 def _tab_pressed(self):
141 def _tab_pressed(self):
141 """ Called when the tab key is pressed. Returns whether to continue
142 """ Called when the tab key is pressed. Returns whether to continue
142 processing the event.
143 processing the event.
143 """
144 """
144 self._keep_cursor_in_buffer()
145 self._keep_cursor_in_buffer()
145 cursor = self._get_cursor()
146 cursor = self._get_cursor()
146 return not self._complete()
147 return not self._complete()
147
148
148 #---------------------------------------------------------------------------
149 #---------------------------------------------------------------------------
149 # 'FrontendWidget' interface
150 # 'BaseFrontendMixin' abstract interface
150 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
151
152
152 def execute_file(self, path, hidden=False):
153 def _handle_complete_reply(self, rep):
153 """ Attempts to execute file with 'path'. If 'hidden', no output is
154 """ Handle replies for tab completion.
154 shown.
155 """
155 """
156 self.execute('execfile("%s")' % path, hidden=hidden)
156 cursor = self._get_cursor()
157 if rep['parent_header']['msg_id'] == self._complete_id and \
158 cursor.position() == self._complete_pos:
159 text = '.'.join(self._get_context())
160 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
161 self._complete_with_items(cursor, rep['content']['matches'])
157
162
158 def _get_kernel_manager(self):
163 def _handle_execute_reply(self, msg):
159 """ Returns the current kernel manager.
164 """ Handles replies for code execution.
160 """
165 """
161 return self._kernel_manager
166 if not self._hidden:
167 # Make sure that all output from the SUB channel has been processed
168 # before writing a new prompt.
169 self.kernel_manager.sub_channel.flush()
170
171 content = msg['content']
172 status = content['status']
173 if status == 'ok':
174 self._process_execute_ok(msg)
175 elif status == 'error':
176 self._process_execute_error(msg)
177 elif status == 'abort':
178 self._process_execute_abort(msg)
179
180 self._hidden = True
181 self._show_interpreter_prompt()
182 self.executed.emit(msg)
183
184 def _handle_input_request(self, msg):
185 """ Handle requests for raw_input.
186 """
187 # Make sure that all output from the SUB channel has been processed
188 # before entering readline mode.
189 self.kernel_manager.sub_channel.flush()
190
191 def callback(line):
192 self.kernel_manager.rep_channel.input(line)
193 self._readline(msg['content']['prompt'], callback=callback)
162
194
163 def _set_kernel_manager(self, kernel_manager):
195 def _handle_object_info_reply(self, rep):
164 """ Disconnect from the current kernel manager (if any) and set a new
196 """ Handle replies for call tips.
165 kernel manager.
166 """
197 """
167 # Disconnect the old kernel manager, if necessary.
198 cursor = self._get_cursor()
168 if self._kernel_manager is not None:
199 if rep['parent_header']['msg_id'] == self._call_tip_id and \
169 self._kernel_manager.started_channels.disconnect(
200 cursor.position() == self._call_tip_pos:
170 self._started_channels)
201 doc = rep['content']['docstring']
171 self._kernel_manager.stopped_channels.disconnect(
202 if doc:
172 self._stopped_channels)
203 self._call_tip_widget.show_docstring(doc)
173
174 # Disconnect the old kernel manager's channels.
175 sub = self._kernel_manager.sub_channel
176 xreq = self._kernel_manager.xreq_channel
177 rep = self._kernel_manager.rep_channel
178 sub.message_received.disconnect(self._handle_sub)
179 xreq.execute_reply.disconnect(self._handle_execute_reply)
180 xreq.complete_reply.disconnect(self._handle_complete_reply)
181 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
182 rep.input_requested.disconnect(self._handle_req)
183
184 # Handle the case where the old kernel manager is still listening.
185 if self._kernel_manager.channels_running:
186 self._stopped_channels()
187
188 # Set the new kernel manager.
189 self._kernel_manager = kernel_manager
190 if kernel_manager is None:
191 return
192
204
193 # Connect the new kernel manager.
205 def _handle_pyout(self, msg):
194 kernel_manager.started_channels.connect(self._started_channels)
206 """ Handle display hook output.
195 kernel_manager.stopped_channels.connect(self._stopped_channels)
207 """
196
208 self._append_plain_text(msg['content']['data'] + '\n')
197 # Connect the new kernel manager's channels.
198 sub = kernel_manager.sub_channel
199 xreq = kernel_manager.xreq_channel
200 rep = kernel_manager.rep_channel
201 sub.message_received.connect(self._handle_sub)
202 xreq.execute_reply.connect(self._handle_execute_reply)
203 xreq.complete_reply.connect(self._handle_complete_reply)
204 xreq.object_info_reply.connect(self._handle_object_info_reply)
205 rep.input_requested.connect(self._handle_req)
206
207 # Handle the case where the kernel manager started channels before
208 # we connected.
209 if kernel_manager.channels_running:
210 self._started_channels()
211
209
212 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
210 def _handle_stream(self, msg):
211 """ Handle stdout, stderr, and stdin.
212 """
213 self._append_plain_text(msg['content']['data'])
214 self._control.moveCursor(QtGui.QTextCursor.End)
215
216 def _started_channels(self):
217 """ Called when the KernelManager channels have started listening or
218 when the frontend is assigned an already listening KernelManager.
219 """
220 self._reset()
221 self._append_plain_text(self._get_banner())
222 self._show_interpreter_prompt()
223
224 def _stopped_channels(self):
225 """ Called when the KernelManager channels have stopped listening or
226 when a listening KernelManager is removed from the frontend.
227 """
228 # FIXME: Print a message here?
229 pass
230
231 #---------------------------------------------------------------------------
232 # 'FrontendWidget' interface
233 #---------------------------------------------------------------------------
234
235 def execute_file(self, path, hidden=False):
236 """ Attempts to execute file with 'path'. If 'hidden', no output is
237 shown.
238 """
239 self.execute('execfile("%s")' % path, hidden=hidden)
213
240
214 #---------------------------------------------------------------------------
241 #---------------------------------------------------------------------------
215 # 'FrontendWidget' protected interface
242 # 'FrontendWidget' protected interface
216 #---------------------------------------------------------------------------
243 #---------------------------------------------------------------------------
217
244
218 def _call_tip(self):
245 def _call_tip(self):
219 """ Shows a call tip, if appropriate, at the current cursor location.
246 """ Shows a call tip, if appropriate, at the current cursor location.
220 """
247 """
221 # Decide if it makes sense to show a call tip
248 # Decide if it makes sense to show a call tip
222 cursor = self._get_cursor()
249 cursor = self._get_cursor()
223 cursor.movePosition(QtGui.QTextCursor.Left)
250 cursor.movePosition(QtGui.QTextCursor.Left)
224 document = self._control.document()
251 document = self._control.document()
225 if document.characterAt(cursor.position()).toAscii() != '(':
252 if document.characterAt(cursor.position()).toAscii() != '(':
226 return False
253 return False
227 context = self._get_context(cursor)
254 context = self._get_context(cursor)
228 if not context:
255 if not context:
229 return False
256 return False
230
257
231 # Send the metadata request to the kernel
258 # Send the metadata request to the kernel
232 name = '.'.join(context)
259 name = '.'.join(context)
233 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
260 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
234 self._call_tip_pos = self._get_cursor().position()
261 self._call_tip_pos = self._get_cursor().position()
235 return True
262 return True
236
263
237 def _complete(self):
264 def _complete(self):
238 """ Performs completion at the current cursor location.
265 """ Performs completion at the current cursor location.
239 """
266 """
240 # Decide if it makes sense to do completion
267 # Decide if it makes sense to do completion
241 context = self._get_context()
268 context = self._get_context()
242 if not context:
269 if not context:
243 return False
270 return False
244
271
245 # Send the completion request to the kernel
272 # Send the completion request to the kernel
246 text = '.'.join(context)
273 text = '.'.join(context)
247 self._complete_id = self.kernel_manager.xreq_channel.complete(
274 self._complete_id = self.kernel_manager.xreq_channel.complete(
248 text, self._get_input_buffer_cursor_line(), self.input_buffer)
275 text, self._get_input_buffer_cursor_line(), self.input_buffer)
249 self._complete_pos = self._get_cursor().position()
276 self._complete_pos = self._get_cursor().position()
250 return True
277 return True
251
278
252 def _get_banner(self):
279 def _get_banner(self):
253 """ Gets a banner to display at the beginning of a session.
280 """ Gets a banner to display at the beginning of a session.
254 """
281 """
255 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
282 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
256 '"license" for more information.'
283 '"license" for more information.'
257 return banner % (sys.version, sys.platform)
284 return banner % (sys.version, sys.platform)
258
285
259 def _get_context(self, cursor=None):
286 def _get_context(self, cursor=None):
260 """ Gets the context at the current cursor location.
287 """ Gets the context at the current cursor location.
261 """
288 """
262 if cursor is None:
289 if cursor is None:
263 cursor = self._get_cursor()
290 cursor = self._get_cursor()
264 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
291 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
265 QtGui.QTextCursor.KeepAnchor)
292 QtGui.QTextCursor.KeepAnchor)
266 text = str(cursor.selection().toPlainText())
293 text = str(cursor.selection().toPlainText())
267 return self._completion_lexer.get_context(text)
294 return self._completion_lexer.get_context(text)
268
295
269 def _interrupt_kernel(self):
296 def _interrupt_kernel(self):
270 """ Attempts to the interrupt the kernel.
297 """ Attempts to the interrupt the kernel.
271 """
298 """
272 if self.kernel_manager.has_kernel:
299 if self.kernel_manager.has_kernel:
273 self.kernel_manager.signal_kernel(signal.SIGINT)
300 self.kernel_manager.signal_kernel(signal.SIGINT)
274 else:
301 else:
275 self._append_plain_text('Kernel process is either remote or '
302 self._append_plain_text('Kernel process is either remote or '
276 'unspecified. Cannot interrupt.\n')
303 'unspecified. Cannot interrupt.\n')
277
304
278 def _show_interpreter_prompt(self):
305 def _process_execute_abort(self, msg):
279 """ Shows a prompt for the interpreter.
306 """ Process a reply for an aborted execution request.
280 """
307 """
281 self._show_prompt('>>> ')
308 self._append_plain_text("ERROR: execution aborted\n")
282
283 #------ Signal handlers ----------------------------------------------------
284
309
285 def _started_channels(self):
310 def _process_execute_error(self, msg):
286 """ Called when the kernel manager has started listening.
311 """ Process a reply for an execution request that resulted in an error.
287 """
312 """
288 self._reset()
313 content = msg['content']
289 self._append_plain_text(self._get_banner())
314 traceback = ''.join(content['traceback'])
290 self._show_interpreter_prompt()
315 self._append_plain_text(traceback)
291
316
292 def _stopped_channels(self):
317 def _process_execute_ok(self, msg):
293 """ Called when the kernel manager has stopped listening.
318 """ Process a reply for a successful execution equest.
294 """
319 """
295 # FIXME: Print a message here?
320 # The basic FrontendWidget doesn't handle payloads, as they are a
321 # mechanism for going beyond the standard Python interpreter model.
296 pass
322 pass
297
323
324 def _show_interpreter_prompt(self):
325 """ Shows a prompt for the interpreter.
326 """
327 self._show_prompt('>>> ')
328
329 #------ Signal handlers ----------------------------------------------------
330
298 def _document_contents_change(self, position, removed, added):
331 def _document_contents_change(self, position, removed, added):
299 """ Called whenever the document's content changes. Display a call tip
332 """ Called whenever the document's content changes. Display a call tip
300 if appropriate.
333 if appropriate.
301 """
334 """
302 # Calculate where the cursor should be *after* the change:
335 # Calculate where the cursor should be *after* the change:
303 position += added
336 position += added
304
337
305 document = self._control.document()
338 document = self._control.document()
306 if position == self._get_cursor().position():
339 if position == self._get_cursor().position():
307 self._call_tip()
340 self._call_tip()
308
309 def _handle_req(self, req):
310 # Make sure that all output from the SUB channel has been processed
311 # before entering readline mode.
312 self.kernel_manager.sub_channel.flush()
313
314 def callback(line):
315 self.kernel_manager.rep_channel.input(line)
316 self._readline(req['content']['prompt'], callback=callback)
317
318 def _handle_sub(self, omsg):
319 if self._hidden:
320 return
321 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
322 if handler is not None:
323 handler(omsg)
324
325 def _handle_pyout(self, omsg):
326 self._append_plain_text(omsg['content']['data'] + '\n')
327
328 def _handle_stream(self, omsg):
329 self._append_plain_text(omsg['content']['data'])
330 self._control.moveCursor(QtGui.QTextCursor.End)
331
332 def _handle_execute_reply(self, reply):
333 if self._hidden:
334 return
335
336 # Make sure that all output from the SUB channel has been processed
337 # before writing a new prompt.
338 self.kernel_manager.sub_channel.flush()
339
340 content = reply['content']
341 status = content['status']
342 if status == 'ok':
343 self._handle_execute_payload(content['payload'])
344 elif status == 'error':
345 self._handle_execute_error(reply)
346 elif status == 'aborted':
347 text = "ERROR: ABORTED\n"
348 self._append_plain_text(text)
349
350 self._hidden = True
351 self._show_interpreter_prompt()
352 self.executed.emit(reply)
353
354 def _handle_execute_error(self, reply):
355 content = reply['content']
356 traceback = ''.join(content['traceback'])
357 self._append_plain_text(traceback)
358
359 def _handle_execute_payload(self, payload):
360 pass
361
362 def _handle_complete_reply(self, rep):
363 cursor = self._get_cursor()
364 if rep['parent_header']['msg_id'] == self._complete_id and \
365 cursor.position() == self._complete_pos:
366 text = '.'.join(self._get_context())
367 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
368 self._complete_with_items(cursor, rep['content']['matches'])
369
370 def _handle_object_info_reply(self, rep):
371 cursor = self._get_cursor()
372 if rep['parent_header']['msg_id'] == self._call_tip_id and \
373 cursor.position() == self._call_tip_pos:
374 doc = rep['content']['docstring']
375 if doc:
376 self._call_tip_widget.show_docstring(doc)
@@ -1,184 +1,186 b''
1 # System library imports
1 # System library imports
2 from PyQt4 import QtCore, QtGui
2 from PyQt4 import QtCore, QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.core.usage import default_banner
5 from IPython.core.usage import default_banner
6 from frontend_widget import FrontendWidget
6 from frontend_widget import FrontendWidget
7
7
8
8
9 class IPythonWidget(FrontendWidget):
9 class IPythonWidget(FrontendWidget):
10 """ A FrontendWidget for an IPython kernel.
10 """ A FrontendWidget for an IPython kernel.
11 """
11 """
12
12
13 # The default stylesheet: black text on a white background.
13 # The default stylesheet: black text on a white background.
14 default_stylesheet = """
14 default_stylesheet = """
15 .error { color: red; }
15 .error { color: red; }
16 .in-prompt { color: navy; }
16 .in-prompt { color: navy; }
17 .in-prompt-number { font-weight: bold; }
17 .in-prompt-number { font-weight: bold; }
18 .out-prompt { color: darkred; }
18 .out-prompt { color: darkred; }
19 .out-prompt-number { font-weight: bold; }
19 .out-prompt-number { font-weight: bold; }
20 """
20 """
21
21
22 # A dark stylesheet: white text on a black background.
22 # A dark stylesheet: white text on a black background.
23 dark_stylesheet = """
23 dark_stylesheet = """
24 QPlainTextEdit { background-color: black; color: white }
24 QPlainTextEdit { background-color: black; color: white }
25 QFrame { border: 1px solid grey; }
25 QFrame { border: 1px solid grey; }
26 .error { color: red; }
26 .error { color: red; }
27 .in-prompt { color: lime; }
27 .in-prompt { color: lime; }
28 .in-prompt-number { color: lime; font-weight: bold; }
28 .in-prompt-number { color: lime; font-weight: bold; }
29 .out-prompt { color: red; }
29 .out-prompt { color: red; }
30 .out-prompt-number { color: red; font-weight: bold; }
30 .out-prompt-number { color: red; font-weight: bold; }
31 """
31 """
32
32
33 # Default prompts.
33 # Default prompts.
34 in_prompt = '<br/>In [<span class="in-prompt-number">%i</span>]: '
34 in_prompt = '<br/>In [<span class="in-prompt-number">%i</span>]: '
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36
36
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38 # 'object' interface
38 # 'object' interface
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40
40
41 def __init__(self, *args, **kw):
41 def __init__(self, *args, **kw):
42 super(IPythonWidget, self).__init__(*args, **kw)
42 super(IPythonWidget, self).__init__(*args, **kw)
43
43
44 # Initialize protected variables.
44 # Initialize protected variables.
45 self._previous_prompt_blocks = []
45 self._previous_prompt_blocks = []
46 self._prompt_count = 0
46 self._prompt_count = 0
47
47
48 # Set a default stylesheet.
48 # Set a default stylesheet.
49 self.reset_styling()
49 self.reset_styling()
50
50
51 #---------------------------------------------------------------------------
51 #---------------------------------------------------------------------------
52 # 'BaseFrontendMixin' abstract interface
53 #---------------------------------------------------------------------------
54
55 def _handle_pyout(self, msg):
56 """ Reimplemented for IPython-style "display hook".
57 """
58 self._append_html(self._make_out_prompt(self._prompt_count))
59 self._save_prompt_block()
60
61 self._append_plain_text(msg['content']['data'] + '\n')
62
63 #---------------------------------------------------------------------------
52 # 'FrontendWidget' interface
64 # 'FrontendWidget' interface
53 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
54
66
55 def execute_file(self, path, hidden=False):
67 def execute_file(self, path, hidden=False):
56 """ Reimplemented to use the 'run' magic.
68 """ Reimplemented to use the 'run' magic.
57 """
69 """
58 self.execute('run %s' % path, hidden=hidden)
70 self.execute('run %s' % path, hidden=hidden)
59
71
60 #---------------------------------------------------------------------------
72 #---------------------------------------------------------------------------
61 # 'FrontendWidget' protected interface
73 # 'FrontendWidget' protected interface
62 #---------------------------------------------------------------------------
74 #---------------------------------------------------------------------------
63
75
64 def _get_banner(self):
76 def _get_banner(self):
65 """ Reimplemented to return IPython's default banner.
77 """ Reimplemented to return IPython's default banner.
66 """
78 """
67 return default_banner
79 return default_banner
68
80
81 def _process_execute_error(self, msg):
82 """ Reimplemented for IPython-style traceback formatting.
83 """
84 content = msg['content']
85 traceback_lines = content['traceback'][:]
86 traceback = ''.join(traceback_lines)
87 traceback = traceback.replace(' ', '&nbsp;')
88 traceback = traceback.replace('\n', '<br/>')
89
90 ename = content['ename']
91 ename_styled = '<span class="error">%s</span>' % ename
92 traceback = traceback.replace(ename, ename_styled)
93
94 self._append_html(traceback)
95
69 def _show_interpreter_prompt(self):
96 def _show_interpreter_prompt(self):
70 """ Reimplemented for IPython-style prompts.
97 """ Reimplemented for IPython-style prompts.
71 """
98 """
72 # Update old prompt numbers if necessary.
99 # Update old prompt numbers if necessary.
73 previous_prompt_number = self._prompt_count
100 previous_prompt_number = self._prompt_count
74 if previous_prompt_number != self._prompt_count:
101 if previous_prompt_number != self._prompt_count:
75 for i, (block, length) in enumerate(self._previous_prompt_blocks):
102 for i, (block, length) in enumerate(self._previous_prompt_blocks):
76 if block.isValid():
103 if block.isValid():
77 cursor = QtGui.QTextCursor(block)
104 cursor = QtGui.QTextCursor(block)
78 cursor.movePosition(QtGui.QTextCursor.Right,
105 cursor.movePosition(QtGui.QTextCursor.Right,
79 QtGui.QTextCursor.KeepAnchor, length-1)
106 QtGui.QTextCursor.KeepAnchor, length-1)
80 if i == 0:
107 if i == 0:
81 prompt = self._make_in_prompt(previous_prompt_number)
108 prompt = self._make_in_prompt(previous_prompt_number)
82 else:
109 else:
83 prompt = self._make_out_prompt(previous_prompt_number)
110 prompt = self._make_out_prompt(previous_prompt_number)
84 self._insert_html(cursor, prompt)
111 self._insert_html(cursor, prompt)
85 self._previous_prompt_blocks = []
112 self._previous_prompt_blocks = []
86
113
87 # Show a new prompt.
114 # Show a new prompt.
88 self._prompt_count += 1
115 self._prompt_count += 1
89 self._show_prompt(self._make_in_prompt(self._prompt_count), html=True)
116 self._show_prompt(self._make_in_prompt(self._prompt_count), html=True)
90 self._save_prompt_block()
117 self._save_prompt_block()
91
118
92 # Update continuation prompt to reflect (possibly) new prompt length.
119 # Update continuation prompt to reflect (possibly) new prompt length.
93 self._set_continuation_prompt(
120 self._set_continuation_prompt(
94 self._make_continuation_prompt(self._prompt), html=True)
121 self._make_continuation_prompt(self._prompt), html=True)
95
122
96 #------ Signal handlers ----------------------------------------------------
97
98 def _handle_execute_error(self, reply):
99 """ Reimplemented for IPython-style traceback formatting.
100 """
101 content = reply['content']
102 traceback_lines = content['traceback'][:]
103 traceback = ''.join(traceback_lines)
104 traceback = traceback.replace(' ', '&nbsp;')
105 traceback = traceback.replace('\n', '<br/>')
106
107 ename = content['ename']
108 ename_styled = '<span class="error">%s</span>' % ename
109 traceback = traceback.replace(ename, ename_styled)
110
111 self._append_html(traceback)
112
113 def _handle_pyout(self, omsg):
114 """ Reimplemented for IPython-style "display hook".
115 """
116 self._append_html(self._make_out_prompt(self._prompt_count))
117 self._save_prompt_block()
118
119 self._append_plain_text(omsg['content']['data'] + '\n')
120
121 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
122 # 'IPythonWidget' interface
124 # 'IPythonWidget' interface
123 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
124
126
125 def reset_styling(self):
127 def reset_styling(self):
126 """ Restores the default IPythonWidget styling.
128 """ Restores the default IPythonWidget styling.
127 """
129 """
128 self.set_styling(self.default_stylesheet, syntax_style='default')
130 self.set_styling(self.default_stylesheet, syntax_style='default')
129 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
131 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
130
132
131 def set_styling(self, stylesheet, syntax_style=None):
133 def set_styling(self, stylesheet, syntax_style=None):
132 """ Sets the IPythonWidget styling.
134 """ Sets the IPythonWidget styling.
133
135
134 Parameters:
136 Parameters:
135 -----------
137 -----------
136 stylesheet : str
138 stylesheet : str
137 A CSS stylesheet. The stylesheet can contain classes for:
139 A CSS stylesheet. The stylesheet can contain classes for:
138 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
140 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
139 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
141 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
140 3. IPython: .error, .in-prompt, .out-prompt, etc.
142 3. IPython: .error, .in-prompt, .out-prompt, etc.
141
143
142 syntax_style : str or None [default None]
144 syntax_style : str or None [default None]
143 If specified, use the Pygments style with given name. Otherwise,
145 If specified, use the Pygments style with given name. Otherwise,
144 the stylesheet is queried for Pygments style information.
146 the stylesheet is queried for Pygments style information.
145 """
147 """
146 self.setStyleSheet(stylesheet)
148 self.setStyleSheet(stylesheet)
147 self._control.document().setDefaultStyleSheet(stylesheet)
149 self._control.document().setDefaultStyleSheet(stylesheet)
148
150
149 if syntax_style is None:
151 if syntax_style is None:
150 self._highlighter.set_style_sheet(stylesheet)
152 self._highlighter.set_style_sheet(stylesheet)
151 else:
153 else:
152 self._highlighter.set_style(syntax_style)
154 self._highlighter.set_style(syntax_style)
153
155
154 #---------------------------------------------------------------------------
156 #---------------------------------------------------------------------------
155 # 'IPythonWidget' protected interface
157 # 'IPythonWidget' protected interface
156 #---------------------------------------------------------------------------
158 #---------------------------------------------------------------------------
157
159
158 def _make_in_prompt(self, number):
160 def _make_in_prompt(self, number):
159 """ Given a prompt number, returns an HTML In prompt.
161 """ Given a prompt number, returns an HTML In prompt.
160 """
162 """
161 body = self.in_prompt % number
163 body = self.in_prompt % number
162 return '<span class="in-prompt">%s</span>' % body
164 return '<span class="in-prompt">%s</span>' % body
163
165
164 def _make_continuation_prompt(self, prompt):
166 def _make_continuation_prompt(self, prompt):
165 """ Given a plain text version of an In prompt, returns an HTML
167 """ Given a plain text version of an In prompt, returns an HTML
166 continuation prompt.
168 continuation prompt.
167 """
169 """
168 end_chars = '...: '
170 end_chars = '...: '
169 space_count = len(prompt.lstrip('\n')) - len(end_chars)
171 space_count = len(prompt.lstrip('\n')) - len(end_chars)
170 body = '&nbsp;' * space_count + end_chars
172 body = '&nbsp;' * space_count + end_chars
171 return '<span class="in-prompt">%s</span>' % body
173 return '<span class="in-prompt">%s</span>' % body
172
174
173 def _make_out_prompt(self, number):
175 def _make_out_prompt(self, number):
174 """ Given a prompt number, returns an HTML Out prompt.
176 """ Given a prompt number, returns an HTML Out prompt.
175 """
177 """
176 body = self.out_prompt % number
178 body = self.out_prompt % number
177 return '<span class="out-prompt">%s</span>' % body
179 return '<span class="out-prompt">%s</span>' % body
178
180
179 def _save_prompt_block(self):
181 def _save_prompt_block(self):
180 """ Assuming a prompt has just been written at the end of the buffer,
182 """ Assuming a prompt has just been written at the end of the buffer,
181 store the QTextBlock that contains it and its length.
183 store the QTextBlock that contains it and its length.
182 """
184 """
183 block = self._control.document().lastBlock()
185 block = self._control.document().lastBlock()
184 self._previous_prompt_blocks.append((block, block.length()))
186 self._previous_prompt_blocks.append((block, block.length()))
@@ -1,118 +1,119 b''
1 # System library imports
1 # System library imports
2 from PyQt4 import QtCore, QtGui
2 from PyQt4 import QtCore, QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
5 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
6 from ipython_widget import IPythonWidget
6 from ipython_widget import IPythonWidget
7
7
8
8
9 class RichIPythonWidget(IPythonWidget):
9 class RichIPythonWidget(IPythonWidget):
10 """ An IPythonWidget that supports rich text, including lists, images, and
10 """ An IPythonWidget that supports rich text, including lists, images, and
11 tables. Note that raw performance will be reduced compared to the plain
11 tables. Note that raw performance will be reduced compared to the plain
12 text version.
12 text version.
13 """
13 """
14
14
15 # Protected class variables.
15 # Protected class variables.
16 _svg_text_format_property = 1
16 _svg_text_format_property = 1
17
17
18 #---------------------------------------------------------------------------
18 #---------------------------------------------------------------------------
19 # 'QObject' interface
19 # 'QObject' interface
20 #---------------------------------------------------------------------------
20 #---------------------------------------------------------------------------
21
21
22 def __init__(self, parent=None):
22 def __init__(self, parent=None):
23 """ Create a RichIPythonWidget.
23 """ Create a RichIPythonWidget.
24 """
24 """
25 super(RichIPythonWidget, self).__init__(kind='rich', parent=parent)
25 super(RichIPythonWidget, self).__init__(kind='rich', parent=parent)
26
26
27 #---------------------------------------------------------------------------
27 #---------------------------------------------------------------------------
28 # 'ConsoleWidget' protected interface
28 # 'ConsoleWidget' protected interface
29 #---------------------------------------------------------------------------
29 #---------------------------------------------------------------------------
30
30
31 def _show_context_menu(self, pos):
31 def _show_context_menu(self, pos):
32 """ Reimplemented to show a custom context menu for images.
32 """ Reimplemented to show a custom context menu for images.
33 """
33 """
34 format = self._control.cursorForPosition(pos).charFormat()
34 format = self._control.cursorForPosition(pos).charFormat()
35 name = format.stringProperty(QtGui.QTextFormat.ImageName)
35 name = format.stringProperty(QtGui.QTextFormat.ImageName)
36 if name.isEmpty():
36 if name.isEmpty():
37 super(RichIPythonWidget, self)._show_context_menu(pos)
37 super(RichIPythonWidget, self)._show_context_menu(pos)
38 else:
38 else:
39 menu = QtGui.QMenu()
39 menu = QtGui.QMenu()
40
40
41 menu.addAction('Copy Image', lambda: self._copy_image(name))
41 menu.addAction('Copy Image', lambda: self._copy_image(name))
42 menu.addAction('Save Image As...', lambda: self._save_image(name))
42 menu.addAction('Save Image As...', lambda: self._save_image(name))
43 menu.addSeparator()
43 menu.addSeparator()
44
44
45 svg = format.stringProperty(self._svg_text_format_property)
45 svg = format.stringProperty(self._svg_text_format_property)
46 if not svg.isEmpty():
46 if not svg.isEmpty():
47 menu.addSeparator()
47 menu.addSeparator()
48 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
48 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
49 menu.addAction('Save SVG As...',
49 menu.addAction('Save SVG As...',
50 lambda: save_svg(svg, self._control))
50 lambda: save_svg(svg, self._control))
51
51
52 menu.exec_(self._control.mapToGlobal(pos))
52 menu.exec_(self._control.mapToGlobal(pos))
53
53
54 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
55 # 'FrontendWidget' protected interface
55 # 'FrontendWidget' protected interface
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57
57
58 def _handle_execute_payload(self, payload):
58 def _process_execute_ok(self, msg):
59 """ Reimplemented to handle pylab plot payloads.
59 """ Reimplemented to handle matplotlib plot payloads.
60 """
60 """
61 payload = msg['content']['payload']
61 plot_payload = payload.get('plot', None)
62 plot_payload = payload.get('plot', None)
62 if plot_payload and plot_payload['format'] == 'svg':
63 if plot_payload and plot_payload['format'] == 'svg':
63 svg = plot_payload['data']
64 svg = plot_payload['data']
64 try:
65 try:
65 image = svg_to_image(svg)
66 image = svg_to_image(svg)
66 except ValueError:
67 except ValueError:
67 self._append_plain_text('Received invalid plot data.')
68 self._append_plain_text('Received invalid plot data.')
68 else:
69 else:
69 format = self._add_image(image)
70 format = self._add_image(image)
70 format.setProperty(self._svg_text_format_property, svg)
71 format.setProperty(self._svg_text_format_property, svg)
71 cursor = self._get_end_cursor()
72 cursor = self._get_end_cursor()
72 cursor.insertBlock()
73 cursor.insertBlock()
73 cursor.insertImage(format)
74 cursor.insertImage(format)
74 cursor.insertBlock()
75 cursor.insertBlock()
75 else:
76 else:
76 super(RichIPythonWidget, self)._handle_execute_payload(payload)
77 super(RichIPythonWidget, self)._process_execute_ok(msg)
77
78
78 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
79 # 'RichIPythonWidget' protected interface
80 # 'RichIPythonWidget' protected interface
80 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
81
82
82 def _add_image(self, image):
83 def _add_image(self, image):
83 """ Adds the specified QImage to the document and returns a
84 """ Adds the specified QImage to the document and returns a
84 QTextImageFormat that references it.
85 QTextImageFormat that references it.
85 """
86 """
86 document = self._control.document()
87 document = self._control.document()
87 name = QtCore.QString.number(image.cacheKey())
88 name = QtCore.QString.number(image.cacheKey())
88 document.addResource(QtGui.QTextDocument.ImageResource,
89 document.addResource(QtGui.QTextDocument.ImageResource,
89 QtCore.QUrl(name), image)
90 QtCore.QUrl(name), image)
90 format = QtGui.QTextImageFormat()
91 format = QtGui.QTextImageFormat()
91 format.setName(name)
92 format.setName(name)
92 return format
93 return format
93
94
94 def _copy_image(self, name):
95 def _copy_image(self, name):
95 """ Copies the ImageResource with 'name' to the clipboard.
96 """ Copies the ImageResource with 'name' to the clipboard.
96 """
97 """
97 image = self._get_image(name)
98 image = self._get_image(name)
98 QtGui.QApplication.clipboard().setImage(image)
99 QtGui.QApplication.clipboard().setImage(image)
99
100
100 def _get_image(self, name):
101 def _get_image(self, name):
101 """ Returns the QImage stored as the ImageResource with 'name'.
102 """ Returns the QImage stored as the ImageResource with 'name'.
102 """
103 """
103 document = self._control.document()
104 document = self._control.document()
104 variant = document.resource(QtGui.QTextDocument.ImageResource,
105 variant = document.resource(QtGui.QTextDocument.ImageResource,
105 QtCore.QUrl(name))
106 QtCore.QUrl(name))
106 return variant.toPyObject()
107 return variant.toPyObject()
107
108
108 def _save_image(self, name, format='PNG'):
109 def _save_image(self, name, format='PNG'):
109 """ Shows a save dialog for the ImageResource with 'name'.
110 """ Shows a save dialog for the ImageResource with 'name'.
110 """
111 """
111 dialog = QtGui.QFileDialog(self._control, 'Save Image')
112 dialog = QtGui.QFileDialog(self._control, 'Save Image')
112 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
113 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
113 dialog.setDefaultSuffix(format.lower())
114 dialog.setDefaultSuffix(format.lower())
114 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
115 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
115 if dialog.exec_():
116 if dialog.exec_():
116 filename = dialog.selectedFiles()[0]
117 filename = dialog.selectedFiles()[0]
117 image = self._get_image(name)
118 image = self._get_image(name)
118 image.save(filename, format)
119 image.save(filename, format)
@@ -1,180 +1,192 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelManager that provides signals and slots.
2 """
2 """
3
3
4 # System library imports.
4 # System library imports.
5 from PyQt4 import QtCore
5 from PyQt4 import QtCore
6 import zmq
6 import zmq
7
7
8 # IPython imports.
8 # IPython imports.
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 XReqSocketChannel, RepSocketChannel
10 XReqSocketChannel, RepSocketChannel
11 from util import MetaQObjectHasTraits
11 from util import MetaQObjectHasTraits
12
12
13 # When doing multiple inheritance from QtCore.QObject and other classes
13 # When doing multiple inheritance from QtCore.QObject and other classes
14 # the calling of the parent __init__'s is a subtle issue:
14 # the calling of the parent __init__'s is a subtle issue:
15 # * QtCore.QObject does not call super so you can't use super and put
15 # * QtCore.QObject does not call super so you can't use super and put
16 # QObject first in the inheritance list.
16 # QObject first in the inheritance list.
17 # * QtCore.QObject.__init__ takes 1 argument, the parent. So if you are going
17 # * QtCore.QObject.__init__ takes 1 argument, the parent. So if you are going
18 # to use super, any class that comes before QObject must pass it something
18 # to use super, any class that comes before QObject must pass it something
19 # reasonable.
19 # reasonable.
20 # In summary, I don't think using super in these situations will work.
20 # In summary, I don't think using super in these situations will work.
21 # Instead we will need to call the __init__ methods of both parents
21 # Instead we will need to call the __init__ methods of both parents
22 # by hand. Not pretty, but it works.
22 # by hand. Not pretty, but it works.
23
23
24 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
24 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
25
25
26 # Emitted when any message is received.
26 # Emitted when any message is received.
27 message_received = QtCore.pyqtSignal(object)
27 message_received = QtCore.pyqtSignal(object)
28
28
29 # Emitted when a message of type 'pyout' or 'stdout' is received.
29 # Emitted when a message of type 'stream' is received.
30 output_received = QtCore.pyqtSignal(object)
30 stream_received = QtCore.pyqtSignal(object)
31
31
32 # Emitted when a message of type 'pyerr' or 'stderr' is received.
32 # Emitted when a message of type 'pyin' is received.
33 error_received = QtCore.pyqtSignal(object)
33 pyin_received = QtCore.pyqtSignal(object)
34
35 # Emitted when a message of type 'pyout' is received.
36 pyout_received = QtCore.pyqtSignal(object)
37
38 # Emitted when a message of type 'pyerr' is received.
39 pyerr_received = QtCore.pyqtSignal(object)
40
41 # Emitted when a crash report message is received from the kernel's
42 # last-resort sys.excepthook.
43 crash_received = QtCore.pyqtSignal(object)
34
44
35 #---------------------------------------------------------------------------
45 #---------------------------------------------------------------------------
36 # 'object' interface
46 # 'object' interface
37 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
38
48
39 def __init__(self, *args, **kw):
49 def __init__(self, *args, **kw):
40 """ Reimplemented to ensure that QtCore.QObject is initialized first.
50 """ Reimplemented to ensure that QtCore.QObject is initialized first.
41 """
51 """
42 QtCore.QObject.__init__(self)
52 QtCore.QObject.__init__(self)
43 SubSocketChannel.__init__(self, *args, **kw)
53 SubSocketChannel.__init__(self, *args, **kw)
44
54
45 #---------------------------------------------------------------------------
55 #---------------------------------------------------------------------------
46 # 'SubSocketChannel' interface
56 # 'SubSocketChannel' interface
47 #---------------------------------------------------------------------------
57 #---------------------------------------------------------------------------
48
58
49 def call_handlers(self, msg):
59 def call_handlers(self, msg):
50 """ Reimplemented to emit signals instead of making callbacks.
60 """ Reimplemented to emit signals instead of making callbacks.
51 """
61 """
52 # Emit the generic signal.
62 # Emit the generic signal.
53 self.message_received.emit(msg)
63 self.message_received.emit(msg)
54
64
55 # Emit signals for specialized message types.
65 # Emit signals for specialized message types.
56 msg_type = msg['msg_type']
66 msg_type = msg['msg_type']
57 if msg_type in ('pyout', 'stdout'):
67 signal = getattr(self, msg_type + '_received', None)
58 self.output_received.emit(msg)
68 if signal:
59 elif msg_type in ('pyerr', 'stderr'):
69 signal.emit(msg)
60 self.error_received.emit(msg)
70 elif msg_type in ('stdout', 'stderr'):
71 self.stream_received.emit(msg)
61
72
62 def flush(self):
73 def flush(self):
63 """ Reimplemented to ensure that signals are dispatched immediately.
74 """ Reimplemented to ensure that signals are dispatched immediately.
64 """
75 """
65 super(QtSubSocketChannel, self).flush()
76 super(QtSubSocketChannel, self).flush()
66 QtCore.QCoreApplication.instance().processEvents()
77 QtCore.QCoreApplication.instance().processEvents()
67
78
68
79
69 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
80 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
70
81
71 # Emitted when any message is received.
82 # Emitted when any message is received.
72 message_received = QtCore.pyqtSignal(object)
83 message_received = QtCore.pyqtSignal(object)
73
84
74 # Emitted when a reply has been received for the corresponding request type.
85 # Emitted when a reply has been received for the corresponding request type.
75 execute_reply = QtCore.pyqtSignal(object)
86 execute_reply = QtCore.pyqtSignal(object)
76 complete_reply = QtCore.pyqtSignal(object)
87 complete_reply = QtCore.pyqtSignal(object)
77 object_info_reply = QtCore.pyqtSignal(object)
88 object_info_reply = QtCore.pyqtSignal(object)
78
89
79 #---------------------------------------------------------------------------
90 #---------------------------------------------------------------------------
80 # 'object' interface
91 # 'object' interface
81 #---------------------------------------------------------------------------
92 #---------------------------------------------------------------------------
82
93
83 def __init__(self, *args, **kw):
94 def __init__(self, *args, **kw):
84 """ Reimplemented to ensure that QtCore.QObject is initialized first.
95 """ Reimplemented to ensure that QtCore.QObject is initialized first.
85 """
96 """
86 QtCore.QObject.__init__(self)
97 QtCore.QObject.__init__(self)
87 XReqSocketChannel.__init__(self, *args, **kw)
98 XReqSocketChannel.__init__(self, *args, **kw)
88
99
89 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
90 # 'XReqSocketChannel' interface
101 # 'XReqSocketChannel' interface
91 #---------------------------------------------------------------------------
102 #---------------------------------------------------------------------------
92
103
93 def call_handlers(self, msg):
104 def call_handlers(self, msg):
94 """ Reimplemented to emit signals instead of making callbacks.
105 """ Reimplemented to emit signals instead of making callbacks.
95 """
106 """
96 # Emit the generic signal.
107 # Emit the generic signal.
97 self.message_received.emit(msg)
108 self.message_received.emit(msg)
98
109
99 # Emit signals for specialized message types.
110 # Emit signals for specialized message types.
100 msg_type = msg['msg_type']
111 msg_type = msg['msg_type']
101 signal = getattr(self, msg_type, None)
112 signal = getattr(self, msg_type, None)
102 if signal:
113 if signal:
103 signal.emit(msg)
114 signal.emit(msg)
104
115
105
116
106 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
117 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
107
118
108 # Emitted when any message is received.
119 # Emitted when any message is received.
109 message_received = QtCore.pyqtSignal(object)
120 message_received = QtCore.pyqtSignal(object)
110
121
111 # Emitted when an input request is received.
122 # Emitted when an input request is received.
112 input_requested = QtCore.pyqtSignal(object)
123 input_requested = QtCore.pyqtSignal(object)
113
124
114 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
115 # 'object' interface
126 # 'object' interface
116 #---------------------------------------------------------------------------
127 #---------------------------------------------------------------------------
117
128
118 def __init__(self, *args, **kw):
129 def __init__(self, *args, **kw):
119 """ Reimplemented to ensure that QtCore.QObject is initialized first.
130 """ Reimplemented to ensure that QtCore.QObject is initialized first.
120 """
131 """
121 QtCore.QObject.__init__(self)
132 QtCore.QObject.__init__(self)
122 RepSocketChannel.__init__(self, *args, **kw)
133 RepSocketChannel.__init__(self, *args, **kw)
123
134
124 #---------------------------------------------------------------------------
135 #---------------------------------------------------------------------------
125 # 'RepSocketChannel' interface
136 # 'RepSocketChannel' interface
126 #---------------------------------------------------------------------------
137 #---------------------------------------------------------------------------
127
138
128 def call_handlers(self, msg):
139 def call_handlers(self, msg):
129 """ Reimplemented to emit signals instead of making callbacks.
140 """ Reimplemented to emit signals instead of making callbacks.
130 """
141 """
131 # Emit the generic signal.
142 # Emit the generic signal.
132 self.message_received.emit(msg)
143 self.message_received.emit(msg)
133
144
134 # Emit signals for specialized message types.
145 # Emit signals for specialized message types.
135 msg_type = msg['msg_type']
146 msg_type = msg['msg_type']
136 if msg_type == 'input_request':
147 if msg_type == 'input_request':
137 self.input_requested.emit(msg)
148 self.input_requested.emit(msg)
138
149
150
139 class QtKernelManager(KernelManager, QtCore.QObject):
151 class QtKernelManager(KernelManager, QtCore.QObject):
140 """ A KernelManager that provides signals and slots.
152 """ A KernelManager that provides signals and slots.
141 """
153 """
142
154
143 __metaclass__ = MetaQObjectHasTraits
155 __metaclass__ = MetaQObjectHasTraits
144
156
145 # Emitted when the kernel manager has started listening.
157 # Emitted when the kernel manager has started listening.
146 started_channels = QtCore.pyqtSignal()
158 started_channels = QtCore.pyqtSignal()
147
159
148 # Emitted when the kernel manager has stopped listening.
160 # Emitted when the kernel manager has stopped listening.
149 stopped_channels = QtCore.pyqtSignal()
161 stopped_channels = QtCore.pyqtSignal()
150
162
151 # Use Qt-specific channel classes that emit signals.
163 # Use Qt-specific channel classes that emit signals.
152 sub_channel_class = QtSubSocketChannel
164 sub_channel_class = QtSubSocketChannel
153 xreq_channel_class = QtXReqSocketChannel
165 xreq_channel_class = QtXReqSocketChannel
154 rep_channel_class = QtRepSocketChannel
166 rep_channel_class = QtRepSocketChannel
155
167
156 #---------------------------------------------------------------------------
168 #---------------------------------------------------------------------------
157 # 'object' interface
169 # 'object' interface
158 #---------------------------------------------------------------------------
170 #---------------------------------------------------------------------------
159
171
160 def __init__(self, *args, **kw):
172 def __init__(self, *args, **kw):
161 """ Reimplemented to ensure that QtCore.QObject is initialized first.
173 """ Reimplemented to ensure that QtCore.QObject is initialized first.
162 """
174 """
163 QtCore.QObject.__init__(self)
175 QtCore.QObject.__init__(self)
164 KernelManager.__init__(self, *args, **kw)
176 KernelManager.__init__(self, *args, **kw)
165
177
166 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
167 # 'KernelManager' interface
179 # 'KernelManager' interface
168 #---------------------------------------------------------------------------
180 #---------------------------------------------------------------------------
169
181
170 def start_channels(self):
182 def start_channels(self):
171 """ Reimplemented to emit signal.
183 """ Reimplemented to emit signal.
172 """
184 """
173 super(QtKernelManager, self).start_channels()
185 super(QtKernelManager, self).start_channels()
174 self.started_channels.emit()
186 self.started_channels.emit()
175
187
176 def stop_channels(self):
188 def stop_channels(self):
177 """ Reimplemented to emit signal.
189 """ Reimplemented to emit signal.
178 """
190 """
179 super(QtKernelManager, self).stop_channels()
191 super(QtKernelManager, self).stop_channels()
180 self.stopped_channels.emit()
192 self.stopped_channels.emit()
General Comments 0
You need to be logged in to leave comments. Login now