1#!/usr/bin/env python 2 3# Interactive Python-GTK Console 4# Copyright (C), 5# 1998 James Henstridge 6# 2004 John Finlay 7# 8# This program is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 2 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program; if not, write to the Free Software 20# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 22# This module implements an interactive python session in a GTK window. To 23# start the session, use the gtk_console command. Its specification is: 24# gtk_console(namespace, title, copyright) 25# where namespace is a dictionary representing the namespace of the session, 26# title is the title on the window and 27# copyright is any additional copyright info to print. 28# 29# As well as the starting attributes in namespace, the session will also 30# have access to the list __history__, which is the command history. 31 32import sys, string, traceback 33import pygtk 34pygtk.require('2.0') 35import gobject 36import gtk 37 38stdout = sys.stdout 39 40if not hasattr(sys, 'ps1'): sys.ps1 = '>>> ' 41if not hasattr(sys, 'ps2'): sys.ps2 = '... ' 42 43# some functions to help recognise breaks between commands 44def remQuotStr(s): 45 '''Returns s with any quoted strings removed (leaving quote marks)''' 46 r = '' 47 inq = 0 48 qt = '' 49 prev = '_' 50 while len(s): 51 s0, s = s[0], s[1:] 52 if inq and (s0 != qt or prev == '\\'): 53 prev = s0 54 continue 55 prev = s0 56 if s0 in '\'"': 57 if inq: 58 inq = 0 59 else: 60 inq = 1 61 qt = s0 62 r = r + s0 63 return r 64 65def bracketsBalanced(s): 66 '''Returns true iff the brackets in s are balanced''' 67 s = filter(lambda x: x in '()[]{}', s) 68 stack = [] 69 brackets = {'(':')', '[':']', '{':'}'} 70 while len(s) != 0: 71 if s[0] in ")]}": 72 if len(stack) != 0 and brackets[stack[-1]] == s[0]: 73 del stack[-1] 74 else: 75 return 0 76 else: 77 stack.append(s[0]) 78 s = s[1:] 79 return len(stack) == 0 80 81class gtkoutfile: 82 '''A fake output file object. It sends output to a GTK TextView widget, 83 and if asked for a file number, returns one set on instance creation''' 84 def __init__(self, w, fn, font): 85 self.__fn = fn 86 self.__w = w 87 self.__b = w.get_buffer() 88 self.__ins = self.__b.get_mark('insert') 89 self.__font = font 90 def close(self): pass 91 flush = close 92 def fileno(self): return self.__fn 93 def isatty(self): return 0 94 def read(self, a): return '' 95 def readline(self): return '' 96 def readlines(self): return [] 97 def write(self, s): 98 #stdout.write(str(self.__w.get_point()) + '\n') 99 iter = self.__b.get_iter_at_mark(self.__ins) 100 self.__b.insert_with_tags(iter, s, self.__font) 101 self.__w.scroll_to_mark(self.__ins, 0.0) 102 self.__w.queue_draw() 103 def writelines(self, l): 104 iter = self.__b.get_iter_at_mark(self.__ins) 105 for s in l: 106 self.__b.insert_with_tags(iter, s, self.__font) 107 self.__w.scroll_to_mark(self.__ins, 0.0) 108 self.__w.queue_draw() 109 def seek(self, a): raise IOError, (29, 'Illegal seek') 110 def tell(self): raise IOError, (29, 'Illegal seek') 111 truncate = tell 112 113class Console(gtk.VBox): 114 def __init__(self, namespace={}, copyright='', quit_cb=None): 115 gtk.VBox.__init__(self, spacing=2) 116 self.set_border_width(2) 117 self.copyright = copyright 118 #self.set_size_request(475, 300) 119 120 self.quit_cb = quit_cb 121 122 self.inp = gtk.HBox() 123 self.pack_start(self.inp) 124 self.inp.show() 125 126 self.scrolledwin = gtk.ScrolledWindow() 127 self.scrolledwin.show() 128 self.inp.pack_start(self.scrolledwin, padding=1) 129 self.text = gtk.TextView() 130 self.text.set_editable(False) 131 self.text.set_wrap_mode(gtk.WRAP_WORD) 132 self.text.set_size_request(500, 400) 133 self.scrolledwin.add(self.text) 134 self.text.show() 135 self.buffer = self.text.get_buffer() 136 #create the tags we will use 137 self.normal = self.buffer.create_tag('Normal', font='Helvetica 10', 138 foreground='black') 139 self.title = self.buffer.create_tag('Title', font='Helvetica Bold 10', 140 foreground='darkgreen') 141 self.error = self.buffer.create_tag('Error', font='Helvetica 12', 142 foreground='red') 143 self.command = self.buffer.create_tag('Command', 144 font='Helvetica Bold 10', 145 foreground='blue') 146 147 self.inputbox = gtk.HBox(spacing=2) 148 self.pack_end(self.inputbox, expand=False) 149 self.inputbox.show() 150 151 self.prompt = gtk.Label(sys.ps1) 152 self.prompt.set_padding(xpad=2, ypad=0) 153 self.prompt.set_size_request(26, -1) 154 self.inputbox.pack_start(self.prompt, fill=False, expand=False) 155 self.prompt.show() 156 157 self.closer = gtk.Button(stock=gtk.STOCK_CLOSE) 158 self.closer.connect("clicked", self.quit) 159 self.inputbox.pack_end(self.closer, fill=False, expand=False) 160 self.closer.show() 161 162 self.line = gtk.Entry() 163 self.line.set_size_request(400,-1) 164 self.line.connect("key_press_event", self.key_function) 165 self.inputbox.pack_start(self.line, padding=2) 166 self.line.show() 167 168 # now let the text box be resized 169 self.text.set_size_request(0, 0) 170 self.line.set_size_request(0, -1) 171 172 self.namespace = namespace 173 174 self.cmd = '' 175 self.cmd2 = '' 176 177 # set up hooks for standard output. 178 self.stdout = gtkoutfile(self.text, sys.stdout.fileno(), 179 self.normal) 180 self.stderr = gtkoutfile(self.text, sys.stderr.fileno(), 181 self.error) 182 183 # set up command history 184 self.history = [''] 185 self.histpos = 0 186 self.namespace['__history__'] = self.history 187 188 def init(self): 189 self.text.realize() 190 self.insert = self.buffer.get_mark('insert') 191 iter = self.buffer.get_iter_at_mark(self.insert) 192 self.buffer.insert_with_tags(iter, 'Python %s\n%s\n\n' % 193 (sys.version, sys.copyright) + 194 'Interactive Python-GTK Console - \n' + 195 'Copyright (C)\n' \ 196 '1998 James Henstridge\n' \ 197 '2004 John Finlay\n\n' + 198 self.copyright + '\n', self.title) 199 self.text.scroll_to_mark(self.insert, 0.0) 200 self.line.grab_focus() 201 202 def quit(self, *args): 203 self.hide() 204 self.destroy() 205 if self.quit_cb: self.quit_cb() 206 207 def key_function(self, entry, event): 208 if event.keyval == gtk.keysyms.Return: 209 self.line.emit_stop_by_name("key_press_event") 210 self.eval() 211 if event.keyval == gtk.keysyms.Tab: 212 self.line.emit_stop_by_name("key_press_event") 213 self.line.append_text('\t') 214 gobject.idle_add(self.focus_text) 215 elif event.keyval in (gtk.keysyms.KP_Up, gtk.keysyms.Up): 216 self.line.emit_stop_by_name("key_press_event") 217 self.historyUp() 218 gobject.idle_add(self.focus_text) 219 elif event.keyval in (gtk.keysyms.KP_Down, gtk.keysyms.Down): 220 self.line.emit_stop_by_name("key_press_event") 221 self.historyDown() 222 gobject.idle_add(self.focus_text) 223 elif event.keyval in (gtk.keysyms.D, gtk.keysyms.d) and \ 224 event.state & gtk.gdk.CONTROL_MASK: 225 self.line.emit_stop_by_name("key_press_event") 226 self.ctrld() 227 228 def focus_text(self): 229 self.line.grab_focus() 230 return False # don't requeue this handler 231 232 def ctrld(self): 233 self.quit() 234 235 def historyUp(self): 236 if self.histpos > 0: 237 l = self.line.get_text() 238 if len(l) > 0 and l[0] == '\n': l = l[1:] 239 if len(l) > 0 and l[-1] == '\n': l = l[:-1] 240 self.history[self.histpos] = l 241 self.histpos = self.histpos - 1 242 self.line.set_text(self.history[self.histpos]) 243 244 def historyDown(self): 245 if self.histpos < len(self.history) - 1: 246 l = self.line.get_text() 247 if len(l) > 0 and l[0] == '\n': l = l[1:] 248 if len(l) > 0 and l[-1] == '\n': l = l[:-1] 249 self.history[self.histpos] = l 250 self.histpos = self.histpos + 1 251 self.line.set_text(self.history[self.histpos]) 252 253 def eval(self): 254 l = self.line.get_text() + '\n' 255 if len(l) > 1 and l[0] == '\n': l = l[1:] 256 self.histpos = len(self.history) - 1 257 if len(l) > 0 and l[-1] == '\n': 258 self.history[self.histpos] = l[:-1] 259 else: 260 self.history[self.histpos] = l 261 self.line.set_text('') 262 iter = self.buffer.get_iter_at_mark(self.insert) 263 self.buffer.insert_with_tags(iter, self.prompt.get() + l, self.command) 264 self.text.scroll_to_mark(self.insert, 0.0) 265 if l == '\n': 266 self.run(self.cmd) 267 self.cmd = '' 268 self.cmd2 = '' 269 return 270 self.histpos = self.histpos + 1 271 self.history.append('') 272 self.cmd = self.cmd + l 273 self.cmd2 = self.cmd2 + remQuotStr(l) 274 l = string.rstrip(l) 275 if not bracketsBalanced(self.cmd2) or l[-1] == ':' or \ 276 l[-1] == '\\' or l[0] in ' \11': 277 self.prompt.set_text(sys.ps2) 278 self.prompt.queue_draw() 279 return 280 self.run(self.cmd) 281 self.cmd = '' 282 self.cmd2 = '' 283 284 def run(self, cmd): 285 sys.stdout, self.stdout = self.stdout, sys.stdout 286 sys.stderr, self.stderr = self.stderr, sys.stderr 287 try: 288 try: 289 r = eval(cmd, self.namespace, self.namespace) 290 if r is not None: 291 print `r` 292 except SyntaxError: 293 exec cmd in self.namespace 294 except: 295 if hasattr(sys, 'last_type') and \ 296 sys.last_type == SystemExit: 297 self.quit() 298 else: 299 traceback.print_exc() 300 self.prompt.set_text(sys.ps1) 301 self.prompt.queue_draw() 302 sys.stdout, self.stdout = self.stdout, sys.stdout 303 sys.stderr, self.stderr = self.stderr, sys.stderr 304 305def gtk_console(ns, title='Python', copyright='', menu=None): 306 win = gtk.Window() 307 win.set_size_request(475, 300) 308 win.connect("destroy", lambda w: gtk.main_quit()) 309 win.connect("delete_event", lambda w,e: gtk.main_quit()) 310 win.set_title(title) 311 cons = Console(namespace=ns, copyright=copyright, 312 quit_cb=lambda w: gtk.main_quit()) 313 if menu: 314 box = gtk.VBox() 315 win.add(box) 316 box.show() 317 box.pack_start(menu, expand=False) 318 menu.show() 319 box.pack_start(cons) 320 else: 321 win.add(cons) 322 cons.show() 323 win.show() 324 win.set_size_request(0,0) 325 cons.init() 326 gtk.main() 327 328if __name__ == '__main__': 329 gtk_console({'__builtins__': __builtins__, '__name__': '__main__', 330 '__doc__': None}) 331