Permalink
Browse files

Add two examples of GUI applications that can summon Qt consoles.

These show how to instantiate a kernel inside a GUI app and then
connect Qt consoles to talk to it, providing interactive access to the
namespace of the GUI.
  • Loading branch information...
fperez committed Aug 5, 2011
1 parent b63d2de commit 8af09932f39a2e522801976310ad6a2cdc48a272
Showing with 262 additions and 0 deletions.
  1. +68 −0 docs/examples/lib/internal_ipkernel.py
  2. +75 −0 docs/examples/lib/ipkernel_qtapp.py
  3. +119 −0 docs/examples/lib/ipkernel_wxapp.py
@@ -0,0 +1,68 @@
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+import subprocess
+import sys
+
+from IPython.zmq.ipkernel import IPKernelApp
+
+#-----------------------------------------------------------------------------
+# Functions and classes
+#-----------------------------------------------------------------------------
+def pylab_kernel(gui):
+ """Launch and return an IPython kernel with pylab support for the desired gui
+ """
+ kernel = IPKernelApp()
+ # FIXME: we're hardcoding the heartbeat port b/c of a bug in IPython 0.11
+ # that will set it to 0 if not specified. Once this is fixed, the --hb
+ # parameter can be omitted and all ports will be automatic
+ kernel.initialize(['python', '--pylab=%s' % gui, '--hb=19999',
+ #'--log-level=10'
+ ])
+ return kernel
+
+
+def qtconsole_cmd(kernel):
+ """Compute the command to connect a qt console to an already running kernel
+ """
+ ports = ['--%s=%d' % (name, port) for name, port in kernel.ports.items()]
+ return ['ipython', 'qtconsole', '--existing'] + ports
+
+
+class InternalIPKernel(object):
+
+ def init_ipkernel(self, backend):
+ # Start IPython kernel with GUI event loop and pylab support
+ self.ipkernel = pylab_kernel(backend)
+ # To create and track active qt consoles
+ self._qtconsole_cmd = qtconsole_cmd(self.ipkernel)
+ self.consoles = []
+
+ # This application will also act on the shell user namespace
+ self.namespace = self.ipkernel.shell.user_ns
+ # Keys present at startup so we don't print the entire pylab/numpy
+ # namespace when the user clicks the 'namespace' button
+ self._init_keys = set(self.namespace.keys())
+
+ # Example: a variable that will be seen by the user in the shell, and
+ # that the GUI modifies (the 'Counter++' button increments it):
+ self.namespace['app_counter'] = 0
+ #self.namespace['ipkernel'] = self.ipkernel # dbg
+
+ def print_namespace(self, evt=None):
+ print "\n***Variables in User namespace***"
+ for k, v in self.namespace.iteritems():
+ if k not in self._init_keys and not k.startswith('_'):
+ print '%s -> %r' % (k, v)
+ sys.stdout.flush()
+
+ def new_qt_console(self, evt=None):
+ self.consoles.append(subprocess.Popen(self._qtconsole_cmd))
+
+ def count(self, evt=None):
+ self.namespace['app_counter'] += 1
+
+ def cleanup_consoles(self, evt=None):
+ for c in self.consoles:
+ c.kill()
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+"""Example integrating an IPython kernel into a GUI App.
+
+This trivial GUI application internally starts an IPython kernel, to which Qt
+consoles can be connected either by the user at the command line or started
+from the GUI itself, via a button. The GUI can also manipulate one variable in
+the kernel's namespace, and print the namespace to the console.
+
+Play with it by running the script and then opening one or more consoles, and
+pushing the 'Counter++' and 'Namespace' buttons.
+
+Upon exit, it should automatically close all consoles opened from the GUI.
+
+Consoles attached separately from a terminal will not be terminated, though
+they will notice that their kernel died.
+"""
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
+from PyQt4 import Qt
+
+from internal_ipkernel import InternalIPKernel
+
+#-----------------------------------------------------------------------------
+# Functions and classes
+#-----------------------------------------------------------------------------
+class SimpleWindow(Qt.QWidget, InternalIPKernel):
+
+ def __init__(self, app):
+ Qt.QWidget.__init__(self)
+ self.app = app
+ self.add_widgets()
+ self.init_ipkernel('qt')
+
+ def add_widgets(self):
+ self.setGeometry(300, 300, 400, 70)
+ self.setWindowTitle('IPython in your app')
+
+ # Add simple buttons:
+ console = Qt.QPushButton('Qt Console', self)
+ console.setGeometry(10, 10, 100, 35)
+ self.connect(console, Qt.SIGNAL('clicked()'), self.new_qt_console)
+
+ namespace = Qt.QPushButton('Namespace', self)
+ namespace.setGeometry(120, 10, 100, 35)
+ self.connect(namespace, Qt.SIGNAL('clicked()'), self.print_namespace)
+
+ count = Qt.QPushButton('Count++', self)
+ count.setGeometry(230, 10, 80, 35)
+ self.connect(count, Qt.SIGNAL('clicked()'), self.count)
+
+ # Quit and cleanup
+ quit = Qt.QPushButton('Quit', self)
+ quit.setGeometry(320, 10, 60, 35)
+ self.connect(quit, Qt.SIGNAL('clicked()'), Qt.qApp, Qt.SLOT('quit()'))
+
+ self.app.connect(self.app, Qt.SIGNAL("lastWindowClosed()"),
+ self.app, Qt.SLOT("quit()"))
+
+ self.app.aboutToQuit.connect(self.cleanup_consoles)
+
+#-----------------------------------------------------------------------------
+# Main script
+#-----------------------------------------------------------------------------
+
+if __name__ == "__main__":
+ app = Qt.QApplication([])
+ # Create our window
+ win = SimpleWindow(app)
+ win.show()
+
+ # Very important, IPython-specific step: this gets GUI event loop
+ # integration going, and it replaces calling app.exec_()
+ win.ipkernel.start()
@@ -0,0 +1,119 @@
+#!/usr/bin/env python
+"""Example integrating an IPython kernel into a GUI App.
+
+This trivial GUI application internally starts an IPython kernel, to which Qt
+consoles can be connected either by the user at the command line or started
+from the GUI itself, via a button. The GUI can also manipulate one variable in
+the kernel's namespace, and print the namespace to the console.
+
+Play with it by running the script and then opening one or more consoles, and
+pushing the 'Counter++' and 'Namespace' buttons.
+
+Upon exit, it should automatically close all consoles opened from the GUI.
+
+Consoles attached separately from a terminal will not be terminated, though
+they will notice that their kernel died.
+
+Ref: Modified from wxPython source code wxPython/samples/simple/simple.py
+"""
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+import sys
+
+import wx
+
+from internal_ipkernel import InternalIPKernel
+
+#-----------------------------------------------------------------------------
+# Functions and classes
+#-----------------------------------------------------------------------------
+
+class MyFrame(wx.Frame, InternalIPKernel):
+ """
+ This is MyFrame. It just shows a few controls on a wxPanel,
+ and has a simple menu.
+ """
+
+ def __init__(self, parent, title):
+ wx.Frame.__init__(self, parent, -1, title,
+ pos=(150, 150), size=(350, 285))
+
+ # Create the menubar
+ menuBar = wx.MenuBar()
+
+ # and a menu
+ menu = wx.Menu()
+
+ # add an item to the menu, using \tKeyName automatically
+ # creates an accelerator, the third param is some help text
+ # that will show up in the statusbar
+ menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Exit this simple sample")
+
+ # bind the menu event to an event handler
+ self.Bind(wx.EVT_MENU, self.OnTimeToClose, id=wx.ID_EXIT)
+
+ # and put the menu on the menubar
+ menuBar.Append(menu, "&File")
+ self.SetMenuBar(menuBar)
+
+ self.CreateStatusBar()
+
+ # Now create the Panel to put the other controls on.
+ panel = wx.Panel(self)
+
+ # and a few controls
+ text = wx.StaticText(panel, -1, "Hello World!")
+ text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD))
+ text.SetSize(text.GetBestSize())
+ qtconsole_btn = wx.Button(panel, -1, "Qt Console")
+ ns_btn = wx.Button(panel, -1, "Namespace")
+ count_btn = wx.Button(panel, -1, "Count++")
+ close_btn = wx.Button(panel, -1, "Quit")
+
+ # bind the button events to handlers
+ self.Bind(wx.EVT_BUTTON, self.new_qt_console, qtconsole_btn)
+ self.Bind(wx.EVT_BUTTON, self.print_namespace, ns_btn)
+ self.Bind(wx.EVT_BUTTON, self.count, count_btn)
+ self.Bind(wx.EVT_BUTTON, self.OnTimeToClose, close_btn)
+
+ # Use a sizer to layout the controls, stacked vertically and with
+ # a 10 pixel border around each
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ for ctrl in [text, qtconsole_btn, ns_btn, count_btn, close_btn]:
+ sizer.Add(ctrl, 0, wx.ALL, 10)
+ panel.SetSizer(sizer)
+ panel.Layout()
+
+ # Start the IPython kernel with gui support
+ self.init_ipkernel('wx')
+
+ def OnTimeToClose(self, evt):
+ """Event handler for the button click."""
+ print "See ya later!"
+ sys.stdout.flush()
+ self.cleanup_consoles(evt)
+ self.Close()
+ # Not sure why, but our IPython kernel seems to prevent normal WX
+ # shutdown, so an explicit exit() call is needed.
+ sys.exit()
+
+
+class MyApp(wx.App):
+ def OnInit(self):
+ frame = MyFrame(None, "Simple wxPython App")
+ self.SetTopWindow(frame)
+ frame.Show(True)
+ self.ipkernel = frame.ipkernel
+ return True
+
+#-----------------------------------------------------------------------------
+# Main script
+#-----------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ app = MyApp(redirect=False, clearSigInt=False)
+
+ # Very important, IPython-specific step: this gets GUI event loop
+ # integration going, and it replaces calling app.MainLoop()
+ app.ipkernel.start()

0 comments on commit 8af0993

Please sign in to comment.