From 65912dee5e2b8b1c42bb6cc857b3a324f6c8a158 2010-08-12 21:19:25 From: epatters Date: 2010-08-12 21:19:25 Subject: [PATCH] * The SVG payload matplotlib backend now works. * Added RichIPythonWidget, which supports plot payloads. * Added Qt console demo applicaton. * Fixed calltips not being resized properly. --- diff --git a/IPython/frontend/qt/console/call_tip_widget.py b/IPython/frontend/qt/console/call_tip_widget.py index cfac128..32801b9 100644 --- a/IPython/frontend/qt/console/call_tip_widget.py +++ b/IPython/frontend/qt/console/call_tip_widget.py @@ -111,10 +111,8 @@ class CallTipWidget(QtGui.QLabel): point = text_edit.mapToGlobal(point) self.move(point) self.setText(tip) - if self.isVisible(): - self.resize(self.sizeHint()) - else: - self.show() + self.resize(self.sizeHint()) + self.show() return True #-------------------------------------------------------------------------- diff --git a/IPython/frontend/qt/console/demo.py b/IPython/frontend/qt/console/demo.py new file mode 100644 index 0000000..3560603 --- /dev/null +++ b/IPython/frontend/qt/console/demo.py @@ -0,0 +1,54 @@ +""" A demo of Qt console-style IPython frontend. +""" + +# Systemm library imports +from PyQt4 import QtCore, QtGui + +# Local imports +from IPython.external.argparse import ArgumentParser +from IPython.frontend.qt.kernelmanager import QtKernelManager +from ipython_widget import IPythonWidget +from rich_ipython_widget import RichIPythonWidget + + +def main(): + """ Entry point for demo. + """ + # Parse command line arguments. + parser = ArgumentParser() + parser.add_argument('--pylab', action='store_true', + help='start kernel with pylab enabled') + parser.add_argument('--rich', action='store_true', + help='use rich text frontend') + namespace = parser.parse_args() + + # Don't let Qt or ZMQ swallow KeyboardInterupts. + import signal + signal.signal(signal.SIGINT, signal.SIG_DFL) + + # Create a KernelManager and start a kernel. + kernel_manager = QtKernelManager() + if namespace.pylab: + if namespace.rich: + kernel_manager.start_kernel(pylab='payload-svg') + else: + kernel_manager.start_kernel(pylab='qt4') + else: + kernel_manager.start_kernel() + kernel_manager.start_channels() + + # Launch the application. + app = QtGui.QApplication([]) + if namespace.rich: + widget = RichIPythonWidget() + else: + widget = IPythonWidget() + widget.kernel_manager = kernel_manager + widget.setWindowTitle('Python') + widget.resize(640, 480) + widget.show() + app.exec_() + + +if __name__ == '__main__': + main() diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py index 5199551..26ba28d 100644 --- a/IPython/frontend/qt/console/frontend_widget.py +++ b/IPython/frontend/qt/console/frontend_widget.py @@ -337,12 +337,16 @@ class FrontendWidget(HistoryConsoleWidget): # before writing a new prompt. self.kernel_manager.sub_channel.flush() - status = reply['content']['status'] - if status == 'error': + content = reply['content'] + status = content['status'] + if status == 'ok': + self._handle_execute_payload(content['payload']) + elif status == 'error': self._handle_execute_error(reply) elif status == 'aborted': text = "ERROR: ABORTED\n" self._append_plain_text(text) + self._hidden = True self._show_interpreter_prompt() self.executed.emit(reply) @@ -352,6 +356,9 @@ class FrontendWidget(HistoryConsoleWidget): traceback = ''.join(content['traceback']) self._append_plain_text(traceback) + def _handle_execute_payload(self, payload): + pass + def _handle_complete_reply(self, rep): cursor = self._get_cursor() if rep['parent_header']['msg_id'] == self._complete_id and \ diff --git a/IPython/frontend/qt/console/ipython_widget.py b/IPython/frontend/qt/console/ipython_widget.py index 4f0bd72..127fc56 100644 --- a/IPython/frontend/qt/console/ipython_widget.py +++ b/IPython/frontend/qt/console/ipython_widget.py @@ -182,25 +182,3 @@ class IPythonWidget(FrontendWidget): """ block = self._control.document().lastBlock() self._previous_prompt_blocks.append((block, block.length())) - - -if __name__ == '__main__': - from IPython.frontend.qt.kernelmanager import QtKernelManager - - # Don't let Qt or ZMQ swallow KeyboardInterupts. - import signal - signal.signal(signal.SIGINT, signal.SIG_DFL) - - # Create a KernelManager. - kernel_manager = QtKernelManager() - kernel_manager.start_kernel() - kernel_manager.start_channels() - - # Launch the application. - app = QtGui.QApplication([]) - widget = IPythonWidget() - widget.kernel_manager = kernel_manager - widget.setWindowTitle('Python') - widget.resize(640, 480) - widget.show() - app.exec_() diff --git a/IPython/frontend/qt/console/rich_ipython_widget.py b/IPython/frontend/qt/console/rich_ipython_widget.py new file mode 100644 index 0000000..f87e3ec --- /dev/null +++ b/IPython/frontend/qt/console/rich_ipython_widget.py @@ -0,0 +1,43 @@ +# System library imports +from PyQt4 import QtCore, QtGui + +# Local imports +from IPython.frontend.qt.util import image_from_svg +from ipython_widget import IPythonWidget + + +class RichIPythonWidget(IPythonWidget): + """ An IPythonWidget that supports rich text, including lists, images, and + tables. Note that raw performance will be reduced compared to the plain + text version. + """ + + #--------------------------------------------------------------------------- + # 'QObject' interface + #--------------------------------------------------------------------------- + + def __init__(self, parent=None): + """ Create a RichIPythonWidget. + """ + super(RichIPythonWidget, self).__init__(kind='rich', parent=parent) + + #--------------------------------------------------------------------------- + # 'FrontendWidget' interface + #--------------------------------------------------------------------------- + + def _handle_execute_payload(self, payload): + """ Reimplemented to handle pylab plot payloads. + """ + super(RichIPythonWidget, self)._handle_execute_payload(payload) + + plot_payload = payload.get('plot', None) + if plot_payload and plot_payload['format'] == 'svg': + try: + image = image_from_svg(plot_payload['data']) + except ValueError: + self._append_plain_text('Received invalid plot data.') + else: + cursor = self._get_end_cursor() + cursor.insertBlock() + cursor.insertImage(image) + cursor.insertBlock() diff --git a/IPython/frontend/qt/kernelmanager.py b/IPython/frontend/qt/kernelmanager.py index bc84a32..ee3ba64 100644 --- a/IPython/frontend/qt/kernelmanager.py +++ b/IPython/frontend/qt/kernelmanager.py @@ -153,10 +153,6 @@ class QtKernelManager(KernelManager, QtCore.QObject): xreq_channel_class = QtXReqSocketChannel rep_channel_class = QtRepSocketChannel - def __init__(self, *args, **kw): - QtCore.QObject.__init__(self) - KernelManager.__init__(self, *args, **kw) - #--------------------------------------------------------------------------- # 'object' interface #--------------------------------------------------------------------------- diff --git a/IPython/zmq/kernel.py b/IPython/zmq/kernel.py index 91d41f1..46d1956 100755 --- a/IPython/zmq/kernel.py +++ b/IPython/zmq/kernel.py @@ -181,15 +181,16 @@ class Kernel(object): # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate. # Common funtionality should be refactored. - import matplotlib - # We must set the desired backend before importing pylab. + import matplotlib if backend: - matplotlib.use(self._pylab_map[backend]) - - # This must be imported last in the matplotlib series, after - # backend/interactivity choices have been made. - import matplotlib.pylab as pylab + backend_id = self._pylab_map[backend] + if backend_id.startswith('module://'): + # Work around bug in matplotlib: matplotlib.use converts the + # backend_id to lowercase even if a module name is specified! + matplotlib.rcParams['backend'] = backend_id + else: + matplotlib.use(backend_id) # Import numpy as np/pyplot as plt are conventions we're trying to # somewhat standardize on. Making them available to users by default @@ -511,7 +512,8 @@ given, the GUI backend is matplotlib's, otherwise use one of: \ kernel.start() -def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False): +def launch_kernel(xrep_port=0, pub_port=0, req_port=0, + pylab=False, independent=False): """ Launches a localhost kernel, binding to the specified ports. Parameters @@ -525,6 +527,11 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False): req_port : int, optional The port to use for the REQ (raw input) channel. + pylab : bool or string, optional (default False) + If not False, the kernel will be launched with pylab enabled. If a + string is passed, matplotlib will use the specified backend. Otherwise, + matplotlib's default backend will be used. + independent : bool, optional (default False) If set, the kernel process is guaranteed to survive if this process dies. If not set, an effort is made to ensure that the kernel is killed @@ -557,11 +564,17 @@ def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False): pub_port = ports.pop(0) if req_port <= 0: req_port = ports.pop(0) - - # Spawn a kernel. + + # Build the kernel launch command. command = 'from IPython.zmq.kernel import main; main()' arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port), '--pub', str(pub_port), '--req', str(req_port) ] + if pylab: + arguments.append('--pylab') + if isinstance(pylab, basestring): + arguments.append(pylab) + + # Spawn a kernel. if independent: if sys.platform == 'win32': proc = Popen(['start', '/b'] + arguments, shell=True) diff --git a/IPython/zmq/kernelmanager.py b/IPython/zmq/kernelmanager.py index 4981f10..7becccc 100644 --- a/IPython/zmq/kernelmanager.py +++ b/IPython/zmq/kernelmanager.py @@ -433,23 +433,22 @@ class KernelManager(HasTraits): # The kernel process with which the KernelManager is communicating. kernel = Instance(Popen) + # The addresses for the communication channels. + xreq_address = TCPAddress((LOCALHOST, 0)) + sub_address = TCPAddress((LOCALHOST, 0)) + rep_address = TCPAddress((LOCALHOST, 0)) + # The classes to use for the various channels. xreq_channel_class = Type(XReqSocketChannel) sub_channel_class = Type(SubSocketChannel) rep_channel_class = Type(RepSocketChannel) # Protected traits. - xreq_address = TCPAddress((LOCALHOST, 0)) - sub_address = TCPAddress((LOCALHOST, 0)) - rep_address = TCPAddress((LOCALHOST, 0)) _xreq_channel = Any _sub_channel = Any _rep_channel = Any - def __init__(self, **kwargs): - super(KernelManager, self).__init__(**kwargs) - - #--------------------------------- ----------------------------------------- + #-------------------------------------------------------------------------- # Channel management methods: #-------------------------------------------------------------------------- @@ -486,11 +485,16 @@ class KernelManager(HasTraits): # Kernel process management methods: #-------------------------------------------------------------------------- - def start_kernel(self): + def start_kernel(self, pylab=False): """Starts a kernel process and configures the manager to use it. If random ports (port=0) are being used, this method must be called before the channels are created. + + Parameters: + ----------- + pylab : bool or string, optional (default False) + See IPython.zmq.kernel.launch_kernel for documentation. """ xreq, sub, rep = self.xreq_address, self.sub_address, self.rep_address if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST: @@ -499,7 +503,7 @@ class KernelManager(HasTraits): "configured properly.") self.kernel, xrep, pub, req = launch_kernel( - xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1]) + xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1], pylab=pylab) self.xreq_address = (LOCALHOST, xrep) self.sub_address = (LOCALHOST, pub) self.rep_address = (LOCALHOST, req) diff --git a/IPython/zmq/pylab/backend_payload_svg.py b/IPython/zmq/pylab/backend_payload_svg.py index 5dd2743..ba1b38e 100644 --- a/IPython/zmq/pylab/backend_payload_svg.py +++ b/IPython/zmq/pylab/backend_payload_svg.py @@ -12,7 +12,7 @@ from backend_payload import add_plot_payload def show(): """ Deliver a SVG payload. """ - figure_manager = Gcf.get_actve() + figure_manager = Gcf.get_active() if figure_manager is not None: data = svg_from_canvas(figure_manager.canvas) add_plot_payload('svg', data)