Show More
@@ -199,6 +199,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
199 |
|
199 | |||
200 | # Override shortcuts for all filtered widgets. |
|
200 | # Override shortcuts for all filtered widgets. | |
201 | elif etype == QtCore.QEvent.ShortcutOverride and \ |
|
201 | elif etype == QtCore.QEvent.ShortcutOverride and \ | |
|
202 | self.override_shortcuts and \ | |||
202 | self._control_key_down(event.modifiers()) and \ |
|
203 | self._control_key_down(event.modifiers()) and \ | |
203 | event.key() in self._shortcuts: |
|
204 | event.key() in self._shortcuts: | |
204 | event.accept() |
|
205 | event.accept() |
@@ -2,7 +2,6 b'' | |||||
2 | from collections import namedtuple |
|
2 | from collections import namedtuple | |
3 | import signal |
|
3 | import signal | |
4 | import sys |
|
4 | import sys | |
5 | import time |
|
|||
6 |
|
5 | |||
7 | # System library imports |
|
6 | # System library imports | |
8 | from pygments.lexers import PythonLexer |
|
7 | from pygments.lexers import PythonLexer | |
@@ -90,6 +89,9 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
90 | # Emitted when an 'execute_reply' has been received from the kernel and |
|
89 | # Emitted when an 'execute_reply' has been received from the kernel and | |
91 | # processed by the FrontendWidget. |
|
90 | # processed by the FrontendWidget. | |
92 | executed = QtCore.pyqtSignal(object) |
|
91 | executed = QtCore.pyqtSignal(object) | |
|
92 | ||||
|
93 | # Emitted when an exit request has been received from the kernel. | |||
|
94 | exit_requested = QtCore.pyqtSignal() | |||
93 |
|
95 | |||
94 | # Protected class variables. |
|
96 | # Protected class variables. | |
95 | _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos']) |
|
97 | _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos']) | |
@@ -137,27 +139,12 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
137 | complete = not self._input_splitter.push_accepts_more() |
|
139 | complete = not self._input_splitter.push_accepts_more() | |
138 | return complete |
|
140 | return complete | |
139 |
|
141 | |||
140 |
def _execute(self, source, hidden |
|
142 | def _execute(self, source, hidden): | |
141 | user_expressions=None): |
|
|||
142 | """ Execute 'source'. If 'hidden', do not show any output. |
|
143 | """ Execute 'source'. If 'hidden', do not show any output. | |
143 |
|
144 | |||
144 | See parent class :meth:`execute` docstring for full details. |
|
145 | See parent class :meth:`execute` docstring for full details. | |
145 | """ |
|
146 | """ | |
146 | # tmp code for testing, disable in real use with 'if 0'. Only delete |
|
147 | msg_id = self.kernel_manager.xreq_channel.execute(source, hidden) | |
147 | # this code once we have automated tests for these fields. |
|
|||
148 | if 0: |
|
|||
149 | user_variables = ['x', 'y', 'z'] |
|
|||
150 | user_expressions = {'sum' : '1+1', |
|
|||
151 | 'bad syntax' : 'klsdafj kasd f', |
|
|||
152 | 'bad call' : 'range("hi")', |
|
|||
153 | 'time' : 'time.time()', |
|
|||
154 | } |
|
|||
155 | # /end tmp code |
|
|||
156 |
|
||||
157 | # FIXME - user_variables/expressions are not visible in API above us. |
|
|||
158 | msg_id = self.kernel_manager.xreq_channel.execute(source, hidden, |
|
|||
159 | user_variables, |
|
|||
160 | user_expressions) |
|
|||
161 | self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user') |
|
148 | self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user') | |
162 | self._hidden = hidden |
|
149 | self._hidden = hidden | |
163 |
|
150 | |||
@@ -235,7 +222,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
235 | """ |
|
222 | """ | |
236 | info = self._request_info.get('execute') |
|
223 | info = self._request_info.get('execute') | |
237 | if info and info.id == msg['parent_header']['msg_id'] and \ |
|
224 | if info and info.id == msg['parent_header']['msg_id'] and \ | |
238 | not self._hidden: |
|
225 | info.kind == 'user' and not self._hidden: | |
239 | # Make sure that all output from the SUB channel has been processed |
|
226 | # Make sure that all output from the SUB channel has been processed | |
240 | # before writing a new prompt. |
|
227 | # before writing a new prompt. | |
241 | self.kernel_manager.sub_channel.flush() |
|
228 | self.kernel_manager.sub_channel.flush() | |
@@ -371,38 +358,6 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):' | |||||
371 | self._append_plain_text('Kernel process is either remote or ' |
|
358 | self._append_plain_text('Kernel process is either remote or ' | |
372 | 'unspecified. Cannot restart.\n') |
|
359 | 'unspecified. Cannot restart.\n') | |
373 |
|
360 | |||
374 | def closeEvent(self, event): |
|
|||
375 | reply = QtGui.QMessageBox.question(self, 'Python', |
|
|||
376 | 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) |
|
|||
377 | if reply == QtGui.QMessageBox.Yes: |
|
|||
378 | self.shutdown_kernel() |
|
|||
379 | event.accept() |
|
|||
380 | else: |
|
|||
381 | event.ignore() |
|
|||
382 |
|
||||
383 | # Move elsewhere to a better location later, possibly rename |
|
|||
384 | def shutdown_kernel(self): |
|
|||
385 | # Send quit message to kernel. Once we implement kernel-side setattr, |
|
|||
386 | # this should probably be done that way, but for now this will do. |
|
|||
387 | self.kernel_manager.xreq_channel.execute( |
|
|||
388 | 'get_ipython().exit_now=True', silent=True) |
|
|||
389 |
|
||||
390 | # Don't send any additional kernel kill messages immediately, to give |
|
|||
391 | # the kernel a chance to properly execute shutdown actions. |
|
|||
392 | # Wait for at most 2s, check every 0.1s. |
|
|||
393 | for i in range(20): |
|
|||
394 | if self.kernel_manager.is_alive: |
|
|||
395 | time.sleep(0.1) |
|
|||
396 | else: |
|
|||
397 | break |
|
|||
398 | else: |
|
|||
399 | # OK, we've waited long enough. |
|
|||
400 | self.kernel_manager.kill_kernel() |
|
|||
401 |
|
||||
402 | # FIXME: This logic may not be quite right... Perhaps the quit call is |
|
|||
403 | # made elsewhere by Qt... |
|
|||
404 | QtGui.QApplication.quit() |
|
|||
405 |
|
||||
406 | #--------------------------------------------------------------------------- |
|
361 | #--------------------------------------------------------------------------- | |
407 | # 'FrontendWidget' protected interface |
|
362 | # 'FrontendWidget' protected interface | |
408 | #--------------------------------------------------------------------------- |
|
363 | #--------------------------------------------------------------------------- |
@@ -117,12 +117,11 b' class IPythonWidget(FrontendWidget):' | |||||
117 | super(IPythonWidget, self).__init__(*args, **kw) |
|
117 | super(IPythonWidget, self).__init__(*args, **kw) | |
118 |
|
118 | |||
119 | # IPythonWidget protected variables. |
|
119 | # IPythonWidget protected variables. | |
120 | self._previous_prompt_obj = None |
|
120 | self._payload_handlers = { | |
121 | self._payload_handlers = { |
|
|||
122 | self._payload_source_edit : self._handle_payload_edit, |
|
121 | self._payload_source_edit : self._handle_payload_edit, | |
123 | self._payload_source_exit : self._handle_payload_exit, |
|
122 | self._payload_source_exit : self._handle_payload_exit, | |
124 |
self._payload_source_page : self._handle_payload_page |
|
123 | self._payload_source_page : self._handle_payload_page } | |
125 | } |
|
124 | self._previous_prompt_obj = None | |
126 |
|
125 | |||
127 | # Initialize widget styling. |
|
126 | # Initialize widget styling. | |
128 | if self.style_sheet: |
|
127 | if self.style_sheet: | |
@@ -253,22 +252,10 b' class IPythonWidget(FrontendWidget):' | |||||
253 | self._append_html(traceback) |
|
252 | self._append_html(traceback) | |
254 | else: |
|
253 | else: | |
255 | # This is the fallback for now, using plain text with ansi escapes |
|
254 | # This is the fallback for now, using plain text with ansi escapes | |
256 | self._append_plain_text(traceback) |
|
255 | self._append_plain_text(traceback) | |
257 |
|
||||
258 | # Payload handlers with generic interface: each takes the opaque payload |
|
|||
259 | # dict, unpacks it and calls the underlying functions with the necessary |
|
|||
260 | # arguments |
|
|||
261 | def _handle_payload_edit(self, item): |
|
|||
262 | self._edit(item['filename'], item['line_number']) |
|
|||
263 |
|
||||
264 | def _handle_payload_exit(self, item): |
|
|||
265 | QtCore.QCoreApplication.postEvent(self, QtGui.QCloseEvent()) |
|
|||
266 |
|
||||
267 | def _handle_payload_page(self, item): |
|
|||
268 | self._page(item['data']) |
|
|||
269 |
|
256 | |||
270 | def _process_execute_payload(self, item): |
|
257 | def _process_execute_payload(self, item): | |
271 |
""" Reimplemented to |
|
258 | """ Reimplemented to dispatch payloads to handler methods. | |
272 | """ |
|
259 | """ | |
273 | handler = self._payload_handlers.get(item['source']) |
|
260 | handler = self._payload_handlers.get(item['source']) | |
274 | if handler is None: |
|
261 | if handler is None: | |
@@ -413,6 +400,21 b' class IPythonWidget(FrontendWidget):' | |||||
413 | body = self.out_prompt % number |
|
400 | body = self.out_prompt % number | |
414 | return '<span class="out-prompt">%s</span>' % body |
|
401 | return '<span class="out-prompt">%s</span>' % body | |
415 |
|
402 | |||
|
403 | #------ Payload handlers -------------------------------------------------- | |||
|
404 | ||||
|
405 | # Payload handlers with a generic interface: each takes the opaque payload | |||
|
406 | # dict, unpacks it and calls the underlying functions with the necessary | |||
|
407 | # arguments. | |||
|
408 | ||||
|
409 | def _handle_payload_edit(self, item): | |||
|
410 | self._edit(item['filename'], item['line_number']) | |||
|
411 | ||||
|
412 | def _handle_payload_exit(self, item): | |||
|
413 | self.exit_requested.emit() | |||
|
414 | ||||
|
415 | def _handle_payload_page(self, item): | |||
|
416 | self._page(item['data']) | |||
|
417 | ||||
416 | #------ Trait change handlers --------------------------------------------- |
|
418 | #------ Trait change handlers --------------------------------------------- | |
417 |
|
419 | |||
418 | def _style_sheet_changed(self): |
|
420 | def _style_sheet_changed(self): |
@@ -1,6 +1,10 b'' | |||||
1 | """ A minimal application using the Qt console-style IPython frontend. |
|
1 | """ A minimal application using the Qt console-style IPython frontend. | |
2 | """ |
|
2 | """ | |
3 |
|
3 | |||
|
4 | #----------------------------------------------------------------------------- | |||
|
5 | # Imports | |||
|
6 | #----------------------------------------------------------------------------- | |||
|
7 | ||||
4 | # Systemm library imports |
|
8 | # Systemm library imports | |
5 | from PyQt4 import QtGui |
|
9 | from PyQt4 import QtGui | |
6 |
|
10 | |||
@@ -11,9 +15,48 b' from IPython.frontend.qt.console.ipython_widget import IPythonWidget' | |||||
11 | from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget |
|
15 | from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget | |
12 | from IPython.frontend.qt.kernelmanager import QtKernelManager |
|
16 | from IPython.frontend.qt.kernelmanager import QtKernelManager | |
13 |
|
17 | |||
|
18 | #----------------------------------------------------------------------------- | |||
14 | # Constants |
|
19 | # Constants | |
|
20 | #----------------------------------------------------------------------------- | |||
|
21 | ||||
15 | LOCALHOST = '127.0.0.1' |
|
22 | LOCALHOST = '127.0.0.1' | |
16 |
|
23 | |||
|
24 | #----------------------------------------------------------------------------- | |||
|
25 | # Classes | |||
|
26 | #----------------------------------------------------------------------------- | |||
|
27 | ||||
|
28 | class MainWindow(QtGui.QMainWindow): | |||
|
29 | ||||
|
30 | #--------------------------------------------------------------------------- | |||
|
31 | # 'object' interface | |||
|
32 | #--------------------------------------------------------------------------- | |||
|
33 | ||||
|
34 | def __init__(self, frontend): | |||
|
35 | """ Create a MainWindow for the specified FrontendWidget. | |||
|
36 | """ | |||
|
37 | super(MainWindow, self).__init__() | |||
|
38 | self._frontend = frontend | |||
|
39 | self._frontend.exit_requested.connect(self.close) | |||
|
40 | self.setCentralWidget(frontend) | |||
|
41 | ||||
|
42 | #--------------------------------------------------------------------------- | |||
|
43 | # QWidget interface | |||
|
44 | #--------------------------------------------------------------------------- | |||
|
45 | ||||
|
46 | def closeEvent(self, event): | |||
|
47 | """ Reimplemented to prompt the user and close the kernel cleanly. | |||
|
48 | """ | |||
|
49 | reply = QtGui.QMessageBox.question(self, self.window().windowTitle(), | |||
|
50 | 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) | |||
|
51 | if reply == QtGui.QMessageBox.Yes: | |||
|
52 | self._frontend.kernel_manager.shutdown_kernel() | |||
|
53 | event.accept() | |||
|
54 | else: | |||
|
55 | event.ignore() | |||
|
56 | ||||
|
57 | #----------------------------------------------------------------------------- | |||
|
58 | # Main entry point | |||
|
59 | #----------------------------------------------------------------------------- | |||
17 |
|
60 | |||
18 | def main(): |
|
61 | def main(): | |
19 | """ Entry point for application. |
|
62 | """ Entry point for application. | |
@@ -89,8 +132,11 b' def main():' | |||||
89 | widget = IPythonWidget(paging=args.paging) |
|
132 | widget = IPythonWidget(paging=args.paging) | |
90 | widget.gui_completion = args.gui_completion |
|
133 | widget.gui_completion = args.gui_completion | |
91 | widget.kernel_manager = kernel_manager |
|
134 | widget.kernel_manager = kernel_manager | |
92 | widget.setWindowTitle('Python' if args.pure else 'IPython') |
|
135 | ||
93 | widget.show() |
|
136 | # Create the main window. | |
|
137 | window = MainWindow(widget) | |||
|
138 | window.setWindowTitle('Python' if args.pure else 'IPython') | |||
|
139 | window.show() | |||
94 |
|
140 | |||
95 | # Start the application main loop. |
|
141 | # Start the application main loop. | |
96 | app.exec_() |
|
142 | app.exec_() |
@@ -685,10 +685,10 b' class KernelManager(HasTraits):' | |||||
685 |
|
685 | |||
686 | self._launch_args = kw.copy() |
|
686 | self._launch_args = kw.copy() | |
687 | if kw.pop('ipython', True): |
|
687 | if kw.pop('ipython', True): | |
688 |
from ipkernel import launch_kernel |
|
688 | from ipkernel import launch_kernel | |
689 | else: |
|
689 | else: | |
690 |
from pykernel import launch_kernel |
|
690 | from pykernel import launch_kernel | |
691 | self.kernel, xrep, pub, req, hb = launch( |
|
691 | self.kernel, xrep, pub, req, hb = launch_kernel( | |
692 | xrep_port=xreq[1], pub_port=sub[1], |
|
692 | xrep_port=xreq[1], pub_port=sub[1], | |
693 | req_port=rep[1], hb_port=hb[1], **kw) |
|
693 | req_port=rep[1], hb_port=hb[1], **kw) | |
694 | self.xreq_address = (LOCALHOST, xrep) |
|
694 | self.xreq_address = (LOCALHOST, xrep) | |
@@ -696,6 +696,27 b' class KernelManager(HasTraits):' | |||||
696 | self.rep_address = (LOCALHOST, req) |
|
696 | self.rep_address = (LOCALHOST, req) | |
697 | self.hb_address = (LOCALHOST, hb) |
|
697 | self.hb_address = (LOCALHOST, hb) | |
698 |
|
698 | |||
|
699 | def shutdown_kernel(self): | |||
|
700 | """ Attempts to the stop the kernel process cleanly. If the kernel | |||
|
701 | cannot be stopped, it is killed, if possible. | |||
|
702 | """ | |||
|
703 | # Send quit message to kernel. Once we implement kernel-side setattr, | |||
|
704 | # this should probably be done that way, but for now this will do. | |||
|
705 | self.xreq_channel.execute('get_ipython().exit_now=True', silent=True) | |||
|
706 | ||||
|
707 | # Don't send any additional kernel kill messages immediately, to give | |||
|
708 | # the kernel a chance to properly execute shutdown actions. Wait for at | |||
|
709 | # most 2s, checking every 0.1s. | |||
|
710 | for i in range(20): | |||
|
711 | if self.is_alive: | |||
|
712 | time.sleep(0.1) | |||
|
713 | else: | |||
|
714 | break | |||
|
715 | else: | |||
|
716 | # OK, we've waited long enough. | |||
|
717 | if self.has_kernel: | |||
|
718 | self.kill_kernel() | |||
|
719 | ||||
699 | def restart_kernel(self): |
|
720 | def restart_kernel(self): | |
700 | """Restarts a kernel with the same arguments that were used to launch |
|
721 | """Restarts a kernel with the same arguments that were used to launch | |
701 | it. If the old kernel was launched with random ports, the same ports |
|
722 | it. If the old kernel was launched with random ports, the same ports |
General Comments 0
You need to be logged in to leave comments.
Login now