##// END OF EJS Templates
* Added 'started_listening' and 'stopped_listening' signals to QtKernelManager. The FrontendWidget listens for these signals....
epatters -
Show More
@@ -0,0 +1,25 b''
1 """ Defines miscellaneous Qt-related helper classes and functions.
2 """
3
4 # System library imports.
5 from PyQt4 import QtCore
6
7 # IPython imports.
8 from IPython.utils.traitlets import HasTraits
9
10
11 MetaHasTraits = type(HasTraits)
12 MetaQObject = type(QtCore.QObject)
13
14 class MetaQObjectHasTraits(MetaQObject, MetaHasTraits):
15 """ A metaclass that inherits from the metaclasses of both HasTraits and
16 QObject.
17
18 Using this metaclass allows a class to inherit from both HasTraits and
19 QObject. See QtKernelManager for an example.
20 """
21
22 def __init__(cls, name, bases, dct):
23 MetaQObject.__init__(cls, name, bases, dct)
24 MetaHasTraits.__init__(cls, name, bases, dct)
25
@@ -1,674 +1,688 b''
1 # Standard library imports
1 # Standard library imports
2 import re
2 import re
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from completion_widget import CompletionWidget
8 from completion_widget import CompletionWidget
9
9
10
10
11 class AnsiCodeProcessor(object):
11 class AnsiCodeProcessor(object):
12 """ Translates ANSI escape codes into readable attributes.
12 """ Translates ANSI escape codes into readable attributes.
13 """
13 """
14
14
15 def __init__(self):
15 def __init__(self):
16 self.ansi_colors = ( # Normal, Bright/Light
16 self.ansi_colors = ( # Normal, Bright/Light
17 ('#000000', '#7f7f7f'), # 0: black
17 ('#000000', '#7f7f7f'), # 0: black
18 ('#cd0000', '#ff0000'), # 1: red
18 ('#cd0000', '#ff0000'), # 1: red
19 ('#00cd00', '#00ff00'), # 2: green
19 ('#00cd00', '#00ff00'), # 2: green
20 ('#cdcd00', '#ffff00'), # 3: yellow
20 ('#cdcd00', '#ffff00'), # 3: yellow
21 ('#0000ee', '#0000ff'), # 4: blue
21 ('#0000ee', '#0000ff'), # 4: blue
22 ('#cd00cd', '#ff00ff'), # 5: magenta
22 ('#cd00cd', '#ff00ff'), # 5: magenta
23 ('#00cdcd', '#00ffff'), # 6: cyan
23 ('#00cdcd', '#00ffff'), # 6: cyan
24 ('#e5e5e5', '#ffffff')) # 7: white
24 ('#e5e5e5', '#ffffff')) # 7: white
25 self.reset()
25 self.reset()
26
26
27 def set_code(self, code):
27 def set_code(self, code):
28 """ Set attributes based on code.
28 """ Set attributes based on code.
29 """
29 """
30 if code == 0:
30 if code == 0:
31 self.reset()
31 self.reset()
32 elif code == 1:
32 elif code == 1:
33 self.intensity = 1
33 self.intensity = 1
34 self.bold = True
34 self.bold = True
35 elif code == 3:
35 elif code == 3:
36 self.italic = True
36 self.italic = True
37 elif code == 4:
37 elif code == 4:
38 self.underline = True
38 self.underline = True
39 elif code == 22:
39 elif code == 22:
40 self.intensity = 0
40 self.intensity = 0
41 self.bold = False
41 self.bold = False
42 elif code == 23:
42 elif code == 23:
43 self.italic = False
43 self.italic = False
44 elif code == 24:
44 elif code == 24:
45 self.underline = False
45 self.underline = False
46 elif code >= 30 and code <= 37:
46 elif code >= 30 and code <= 37:
47 self.foreground_color = code - 30
47 self.foreground_color = code - 30
48 elif code == 39:
48 elif code == 39:
49 self.foreground_color = None
49 self.foreground_color = None
50 elif code >= 40 and code <= 47:
50 elif code >= 40 and code <= 47:
51 self.background_color = code - 40
51 self.background_color = code - 40
52 elif code == 49:
52 elif code == 49:
53 self.background_color = None
53 self.background_color = None
54
54
55 def reset(self):
55 def reset(self):
56 """ Reset attributs to their default values.
56 """ Reset attributs to their default values.
57 """
57 """
58 self.intensity = 0
58 self.intensity = 0
59 self.italic = False
59 self.italic = False
60 self.bold = False
60 self.bold = False
61 self.underline = False
61 self.underline = False
62 self.foreground_color = None
62 self.foreground_color = None
63 self.background_color = None
63 self.background_color = None
64
64
65
65
66 class QtAnsiCodeProcessor(AnsiCodeProcessor):
66 class QtAnsiCodeProcessor(AnsiCodeProcessor):
67 """ Translates ANSI escape codes into QTextCharFormats.
67 """ Translates ANSI escape codes into QTextCharFormats.
68 """
68 """
69
69
70 def get_format(self):
70 def get_format(self):
71 """ Returns a QTextCharFormat that encodes the current style attributes.
71 """ Returns a QTextCharFormat that encodes the current style attributes.
72 """
72 """
73 format = QtGui.QTextCharFormat()
73 format = QtGui.QTextCharFormat()
74
74
75 # Set foreground color
75 # Set foreground color
76 if self.foreground_color is not None:
76 if self.foreground_color is not None:
77 color = self.ansi_colors[self.foreground_color][self.intensity]
77 color = self.ansi_colors[self.foreground_color][self.intensity]
78 format.setForeground(QtGui.QColor(color))
78 format.setForeground(QtGui.QColor(color))
79
79
80 # Set background color
80 # Set background color
81 if self.background_color is not None:
81 if self.background_color is not None:
82 color = self.ansi_colors[self.background_color][self.intensity]
82 color = self.ansi_colors[self.background_color][self.intensity]
83 format.setBackground(QtGui.QColor(color))
83 format.setBackground(QtGui.QColor(color))
84
84
85 # Set font weight/style options
85 # Set font weight/style options
86 if self.bold:
86 if self.bold:
87 format.setFontWeight(QtGui.QFont.Bold)
87 format.setFontWeight(QtGui.QFont.Bold)
88 else:
88 else:
89 format.setFontWeight(QtGui.QFont.Normal)
89 format.setFontWeight(QtGui.QFont.Normal)
90 format.setFontItalic(self.italic)
90 format.setFontItalic(self.italic)
91 format.setFontUnderline(self.underline)
91 format.setFontUnderline(self.underline)
92
92
93 return format
93 return format
94
94
95
95
96 class ConsoleWidget(QtGui.QPlainTextEdit):
96 class ConsoleWidget(QtGui.QPlainTextEdit):
97 """ Base class for console-type widgets. This class is mainly concerned with
97 """ Base class for console-type widgets. This class is mainly concerned with
98 dealing with the prompt, keeping the cursor inside the editing line, and
98 dealing with the prompt, keeping the cursor inside the editing line, and
99 handling ANSI escape sequences.
99 handling ANSI escape sequences.
100 """
100 """
101
101
102 # Regex to match ANSI escape sequences
102 # Regex to match ANSI escape sequences
103 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
103 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)m\x02?')
104
104
105 # When ctrl is pressed, map certain keys to other keys (without the ctrl):
105 # When ctrl is pressed, map certain keys to other keys (without the ctrl):
106 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
106 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
107 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
107 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
108 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
108 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
109 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
109 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
110 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
110 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
111 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
111 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
112 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
112 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
113
113
114 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
115 # 'QWidget' interface
115 # 'QWidget' interface
116 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
117
117
118 def __init__(self, parent=None):
118 def __init__(self, parent=None):
119 QtGui.QPlainTextEdit.__init__(self, parent)
119 QtGui.QPlainTextEdit.__init__(self, parent)
120
120
121 # Initialize public and protected variables
121 # Initialize public and protected variables
122 self.ansi_codes = True
122 self.ansi_codes = True
123 self.buffer_size = 500
123 self.buffer_size = 500
124 self.continuation_prompt = '> '
125 self.gui_completion = True
124 self.gui_completion = True
126 self._ansi_processor = QtAnsiCodeProcessor()
125 self._ansi_processor = QtAnsiCodeProcessor()
127 self._completion_widget = CompletionWidget(self)
126 self._completion_widget = CompletionWidget(self)
127 self._continuation_prompt = '> '
128 self._executing = False
128 self._executing = False
129 self._prompt = ''
129 self._prompt = ''
130 self._prompt_pos = 0
130 self._prompt_pos = 0
131 self._reading = False
131 self._reading = False
132
132
133 # Set a monospaced font
133 # Set a monospaced font
134 point_size = QtGui.QApplication.font().pointSize()
134 point_size = QtGui.QApplication.font().pointSize()
135 font = QtGui.QFont('Monospace', point_size)
135 font = QtGui.QFont('Monospace', point_size)
136 font.setStyleHint(QtGui.QFont.TypeWriter)
136 font.setStyleHint(QtGui.QFont.TypeWriter)
137 self._completion_widget.setFont(font)
137 self._completion_widget.setFont(font)
138 self.document().setDefaultFont(font)
138 self.document().setDefaultFont(font)
139
139
140 # Define a custom context menu
140 # Define a custom context menu
141 self._context_menu = QtGui.QMenu(self)
141 self._context_menu = QtGui.QMenu(self)
142
142
143 copy_action = QtGui.QAction('Copy', self)
143 copy_action = QtGui.QAction('Copy', self)
144 copy_action.triggered.connect(self.copy)
144 copy_action.triggered.connect(self.copy)
145 self.copyAvailable.connect(copy_action.setEnabled)
145 self.copyAvailable.connect(copy_action.setEnabled)
146 self._context_menu.addAction(copy_action)
146 self._context_menu.addAction(copy_action)
147
147
148 self._paste_action = QtGui.QAction('Paste', self)
148 self._paste_action = QtGui.QAction('Paste', self)
149 self._paste_action.triggered.connect(self.paste)
149 self._paste_action.triggered.connect(self.paste)
150 self._context_menu.addAction(self._paste_action)
150 self._context_menu.addAction(self._paste_action)
151 self._context_menu.addSeparator()
151 self._context_menu.addSeparator()
152
152
153 select_all_action = QtGui.QAction('Select All', self)
153 select_all_action = QtGui.QAction('Select All', self)
154 select_all_action.triggered.connect(self.selectAll)
154 select_all_action.triggered.connect(self.selectAll)
155 self._context_menu.addAction(select_all_action)
155 self._context_menu.addAction(select_all_action)
156
156
157 def contextMenuEvent(self, event):
157 def contextMenuEvent(self, event):
158 """ Reimplemented to create a menu without destructive actions like
158 """ Reimplemented to create a menu without destructive actions like
159 'Cut' and 'Delete'.
159 'Cut' and 'Delete'.
160 """
160 """
161 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
161 clipboard_empty = QtGui.QApplication.clipboard().text().isEmpty()
162 self._paste_action.setEnabled(not clipboard_empty)
162 self._paste_action.setEnabled(not clipboard_empty)
163
163
164 self._context_menu.exec_(event.globalPos())
164 self._context_menu.exec_(event.globalPos())
165
165
166 def keyPressEvent(self, event):
166 def keyPressEvent(self, event):
167 """ Reimplemented to create a console-like interface.
167 """ Reimplemented to create a console-like interface.
168 """
168 """
169 intercepted = False
169 intercepted = False
170 cursor = self.textCursor()
170 cursor = self.textCursor()
171 position = cursor.position()
171 position = cursor.position()
172 key = event.key()
172 key = event.key()
173 ctrl_down = event.modifiers() & QtCore.Qt.ControlModifier
173 ctrl_down = event.modifiers() & QtCore.Qt.ControlModifier
174 alt_down = event.modifiers() & QtCore.Qt.AltModifier
174 alt_down = event.modifiers() & QtCore.Qt.AltModifier
175 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
175 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
176
176
177 # Even though we have reimplemented 'paste', the C++ level slot is still
177 # Even though we have reimplemented 'paste', the C++ level slot is still
178 # called by Qt. So we intercept the key press here.
178 # called by Qt. So we intercept the key press here.
179 if event.matches(QtGui.QKeySequence.Paste):
179 if event.matches(QtGui.QKeySequence.Paste):
180 self.paste()
180 self.paste()
181 intercepted = True
181 intercepted = True
182
182
183 elif ctrl_down:
183 elif ctrl_down:
184 if key in self._ctrl_down_remap:
184 if key in self._ctrl_down_remap:
185 ctrl_down = False
185 ctrl_down = False
186 key = self._ctrl_down_remap[key]
186 key = self._ctrl_down_remap[key]
187 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
187 event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key,
188 QtCore.Qt.NoModifier)
188 QtCore.Qt.NoModifier)
189
189
190 elif key == QtCore.Qt.Key_K:
190 elif key == QtCore.Qt.Key_K:
191 if self._in_buffer(position):
191 if self._in_buffer(position):
192 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
192 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
193 QtGui.QTextCursor.KeepAnchor)
193 QtGui.QTextCursor.KeepAnchor)
194 cursor.removeSelectedText()
194 cursor.removeSelectedText()
195 intercepted = True
195 intercepted = True
196
196
197 elif key == QtCore.Qt.Key_Y:
197 elif key == QtCore.Qt.Key_Y:
198 self.paste()
198 self.paste()
199 intercepted = True
199 intercepted = True
200
200
201 elif alt_down:
201 elif alt_down:
202 if key == QtCore.Qt.Key_B:
202 if key == QtCore.Qt.Key_B:
203 self.setTextCursor(self._get_word_start_cursor(position))
203 self.setTextCursor(self._get_word_start_cursor(position))
204 intercepted = True
204 intercepted = True
205
205
206 elif key == QtCore.Qt.Key_F:
206 elif key == QtCore.Qt.Key_F:
207 self.setTextCursor(self._get_word_end_cursor(position))
207 self.setTextCursor(self._get_word_end_cursor(position))
208 intercepted = True
208 intercepted = True
209
209
210 elif key == QtCore.Qt.Key_Backspace:
210 elif key == QtCore.Qt.Key_Backspace:
211 cursor = self._get_word_start_cursor(position)
211 cursor = self._get_word_start_cursor(position)
212 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
212 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
213 cursor.removeSelectedText()
213 cursor.removeSelectedText()
214 intercepted = True
214 intercepted = True
215
215
216 elif key == QtCore.Qt.Key_D:
216 elif key == QtCore.Qt.Key_D:
217 cursor = self._get_word_end_cursor(position)
217 cursor = self._get_word_end_cursor(position)
218 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
218 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
219 cursor.removeSelectedText()
219 cursor.removeSelectedText()
220 intercepted = True
220 intercepted = True
221
221
222 if self._completion_widget.isVisible():
222 if self._completion_widget.isVisible():
223 self._completion_widget.keyPressEvent(event)
223 self._completion_widget.keyPressEvent(event)
224 intercepted = event.isAccepted()
224 intercepted = event.isAccepted()
225
225
226 else:
226 else:
227 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
227 if key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
228 if self._reading:
228 if self._reading:
229 self._reading = False
229 self._reading = False
230 elif not self._executing:
230 elif not self._executing:
231 self.execute(interactive=True)
231 self.execute(interactive=True)
232 intercepted = True
232 intercepted = True
233
233
234 elif key == QtCore.Qt.Key_Up:
234 elif key == QtCore.Qt.Key_Up:
235 if self._reading or not self._up_pressed():
235 if self._reading or not self._up_pressed():
236 intercepted = True
236 intercepted = True
237 else:
237 else:
238 prompt_line = self._get_prompt_cursor().blockNumber()
238 prompt_line = self._get_prompt_cursor().blockNumber()
239 intercepted = cursor.blockNumber() <= prompt_line
239 intercepted = cursor.blockNumber() <= prompt_line
240
240
241 elif key == QtCore.Qt.Key_Down:
241 elif key == QtCore.Qt.Key_Down:
242 if self._reading or not self._down_pressed():
242 if self._reading or not self._down_pressed():
243 intercepted = True
243 intercepted = True
244 else:
244 else:
245 end_line = self._get_end_cursor().blockNumber()
245 end_line = self._get_end_cursor().blockNumber()
246 intercepted = cursor.blockNumber() == end_line
246 intercepted = cursor.blockNumber() == end_line
247
247
248 elif key == QtCore.Qt.Key_Tab:
248 elif key == QtCore.Qt.Key_Tab:
249 if self._reading:
249 if self._reading:
250 intercepted = False
250 intercepted = False
251 else:
251 else:
252 intercepted = not self._tab_pressed()
252 intercepted = not self._tab_pressed()
253
253
254 elif key == QtCore.Qt.Key_Left:
254 elif key == QtCore.Qt.Key_Left:
255 intercepted = not self._in_buffer(position - 1)
255 intercepted = not self._in_buffer(position - 1)
256
256
257 elif key == QtCore.Qt.Key_Home:
257 elif key == QtCore.Qt.Key_Home:
258 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
258 cursor.movePosition(QtGui.QTextCursor.StartOfLine)
259 start_pos = cursor.position()
259 start_pos = cursor.position()
260 start_line = cursor.blockNumber()
260 start_line = cursor.blockNumber()
261 if start_line == self._get_prompt_cursor().blockNumber():
261 if start_line == self._get_prompt_cursor().blockNumber():
262 start_pos += len(self._prompt)
262 start_pos += len(self._prompt)
263 else:
263 else:
264 start_pos += len(self.continuation_prompt)
264 start_pos += len(self._continuation_prompt)
265 if shift_down and self._in_buffer(position):
265 if shift_down and self._in_buffer(position):
266 self._set_selection(position, start_pos)
266 self._set_selection(position, start_pos)
267 else:
267 else:
268 self._set_position(start_pos)
268 self._set_position(start_pos)
269 intercepted = True
269 intercepted = True
270
270
271 elif key == QtCore.Qt.Key_Backspace and not alt_down:
271 elif key == QtCore.Qt.Key_Backspace and not alt_down:
272
272
273 # Line deletion (remove continuation prompt)
273 # Line deletion (remove continuation prompt)
274 len_prompt = len(self.continuation_prompt)
274 len_prompt = len(self._continuation_prompt)
275 if cursor.columnNumber() == len_prompt and \
275 if cursor.columnNumber() == len_prompt and \
276 position != self._prompt_pos:
276 position != self._prompt_pos:
277 cursor.setPosition(position - len_prompt,
277 cursor.setPosition(position - len_prompt,
278 QtGui.QTextCursor.KeepAnchor)
278 QtGui.QTextCursor.KeepAnchor)
279 cursor.removeSelectedText()
279 cursor.removeSelectedText()
280
280
281 # Regular backwards deletion
281 # Regular backwards deletion
282 else:
282 else:
283 anchor = cursor.anchor()
283 anchor = cursor.anchor()
284 if anchor == position:
284 if anchor == position:
285 intercepted = not self._in_buffer(position - 1)
285 intercepted = not self._in_buffer(position - 1)
286 else:
286 else:
287 intercepted = not self._in_buffer(min(anchor, position))
287 intercepted = not self._in_buffer(min(anchor, position))
288
288
289 elif key == QtCore.Qt.Key_Delete:
289 elif key == QtCore.Qt.Key_Delete:
290 anchor = cursor.anchor()
290 anchor = cursor.anchor()
291 intercepted = not self._in_buffer(min(anchor, position))
291 intercepted = not self._in_buffer(min(anchor, position))
292
292
293 # Don't move cursor if control is down to allow copy-paste using
293 # Don't move cursor if control is down to allow copy-paste using
294 # the keyboard in any part of the buffer.
294 # the keyboard in any part of the buffer.
295 if not ctrl_down:
295 if not ctrl_down:
296 self._keep_cursor_in_buffer()
296 self._keep_cursor_in_buffer()
297
297
298 if not intercepted:
298 if not intercepted:
299 QtGui.QPlainTextEdit.keyPressEvent(self, event)
299 QtGui.QPlainTextEdit.keyPressEvent(self, event)
300
300
301 #--------------------------------------------------------------------------
301 #--------------------------------------------------------------------------
302 # 'QPlainTextEdit' interface
302 # 'QPlainTextEdit' interface
303 #--------------------------------------------------------------------------
303 #--------------------------------------------------------------------------
304
304
305 def appendPlainText(self, text):
305 def appendPlainText(self, text):
306 """ Reimplemented to not append text as a new paragraph, which doesn't
306 """ Reimplemented to not append text as a new paragraph, which doesn't
307 make sense for a console widget. Also, if enabled, handle ANSI
307 make sense for a console widget. Also, if enabled, handle ANSI
308 codes.
308 codes.
309 """
309 """
310 cursor = self.textCursor()
310 cursor = self.textCursor()
311 cursor.movePosition(QtGui.QTextCursor.End)
311 cursor.movePosition(QtGui.QTextCursor.End)
312
312
313 if self.ansi_codes:
313 if self.ansi_codes:
314 format = QtGui.QTextCharFormat()
314 format = QtGui.QTextCharFormat()
315 previous_end = 0
315 previous_end = 0
316 for match in self._ansi_pattern.finditer(text):
316 for match in self._ansi_pattern.finditer(text):
317 cursor.insertText(text[previous_end:match.start()], format)
317 cursor.insertText(text[previous_end:match.start()], format)
318 previous_end = match.end()
318 previous_end = match.end()
319 for code in match.group(1).split(';'):
319 for code in match.group(1).split(';'):
320 self._ansi_processor.set_code(int(code))
320 self._ansi_processor.set_code(int(code))
321 format = self._ansi_processor.get_format()
321 format = self._ansi_processor.get_format()
322 cursor.insertText(text[previous_end:], format)
322 cursor.insertText(text[previous_end:], format)
323 else:
323 else:
324 cursor.insertText(text)
324 cursor.insertText(text)
325
325
326 def clear(self, keep_input=False):
327 """ Reimplemented to write a new prompt. If 'keep_input' is set,
328 restores the old input buffer when the new prompt is written.
329 """
330 super(ConsoleWidget, self).clear()
331
332 if keep_input:
333 input_buffer = self.input_buffer
334 self._show_prompt()
335 if keep_input:
336 self.input_buffer = input_buffer
337
326 def paste(self):
338 def paste(self):
327 """ Reimplemented to ensure that text is pasted in the editing region.
339 """ Reimplemented to ensure that text is pasted in the editing region.
328 """
340 """
329 self._keep_cursor_in_buffer()
341 self._keep_cursor_in_buffer()
330 QtGui.QPlainTextEdit.paste(self)
342 QtGui.QPlainTextEdit.paste(self)
331
343
332 def print_(self, printer):
344 def print_(self, printer):
333 """ Reimplemented to work around bug in PyQt where the C++ level
345 """ Reimplemented to work around a bug in PyQt: the C++ level 'print_'
334 'print_' slot has the wrong signature.
346 slot has the wrong signature.
335 """
347 """
336 QtGui.QPlainTextEdit.print_(self, printer)
348 QtGui.QPlainTextEdit.print_(self, printer)
337
349
338 #---------------------------------------------------------------------------
350 #---------------------------------------------------------------------------
339 # 'ConsoleWidget' public interface
351 # 'ConsoleWidget' public interface
340 #---------------------------------------------------------------------------
352 #---------------------------------------------------------------------------
341
353
342 def execute(self, interactive=False):
354 def execute(self, interactive=False):
343 """ Execute the text in the input buffer. Returns whether the input
355 """ Execute the text in the input buffer. Returns whether the input
344 buffer was completely processed and a new prompt created.
356 buffer was completely processed and a new prompt created.
345 """
357 """
346 self.appendPlainText('\n')
358 self.appendPlainText('\n')
347 self._executing_input_buffer = self.input_buffer
359 self._executing_input_buffer = self.input_buffer
348 self._executing = True
360 self._executing = True
349 self._prompt_finished()
361 self._prompt_finished()
350 return self._execute(interactive=interactive)
362 return self._execute(interactive=interactive)
351
363
352 def _get_input_buffer(self):
364 def _get_input_buffer(self):
353 # If we're executing, the input buffer may not even exist anymore due
365 # If we're executing, the input buffer may not even exist anymore due
354 # the limit imposed by 'buffer_size'. Therefore, we store it.
366 # the limit imposed by 'buffer_size'. Therefore, we store it.
355 if self._executing:
367 if self._executing:
356 return self._executing_input_buffer
368 return self._executing_input_buffer
357
369
358 cursor = self._get_end_cursor()
370 cursor = self._get_end_cursor()
359 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
371 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
360
372
361 # Use QTextDocumentFragment intermediate object because it strips
373 # Use QTextDocumentFragment intermediate object because it strips
362 # out the Unicode line break characters that Qt insists on inserting.
374 # out the Unicode line break characters that Qt insists on inserting.
363 input_buffer = str(cursor.selection().toPlainText())
375 input_buffer = str(cursor.selection().toPlainText())
364
376
365 # Strip out continuation prompts
377 # Strip out continuation prompts
366 return input_buffer.replace('\n' + self.continuation_prompt, '\n')
378 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
367
379
368 def _set_input_buffer(self, string):
380 def _set_input_buffer(self, string):
369 # Add continuation prompts where necessary
381 # Add continuation prompts where necessary
370 lines = string.splitlines()
382 lines = string.splitlines()
371 for i in xrange(1, len(lines)):
383 for i in xrange(1, len(lines)):
372 lines[i] = self.continuation_prompt + lines[i]
384 lines[i] = self._continuation_prompt + lines[i]
373 string = '\n'.join(lines)
385 string = '\n'.join(lines)
374
386
375 # Replace buffer with new text
387 # Replace buffer with new text
376 cursor = self._get_end_cursor()
388 cursor = self._get_end_cursor()
377 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
389 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
378 cursor.insertText(string)
390 cursor.insertText(string)
379 self.moveCursor(QtGui.QTextCursor.End)
391 self.moveCursor(QtGui.QTextCursor.End)
380
392
381 input_buffer = property(_get_input_buffer, _set_input_buffer)
393 input_buffer = property(_get_input_buffer, _set_input_buffer)
382
394
383 def _get_input_buffer_cursor_line(self):
395 def _get_input_buffer_cursor_line(self):
384 if self._executing:
396 if self._executing:
385 return None
397 return None
386 cursor = self.textCursor()
398 cursor = self.textCursor()
387 if cursor.position() >= self._prompt_pos:
399 if cursor.position() >= self._prompt_pos:
388 text = str(cursor.block().text())
400 text = str(cursor.block().text())
389 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
401 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
390 return text[len(self._prompt):]
402 return text[len(self._prompt):]
391 else:
403 else:
392 return text[len(self.continuation_prompt):]
404 return text[len(self._continuation_prompt):]
393 else:
405 else:
394 return None
406 return None
395
407
396 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
408 input_buffer_cursor_line = property(_get_input_buffer_cursor_line)
397
409
398 #---------------------------------------------------------------------------
410 #---------------------------------------------------------------------------
399 # 'ConsoleWidget' abstract interface
411 # 'ConsoleWidget' abstract interface
400 #---------------------------------------------------------------------------
412 #---------------------------------------------------------------------------
401
413
402 def _execute(self, interactive):
414 def _execute(self, interactive):
403 """ Called to execute the input buffer. When triggered by an the enter
415 """ Called to execute the input buffer. When triggered by an the enter
404 key press, 'interactive' is True; otherwise, it is False. Returns
416 key press, 'interactive' is True; otherwise, it is False. Returns
405 whether the input buffer was completely processed and a new prompt
417 whether the input buffer was completely processed and a new prompt
406 created.
418 created.
407 """
419 """
408 raise NotImplementedError
420 raise NotImplementedError
409
421
410 def _prompt_started_hook(self):
422 def _prompt_started_hook(self):
411 """ Called immediately after a new prompt is displayed.
423 """ Called immediately after a new prompt is displayed.
412 """
424 """
413 pass
425 pass
414
426
415 def _prompt_finished_hook(self):
427 def _prompt_finished_hook(self):
416 """ Called immediately after a prompt is finished, i.e. when some input
428 """ Called immediately after a prompt is finished, i.e. when some input
417 will be processed and a new prompt displayed.
429 will be processed and a new prompt displayed.
418 """
430 """
419 pass
431 pass
420
432
421 def _up_pressed(self):
433 def _up_pressed(self):
422 """ Called when the up key is pressed. Returns whether to continue
434 """ Called when the up key is pressed. Returns whether to continue
423 processing the event.
435 processing the event.
424 """
436 """
425 return True
437 return True
426
438
427 def _down_pressed(self):
439 def _down_pressed(self):
428 """ Called when the down key is pressed. Returns whether to continue
440 """ Called when the down key is pressed. Returns whether to continue
429 processing the event.
441 processing the event.
430 """
442 """
431 return True
443 return True
432
444
433 def _tab_pressed(self):
445 def _tab_pressed(self):
434 """ Called when the tab key is pressed. Returns whether to continue
446 """ Called when the tab key is pressed. Returns whether to continue
435 processing the event.
447 processing the event.
436 """
448 """
437 return False
449 return False
438
450
439 #--------------------------------------------------------------------------
451 #--------------------------------------------------------------------------
440 # 'ConsoleWidget' protected interface
452 # 'ConsoleWidget' protected interface
441 #--------------------------------------------------------------------------
453 #--------------------------------------------------------------------------
442
454
443 def _complete_with_items(self, cursor, items):
455 def _complete_with_items(self, cursor, items):
444 """ Performs completion with 'items' at the specified cursor location.
456 """ Performs completion with 'items' at the specified cursor location.
445 """
457 """
446 if len(items) == 1:
458 if len(items) == 1:
447 cursor.setPosition(self.textCursor().position(),
459 cursor.setPosition(self.textCursor().position(),
448 QtGui.QTextCursor.KeepAnchor)
460 QtGui.QTextCursor.KeepAnchor)
449 cursor.insertText(items[0])
461 cursor.insertText(items[0])
450 elif len(items) > 1:
462 elif len(items) > 1:
451 if self.gui_completion:
463 if self.gui_completion:
452 self._completion_widget.show_items(cursor, items)
464 self._completion_widget.show_items(cursor, items)
453 else:
465 else:
454 text = '\n'.join(items) + '\n'
466 text = '\n'.join(items) + '\n'
455 self._write_text_keeping_prompt(text)
467 self._write_text_keeping_prompt(text)
456
468
457 def _get_end_cursor(self):
469 def _get_end_cursor(self):
458 """ Convenience method that returns a cursor for the last character.
470 """ Convenience method that returns a cursor for the last character.
459 """
471 """
460 cursor = self.textCursor()
472 cursor = self.textCursor()
461 cursor.movePosition(QtGui.QTextCursor.End)
473 cursor.movePosition(QtGui.QTextCursor.End)
462 return cursor
474 return cursor
463
475
464 def _get_prompt_cursor(self):
476 def _get_prompt_cursor(self):
465 """ Convenience method that returns a cursor for the prompt position.
477 """ Convenience method that returns a cursor for the prompt position.
466 """
478 """
467 cursor = self.textCursor()
479 cursor = self.textCursor()
468 cursor.setPosition(self._prompt_pos)
480 cursor.setPosition(self._prompt_pos)
469 return cursor
481 return cursor
470
482
471 def _get_selection_cursor(self, start, end):
483 def _get_selection_cursor(self, start, end):
472 """ Convenience method that returns a cursor with text selected between
484 """ Convenience method that returns a cursor with text selected between
473 the positions 'start' and 'end'.
485 the positions 'start' and 'end'.
474 """
486 """
475 cursor = self.textCursor()
487 cursor = self.textCursor()
476 cursor.setPosition(start)
488 cursor.setPosition(start)
477 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
489 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
478 return cursor
490 return cursor
479
491
480 def _get_word_start_cursor(self, position):
492 def _get_word_start_cursor(self, position):
481 """ Find the start of the word to the left the given position. If a
493 """ Find the start of the word to the left the given position. If a
482 sequence of non-word characters precedes the first word, skip over
494 sequence of non-word characters precedes the first word, skip over
483 them. (This emulates the behavior of bash, emacs, etc.)
495 them. (This emulates the behavior of bash, emacs, etc.)
484 """
496 """
485 document = self.document()
497 document = self.document()
486 position -= 1
498 position -= 1
487 while self._in_buffer(position) and \
499 while self._in_buffer(position) and \
488 not document.characterAt(position).isLetterOrNumber():
500 not document.characterAt(position).isLetterOrNumber():
489 position -= 1
501 position -= 1
490 while self._in_buffer(position) and \
502 while self._in_buffer(position) and \
491 document.characterAt(position).isLetterOrNumber():
503 document.characterAt(position).isLetterOrNumber():
492 position -= 1
504 position -= 1
493 cursor = self.textCursor()
505 cursor = self.textCursor()
494 cursor.setPosition(position + 1)
506 cursor.setPosition(position + 1)
495 return cursor
507 return cursor
496
508
497 def _get_word_end_cursor(self, position):
509 def _get_word_end_cursor(self, position):
498 """ Find the end of the word to the right the given position. If a
510 """ Find the end of the word to the right the given position. If a
499 sequence of non-word characters precedes the first word, skip over
511 sequence of non-word characters precedes the first word, skip over
500 them. (This emulates the behavior of bash, emacs, etc.)
512 them. (This emulates the behavior of bash, emacs, etc.)
501 """
513 """
502 document = self.document()
514 document = self.document()
503 end = self._get_end_cursor().position()
515 end = self._get_end_cursor().position()
504 while position < end and \
516 while position < end and \
505 not document.characterAt(position).isLetterOrNumber():
517 not document.characterAt(position).isLetterOrNumber():
506 position += 1
518 position += 1
507 while position < end and \
519 while position < end and \
508 document.characterAt(position).isLetterOrNumber():
520 document.characterAt(position).isLetterOrNumber():
509 position += 1
521 position += 1
510 cursor = self.textCursor()
522 cursor = self.textCursor()
511 cursor.setPosition(position)
523 cursor.setPosition(position)
512 return cursor
524 return cursor
513
525
514 def _prompt_started(self):
526 def _prompt_started(self):
515 """ Called immediately after a new prompt is displayed.
527 """ Called immediately after a new prompt is displayed.
516 """
528 """
517 # Temporarily disable the maximum block count to permit undo/redo.
529 # Temporarily disable the maximum block count to permit undo/redo.
518 self.setMaximumBlockCount(0)
530 self.setMaximumBlockCount(0)
519 self.setUndoRedoEnabled(True)
531 self.setUndoRedoEnabled(True)
520
532
521 self.setReadOnly(False)
533 self.setReadOnly(False)
522 self.moveCursor(QtGui.QTextCursor.End)
534 self.moveCursor(QtGui.QTextCursor.End)
523 self.centerCursor()
535 self.centerCursor()
524
536
525 self._executing = False
537 self._executing = False
526 self._prompt_started_hook()
538 self._prompt_started_hook()
527
539
528 def _prompt_finished(self):
540 def _prompt_finished(self):
529 """ Called immediately after a prompt is finished, i.e. when some input
541 """ Called immediately after a prompt is finished, i.e. when some input
530 will be processed and a new prompt displayed.
542 will be processed and a new prompt displayed.
531 """
543 """
532 # This has the (desired) side effect of disabling the undo/redo history.
544 # This has the (desired) side effect of disabling the undo/redo history.
533 self.setMaximumBlockCount(self.buffer_size)
545 self.setMaximumBlockCount(self.buffer_size)
534
546
535 self.setReadOnly(True)
547 self.setReadOnly(True)
536 self._prompt_finished_hook()
548 self._prompt_finished_hook()
537
549
538 def _set_position(self, position):
550 def _set_position(self, position):
539 """ Convenience method to set the position of the cursor.
551 """ Convenience method to set the position of the cursor.
540 """
552 """
541 cursor = self.textCursor()
553 cursor = self.textCursor()
542 cursor.setPosition(position)
554 cursor.setPosition(position)
543 self.setTextCursor(cursor)
555 self.setTextCursor(cursor)
544
556
545 def _set_selection(self, start, end):
557 def _set_selection(self, start, end):
546 """ Convenience method to set the current selected text.
558 """ Convenience method to set the current selected text.
547 """
559 """
548 self.setTextCursor(self._get_selection_cursor(start, end))
560 self.setTextCursor(self._get_selection_cursor(start, end))
549
561
550 def _show_prompt(self, prompt):
562 def _show_prompt(self, prompt=None):
551 """ Writes a new prompt at the end of the buffer.
563 """ Writes a new prompt at the end of the buffer. If 'prompt' is not
564 specified, uses the previous prompt.
552 """
565 """
553 self.appendPlainText('\n' + prompt)
566 if prompt is not None:
554 self._prompt = prompt
567 self._prompt = prompt
568 self.appendPlainText('\n' + self._prompt)
555 self._prompt_pos = self._get_end_cursor().position()
569 self._prompt_pos = self._get_end_cursor().position()
556 self._prompt_started()
570 self._prompt_started()
557
571
558 def _show_continuation_prompt(self):
572 def _show_continuation_prompt(self):
559 """ Writes a new continuation prompt at the end of the buffer.
573 """ Writes a new continuation prompt at the end of the buffer.
560 """
574 """
561 self.appendPlainText(self.continuation_prompt)
575 self.appendPlainText(self._continuation_prompt)
562 self._prompt_started()
576 self._prompt_started()
563
577
564 def _write_text_keeping_prompt(self, text):
578 def _write_text_keeping_prompt(self, text):
565 """ Writes 'text' after the current prompt, then restores the old prompt
579 """ Writes 'text' after the current prompt, then restores the old prompt
566 with its old input buffer.
580 with its old input buffer.
567 """
581 """
568 input_buffer = self.input_buffer
582 input_buffer = self.input_buffer
569 self.appendPlainText('\n')
583 self.appendPlainText('\n')
570 self._prompt_finished()
584 self._prompt_finished()
571
585
572 self.appendPlainText(text)
586 self.appendPlainText(text)
573 self._show_prompt(self._prompt)
587 self._show_prompt()
574 self.input_buffer = input_buffer
588 self.input_buffer = input_buffer
575
589
576 def _in_buffer(self, position):
590 def _in_buffer(self, position):
577 """ Returns whether the given position is inside the editing region.
591 """ Returns whether the given position is inside the editing region.
578 """
592 """
579 return position >= self._prompt_pos
593 return position >= self._prompt_pos
580
594
581 def _keep_cursor_in_buffer(self):
595 def _keep_cursor_in_buffer(self):
582 """ Ensures that the cursor is inside the editing region. Returns
596 """ Ensures that the cursor is inside the editing region. Returns
583 whether the cursor was moved.
597 whether the cursor was moved.
584 """
598 """
585 cursor = self.textCursor()
599 cursor = self.textCursor()
586 if cursor.position() < self._prompt_pos:
600 if cursor.position() < self._prompt_pos:
587 cursor.movePosition(QtGui.QTextCursor.End)
601 cursor.movePosition(QtGui.QTextCursor.End)
588 self.setTextCursor(cursor)
602 self.setTextCursor(cursor)
589 return True
603 return True
590 else:
604 else:
591 return False
605 return False
592
606
593
607
594 class HistoryConsoleWidget(ConsoleWidget):
608 class HistoryConsoleWidget(ConsoleWidget):
595 """ A ConsoleWidget that keeps a history of the commands that have been
609 """ A ConsoleWidget that keeps a history of the commands that have been
596 executed.
610 executed.
597 """
611 """
598
612
599 #---------------------------------------------------------------------------
613 #---------------------------------------------------------------------------
600 # 'QWidget' interface
614 # 'QWidget' interface
601 #---------------------------------------------------------------------------
615 #---------------------------------------------------------------------------
602
616
603 def __init__(self, parent=None):
617 def __init__(self, parent=None):
604 super(HistoryConsoleWidget, self).__init__(parent)
618 super(HistoryConsoleWidget, self).__init__(parent)
605
619
606 self._history = []
620 self._history = []
607 self._history_index = 0
621 self._history_index = 0
608
622
609 #---------------------------------------------------------------------------
623 #---------------------------------------------------------------------------
610 # 'ConsoleWidget' public interface
624 # 'ConsoleWidget' public interface
611 #---------------------------------------------------------------------------
625 #---------------------------------------------------------------------------
612
626
613 def execute(self, interactive=False):
627 def execute(self, interactive=False):
614 """ Reimplemented to the store history.
628 """ Reimplemented to the store history.
615 """
629 """
616 stripped = self.input_buffer.rstrip()
630 stripped = self.input_buffer.rstrip()
617 executed = super(HistoryConsoleWidget, self).execute(interactive)
631 executed = super(HistoryConsoleWidget, self).execute(interactive)
618 if executed:
632 if executed:
619 self._history.append(stripped)
633 self._history.append(stripped)
620 self._history_index = len(self._history)
634 self._history_index = len(self._history)
621 return executed
635 return executed
622
636
623 #---------------------------------------------------------------------------
637 #---------------------------------------------------------------------------
624 # 'ConsoleWidget' abstract interface
638 # 'ConsoleWidget' abstract interface
625 #---------------------------------------------------------------------------
639 #---------------------------------------------------------------------------
626
640
627 def _up_pressed(self):
641 def _up_pressed(self):
628 """ Called when the up key is pressed. Returns whether to continue
642 """ Called when the up key is pressed. Returns whether to continue
629 processing the event.
643 processing the event.
630 """
644 """
631 prompt_cursor = self._get_prompt_cursor()
645 prompt_cursor = self._get_prompt_cursor()
632 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
646 if self.textCursor().blockNumber() == prompt_cursor.blockNumber():
633 self.history_previous()
647 self.history_previous()
634
648
635 # Go to the first line of prompt for seemless history scrolling.
649 # Go to the first line of prompt for seemless history scrolling.
636 cursor = self._get_prompt_cursor()
650 cursor = self._get_prompt_cursor()
637 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
651 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
638 self.setTextCursor(cursor)
652 self.setTextCursor(cursor)
639
653
640 return False
654 return False
641 return True
655 return True
642
656
643 def _down_pressed(self):
657 def _down_pressed(self):
644 """ Called when the down key is pressed. Returns whether to continue
658 """ Called when the down key is pressed. Returns whether to continue
645 processing the event.
659 processing the event.
646 """
660 """
647 end_cursor = self._get_end_cursor()
661 end_cursor = self._get_end_cursor()
648 if self.textCursor().blockNumber() == end_cursor.blockNumber():
662 if self.textCursor().blockNumber() == end_cursor.blockNumber():
649 self.history_next()
663 self.history_next()
650 return False
664 return False
651 return True
665 return True
652
666
653 #---------------------------------------------------------------------------
667 #---------------------------------------------------------------------------
654 # 'HistoryConsoleWidget' interface
668 # 'HistoryConsoleWidget' interface
655 #---------------------------------------------------------------------------
669 #---------------------------------------------------------------------------
656
670
657 def history_previous(self):
671 def history_previous(self):
658 """ If possible, set the input buffer to the previous item in the
672 """ If possible, set the input buffer to the previous item in the
659 history.
673 history.
660 """
674 """
661 if self._history_index > 0:
675 if self._history_index > 0:
662 self._history_index -= 1
676 self._history_index -= 1
663 self.input_buffer = self._history[self._history_index]
677 self.input_buffer = self._history[self._history_index]
664
678
665 def history_next(self):
679 def history_next(self):
666 """ Set the input buffer to the next item in the history, or a blank
680 """ Set the input buffer to the next item in the history, or a blank
667 line if there is no subsequent item.
681 line if there is no subsequent item.
668 """
682 """
669 if self._history_index < len(self._history):
683 if self._history_index < len(self._history):
670 self._history_index += 1
684 self._history_index += 1
671 if self._history_index < len(self._history):
685 if self._history_index < len(self._history):
672 self.input_buffer = self._history[self._history_index]
686 self.input_buffer = self._history[self._history_index]
673 else:
687 else:
674 self.input_buffer = ''
688 self.input_buffer = ''
@@ -1,276 +1,302 b''
1 # System library imports
1 # System library imports
2 from pygments.lexers import PythonLexer
2 from pygments.lexers import PythonLexer
3 from PyQt4 import QtCore, QtGui
3 from PyQt4 import QtCore, QtGui
4 import zmq
4 import zmq
5
5
6 # Local imports
6 # Local imports
7 from IPython.core.blockbreaker import BlockBreaker
7 from IPython.core.blockbreaker import BlockBreaker
8 from call_tip_widget import CallTipWidget
8 from call_tip_widget import CallTipWidget
9 from completion_lexer import CompletionLexer
9 from completion_lexer import CompletionLexer
10 from console_widget import HistoryConsoleWidget
10 from console_widget import HistoryConsoleWidget
11 from pygments_highlighter import PygmentsHighlighter
11 from pygments_highlighter import PygmentsHighlighter
12
12
13
13
14 class FrontendHighlighter(PygmentsHighlighter):
14 class FrontendHighlighter(PygmentsHighlighter):
15 """ A Python PygmentsHighlighter that can be turned on and off and which
15 """ A Python PygmentsHighlighter that can be turned on and off and which
16 knows about continuation prompts.
16 knows about continuation prompts.
17 """
17 """
18
18
19 def __init__(self, frontend):
19 def __init__(self, frontend):
20 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
20 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
21 self._current_offset = 0
21 self._current_offset = 0
22 self._frontend = frontend
22 self._frontend = frontend
23 self.highlighting_on = False
23 self.highlighting_on = False
24
24
25 def highlightBlock(self, qstring):
25 def highlightBlock(self, qstring):
26 """ Highlight a block of text. Reimplemented to highlight selectively.
26 """ Highlight a block of text. Reimplemented to highlight selectively.
27 """
27 """
28 if self.highlighting_on:
28 if self.highlighting_on:
29 for prompt in (self._frontend._prompt,
29 for prompt in (self._frontend._continuation_prompt,
30 self._frontend.continuation_prompt):
30 self._frontend._prompt):
31 if qstring.startsWith(prompt):
31 if qstring.startsWith(prompt):
32 qstring.remove(0, len(prompt))
32 qstring.remove(0, len(prompt))
33 self._current_offset = len(prompt)
33 self._current_offset = len(prompt)
34 break
34 break
35 PygmentsHighlighter.highlightBlock(self, qstring)
35 PygmentsHighlighter.highlightBlock(self, qstring)
36
36
37 def setFormat(self, start, count, format):
37 def setFormat(self, start, count, format):
38 """ Reimplemented to avoid highlighting continuation prompts.
38 """ Reimplemented to avoid highlighting continuation prompts.
39 """
39 """
40 start += self._current_offset
40 start += self._current_offset
41 PygmentsHighlighter.setFormat(self, start, count, format)
41 PygmentsHighlighter.setFormat(self, start, count, format)
42
42
43
43
44 class FrontendWidget(HistoryConsoleWidget):
44 class FrontendWidget(HistoryConsoleWidget):
45 """ A Qt frontend for a generic Python kernel.
45 """ A Qt frontend for a generic Python kernel.
46 """
46 """
47
47
48 # Emitted when an 'execute_reply' is received from the kernel.
48 # Emitted when an 'execute_reply' is received from the kernel.
49 executed = QtCore.pyqtSignal(object)
49 executed = QtCore.pyqtSignal(object)
50
50
51 #---------------------------------------------------------------------------
51 #---------------------------------------------------------------------------
52 # 'QWidget' interface
52 # 'QWidget' interface
53 #---------------------------------------------------------------------------
53 #---------------------------------------------------------------------------
54
54
55 def __init__(self, kernel_manager, parent=None):
55 def __init__(self, parent=None):
56 super(FrontendWidget, self).__init__(parent)
56 super(FrontendWidget, self).__init__(parent)
57
57
58 # ConsoleWidget protected variables.
59 self._continuation_prompt = '... '
60 self._prompt = '>>> '
61
62 # FrontendWidget protected variables.
58 self._blockbreaker = BlockBreaker(input_mode='replace')
63 self._blockbreaker = BlockBreaker(input_mode='replace')
59 self._call_tip_widget = CallTipWidget(self)
64 self._call_tip_widget = CallTipWidget(self)
60 self._completion_lexer = CompletionLexer(PythonLexer())
65 self._completion_lexer = CompletionLexer(PythonLexer())
61 self._hidden = True
66 self._hidden = True
62 self._highlighter = FrontendHighlighter(self)
67 self._highlighter = FrontendHighlighter(self)
63 self._kernel_manager = None
68 self._kernel_manager = None
64
69
65 self.continuation_prompt = '... '
66 self.kernel_manager = kernel_manager
67
68 self.document().contentsChange.connect(self._document_contents_change)
70 self.document().contentsChange.connect(self._document_contents_change)
69
71
70 def focusOutEvent(self, event):
72 def focusOutEvent(self, event):
71 """ Reimplemented to hide calltips.
73 """ Reimplemented to hide calltips.
72 """
74 """
73 self._call_tip_widget.hide()
75 self._call_tip_widget.hide()
74 return super(FrontendWidget, self).focusOutEvent(event)
76 return super(FrontendWidget, self).focusOutEvent(event)
75
77
76 def keyPressEvent(self, event):
78 def keyPressEvent(self, event):
77 """ Reimplemented to hide calltips.
79 """ Reimplemented to hide calltips.
78 """
80 """
79 if event.key() == QtCore.Qt.Key_Escape:
81 if event.key() == QtCore.Qt.Key_Escape:
80 self._call_tip_widget.hide()
82 self._call_tip_widget.hide()
81 return super(FrontendWidget, self).keyPressEvent(event)
83 return super(FrontendWidget, self).keyPressEvent(event)
82
84
83 #---------------------------------------------------------------------------
85 #---------------------------------------------------------------------------
84 # 'ConsoleWidget' abstract interface
86 # 'ConsoleWidget' abstract interface
85 #---------------------------------------------------------------------------
87 #---------------------------------------------------------------------------
86
88
87 def _execute(self, interactive):
89 def _execute(self, interactive):
88 """ Called to execute the input buffer. When triggered by an the enter
90 """ Called to execute the input buffer. When triggered by an the enter
89 key press, 'interactive' is True; otherwise, it is False. Returns
91 key press, 'interactive' is True; otherwise, it is False. Returns
90 whether the input buffer was completely processed and a new prompt
92 whether the input buffer was completely processed and a new prompt
91 created.
93 created.
92 """
94 """
93 return self.execute_source(self.input_buffer, interactive=interactive)
95 return self.execute_source(self.input_buffer, interactive=interactive)
94
96
95 def _prompt_started_hook(self):
97 def _prompt_started_hook(self):
96 """ Called immediately after a new prompt is displayed.
98 """ Called immediately after a new prompt is displayed.
97 """
99 """
98 self._highlighter.highlighting_on = True
100 self._highlighter.highlighting_on = True
99
101
100 def _prompt_finished_hook(self):
102 def _prompt_finished_hook(self):
101 """ Called immediately after a prompt is finished, i.e. when some input
103 """ Called immediately after a prompt is finished, i.e. when some input
102 will be processed and a new prompt displayed.
104 will be processed and a new prompt displayed.
103 """
105 """
104 self._highlighter.highlighting_on = False
106 self._highlighter.highlighting_on = False
105
107
106 def _tab_pressed(self):
108 def _tab_pressed(self):
107 """ Called when the tab key is pressed. Returns whether to continue
109 """ Called when the tab key is pressed. Returns whether to continue
108 processing the event.
110 processing the event.
109 """
111 """
110 self._keep_cursor_in_buffer()
112 self._keep_cursor_in_buffer()
111 cursor = self.textCursor()
113 cursor = self.textCursor()
112 if not self._complete():
114 if not self._complete():
113 cursor.insertText(' ')
115 cursor.insertText(' ')
114 return False
116 return False
115
117
116 #---------------------------------------------------------------------------
118 #---------------------------------------------------------------------------
117 # 'FrontendWidget' interface
119 # 'FrontendWidget' interface
118 #---------------------------------------------------------------------------
120 #---------------------------------------------------------------------------
119
121
120 def execute_source(self, source, hidden=False, interactive=False):
122 def execute_source(self, source, hidden=False, interactive=False):
121 """ Execute a string containing Python code. If 'hidden', no output is
123 """ Execute a string containing Python code. If 'hidden', no output is
122 shown. Returns whether the source executed (i.e., returns True only
124 shown. Returns whether the source executed (i.e., returns True only
123 if no more input is necessary).
125 if no more input is necessary).
124 """
126 """
125 self._blockbreaker.push(source)
127 self._blockbreaker.push(source)
126 executed = self._blockbreaker.interactive_block_ready()
128 executed = self._blockbreaker.interactive_block_ready()
127 if executed:
129 if executed:
128 self.kernel_manager.xreq_channel.execute(source)
130 self.kernel_manager.xreq_channel.execute(source)
129 self._hidden = hidden
131 self._hidden = hidden
130 else:
132 else:
131 self._show_continuation_prompt()
133 self._show_continuation_prompt()
132 self.appendPlainText(' ' * self._blockbreaker.indent_spaces)
134 self.appendPlainText(' ' * self._blockbreaker.indent_spaces)
133 return executed
135 return executed
134
136
135 def execute_file(self, path, hidden=False):
137 def execute_file(self, path, hidden=False):
136 """ Attempts to execute file with 'path'. If 'hidden', no output is
138 """ Attempts to execute file with 'path'. If 'hidden', no output is
137 shown.
139 shown.
138 """
140 """
139 self.execute_source('run %s' % path, hidden=hidden)
141 self.execute_source('run %s' % path, hidden=hidden)
140
142
141 def _get_kernel_manager(self):
143 def _get_kernel_manager(self):
142 """ Returns the current kernel manager.
144 """ Returns the current kernel manager.
143 """
145 """
144 return self._kernel_manager
146 return self._kernel_manager
145
147
146 def _set_kernel_manager(self, kernel_manager):
148 def _set_kernel_manager(self, kernel_manager):
147 """ Sets a new kernel manager, configuring its channels as necessary.
149 """ Disconnect from the current kernel manager (if any) and set a new
150 kernel manager.
148 """
151 """
149 # Disconnect the old kernel manager.
152 # Disconnect the old kernel manager, if necessary.
150 if self._kernel_manager is not None:
153 if self._kernel_manager is not None:
154 self._kernel_manager.started_listening.disconnect(
155 self._started_listening)
156 self._kernel_manager.stopped_listening.disconnect(
157 self._stopped_listening)
158
159 # Disconnect the old kernel manager's channels.
151 sub = self._kernel_manager.sub_channel
160 sub = self._kernel_manager.sub_channel
152 xreq = self._kernel_manager.xreq_channel
161 xreq = self._kernel_manager.xreq_channel
153 sub.message_received.disconnect(self._handle_sub)
162 sub.message_received.disconnect(self._handle_sub)
154 xreq.execute_reply.disconnect(self._handle_execute_reply)
163 xreq.execute_reply.disconnect(self._handle_execute_reply)
155 xreq.complete_reply.disconnect(self._handle_complete_reply)
164 xreq.complete_reply.disconnect(self._handle_complete_reply)
156 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
165 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
157
166
158 # Connect the new kernel manager.
167 # Set the new kernel manager.
159 self._kernel_manager = kernel_manager
168 self._kernel_manager = kernel_manager
169 if kernel_manager is None:
170 return
171
172 # Connect the new kernel manager.
173 kernel_manager.started_listening.connect(self._started_listening)
174 kernel_manager.stopped_listening.connect(self._stopped_listening)
175
176 # Connect the new kernel manager's channels.
160 sub = kernel_manager.sub_channel
177 sub = kernel_manager.sub_channel
161 xreq = kernel_manager.xreq_channel
178 xreq = kernel_manager.xreq_channel
162 sub.message_received.connect(self._handle_sub)
179 sub.message_received.connect(self._handle_sub)
163 xreq.execute_reply.connect(self._handle_execute_reply)
180 xreq.execute_reply.connect(self._handle_execute_reply)
164 xreq.complete_reply.connect(self._handle_complete_reply)
181 xreq.complete_reply.connect(self._handle_complete_reply)
165 xreq.object_info_reply.connect(self._handle_object_info_reply)
182 xreq.object_info_reply.connect(self._handle_object_info_reply)
166
183
167 self._show_prompt('>>> ')
184 # Handle the case where the kernel manager started listening before
185 # we connected.
186 if kernel_manager.is_listening:
187 self._started_listening()
168
188
169 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
189 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
170
190
171 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
172 # 'FrontendWidget' protected interface
192 # 'FrontendWidget' protected interface
173 #---------------------------------------------------------------------------
193 #---------------------------------------------------------------------------
174
194
175 def _call_tip(self):
195 def _call_tip(self):
176 """ Shows a call tip, if appropriate, at the current cursor location.
196 """ Shows a call tip, if appropriate, at the current cursor location.
177 """
197 """
178 # Decide if it makes sense to show a call tip
198 # Decide if it makes sense to show a call tip
179 cursor = self.textCursor()
199 cursor = self.textCursor()
180 cursor.movePosition(QtGui.QTextCursor.Left)
200 cursor.movePosition(QtGui.QTextCursor.Left)
181 document = self.document()
201 document = self.document()
182 if document.characterAt(cursor.position()).toAscii() != '(':
202 if document.characterAt(cursor.position()).toAscii() != '(':
183 return False
203 return False
184 context = self._get_context(cursor)
204 context = self._get_context(cursor)
185 if not context:
205 if not context:
186 return False
206 return False
187
207
188 # Send the metadata request to the kernel
208 # Send the metadata request to the kernel
189 name = '.'.join(context)
209 name = '.'.join(context)
190 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
210 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
191 self._calltip_pos = self.textCursor().position()
211 self._calltip_pos = self.textCursor().position()
192 return True
212 return True
193
213
194 def _complete(self):
214 def _complete(self):
195 """ Performs completion at the current cursor location.
215 """ Performs completion at the current cursor location.
196 """
216 """
197 # Decide if it makes sense to do completion
217 # Decide if it makes sense to do completion
198 context = self._get_context()
218 context = self._get_context()
199 if not context:
219 if not context:
200 return False
220 return False
201
221
202 # Send the completion request to the kernel
222 # Send the completion request to the kernel
203 text = '.'.join(context)
223 text = '.'.join(context)
204 self._complete_id = self.kernel_manager.xreq_channel.complete(
224 self._complete_id = self.kernel_manager.xreq_channel.complete(
205 text, self.input_buffer_cursor_line, self.input_buffer)
225 text, self.input_buffer_cursor_line, self.input_buffer)
206 self._complete_pos = self.textCursor().position()
226 self._complete_pos = self.textCursor().position()
207 return True
227 return True
208
228
209 def _get_context(self, cursor=None):
229 def _get_context(self, cursor=None):
210 """ Gets the context at the current cursor location.
230 """ Gets the context at the current cursor location.
211 """
231 """
212 if cursor is None:
232 if cursor is None:
213 cursor = self.textCursor()
233 cursor = self.textCursor()
214 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
234 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
215 QtGui.QTextCursor.KeepAnchor)
235 QtGui.QTextCursor.KeepAnchor)
216 text = unicode(cursor.selectedText())
236 text = unicode(cursor.selectedText())
217 return self._completion_lexer.get_context(text)
237 return self._completion_lexer.get_context(text)
218
238
219 #------ Signal handlers ----------------------------------------------------
239 #------ Signal handlers ----------------------------------------------------
220
240
221 def _document_contents_change(self, position, removed, added):
241 def _document_contents_change(self, position, removed, added):
222 """ Called whenever the document's content changes. Display a calltip
242 """ Called whenever the document's content changes. Display a calltip
223 if appropriate.
243 if appropriate.
224 """
244 """
225 # Calculate where the cursor should be *after* the change:
245 # Calculate where the cursor should be *after* the change:
226 position += added
246 position += added
227
247
228 document = self.document()
248 document = self.document()
229 if position == self.textCursor().position():
249 if position == self.textCursor().position():
230 self._call_tip()
250 self._call_tip()
231
251
232 def _handle_sub(self, omsg):
252 def _handle_sub(self, omsg):
233 if not self._hidden:
253 if not self._hidden:
234 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
254 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
235 if handler is not None:
255 if handler is not None:
236 handler(omsg)
256 handler(omsg)
237
257
238 def _handle_pyout(self, omsg):
258 def _handle_pyout(self, omsg):
239 session = omsg['parent_header']['session']
259 session = omsg['parent_header']['session']
240 if session == self.kernel_manager.session.session:
260 if session == self.kernel_manager.session.session:
241 self.appendPlainText(omsg['content']['data'] + '\n')
261 self.appendPlainText(omsg['content']['data'] + '\n')
242
262
243 def _handle_stream(self, omsg):
263 def _handle_stream(self, omsg):
244 self.appendPlainText(omsg['content']['data'])
264 self.appendPlainText(omsg['content']['data'])
245
265
246 def _handle_execute_reply(self, rep):
266 def _handle_execute_reply(self, rep):
247 # Make sure that all output from the SUB channel has been processed
267 # Make sure that all output from the SUB channel has been processed
248 # before writing a new prompt.
268 # before writing a new prompt.
249 self.kernel_manager.sub_channel.flush()
269 self.kernel_manager.sub_channel.flush()
250
270
251 content = rep['content']
271 content = rep['content']
252 status = content['status']
272 status = content['status']
253 if status == 'error':
273 if status == 'error':
254 self.appendPlainText(content['traceback'][-1])
274 self.appendPlainText(content['traceback'][-1])
255 elif status == 'aborted':
275 elif status == 'aborted':
256 text = "ERROR: ABORTED\n"
276 text = "ERROR: ABORTED\n"
257 self.appendPlainText(text)
277 self.appendPlainText(text)
258 self._hidden = True
278 self._hidden = True
259 self._show_prompt('>>> ')
279 self._show_prompt()
260 self.executed.emit(rep)
280 self.executed.emit(rep)
261
281
262 def _handle_complete_reply(self, rep):
282 def _handle_complete_reply(self, rep):
263 cursor = self.textCursor()
283 cursor = self.textCursor()
264 if rep['parent_header']['msg_id'] == self._complete_id and \
284 if rep['parent_header']['msg_id'] == self._complete_id and \
265 cursor.position() == self._complete_pos:
285 cursor.position() == self._complete_pos:
266 text = '.'.join(self._get_context())
286 text = '.'.join(self._get_context())
267 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
287 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
268 self._complete_with_items(cursor, rep['content']['matches'])
288 self._complete_with_items(cursor, rep['content']['matches'])
269
289
270 def _handle_object_info_reply(self, rep):
290 def _handle_object_info_reply(self, rep):
271 cursor = self.textCursor()
291 cursor = self.textCursor()
272 if rep['parent_header']['msg_id'] == self._calltip_id and \
292 if rep['parent_header']['msg_id'] == self._calltip_id and \
273 cursor.position() == self._calltip_pos:
293 cursor.position() == self._calltip_pos:
274 doc = rep['content']['docstring']
294 doc = rep['content']['docstring']
275 if doc:
295 if doc:
276 self._call_tip_widget.show_tip(doc)
296 self._call_tip_widget.show_tip(doc)
297
298 def _started_listening(self):
299 self.clear()
300
301 def _stopped_listening(self):
302 pass
@@ -1,95 +1,96 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 frontend_widget import FrontendWidget
5 from frontend_widget import FrontendWidget
6
6
7
7
8 class IPythonWidget(FrontendWidget):
8 class IPythonWidget(FrontendWidget):
9 """ A FrontendWidget for an IPython kernel.
9 """ A FrontendWidget for an IPython kernel.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'FrontendWidget' interface
13 # 'FrontendWidget' interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def __init__(self, kernel_manager, parent=None):
16 def __init__(self, parent=None):
17 super(IPythonWidget, self).__init__(kernel_manager, parent)
17 super(IPythonWidget, self).__init__(parent)
18
18
19 self._magic_overrides = {}
19 self._magic_overrides = {}
20
20
21 def execute_source(self, source, hidden=False, interactive=False):
21 def execute_source(self, source, hidden=False, interactive=False):
22 """ Reimplemented to override magic commands.
22 """ Reimplemented to override magic commands.
23 """
23 """
24 magic_source = source.strip()
24 magic_source = source.strip()
25 if magic_source.startswith('%'):
25 if magic_source.startswith('%'):
26 magic_source = magic_source[1:]
26 magic_source = magic_source[1:]
27 magic, sep, arguments = magic_source.partition(' ')
27 magic, sep, arguments = magic_source.partition(' ')
28 if not magic:
28 if not magic:
29 magic = magic_source
29 magic = magic_source
30
30
31 callback = self._magic_overrides.get(magic)
31 callback = self._magic_overrides.get(magic)
32 if callback:
32 if callback:
33 output = callback(arguments)
33 output = callback(arguments)
34 if output:
34 if output:
35 self.appendPlainText(output)
35 self.appendPlainText(output)
36 self._show_prompt('>>> ')
36 self._show_prompt('>>> ')
37 return True
37 return True
38 else:
38 else:
39 return super(IPythonWidget, self).execute_source(source, hidden,
39 return super(IPythonWidget, self).execute_source(source, hidden,
40 interactive)
40 interactive)
41
41
42 #---------------------------------------------------------------------------
42 #---------------------------------------------------------------------------
43 # 'IPythonWidget' interface
43 # 'IPythonWidget' interface
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45
45
46 def set_magic_override(self, magic, callback):
46 def set_magic_override(self, magic, callback):
47 """ Overrides an IPython magic command. This magic will be intercepted
47 """ Overrides an IPython magic command. This magic will be intercepted
48 by the frontend rather than passed on to the kernel and 'callback'
48 by the frontend rather than passed on to the kernel and 'callback'
49 will be called with a single argument: a string of argument(s) for
49 will be called with a single argument: a string of argument(s) for
50 the magic. The callback can (optionally) return text to print to the
50 the magic. The callback can (optionally) return text to print to the
51 console.
51 console.
52 """
52 """
53 self._magic_overrides[magic] = callback
53 self._magic_overrides[magic] = callback
54
54
55 def remove_magic_override(self, magic):
55 def remove_magic_override(self, magic):
56 """ Removes the override for the specified magic, if there is one.
56 """ Removes the override for the specified magic, if there is one.
57 """
57 """
58 try:
58 try:
59 del self._magic_overrides[magic]
59 del self._magic_overrides[magic]
60 except KeyError:
60 except KeyError:
61 pass
61 pass
62
62
63
63
64 if __name__ == '__main__':
64 if __name__ == '__main__':
65 from IPython.external.argparse import ArgumentParser
65 from IPython.external.argparse import ArgumentParser
66 from IPython.frontend.qt.kernelmanager import QtKernelManager
66 from IPython.frontend.qt.kernelmanager import QtKernelManager
67
67
68 # Don't let Qt swallow KeyboardInterupts.
68 # Don't let Qt swallow KeyboardInterupts.
69 import signal
69 import signal
70 signal.signal(signal.SIGINT, signal.SIG_DFL)
70 signal.signal(signal.SIGINT, signal.SIG_DFL)
71
71
72 # Parse command line arguments.
72 # Parse command line arguments.
73 parser = ArgumentParser()
73 parser = ArgumentParser()
74 parser.add_argument('--ip', type=str, default='127.0.0.1',
74 parser.add_argument('--ip', type=str, default='127.0.0.1',
75 help='set the kernel\'s IP address [default localhost]')
75 help='set the kernel\'s IP address [default localhost]')
76 parser.add_argument('--xreq', type=int, metavar='PORT', default=5575,
76 parser.add_argument('--xreq', type=int, metavar='PORT', default=5575,
77 help='set the XREQ Channel port [default %(default)i]')
77 help='set the XREQ Channel port [default %(default)i]')
78 parser.add_argument('--sub', type=int, metavar='PORT', default=5576,
78 parser.add_argument('--sub', type=int, metavar='PORT', default=5576,
79 help='set the SUB Channel port [default %(default)i]')
79 help='set the SUB Channel port [default %(default)i]')
80 namespace = parser.parse_args()
80 namespace = parser.parse_args()
81
81
82 # Create KernelManager
82 # Create KernelManager
83 ip = namespace.ip
83 ip = namespace.ip
84 kernel_manager = QtKernelManager(xreq_address = (ip, namespace.xreq),
84 kernel_manager = QtKernelManager(xreq_address = (ip, namespace.xreq),
85 sub_address = (ip, namespace.sub))
85 sub_address = (ip, namespace.sub))
86 kernel_manager.start_listening()
86 kernel_manager.start_listening()
87
87
88 # Launch application
88 # Launch application
89 app = QtGui.QApplication([])
89 app = QtGui.QApplication([])
90 widget = IPythonWidget(kernel_manager)
90 widget = IPythonWidget()
91 widget.kernel_manager = kernel_manager
91 widget.setWindowTitle('Python')
92 widget.setWindowTitle('Python')
92 widget.resize(640, 480)
93 widget.resize(640, 480)
93 widget.show()
94 widget.show()
94 app.exec_()
95 app.exec_()
95
96
@@ -1,118 +1,144 b''
1 """ A KernelManager that provides channels that use 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
6
7 # IPython imports.
7 # IPython imports.
8 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
8 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
9 XReqSocketChannel, RepSocketChannel
9 XReqSocketChannel, RepSocketChannel
10 from util import MetaQObjectHasTraits
10
11
11
12
12 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
13 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
13
14
14 # Emitted when any message is received.
15 # Emitted when any message is received.
15 message_received = QtCore.pyqtSignal(object)
16 message_received = QtCore.pyqtSignal(object)
16
17
17 # Emitted when a message of type 'pyout' or 'stdout' is received.
18 # Emitted when a message of type 'pyout' or 'stdout' is received.
18 output_received = QtCore.pyqtSignal(object)
19 output_received = QtCore.pyqtSignal(object)
19
20
20 # Emitted when a message of type 'pyerr' or 'stderr' is received.
21 # Emitted when a message of type 'pyerr' or 'stderr' is received.
21 error_received = QtCore.pyqtSignal(object)
22 error_received = QtCore.pyqtSignal(object)
22
23
23 #---------------------------------------------------------------------------
24 #---------------------------------------------------------------------------
24 # 'object' interface
25 # 'object' interface
25 #---------------------------------------------------------------------------
26 #---------------------------------------------------------------------------
26
27
27 def __init__(self, *args, **kw):
28 def __init__(self, *args, **kw):
28 """ Reimplemented to ensure that QtCore.QObject is initialized first.
29 """ Reimplemented to ensure that QtCore.QObject is initialized first.
29 """
30 """
30 QtCore.QObject.__init__(self)
31 QtCore.QObject.__init__(self)
31 SubSocketChannel.__init__(self, *args, **kw)
32 SubSocketChannel.__init__(self, *args, **kw)
32
33
33 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
34 # 'SubSocketChannel' interface
35 # 'SubSocketChannel' interface
35 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
36
37
37 def call_handlers(self, msg):
38 def call_handlers(self, msg):
38 """ Reimplemented to emit signals instead of making callbacks.
39 """ Reimplemented to emit signals instead of making callbacks.
39 """
40 """
40 # Emit the generic signal.
41 # Emit the generic signal.
41 self.message_received.emit(msg)
42 self.message_received.emit(msg)
42
43
43 # Emit signals for specialized message types.
44 # Emit signals for specialized message types.
44 msg_type = msg['msg_type']
45 msg_type = msg['msg_type']
45 if msg_type in ('pyout', 'stdout'):
46 if msg_type in ('pyout', 'stdout'):
46 self.output_received.emit(msg)
47 self.output_received.emit(msg)
47 elif msg_type in ('pyerr', 'stderr'):
48 elif msg_type in ('pyerr', 'stderr'):
48 self.error_received.emit(msg)
49 self.error_received.emit(msg)
49
50
50 def flush(self):
51 def flush(self):
51 """ Reimplemented to ensure that signals are dispatched immediately.
52 """ Reimplemented to ensure that signals are dispatched immediately.
52 """
53 """
53 super(QtSubSocketChannel, self).flush()
54 super(QtSubSocketChannel, self).flush()
54 QtCore.QCoreApplication.instance().processEvents()
55 QtCore.QCoreApplication.instance().processEvents()
55
56
56
57
57 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
58 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
58
59
59 # Emitted when any message is received.
60 # Emitted when any message is received.
60 message_received = QtCore.pyqtSignal(object)
61 message_received = QtCore.pyqtSignal(object)
61
62
62 # Emitted when a reply has been received for the corresponding request type.
63 # Emitted when a reply has been received for the corresponding request type.
63 execute_reply = QtCore.pyqtSignal(object)
64 execute_reply = QtCore.pyqtSignal(object)
64 complete_reply = QtCore.pyqtSignal(object)
65 complete_reply = QtCore.pyqtSignal(object)
65 object_info_reply = QtCore.pyqtSignal(object)
66 object_info_reply = QtCore.pyqtSignal(object)
66
67
67 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
68 # 'object' interface
69 # 'object' interface
69 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
70
71
71 def __init__(self, *args, **kw):
72 def __init__(self, *args, **kw):
72 """ Reimplemented to ensure that QtCore.QObject is initialized first.
73 """ Reimplemented to ensure that QtCore.QObject is initialized first.
73 """
74 """
74 QtCore.QObject.__init__(self)
75 QtCore.QObject.__init__(self)
75 XReqSocketChannel.__init__(self, *args, **kw)
76 XReqSocketChannel.__init__(self, *args, **kw)
76
77
77 #---------------------------------------------------------------------------
78 #---------------------------------------------------------------------------
78 # 'XReqSocketChannel' interface
79 # 'XReqSocketChannel' interface
79 #---------------------------------------------------------------------------
80 #---------------------------------------------------------------------------
80
81
81 def call_handlers(self, msg):
82 def call_handlers(self, msg):
82 """ Reimplemented to emit signals instead of making callbacks.
83 """ Reimplemented to emit signals instead of making callbacks.
83 """
84 """
84 # Emit the generic signal.
85 # Emit the generic signal.
85 self.message_received.emit(msg)
86 self.message_received.emit(msg)
86
87
87 # Emit signals for specialized message types.
88 # Emit signals for specialized message types.
88 msg_type = msg['msg_type']
89 msg_type = msg['msg_type']
89 signal = getattr(self, msg_type, None)
90 signal = getattr(self, msg_type, None)
90 if signal:
91 if signal:
91 signal.emit(msg)
92 signal.emit(msg)
92
93
93 def _queue_request(self, msg, callback):
94 def _queue_request(self, msg, callback):
94 """ Reimplemented to skip callback handling.
95 """ Reimplemented to skip callback handling.
95 """
96 """
96 self.command_queue.put(msg)
97 self.command_queue.put(msg)
97
98
98
99
99 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
100 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
100
101
101 #---------------------------------------------------------------------------
102 #---------------------------------------------------------------------------
102 # 'object' interface
103 # 'object' interface
103 #---------------------------------------------------------------------------
104 #---------------------------------------------------------------------------
104
105
105 def __init__(self, *args, **kw):
106 def __init__(self, *args, **kw):
106 """ Reimplemented to ensure that QtCore.QObject is initialized first.
107 """ Reimplemented to ensure that QtCore.QObject is initialized first.
107 """
108 """
108 QtCore.QObject.__init__(self)
109 QtCore.QObject.__init__(self)
109 RepSocketChannel.__init__(self, *args, **kw)
110 RepSocketChannel.__init__(self, *args, **kw)
110
111
111
112
112 class QtKernelManager(KernelManager):
113 class QtKernelManager(KernelManager, QtCore.QObject):
113 """ A KernelManager that provides channels that use signals and slots.
114 """ A KernelManager that provides signals and slots.
114 """
115 """
115
116
117 __metaclass__ = MetaQObjectHasTraits
118
119 # Emitted when the kernel manager has started listening.
120 started_listening = QtCore.pyqtSignal()
121
122 # Emitted when the kernel manager has stopped listening.
123 stopped_listening = QtCore.pyqtSignal()
124
125 # Use Qt-specific channel classes that emit signals.
116 sub_channel_class = QtSubSocketChannel
126 sub_channel_class = QtSubSocketChannel
117 xreq_channel_class = QtXReqSocketChannel
127 xreq_channel_class = QtXReqSocketChannel
118 rep_channel_class = QtRepSocketChannel
128 rep_channel_class = QtRepSocketChannel
129
130 #---------------------------------------------------------------------------
131 # 'KernelManager' interface
132 #---------------------------------------------------------------------------
133
134 def start_listening(self):
135 """ Reimplemented to emit signal.
136 """
137 super(QtKernelManager, self).start_listening()
138 self.started_listening.emit()
139
140 def stop_listening(self):
141 """ Reimplemented to emit signal.
142 """
143 super(QtKernelManager, self).stop_listening()
144 self.stopped_listening.emit()
General Comments 0
You need to be logged in to leave comments. Login now