From 8af09932f39a2e522801976310ad6a2cdc48a272 Mon Sep 17 00:00:00 2001 From: Fernando Perez Date: Fri, 5 Aug 2011 00:10:41 -0700 Subject: [PATCH] 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. --- docs/examples/lib/internal_ipkernel.py | 68 ++++++++++++++ docs/examples/lib/ipkernel_qtapp.py | 75 ++++++++++++++++ docs/examples/lib/ipkernel_wxapp.py | 119 +++++++++++++++++++++++++ 3 files changed, 262 insertions(+) create mode 100644 docs/examples/lib/internal_ipkernel.py create mode 100755 docs/examples/lib/ipkernel_qtapp.py create mode 100755 docs/examples/lib/ipkernel_wxapp.py diff --git a/docs/examples/lib/internal_ipkernel.py b/docs/examples/lib/internal_ipkernel.py new file mode 100644 index 00000000000..30d58fbacf7 --- /dev/null +++ b/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() diff --git a/docs/examples/lib/ipkernel_qtapp.py b/docs/examples/lib/ipkernel_qtapp.py new file mode 100755 index 00000000000..93ab0c0dd5e --- /dev/null +++ b/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() diff --git a/docs/examples/lib/ipkernel_wxapp.py b/docs/examples/lib/ipkernel_wxapp.py new file mode 100755 index 00000000000..549a5ca6ca0 --- /dev/null +++ b/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()