Hi to all, I'm sending a file with my version of a Python console inside a gtk Gui. Features: -autocompletation using a nice popup window (Ctrl-space) -possibility to save session (command only or command+output)
This code is strongly inspired by Johan Dahlin for console.py Jon Anderson for pygtk-console.py James Henstridge for gtkcons.py (Gtk 1 version) so many thanks to them! I' sending this with the hope it can be usefull to someone else. Best regards Pier Carteri -- Pier Carteri <[EMAIL PROTECTED]>
# # Py_Shell.py : inserts the python prompt in a gtk interface # # Author: Pier Carteri <[EMAIL PROTECTED]> # This script provides an interactive Python shell with : # -autocompletation (using a nice popup window); try Ctrl+space.. # -save sessions (only command or command+output) # # Many thanks to: # Johan Dahlin for console.py # Jon Anderson for pygtk-console.py # James Henstridge for gtkcons.py (Gtk 1 version) # # In the code below the class Completer is from pygtk-console.py of Jon Anderson # # import sys, code, os import __builtin__ import gtk, gobject PS1=">>> " PS2="... " TAB_WIDTH=4 BANNER="Python "+sys.version+"\n" class Completer: """ Taken from rlcompleter, with readline references stripped, and a local dictionary to use. """ def __init__(self,locals): self.locals = locals def complete(self, text, state): """Return the next possible completion for 'text'. This is called successively with state == 0, 1, 2, ... until it returns None. The completion should begin with 'text'. """ if state == 0: if "." in text: self.matches = self.attr_matches(text) else: self.matches = self.global_matches(text) try: return self.matches[state] except IndexError: return None def global_matches(self, text): """Compute matches when text is a simple name. Return a list of all keywords, built-in functions and names currently defines in __main__ that match. """ import keyword matches = [] n = len(text) for list in [keyword.kwlist, __builtin__.__dict__.keys(), self.locals.keys()]: for word in list: if word[:n] == text and word != "__builtins__": matches.append(word) return matches def attr_matches(self, text): """Compute matches when text contains a dot. Assuming the text is of the form NAME.NAME....[NAME], and is evaluatable in the globals of __main__, it will be evaluated and its attributes (as revealed by dir()) are used as possible completions. (For class instances, class members are are also considered.) WARNING: this can still invoke arbitrary C code, if an object with a __getattr__ hook is evaluated. """ import re m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text) if not m: return expr, attr = m.group(1, 3) object = eval(expr, self.locals, self.locals) words = dir(object) if hasattr(object,'__class__'): words.append('__class__') words = words + get_class_members(object.__class__) matches = [] n = len(attr) for word in words: if word[:n] == attr and word != "__builtins__": matches.append("%s.%s" % (expr, word)) return matches def get_class_members(klass): ret = dir(klass) if hasattr(klass,'__bases__'): for base in klass.__bases__: ret = ret + get_class_members(base) return ret class Dummy_File: def __init__(self, buffer, tag): """Implements a file-like object for redirect the stream to the buffer""" self.buffer = buffer self.tag = tag def write(self, text): """Write text into the buffer and apply self.tag""" iter=self.buffer.get_end_iter() self.buffer.insert_with_tags(iter,text,self.tag) def writelines(self, l): map(self.write, l) def flush(self): pass def isatty(self): return 1 class PopUp: def __init__(self, text_view, list, position): self.text_view=text_view self.list=list self.position=position self.list.sort() self.popup=gtk.Window(gtk.WINDOW_POPUP) frame=gtk.Frame() sw=gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) model=gtk.ListStore(gobject.TYPE_STRING) for item in self.list: iter=model.append() model.set(iter, 0, item) self.list_view=gtk.TreeView(model) self.list_view.connect("row-activated", self.hide) self.list_view.set_property("headers-visible", gtk.FALSE) selection=self.list_view.get_selection() selection.connect("changed",self.select_row) selection.select_path((0,)) renderer=gtk.CellRendererText() column=gtk.TreeViewColumn("",renderer,text=0) self.list_view.append_column(column) sw.add(self.list_view) frame.add(sw) self.popup.add(frame) self.popup.set_size_request(150,90) self.show_popup() def hide(self, *arg): self.popup.hide() def show_popup(self): buffer=self.text_view.get_buffer() iter=buffer.get_iter_at_mark(buffer.get_insert()) rectangle=self.text_view.get_iter_location(iter) absX, absY=self.text_view.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, rectangle.x+rectangle.width+20 , rectangle.y+rectangle.height+50) parent=self.text_view.get_parent() self.popup.move(self.position[0]+absX, self.position[1]+absY) self.popup.show_all() def prev(self): sel=self.list_view.get_selection() model, iter=sel.get_selected() newIter=model.get_path(iter) if newIter!=None and newIter[0]>0: path=(newIter[0]-1,) self.list_view.set_cursor(path) def next(self): sel=self.list_view.get_selection() model, iter=sel.get_selected() newIter=model.iter_next(iter) if newIter!=None: path=model.get_path(newIter) self.list_view.set_cursor(path) def sel_confirmed(self): sel=self.list_view.get_selection() self.select_row(sel) self.hide() def select_row(self, selection): model, iter= selection.get_selected() name=model.get_value(iter,0) buffer=self.text_view.get_buffer() end=buffer.get_iter_at_mark(buffer.get_insert()) start=end.copy() start.backward_char() while start.get_char() not in " ,()[]": start.backward_char() start.forward_char() buffer.delete(start,end) iter=buffer.get_iter_at_mark(buffer.get_insert()) buffer.insert(iter,name) class Shell_Gui: def __init__(self,with_window=1,banner=BANNER, label_text="Interactive Python Shell"): self.banner=banner box=gtk.HBox() box.set_homogeneous(gtk.FALSE) box.set_border_width(4) box.set_spacing(4) sw=gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) t_table=gtk.TextTagTable() # creates three tags tag_err=gtk.TextTag("error") tag_err.set_property("foreground","red") tag_err.set_property("font","monospace 10") t_table.add(tag_err) tag_out=gtk.TextTag("output") tag_out.set_property("foreground","blue") tag_out.set_property("font","monospace 10") t_table.add(tag_out) tag_in=gtk.TextTag("input") tag_in.set_property("foreground","black") tag_in.set_property("font","monospace 10") t_table.add(tag_in) tag_no_edit=gtk.TextTag("no_edit") tag_no_edit.set_property("editable",gtk.FALSE) t_table.add(tag_no_edit) self.buffer=gtk.TextBuffer(t_table) #add the banner self.buffer.set_text(self.banner+PS1) start,end=self.buffer.get_bounds() self.buffer.apply_tag_by_name("output",start,end) self.buffer.apply_tag_by_name("no_edit",start,end) self.view=gtk.TextView() self.view.set_buffer(self.buffer) self.view.connect("key_press_event", self.key_press) self.view.connect("drag_data_received",self.drag_data_received) self.view.set_wrap_mode(gtk.WRAP_CHAR) sw.add(self.view) box.pack_start(sw) #creates two dummy files self.dummy_out=Dummy_File(self.buffer,tag_out) self.dummy_err=Dummy_File(self.buffer,tag_err) #creates the console self.core=code.InteractiveInterpreter() #autocompletation capabilities self.completer = Completer(self.core.locals) self.popup=None #creates history capabilities self.history=[" "] self.history_pos=0 #add buttons b_box=gtk.Toolbar() b_box.set_orientation(gtk.ORIENTATION_VERTICAL) b_box.set_style(gtk.TOOLBAR_ICONS) b_box.insert_stock(gtk.STOCK_CLEAR,"Clear the output", None, self.clear_text, None,-1) b_box.insert_stock(gtk.STOCK_SAVE,"Save the output", None, self.save_text, None,-1) #b_box.insert_stock(gtk.STOCK_PREFERENCES,"Preferences", None, None, None,-1) if with_window: b_box.append_space() b_box.insert_stock(gtk.STOCK_QUIT,"Close Shell", None, self.quit, None,-1) box.pack_start(b_box,expand=gtk.FALSE) frame=gtk.Frame(label_text) frame.show_all() frame.add(box) if with_window: self.gui=gtk.Window() self.gui.add(frame) self.gui.connect("delete-event",self.quit) self.gui.set_default_size(520,200) self.gui.show_all() else: self.gui=frame def key_press(self, view, event): if self.popup!=None: if event.keyval ==gtk.gdk.keyval_from_name("Up"): self.popup.prev() return gtk.TRUE elif event.keyval ==gtk.gdk.keyval_from_name("Down"): self.popup.next() return gtk.TRUE elif event.keyval ==gtk.gdk.keyval_from_name("Return"): self.popup.sel_confirmed() self.popup=None return gtk.TRUE else: self.popup.hide() self.popup=None else: if event.keyval ==gtk.gdk.keyval_from_name("Up"): if self.history_pos>0: # remove text into the line... end=self.buffer.get_end_iter() start=self.buffer.get_iter_at_line(end.get_line()) start.forward_chars(4) self.buffer.delete(start,end) #inset the new text pos=self.buffer.get_end_iter() self.buffer.insert(pos, self.history[self.history_pos]) self.history_pos-=1 else: gtk.gdk.beep() self.view.emit_stop_by_name("key-press-event") return gtk.TRUE elif event.keyval ==gtk.gdk.keyval_from_name("Down"): if self.history_pos<len(self.history)-1: # remove text into the line... end=self.buffer.get_end_iter() start=self.buffer.get_iter_at_line(end.get_line()) start.forward_chars(4) self.buffer.delete(start,end) #inset the new text pos=self.buffer.get_end_iter() self.history_pos+=1 self.buffer.insert(pos, self.history[self.history_pos]) else: gtk.gdk.beep() self.view.emit_stop_by_name("key-press-event") return gtk.TRUE elif event.keyval ==gtk.gdk.keyval_from_name("Tab"): iter=self.buffer.get_iter_at_mark(self.buffer.get_insert()) self.buffer.insert(iter,TAB_WIDTH*" ") return gtk.TRUE elif event.keyval ==gtk.gdk.keyval_from_name("Return"): command=self.get_line() self.exec_code(command) start,end=self.buffer.get_bounds() self.buffer.apply_tag_by_name("no_edit",start,end) self.buffer.place_cursor(end) return gtk.TRUE elif event.keyval ==gtk.gdk.keyval_from_name("space") and event.state & gtk.gdk.CONTROL_MASK: self.complete_text() return gtk.TRUE def clear_text(self,*widget): dlg=gtk.Dialog("Clear") dlg.add_button("Clear",1) dlg.add_button("Reset",2) dlg.add_button(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE) dlg.set_default_size(250,150) hbox=gtk.HBox() #add an image img=gtk.Image() img.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_DIALOG) hbox.pack_start(img) #add text text="You have two options:\n" text+=" -clear only the output window\n" text+=" -reset the shell\n" text+="\n What do you want to do?" label=gtk.Label(text) hbox.pack_start(label) hbox.show_all() dlg.vbox.pack_start(hbox) ans=dlg.run() dlg.hide() if ans==1: self.buffer.set_text(self.banner+PS1) start,end=self.buffer.get_bounds() self.buffer.apply_tag_by_name("output",start,end) self.buffer.apply_tag_by_name("no_edit",start,end) elif ans==2: self.buffer.set_text(self.banner+PS1) start,end=self.buffer.get_bounds() self.buffer.apply_tag_by_name("output",start,end) self.buffer.apply_tag_by_name("no_edit",start,end) #creates the console self.core=code.InteractiveConsole() #reset history self.history=[" "] self.history_pos=0 self.view.grab_focus() def save_text(self, *widget): dlg=gtk.Dialog("Save to file") dlg.add_button("Commands",1) dlg.add_button("All",2) dlg.add_button(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE) dlg.set_default_size(250,150) hbox=gtk.HBox() #add an image img=gtk.Image() img.set_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_DIALOG) hbox.pack_start(img) #add text text="You have two options:\n" text+=" -save only commands\n" text+=" -save all\n" text+="\n What do you want to save?" label=gtk.Label(text) hbox.pack_start(label) hbox.show_all() dlg.vbox.pack_start(hbox) ans=dlg.run() dlg.hide() if ans==1 : def ok_save(button, data=None): win =button.get_toplevel() win.hide() name=win.get_filename() if os.path.isfile(name): box=gtk.MessageDialog(dlg, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO, name+" already exists; do you want to overwrite it?" ) ans=box.run() box.hide() if ans==gtk.RESPONSE_NO: return try: file=open(name,'w') for i in self.history: file.write(i) file.write("\n") file.close() except Exception, x: box=gtk.MessageDialog(dlg, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE, "Unable to write \n"+ name+"\n on disk \n\n%s"%(x) ) box.run() box.hide() def cancel_button(button): win.get_toplevel() win.hide() win=gtk.FileSelection("Save Commands...") win.ok_button.connect_object("clicked", ok_save,win.ok_button) win.cancel_button.connect_object("clicked", cancel_button,win.cancel_button) win.show() elif ans==2: def ok_save(button, data=None): win =button.get_toplevel() win.hide() name=win.get_filename() if os.path.isfile(name): box=gtk.MessageDialog(dlg, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION,gtk.BUTTONS_YES_NO, name+" already exists; do you want to overwrite it?" ) ans=box.run() box.hide() if ans==gtk.RESPONSE_NO: return try: start,end=self.buffer.get_bounds() text=self.buffer.get_text(start,end,0) file=open(name,'w') file.write(text) file.close() except Exception, x: box=gtk.MessageDialog(dlg, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE, "Unable to write \n"+ name+"\n on disk \n\n%s"%(x) ) box.run() box.hide() def cancel_button(button): win.get_toplevel() win.hide() win=gtk.FileSelection("Save Log...") win.ok_button.connect_object("clicked", ok_save,win.ok_button) win.cancel_button.connect_object("clicked", cancel_button,win.cancel_button) win.show() dlg.destroy() self.view.grab_focus() def get_line(self): iter=self.buffer.get_iter_at_mark(self.buffer.get_insert()) line=iter.get_line() start=self.buffer.get_iter_at_line(line) end=start.copy() end.forward_line() command=self.buffer.get_text(start,end,0) if (command[:4]==PS1 or command[:4]==PS2): command=command[4:] return command def complete_text(self): end=self.buffer.get_iter_at_mark(self.buffer.get_insert()) start=end.copy() start.backward_char() while start.get_char() not in " ,()[]": start.backward_char() start.forward_char() token=self.buffer.get_text(start,end,0).strip() completions = [] try: p=self.completer.complete(token,len(completions)) while p != None: completions.append(p) p=self.completer.complete(token, len(completions)) except: return if len(completions)==1: self.buffer.delete(start,end) iter=self.buffer.get_iter_at_mark(self.buffer.get_insert()) self.buffer.insert(iter,completions[0]) elif len(completions)>1: #show a popup if isinstance(self.gui, gtk.Frame): rect=self.gui.get_allocation() app=self.gui.window.get_root_origin() position=(app[0]+rect.x,app[1]+rect.y) else: position=self.gui.window.get_root_origin() self.popup=PopUp(self.view, completions, position) def replace_line(self, text): iter=self.buffer.get_iter_at_mark(self.buffer.get_insert()) line=iter.get_line() start=self.buffer.get_iter_at_line(line) start.forward_chars(4) end=start.copy() end.forward_line() self.buffer.delete(start,end) iter=self.buffer.get_iter_at_mark(self.buffer.get_insert()) self.buffer.insert(iter, text) def sdt2files(self): """switch stdin stdout stderr to my dummy files""" self.std_out_saved=sys.stdout self.std_err_saved=sys.stderr sys.stdout=self.dummy_out sys.stderr=self.dummy_err def files2sdt(self): """switch my dummy files to stdin stdout stderr """ sys.stdout=self.std_out_saved sys.stderr=self.std_err_saved def drag_data_received(self, source, drag_context, n1, n2, selection_data, long1, long2): print selection_data.data def exec_code(self, text): """Execute text into the console and display the output into TextView""" #update history self.history.append(text) self.history_pos=len(self.history)-1 self.sdt2files() sys.stdout.write("\n") action=self.core.runsource(text, "<<console>>")#push(text) if action==0: sys.stdout.write(PS1) elif action==1: sys.stdout.write(PS2) self.files2sdt() self.view.scroll_to_iter(self.buffer.get_end_iter(), gtk.FALSE) def quit(self,*args): if __name__=='__main__': gtk.main_quit() else: if self.popup!=None: self.popup.hide() self.gui.hide() if __name__=='__main__': shell=Shell_Gui(with_window=1) gtk.mainloop()
_______________________________________________ pygtk mailing list [EMAIL PROTECTED] http://www.daa.com.au/mailman/listinfo/pygtk Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/