diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 1165d89..dd39ba7 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -199,6 +199,7 @@ class ConsoleWidget(Configurable, QtGui.QWidget): # Override shortcuts for all filtered widgets. elif etype == QtCore.QEvent.ShortcutOverride and \ + self.override_shortcuts and \ self._control_key_down(event.modifiers()) and \ event.key() in self._shortcuts: event.accept() diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index ecf08fe..a56d62a 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -2,7 +2,6 @@ from collections import namedtuple import signal import sys -import time # System library imports from pygments.lexers import PythonLexer @@ -90,6 +89,9 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # Emitted when an 'execute_reply' has been received from the kernel and # processed by the FrontendWidget. executed = QtCore.pyqtSignal(object) + + # Emitted when an exit request has been received from the kernel. + exit_requested = QtCore.pyqtSignal() # Protected class variables. _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos']) @@ -137,27 +139,12 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): complete = not self._input_splitter.push_accepts_more() return complete - def _execute(self, source, hidden, user_variables=None, - user_expressions=None): + def _execute(self, source, hidden): """ Execute 'source'. If 'hidden', do not show any output. See parent class :meth:`execute` docstring for full details. """ - # tmp code for testing, disable in real use with 'if 0'. Only delete - # this code once we have automated tests for these fields. - if 0: - user_variables = ['x', 'y', 'z'] - user_expressions = {'sum' : '1+1', - 'bad syntax' : 'klsdafj kasd f', - 'bad call' : 'range("hi")', - 'time' : 'time.time()', - } - # /end tmp code - - # FIXME - user_variables/expressions are not visible in API above us. - msg_id = self.kernel_manager.xreq_channel.execute(source, hidden, - user_variables, - user_expressions) + msg_id = self.kernel_manager.xreq_channel.execute(source, hidden) self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user') self._hidden = hidden @@ -235,7 +222,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ info = self._request_info.get('execute') if info and info.id == msg['parent_header']['msg_id'] and \ - not self._hidden: + info.kind == 'user' and not self._hidden: # Make sure that all output from the SUB channel has been processed # before writing a new prompt. self.kernel_manager.sub_channel.flush() @@ -371,38 +358,6 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): self._append_plain_text('Kernel process is either remote or ' 'unspecified. Cannot restart.\n') - def closeEvent(self, event): - reply = QtGui.QMessageBox.question(self, 'Python', - 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: - self.shutdown_kernel() - event.accept() - else: - event.ignore() - - # Move elsewhere to a better location later, possibly rename - def shutdown_kernel(self): - # Send quit message to kernel. Once we implement kernel-side setattr, - # this should probably be done that way, but for now this will do. - self.kernel_manager.xreq_channel.execute( - 'get_ipython().exit_now=True', silent=True) - - # Don't send any additional kernel kill messages immediately, to give - # the kernel a chance to properly execute shutdown actions. - # Wait for at most 2s, check every 0.1s. - for i in range(20): - if self.kernel_manager.is_alive: - time.sleep(0.1) - else: - break - else: - # OK, we've waited long enough. - self.kernel_manager.kill_kernel() - - # FIXME: This logic may not be quite right... Perhaps the quit call is - # made elsewhere by Qt... - QtGui.QApplication.quit() - #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface #--------------------------------------------------------------------------- diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index e7da719..910fd8e 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -117,12 +117,11 @@ class IPythonWidget(FrontendWidget): super(IPythonWidget, self).__init__(*args, **kw) # IPythonWidget protected variables. - self._previous_prompt_obj = None - self._payload_handlers = { + self._payload_handlers = { self._payload_source_edit : self._handle_payload_edit, self._payload_source_exit : self._handle_payload_exit, - self._payload_source_page : self._handle_payload_page, - } + self._payload_source_page : self._handle_payload_page } + self._previous_prompt_obj = None # Initialize widget styling. if self.style_sheet: @@ -253,22 +252,10 @@ class IPythonWidget(FrontendWidget): self._append_html(traceback) else: # This is the fallback for now, using plain text with ansi escapes - self._append_plain_text(traceback) - - # Payload handlers with generic interface: each takes the opaque payload - # dict, unpacks it and calls the underlying functions with the necessary - # arguments - def _handle_payload_edit(self, item): - self._edit(item['filename'], item['line_number']) - - def _handle_payload_exit(self, item): - QtCore.QCoreApplication.postEvent(self, QtGui.QCloseEvent()) - - def _handle_payload_page(self, item): - self._page(item['data']) + self._append_plain_text(traceback) def _process_execute_payload(self, item): - """ Reimplemented to handle %edit and paging payloads. + """ Reimplemented to dispatch payloads to handler methods. """ handler = self._payload_handlers.get(item['source']) if handler is None: @@ -413,6 +400,21 @@ class IPythonWidget(FrontendWidget): body = self.out_prompt % number return '%s' % body + #------ Payload handlers -------------------------------------------------- + + # Payload handlers with a generic interface: each takes the opaque payload + # dict, unpacks it and calls the underlying functions with the necessary + # arguments. + + def _handle_payload_edit(self, item): + self._edit(item['filename'], item['line_number']) + + def _handle_payload_exit(self, item): + self.exit_requested.emit() + + def _handle_payload_page(self, item): + self._page(item['data']) + #------ Trait change handlers --------------------------------------------- def _style_sheet_changed(self): diff --git a/IPython/frontend/qt/console/ipythonqt.py b/IPython/frontend/qt/console/ipythonqt.py index a0675d6..60ae3a8 100644 --- a/IPython/frontend/qt/console/ipythonqt.py +++ b/IPython/frontend/qt/console/ipythonqt.py @@ -1,6 +1,10 @@ """ A minimal application using the Qt console-style IPython frontend. """ +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + # Systemm library imports from PyQt4 import QtGui @@ -11,9 +15,48 @@ from IPython.frontend.qt.console.ipython_widget import IPythonWidget from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget from IPython.frontend.qt.kernelmanager import QtKernelManager +#----------------------------------------------------------------------------- # Constants +#----------------------------------------------------------------------------- + LOCALHOST = '127.0.0.1' +#----------------------------------------------------------------------------- +# Classes +#----------------------------------------------------------------------------- + +class MainWindow(QtGui.QMainWindow): + + #--------------------------------------------------------------------------- + # 'object' interface + #--------------------------------------------------------------------------- + + def __init__(self, frontend): + """ Create a MainWindow for the specified FrontendWidget. + """ + super(MainWindow, self).__init__() + self._frontend = frontend + self._frontend.exit_requested.connect(self.close) + self.setCentralWidget(frontend) + + #--------------------------------------------------------------------------- + # QWidget interface + #--------------------------------------------------------------------------- + + def closeEvent(self, event): + """ Reimplemented to prompt the user and close the kernel cleanly. + """ + reply = QtGui.QMessageBox.question(self, self.window().windowTitle(), + 'Close console?', QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + self._frontend.kernel_manager.shutdown_kernel() + event.accept() + else: + event.ignore() + +#----------------------------------------------------------------------------- +# Main entry point +#----------------------------------------------------------------------------- def main(): """ Entry point for application. @@ -89,8 +132,11 @@ def main(): widget = IPythonWidget(paging=args.paging) widget.gui_completion = args.gui_completion widget.kernel_manager = kernel_manager - widget.setWindowTitle('Python' if args.pure else 'IPython') - widget.show() + + # Create the main window. + window = MainWindow(widget) + window.setWindowTitle('Python' if args.pure else 'IPython') + window.show() # Start the application main loop. app.exec_() diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index ee5c3d1..fcbf5ef 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -685,10 +685,10 @@ class KernelManager(HasTraits): self._launch_args = kw.copy() if kw.pop('ipython', True): - from ipkernel import launch_kernel as launch + from ipkernel import launch_kernel else: - from pykernel import launch_kernel as launch - self.kernel, xrep, pub, req, hb = launch( + from pykernel import launch_kernel + self.kernel, xrep, pub, req, hb = launch_kernel( xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1], hb_port=hb[1], **kw) self.xreq_address = (LOCALHOST, xrep) @@ -696,6 +696,27 @@ class KernelManager(HasTraits): self.rep_address = (LOCALHOST, req) self.hb_address = (LOCALHOST, hb) + def shutdown_kernel(self): + """ Attempts to the stop the kernel process cleanly. If the kernel + cannot be stopped, it is killed, if possible. + """ + # Send quit message to kernel. Once we implement kernel-side setattr, + # this should probably be done that way, but for now this will do. + self.xreq_channel.execute('get_ipython().exit_now=True', silent=True) + + # Don't send any additional kernel kill messages immediately, to give + # the kernel a chance to properly execute shutdown actions. Wait for at + # most 2s, checking every 0.1s. + for i in range(20): + if self.is_alive: + time.sleep(0.1) + else: + break + else: + # OK, we've waited long enough. + if self.has_kernel: + self.kill_kernel() + def restart_kernel(self): """Restarts a kernel with the same arguments that were used to launch it. If the old kernel was launched with random ports, the same ports