Skip to content

Commit

Permalink
Add two examples of GUI applications that can summon Qt consoles.
Browse files Browse the repository at this point in the history
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 8af0993
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 0 deletions.
68 changes: 68 additions & 0 deletions docs/examples/lib/internal_ipkernel.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()
75 changes: 75 additions & 0 deletions docs/examples/lib/ipkernel_qtapp.py
@@ -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()
119 changes: 119 additions & 0 deletions docs/examples/lib/ipkernel_wxapp.py
@@ -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.