Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
3 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -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() |