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/