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 |
@@ -121,10 +121,10 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
121 | 121 | # Initialize public and protected variables |
|
122 | 122 | self.ansi_codes = True |
|
123 | 123 | self.buffer_size = 500 |
|
124 | self.continuation_prompt = '> ' | |
|
125 | 124 | self.gui_completion = True |
|
126 | 125 | self._ansi_processor = QtAnsiCodeProcessor() |
|
127 | 126 | self._completion_widget = CompletionWidget(self) |
|
127 | self._continuation_prompt = '> ' | |
|
128 | 128 | self._executing = False |
|
129 | 129 | self._prompt = '' |
|
130 | 130 | self._prompt_pos = 0 |
@@ -261,7 +261,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
261 | 261 | if start_line == self._get_prompt_cursor().blockNumber(): |
|
262 | 262 | start_pos += len(self._prompt) |
|
263 | 263 | else: |
|
264 | start_pos += len(self.continuation_prompt) | |
|
264 | start_pos += len(self._continuation_prompt) | |
|
265 | 265 | if shift_down and self._in_buffer(position): |
|
266 | 266 | self._set_selection(position, start_pos) |
|
267 | 267 | else: |
@@ -271,7 +271,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
271 | 271 | elif key == QtCore.Qt.Key_Backspace and not alt_down: |
|
272 | 272 | |
|
273 | 273 | # Line deletion (remove continuation prompt) |
|
274 | len_prompt = len(self.continuation_prompt) | |
|
274 | len_prompt = len(self._continuation_prompt) | |
|
275 | 275 | if cursor.columnNumber() == len_prompt and \ |
|
276 | 276 | position != self._prompt_pos: |
|
277 | 277 | cursor.setPosition(position - len_prompt, |
@@ -323,6 +323,18 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
323 | 323 | else: |
|
324 | 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 | 338 | def paste(self): |
|
327 | 339 | """ Reimplemented to ensure that text is pasted in the editing region. |
|
328 | 340 | """ |
@@ -330,8 +342,8 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
330 | 342 | QtGui.QPlainTextEdit.paste(self) |
|
331 | 343 | |
|
332 | 344 | def print_(self, printer): |
|
333 |
""" Reimplemented to work around bug in PyQt |
|
|
334 |
|
|
|
345 | """ Reimplemented to work around a bug in PyQt: the C++ level 'print_' | |
|
346 | slot has the wrong signature. | |
|
335 | 347 | """ |
|
336 | 348 | QtGui.QPlainTextEdit.print_(self, printer) |
|
337 | 349 | |
@@ -363,13 +375,13 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
363 | 375 | input_buffer = str(cursor.selection().toPlainText()) |
|
364 | 376 | |
|
365 | 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 | 380 | def _set_input_buffer(self, string): |
|
369 | 381 | # Add continuation prompts where necessary |
|
370 | 382 | lines = string.splitlines() |
|
371 | 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 | 385 | string = '\n'.join(lines) |
|
374 | 386 | |
|
375 | 387 | # Replace buffer with new text |
@@ -389,7 +401,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
389 | 401 | if cursor.blockNumber() == self._get_prompt_cursor().blockNumber(): |
|
390 | 402 | return text[len(self._prompt):] |
|
391 | 403 | else: |
|
392 | return text[len(self.continuation_prompt):] | |
|
404 | return text[len(self._continuation_prompt):] | |
|
393 | 405 | else: |
|
394 | 406 | return None |
|
395 | 407 | |
@@ -547,18 +559,20 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
547 | 559 | """ |
|
548 | 560 | self.setTextCursor(self._get_selection_cursor(start, end)) |
|
549 | 561 | |
|
550 | def _show_prompt(self, prompt): | |
|
551 | """ Writes a new prompt at the end of the buffer. | |
|
562 | def _show_prompt(self, prompt=None): | |
|
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) | |
|
554 | self._prompt = prompt | |
|
566 | if prompt is not None: | |
|
567 | self._prompt = prompt | |
|
568 | self.appendPlainText('\n' + self._prompt) | |
|
555 | 569 | self._prompt_pos = self._get_end_cursor().position() |
|
556 | 570 | self._prompt_started() |
|
557 | 571 | |
|
558 | 572 | def _show_continuation_prompt(self): |
|
559 | 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 | 576 | self._prompt_started() |
|
563 | 577 | |
|
564 | 578 | def _write_text_keeping_prompt(self, text): |
@@ -570,7 +584,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):' | |||
|
570 | 584 | self._prompt_finished() |
|
571 | 585 | |
|
572 | 586 | self.appendPlainText(text) |
|
573 |
self._show_prompt( |
|
|
587 | self._show_prompt() | |
|
574 | 588 | self.input_buffer = input_buffer |
|
575 | 589 | |
|
576 | 590 | def _in_buffer(self, position): |
@@ -26,8 +26,8 b' class FrontendHighlighter(PygmentsHighlighter):' | |||
|
26 | 26 | """ Highlight a block of text. Reimplemented to highlight selectively. |
|
27 | 27 | """ |
|
28 | 28 | if self.highlighting_on: |
|
29 |
for prompt in (self._frontend._prompt, |
|
|
30 |
self._frontend. |
|
|
29 | for prompt in (self._frontend._continuation_prompt, | |
|
30 | self._frontend._prompt): | |
|
31 | 31 | if qstring.startsWith(prompt): |
|
32 | 32 | qstring.remove(0, len(prompt)) |
|
33 | 33 | self._current_offset = len(prompt) |
@@ -52,9 +52,14 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
52 | 52 | # 'QWidget' interface |
|
53 | 53 | #--------------------------------------------------------------------------- |
|
54 | 54 | |
|
55 |
def __init__(self, |
|
|
55 | def __init__(self, parent=None): | |
|
56 | 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 | 63 | self._blockbreaker = BlockBreaker(input_mode='replace') |
|
59 | 64 | self._call_tip_widget = CallTipWidget(self) |
|
60 | 65 | self._completion_lexer = CompletionLexer(PythonLexer()) |
@@ -62,9 +67,6 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
62 | 67 | self._highlighter = FrontendHighlighter(self) |
|
63 | 68 | self._kernel_manager = None |
|
64 | 69 | |
|
65 | self.continuation_prompt = '... ' | |
|
66 | self.kernel_manager = kernel_manager | |
|
67 | ||
|
68 | 70 | self.document().contentsChange.connect(self._document_contents_change) |
|
69 | 71 | |
|
70 | 72 | def focusOutEvent(self, event): |
@@ -144,10 +146,17 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
144 | 146 | return self._kernel_manager |
|
145 | 147 | |
|
146 | 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 | 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 | 160 | sub = self._kernel_manager.sub_channel |
|
152 | 161 | xreq = self._kernel_manager.xreq_channel |
|
153 | 162 | sub.message_received.disconnect(self._handle_sub) |
@@ -155,8 +164,16 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
155 | 164 | xreq.complete_reply.disconnect(self._handle_complete_reply) |
|
156 | 165 | xreq.object_info_reply.disconnect(self._handle_object_info_reply) |
|
157 | 166 | |
|
158 |
# |
|
|
167 | # Set the new kernel manager. | |
|
159 | 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 | 177 | sub = kernel_manager.sub_channel |
|
161 | 178 | xreq = kernel_manager.xreq_channel |
|
162 | 179 | sub.message_received.connect(self._handle_sub) |
@@ -164,7 +181,10 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
164 | 181 | xreq.complete_reply.connect(self._handle_complete_reply) |
|
165 | 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 | 189 | kernel_manager = property(_get_kernel_manager, _set_kernel_manager) |
|
170 | 190 | |
@@ -256,7 +276,7 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
256 | 276 | text = "ERROR: ABORTED\n" |
|
257 | 277 | self.appendPlainText(text) |
|
258 | 278 | self._hidden = True |
|
259 |
self._show_prompt( |
|
|
279 | self._show_prompt() | |
|
260 | 280 | self.executed.emit(rep) |
|
261 | 281 | |
|
262 | 282 | def _handle_complete_reply(self, rep): |
@@ -274,3 +294,9 b' class FrontendWidget(HistoryConsoleWidget):' | |||
|
274 | 294 | doc = rep['content']['docstring'] |
|
275 | 295 | if doc: |
|
276 | 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 |
@@ -13,8 +13,8 b' class IPythonWidget(FrontendWidget):' | |||
|
13 | 13 | # 'FrontendWidget' interface |
|
14 | 14 | #--------------------------------------------------------------------------- |
|
15 | 15 | |
|
16 |
def __init__(self, |
|
|
17 |
super(IPythonWidget, self).__init__( |
|
|
16 | def __init__(self, parent=None): | |
|
17 | super(IPythonWidget, self).__init__(parent) | |
|
18 | 18 | |
|
19 | 19 | self._magic_overrides = {} |
|
20 | 20 | |
@@ -87,7 +87,8 b" if __name__ == '__main__':" | |||
|
87 | 87 | |
|
88 | 88 | # Launch application |
|
89 | 89 | app = QtGui.QApplication([]) |
|
90 |
widget = IPythonWidget( |
|
|
90 | widget = IPythonWidget() | |
|
91 | widget.kernel_manager = kernel_manager | |
|
91 | 92 | widget.setWindowTitle('Python') |
|
92 | 93 | widget.resize(640, 480) |
|
93 | 94 | widget.show() |
@@ -1,4 +1,4 b'' | |||
|
1 |
""" |
|
|
1 | """ Defines a KernelManager that provides signals and slots. | |
|
2 | 2 | """ |
|
3 | 3 | |
|
4 | 4 | # System library imports. |
@@ -7,6 +7,7 b' from PyQt4 import QtCore' | |||
|
7 | 7 | # IPython imports. |
|
8 | 8 | from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \ |
|
9 | 9 | XReqSocketChannel, RepSocketChannel |
|
10 | from util import MetaQObjectHasTraits | |
|
10 | 11 | |
|
11 | 12 | |
|
12 | 13 | class QtSubSocketChannel(SubSocketChannel, QtCore.QObject): |
@@ -109,10 +110,35 b' class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):' | |||
|
109 | 110 | RepSocketChannel.__init__(self, *args, **kw) |
|
110 | 111 | |
|
111 | 112 | |
|
112 | class QtKernelManager(KernelManager): | |
|
113 |
""" A KernelManager that provides |
|
|
113 | class QtKernelManager(KernelManager, QtCore.QObject): | |
|
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 | 126 | sub_channel_class = QtSubSocketChannel |
|
117 | 127 | xreq_channel_class = QtXReqSocketChannel |
|
118 | 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