Okay, I've spent some more time playing with this program. There are a bunch
of changes but the biggest is probably my switch from IDLE to LEO for
development. I don't want to proselytize too much but Leo is a great tool.
It allows you to easily organize a large amount of data, notes, example, and
code in a logical way, and also to generate a number of documents from one
leo file. If anyone is interested I could post my leo file for IdleCE. Some
stuff is missing since I lost things before moving to Leo, but it still could
be insightful.
Anyway this release added/improved on the editing features, incorporates
Sabastion's menus and his fix for the selection bug, and just some general
repairs. I've also added a new bug, don't try to indent a selection if the
selection includes the last line in a file. Its a strange bug but all you
need is a extra newline at the end of the file to avoid it.
I can't remember if I said anything about it before but one of my favorite
features is the 'context' menu. Select some text and tap on the selection, a
menu will appear with common editing options. It might be cool if I could
expand this to be truly context sensitive. Maybe some kind of word
completion/insertion... I also thought a special clipboard might be cool,
something like kclipper on KDE. Oh well, I have some other stuff to try
before I get to the fancy stuff.
#IdleCE
import sys
for p in sys.path:
if p[-12:].lower() == "python23.zip":
sys.path.append(p + "\\lib-tk")
break
import os
#import re
from Tkinter import *
import tkMessageBox
import tkFileDialog
import keyword
from string import ascii_letters, digits, punctuation
OS = os.name.lower() #Should be 'ce' on WinCE
class Idle:
"""The base class for the mini idle."""
def __init__(self,root):
self.root = root
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)
frame = Frame(root)
frame.grid(row=0,column=0,columnspan=2,sticky=NSEW)
self.editor = SyntaxHighlightingText(root)
self.editor.grid(row=1,column=0,columnspan=2,sticky=N+S)
panbar = Scrollbar(root,orient=HORIZONTAL)
panbar.grid(row=2,column=0,columnspan=2,sticky=E+W)
scrollbar = Scrollbar(root)
scrollbar.grid(row=1,column=2,sticky=N+S)
self.editor.configure(xscrollcommand=panbar.set)
self.editor.configure(yscrollcommand=scrollbar.set)
scrollbar.config(command=self.editor.yview)
panbar.config(command=self.editor.xview)
btn = Menubutton(frame,text="File",padx=1,pady=1)
btn.pack(side=LEFT)
submenu = Menu(btn,tearoff=False)
btn["menu"] = submenu
submenu.add_command(label="New",command=self.new)
submenu.add_command(label="Open",command=self.open)
submenu.add_command(label="Save",command=self.save)
submenu.add_command(label="Save as",command=self.saveas)
submenu.add_command(label="Exit",command=self.exit)
btn = Menubutton(frame,text="Edit",padx=1,pady=1)
btn.pack(side=LEFT)
submenu = Menu(btn,tearoff=False)
btn["menu"] = submenu
submenu.add_command(label="Undo",command=self.editor.edit_undo)
submenu.add_command(label="Redo",command=self.editor.edit_redo)
submenu.add_separator()
submenu.add_command(label="Cut",command=self.editor.cut)
submenu.add_command(label="Copy",command=self.editor.copy)
submenu.add_command(label="Paste",command=self.editor.paste)
submenu.add_separator()
submenu.add_command(label="Indent",command=self.editor.indent_region)
submenu.add_command(label="Dedent",command=self.editor.dedent_region)
submenu.add_command(label="Comment",command=self.editor.comment_region)
submenu.add_command(label="UnComment",command=self.editor.uncomment_region)
btn = Menubutton(frame,text="Help",padx=1,pady=1)
btn.pack(side=LEFT)
submenu = Menu(btn,tearoff=False)
btn["menu"] = submenu
submenu.add_command(label="About",command=self.about_dialog)
self.root.protocol("WM_DELETE_WINDOW", self.exit)
def about_dialog(self):
"""Sillyness"""
top = Toplevel()
top.title("about")
top.resizable(0,0)
top.focus_set()
about = """
IdleCE v0.7
A miniaturized imitation of
the python ide: idle.
This software is distibuted
under the Gnu-GPL. Please Visit
http://www.gnu.org/licenses/gpl.txt
to see the license.
"""
info = Label(top,text=about)
info.pack(side=TOP,padx=6)
button = Button(top, text="Dismiss", command=top.destroy)
button.pack(side=BOTTOM,fill=X)
def open(self):
# Opens a file and colorizes it
self.filename = tkFileDialog.askopenfilename()
if OS == 'ce': # Just passing filename fails...
self.filename = self.filename.replace('/','\\')
try:
file = open(self.filename)
self.editor.delete("1.0",END)
text = file.readlines()
file.close()
for i in range(len(text)-1):
self.editor.insert(END,text[i])
self.editor.colorize(i+1,len(text[i])) #colorize(textline,lastcolumn)
self.editor.see("1.0")
except IOError:
print self.filename
def saveas(self):
# Called if no filename is set or Saveas is picked
self.filename = tkFileDialog.asksaveasfilename()
if OS == 'ce':
self.filename = self.filename.replace('/','\\')
try:
file = open(self.filename,'w')
text = self.editor.get("1.0",END)
file.write(text)
file.flush()
file.close()
except Exception, info:
tkMessageBox.showerror('Exception!',info)
def save(self):
try:
file = open(self.filename,'w')
text = self.editor.get("1.0",END)
file.write(text)
file.flush()
file.close()
except:
self.saveas() # If no file has been accessed
def new(self):
if len(self.editor.get("1.0",END)) >= 2:
if tkMessageBox.askokcancel("New File","Discard current file?"):
self.editor.delete("1.0",END)
self.filename = ""
else:
self.editor.delete("1.0",END)
self.filename = ""
def exit(self):
# Ask the user to save if the file isn't empty
if len(self.editor.get("1.0",END)) >= 2:
answer = tkMessageBox._show("Save File","Save the current file?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL)
print answer
if answer == 'yes':
self.save()
elif answer == 'cancel':
return
# End the program firmly.
root.destroy()
root.quit()
class SyntaxHighlightingText(Text):
"""A syntax highlighting text widget from the web customized with
some methods from Idle and some special methods.
This could be moved to a module and generalized. Then imported and
sub-classed here for specialization."""
tags = {'com':'#C00', #comment
'str':'#0A0', #string
'kw': 'orange', #keyword
'obj':'#00F', #function/class name
'int': 'blue' #integers
}
def __init__(self, root):
if OS == 'ce':
w = 40
h = 10
else:
w = 80
h = 25
Text.__init__(self,root,wrap=NONE,bd=0,width=w,height=h,undo=1,maxundo=50)
# Non-wrapping, no border, undo turned on, max undo 50
self.text = self # For the methods taken from IDLE
self.root = root
self.config_tags()
self.characters = ascii_letters + digits + punctuation
self.tabwidth = 8 # for IDLE use, must remain 8 until Tk is fixed
self.indentwidth = 4 # Should perhaps be 2 due to the small screen??
self.indention = 0 # The current indention level
self.set_tabwidth(self.indentwidth) # IDLE...
self.sel_store = []
# create a popup menu
self.menu = Menu(root, tearoff=0)
self.menu.add_command(label="Undo", command=self.edit_undo)
self.menu.add_command(label="Redo", command=self.edit_redo)
self.menu.add_separator()
self.menu.add_command(label="Cut", command=self.cut)
self.menu.add_command(label="Copy", command=self.copy)
self.menu.add_command(label="Paste", command=self.paste)
self.menu.add_separator()
self.menu.add_command(label="Indent", command=self.indent_region)
self.menu.add_command(label="Dedent", command=self.dedent_region)
self.menu.add_command(label="Comment", command=self.comment_region)
self.menu.add_command(label="UnComment", command=self.uncomment_region)
self.bind('<Key>', self.key_press) # For scanning input
self.bind('<Return>',self.autoindent) # Overides default binding
self.bind('<Tab>',self.autoindent) # increments self.indention
self.bind('<BackSpace>',self.autoindent)# decrements self.indention
self.bind_all('<Control-C>',self.copy)
self.bind_all('<Control-X>',self.cut)
self.bind_all('<Control-V>',self.paste)
self.bind_all('<Control-Z>',self.edit_undo)
self.bind_all('<Control-Y>',self.edit_redo)
self.bind('<Double-Button-1>',self.paste)# pastes text
self.tag_bind(SEL,'<Button-1>',self.popup)
self.bind('<Control-i>',self.indent_region)
self.bind('<Control-d>',self.dedent_region)
def popup(self, event):
"""Edit popup menu with a special attribute for the selection."""
self.sel_store = self.get_region() # returns head, tail, chars, lines
self.menu.post(event.x_root, event.y_root)
return "break"
def getline(self,row):
"""the string 'row.end' is an alias for the last index in a row."""
line = ''
lastcol = 0
char = self.get(self.to_index(row, lastcol))
while char != '\n':
line += char
lastcol += 1
char = self.get(self.to_index(row, lastcol))
return line, lastcol
def to_index(self, row,column):
"""The <'%s.%d'%(row, col)> pattern was repeated all over the place,
Now it should be replaced with a call to self.to_index(row,col)."""
return '%s.%d'%(row, column)
def get_tabwidth(self):
# From IDLE
current = self['tabs'] or 5000
return int(current)
def set_tabwidth(self, newtabwidth):
# From IDLE
text = self
if self.get_tabwidth() != newtabwidth:
pixels = text.tk.call("font", "measure", text["font"],
"-displayof", text.master,
"n" * newtabwidth)
text.configure(tabs=pixels)
def config_tags(self):
# Sets up the tags and their colors
for tag, val in self.tags.items():
self.tag_config(tag, foreground=val)
def remove_tags(self, start, end):
for tag in self.tags.keys():
self.tag_remove(tag, start, end)
def get_selection_indices(self):
# If a selection is defined in the text widget, return (start,
# end) as Tkinter text indices, otherwise return (None, None)
try:
first = self.text.index("sel.first")
last = self.text.index("sel.last")
return first, last
except TclError:
return None, None
def get_region(self):
"""Code directly from IDLE, should help with my indent/dedent problems."""
text = self.text
first, last = self.get_selection_indices()
if first and last:
head = text.index(first + " linestart")
tail = text.index(last + "-1c lineend +1c")
else:
head = text.index("insert linestart")
tail = text.index("insert lineend +1c")
chars = text.get(head, tail)
lines = chars.split("\n")
return head, tail, chars, lines
def set_region(self, head, tail, chars, lines):
"""Mate to get_region, should be very helpfull."""
text = self.text
newchars = "\n".join(lines)
if newchars == chars:
text.bell()
return
text.tag_remove("sel", "1.0", "end")
text.mark_set("insert", head)
self.edit_separator()
text.delete(head, tail)
text.insert(head, newchars)
self.edit_separator()
text.tag_add("sel", head, "insert")
def cut(self,event=0):
self.clipboard_clear()
if self.sel_store: # Sent by the popup
self.delete(self.sel_store[0],self.sel_store[1])
self.clipboard_append(self.sel_store[2])
self.sel_store = []
else: # Sent by menu
Selection=self.get_selection_indices()
if len(Selection)>0:
SelectedText = self.get(Selection[0],Selection[1])
self.delete(Selection[0],Selection[1])
self.clipboard_append(SelectedText)
return "break"
def copy(self,event=0):
self.clipboard_clear()
if self.sel_store: # Sent by the popup
self.clipboard_append(self.sel_store[2])
self.sel_store = []
else: # Sent by menu
Selection=self.get_selection_indices()
if len(Selection)>0:
SelectedText = self.get(Selection[0],Selection[1])
self.clipboard_append(SelectedText)
return "break"
def paste(self,event=0):
# This calls colorize for the pasted lines, on windows ctrl-v
# never reaches this code...
SelectedText = self.root.selection_get(selection='CLIPBOARD')
cline = int(self.index(INSERT).split('.')[0])
self.insert(INSERT, SelectedText)
lastcol = int(self.index(str(cline)+'.end').split('.')[1])
print 'colorizing ' + self.to_index(cline,lastcol)
self.colorize(cline,lastcol)
return "break"
def autoindent(self,event):
if event.keysym == 'Return':
self.edit_separator() # For undo/redo
index = self.index(INSERT).split('.')
print index
line = int(index[0])
column = int(index[1])
if self.get(self.to_index(line, column-1)) == ':':
self.indention += 1
self.insert(INSERT,'\n')
self.insert(INSERT,(' '*self.indentwidth)*self.indention)
return 'break' # Overides standard bindings
elif event.keysym == 'Tab':
self.edit_separator()
first,last = self.get_selection_indices()
if first and last:
if event.state == 9:
#this is a <Shift-Tab>, a dedent
self.dedent_region()
else:
self.indent_region()
return "break"
prefix = self.get("insert linestart", "insert")
if prefix.lstrip() != '':
self.insert(INSERT,' '*self.indentwidth)
else:
self.indention += 1
self.insert(INSERT,' '*self.indentwidth)
return "break"
elif event.keysym == 'BackSpace':
# The current idea of virtual tabstops is very vaguely implimented
self.edit_separator()
index = self.index(INSERT).split('.')
print index
line = int(index[0])
column = int(index[1])
if self.indention > 0:
prev = ''
for i in range(self.indentwidth):
# Get the item starting at the column preceeding
# the current to the column on indents width left.
prev += self.get(self.to_index(line, column-(i+1)))
print len(prev)
if prev == ' '*self.indentwidth:
self.delete(self.to_index(line,(column)-self.indentwidth),\
self.to_index(line,column))
self.indention -= 1
return "break" # Don't want the backspace handled twice.
def indent_region(self, event = ''):
"""This code taken from IDLE works almost always.
The only problem I've found occurs if you try to
indent the last line in a file...
Code added for popup menu call..."""
if self.sel_store: # Sent by the popup
head, tail, chars, lines = self.sel_store
self.sel_store = []
else:
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if line:
line = ' '*self.indentwidth + line
lines[pos] = line
self.set_region(head, tail, chars, lines)
return "break"
def dedent_region(self, event = ''):
"""Code taken from IDLE, edited for use with
popup menu."""
if self.sel_store: # Sent by the popup
head, tail, chars, lines = self.sel_store
self.sel_store = []
else:
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if line and line[:self.indentwidth] == ' '*self.indentwidth:
line = line[self.indentwidth:]
print line
lines[pos] = line
self.set_region(head, tail, chars, lines)
return "break"
def comment_region(self, event = ''):
"""Code taken from IDLE, edited for use with
popup menu."""
if self.sel_store: # Sent by the popup
head, tail, chars, lines = self.sel_store
self.sel_store = []
else:
head, tail, chars, lines = self.get_region()
for pos in range(len(lines) - 1):
line = lines[pos]
lines[pos] = '##' + line
self.set_region(head, tail, chars, lines)
def uncomment_region(self, event = ''):
"""Code taken from IDLE, edited for use with
popup menu."""
if self.sel_store: # Sent by the popup
head, tail, chars, lines = self.sel_store
self.sel_store = []
else:
head, tail, chars, lines = self.get_region()
for pos in range(len(lines)):
line = lines[pos]
if not line:
continue
if line[:2] == '##':
line = line[2:]
elif line[:1] == '#':
line = line[1:]
lines[pos] = line
self.set_region(head, tail, chars, lines)
def key_press(self, key):
"""This function was origonaly the home of the colorize code.
Now it is mostly useless unless you want to watch for a certain character."""
if key.char in ' :[(]),"\'':
self.edit_separator() # For undo/redo
cline = self.index(INSERT).split('.')[0]
lastcol = self.getline(cline)[1]
self.colorize(cline,lastcol)
def colorize(self,cline,lastcol):
"""Not so simple syntax highlighting."""
buffer = self.get(self.to_index(cline,0),self.to_index(cline,lastcol))
tokenized = buffer.split(' ')
self.remove_tags(self.to_index(cline, 0), self.to_index(cline, lastcol))
quotes = 0
start = 0
for i in range(len(buffer)):
if buffer[i] in ['"',"'"]: # Doesn't distinguish between single and double quotes...
if quotes:
self.tag_add('str', self.to_index(cline, start), self.to_index(cline, i+1))
quotes = 0
else:
start = i
quotes = 1
elif buffer[i] == '#':
self.tag_add('com', self.to_index(cline, i), self.to_index(cline, len(buffer)))
break
start, end = 0, 0
obj_flag = 0
for token in tokenized:
end = start + len(token)
if obj_flag:
self.tag_add('obj', self.to_index(cline, start), self.to_index(cline, end))
obj_flag = 0
if token.strip() in keyword.kwlist:
self.tag_add('kw', self.to_index(cline, start), self.to_index(cline, end))
if token.strip() in ['def','class']:
obj_flag = 1
else:
for index in range(len(token)):
try:
int(token[index])
except ValueError:
pass
else:
self.tag_add('int', self.to_index(cline, start+index))
start += len(token)+1
if __name__ == '__main__':
root = Tk()
root.title('IdleCE')
if OS=='ce':
sizex, sizey = root.wm_maxsize()
root.wm_geometry("%dx%d+0+%d"%(sizex-6,sizey*37/64+1,sizey/90))
b1motion = root.bind_class('Text','<B1-Motion>')
# Deep magic by Sebastian, fixes text selection issues.
root.bind_class('Text','<B1-Motion>','if {![info exists ::tk::Priv(ignoreB1Motion)]} {%s}'%b1motion)
root.bind_class('Text','<B1-Leave>','set ::tk::Priv(ignoreB1Motion) 1')
root.bind_class('Text','<B1-Enter>','array unset ::tk::Priv ignoreB1Motion')
app = Idle(root)
root.mainloop()
_______________________________________________
PythonCE mailing list
PythonCE@python.org
http://mail.python.org/mailman/listinfo/pythonce