Trash: Old Embedding Tkinter - ShuaiYAN/ipython GitHub Wiki
This is just an extension of the very useful code posted on this cookbook to embed IPython in a GTK text widget to enable IPython to be used within Tkinter.
The original GTK code can be found here.
The GTK code was re-written in Tkinter http://wiki.python.org/moin/TkInter in oder to allow IPython to be embedded in the CCP1GUI chemistry GUI.
The source code is shown below, but can also found in the ipython directory of the CCP1GUI source code, which can be downloaded from sourceforge.
""" This is a modified version of source code from the Accerciser project (http://live.gnome.org/accerciser). Backend to the console plugin. @author: Eitan Isaacson @organization: IBM Corporation @copyright: Copyright (c) 2007 IBM Corporation @license: BSD All rights reserved. This program and the accompanying materials are made available under the terms of the BSD which accompanies this distribution, and is available at U{http://www.opensource.org/licenses/bsd-license.php} """ import re import sys import os from StringIO import StringIO import Tkinter import IPython class IterableIPShell: def __init__(self,argv=None,user_ns=None,user_global_ns=None, cin=None, cout=None,cerr=None, input_func=None): if input_func: IPython.iplib.raw_input_original = input_func if cin: IPython.Shell.Term.cin = cin if cout: IPython.Shell.Term.cout = cout if cerr: IPython.Shell.Term.cerr = cerr if argv is None: argv=[] # This is to get rid of the blockage that occurs during # IPython.Shell.InteractiveShell.user_setup() IPython.iplib.raw_input = lambda x: None self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) os.environ['TERM'] = 'dumb' excepthook = sys.excepthook self.IP = IPython.Shell.make_IPython(argv,user_ns=user_ns, user_global_ns=user_global_ns, embedded=True, shell_class=IPython.Shell.InteractiveShell) self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), header='IPython system call: ', verbose=self.IP.rc.system_verbose) sys.excepthook = excepthook self.iter_more = 0 self.history_level = 0 self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') def execute(self): self.history_level = 0 orig_stdout = sys.stdout sys.stdout = IPython.Shell.Term.cout try: line = self.IP.raw_input(None, self.iter_more) if self.IP.autoindent: self.IP.readline_startup_hook(None) except KeyboardInterrupt: self.IP.write('\nKeyboardInterrupt\n') self.IP.resetbuffer() # keep cache in sync with the prompt counter: self.IP.outputcache.prompt_count -= 1 if self.IP.autoindent: self.IP.indent_current_nsp = 0 self.iter_more = 0 except: self.IP.showtraceback() else: self.iter_more = self.IP.push(line) if (self.IP.SyntaxTB.last_syntax_error and self.IP.rc.autoedit_syntax): self.IP.edit_syntax_error() if self.iter_more: self.prompt = str(self.IP.outputcache.prompt2).strip() if self.IP.autoindent: self.IP.readline_startup_hook(self.IP.pre_readline) else: self.prompt = str(self.IP.outputcache.prompt1).strip() sys.stdout = orig_stdout def historyBack(self): self.history_level -= 1 return self._getHistory() def historyForward(self): self.history_level += 1 return self._getHistory() def _getHistory(self): try: rv = self.IP.user_ns['In'][self.history_level].strip('\n') except IndexError: self.history_level = 0 rv = '' return rv def updateNamespace(self, ns_dict): self.IP.user_ns.update(ns_dict) def complete(self, line): split_line = self.complete_sep.split(line) possibilities = self.IP.complete(split_line[-1]) if possibilities: common_prefix = reduce(self._commonPrefix, possibilities) completed = line[:-len(split_line[-1])]+common_prefix else: completed = line return completed, possibilities def _commonPrefix(self, str1, str2): for i in range(len(str1)): if not str2.startswith(str1[:i+1]): return str1[:i] return str1 def shell(self, cmd,verbose=0,debug=0,header=''): stat = 0 if verbose or debug: print header+cmd # flush stdout so we don't mangle python's buffering if not debug: input, output = os.popen4(cmd) print output.read() output.close() input.close() ansi_colors = {'0;30': 'Black', '0;31': 'Red', '0;32': 'Green', '0;33': 'Brown', '0;34': 'Blue', '0;35': 'Purple', '0;36': 'Cyan', '0;37': 'LightGray', '1;30': 'DarkGray', '1;31': 'DarkRed', '1;32': 'SeaGreen', '1;33': 'Yellow', '1;34': 'LightBlue', '1;35': 'MediumPurple', '1;36': 'LightCyan', '1;37': 'White'} class TkConsoleView(Tkinter.Text): def __init__(self,root): Tkinter.Text.__init__(self,root) # As the stdout,stderr etc. get fiddled about with we need to put any # debug output into a file self.debug=0 if self.debug: self.o = open('debug.out','w') # Keeps track of where the insert cursor should be on the entry line self.mark = 'scroll_mark' self.mark_set(self.mark,Tkinter.END) self.mark_gravity(self.mark,Tkinter.RIGHT) # Set the tags for colouring the text for code in ansi_colors: self.tag_config(code, foreground=ansi_colors[code]) self.tag_config('notouch') # Tag for indicating what areas of the widget aren't editable # colour_pat matches the colour tags and places these in a group # match character with hex value 01 (start of heading?) zero or more times, followed by # the hex character 1b (escape) then "[" and group ...things.. followed by m (?) and then # hex character 02 (start of text) zero or more times self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') self.line_start = 'line_start' # Tracks start of user input on the line (excluding prompt) self.mark_set(self.line_start,Tkinter.INSERT) self.mark_gravity(self.line_start,Tkinter.LEFT) self._setBindings() def write(self, text, editable=False): segments = self.color_pat.split(text) # First is blank line segment = segments.pop(0) # Keep track of where we started entering text so we can set as non-editable self.start_mark = 'start_mark' self.mark_set(self.start_mark,Tkinter.INSERT) self.mark_gravity(self.start_mark,Tkinter.LEFT) self.insert(Tkinter.END, segment) if segments: # Just return the colour tags ansi_tags = self.color_pat.findall(text) for tag in ansi_tags: i = segments.index(tag) self.insert(Tkinter.END,segments[i+1],tag) segments.pop(i) if not editable: if self.debug: print "adding notouch between %s : %s" % ( self.index(self.start_mark),\ self.index(Tkinter.INSERT) ) self.tag_add('notouch',self.start_mark,"%s-1c" % Tkinter.INSERT) self.mark_unset(self.start_mark) #jmht self.scroll_mark_onscreen(self.mark) def showBanner(self,banner): """Print the supplied banner on starting the shell""" self.write(banner) def showPrompt(self, prompt): self.write(prompt) self.mark_set(self.line_start,Tkinter.INSERT) self.see(Tkinter.INSERT) #Make sure we can always see the prompt def changeLine(self, text): self.delete(self.line_start,"%s lineend" % self.line_start) self.write(text, True) def getCurrentLine(self): rv = self.get(self.line_start,Tkinter.END) if self.debug: print >> self.o,"getCurrentline: %s" % rv print >> self.o,"INSERT: %s" % Tkinter.END print >> self.o,"END: %s" % Tkinter.INSERT print >> self.o,"line_start: %s" % self.index(self.line_start) return rv def showReturned(self, text): self.tag_add('notouch',self.line_start,"%s lineend" % self.line_start ) self.write('\n'+text) if text: self.write('\n') self.showPrompt(self.prompt) #self.mark_set(self.line_start,Tkinter.END) #jmht don't need this as showprompt sets mark def _setBindings(self): """ Bind the keys we require. REM: if a bound function returns "break" then no other bindings are called If it returns None, then the other default bindings are called. """ self.bind("<Key>",self.processKeyPress) self.bind("<Return>",self.processEnterPress) self.bind("<Up>",self.processUpPress) self.bind("<Down>",self.processDownPress) self.bind("<Tab>",self.processTabPress) self.bind("<BackSpace>",self.processBackSpacePress) def isEditable(self): """ Scan the notouch tag range in pairs and see if the INSERT index falls between any of them. """ ranges = self.tag_ranges('notouch') first=None for idx in ranges: if not first: first=idx continue else: if self.debug: print "Comparing %s between %s : %s " % (self.index(Tkinter.INSERT),first,idx) if self.compare( Tkinter.INSERT,'>=',first ) and \ self.compare( Tkinter.INSERT,'<=',idx ): return False first=None return True def processKeyPress(self,event): if self.debug: print >>self.o,"processKeyPress got key: %s" % event.char print >>self.o,"processKeyPress INSERT: %s" % self.index(Tkinter.INSERT) print >>self.o,"processKeyPress END: %s" % self.index(Tkinter.END) if not self.isEditable(): # Move cursor mark to start of line self.mark_set(Tkinter.INSERT,self.mark) # Make sure line_start follows inserted text self.mark_set(self.mark,"%s+1c" % Tkinter.INSERT) def processBackSpacePress(self,event): if not self.isEditable(): return "break" def processEnterPress(self,event): self._processLine() return "break" # Need break to stop the other bindings being called def processUpPress(self,event): self.changeLine(self.historyBack()) return "break" def processDownPress(self,event): self.changeLine(self.historyForward()) return "break" def processTabPress(self,event): if not self.getCurrentLine().strip(): return completed, possibilities = self.complete(self.getCurrentLine()) if len(possibilities) > 1: slice = self.getCurrentLine() self.write('\n') for symbol in possibilities: self.write(symbol+'\n') self.showPrompt(self.prompt) self.changeLine(completed or slice) return "break" class IPythonView(TkConsoleView, IterableIPShell): def __init__(self,root,banner=None): TkConsoleView.__init__(self,root) self.cout = StringIO() IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, input_func=self.raw_input) if banner: self.showBanner(banner) self.execute() self.cout.truncate(0) self.showPrompt(self.prompt) self.interrupt = False def raw_input(self, prompt=''): if self.interrupt: self.interrupt = False raise KeyboardInterrupt return self.getCurrentLine() def _processLine(self): self.history_pos = 0 self.execute() rv = self.cout.getvalue() if self.debug: print >>self.o,"_processLine got rv: %s" % rv if rv: rv = rv.strip('\n') self.showReturned(rv) self.cout.truncate(0) if __name__ == "__main__": root = Tkinter.Tk() s=IPythonView(root) s.pack() root.mainloop()