#IdleCE vanilla = globals().copy() import sys for p in sys.path: if p[-12:].lower() == "python23.zip": sys.path.append(p + "\\lib-tk") break import os, time, cPickle from Tkinter import * import tkMessageBox import tkFileDialog import tkSimpleDialog 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): """This is where the interface is created. This stuff is mostly straight forward except the wframe and how the work spaces are implemented. The wframe is a container for the current work space. When the user clicks on the editor/clipboard button the wframe is told to forget about what it's doing and instead pack the desired set of widgets. """ self.top = None self.root = root root.grid_rowconfigure(1, weight=2) root.grid_columnconfigure(0, weight=2) frame = Frame(root) frame.grid(row=0,column=0,sticky=E+W) wframe = Frame(root) # work frame wframe.grid(row=1,column=0,sticky=NSEW) # Editor widget group self.edit = Frame(wframe) self.edit.grid_rowconfigure(0, weight=2) self.edit.grid_columnconfigure(0, weight=2) self.edit.pack(fill=BOTH, expand=1) self.editor = SyntaxHighlightingText(self.edit) self.editor.grid(row=0,column=0,sticky=N+S+E+W) self.editor.clipper = self.addClip panbar = Scrollbar(self.edit, orient=HORIZONTAL) panbar.grid(row=1,column=0,sticky=E+W) scrollbar = Scrollbar(self.edit) scrollbar.grid(row=0,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) # Clipper widget group self.clip = Frame(wframe, bd=2, relief=SUNKEN) scrollbar = Scrollbar(self.clip) scrollbar.pack(side=RIGHT, fill=Y) self.clipper = Listbox(self.clip, bd=0, yscrollcommand=scrollbar.set) self.clipper.pack(fill=BOTH, expand=1) self.clipper.bind('', self.changeClip) scrollbar.config(command=self.clipper.yview) # Menus 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_separator() submenu.add_command(label="Options",command=self.options) submenu.add_separator() 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="Find",padx=1,pady=1) btn.pack(side=LEFT) submenu = Menu(btn,tearoff=False) btn["menu"] = submenu submenu.add_command(label="Finder",command=self.finder) submenu.add_command(label="Goto",command=self.goto) btn = Button(frame,text="Run",padx=1,pady=1, relief=FLAT,command=self.run) btn.pack(side=LEFT) # btn = Button(frame,text="Options",padx=1,pady=1, relief=FLAT,command=self.options) # btn.pack(side=LEFT) self.editbtn = Button(frame,text="Editor",padx=1,pady=1, relief=FLAT, state=DISABLED, command=self.showEditor) self.editbtn.pack(side=LEFT) self.clipbtn = Button(frame,text="Clipper",padx=1,pady=1, relief=FLAT,command=self.showClipper) self.clipbtn.pack(side=LEFT) 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) self.editor.focus() def showEditor(self): self.editbtn.config(state=DISABLED) self.clipbtn.config(state=NORMAL) self.clip.forget() self.edit.pack() def showClipper(self): self.clipbtn.config(state=DISABLED) self.editbtn.config(state=NORMAL) self.edit.forget() self.clip.pack(fill=BOTH, expand=1) def addClip(self, item): """Called by the editor to add items to the clipper list.""" items = self.clipper.get(0,END) if item not in items: self.clipper.insert(0, item) if self.clipper.size() > 20: self.clipper.delete(0) def changeClip(self, evt=None): """Gets the selected text from the clipper and puts it on the clipboard.""" index = self.clipper.curselection() text = self.clipper.get(index) self.editor.clipboard_clear() self.editor.clipboard_append(text) return 'break' def finder(self): """Displays a text find dialog.""" try: self.finder.show() except: self.finder = Finder(self) def goto(self): line = tkSimpleDialog.askinteger("Goto line", "line number:") self.editor.mark_set(INSERT,self.editor.to_index(line,0)) self.editor.see(INSERT) self.editor.focus() def run(self): """Executes the code in the buffer. """ answer = tkMessageBox._show("Save File","Save the current file?",icon=tkMessageBox.QUESTION,type=tkMessageBox.YESNOCANCEL) if answer == 'yes': self.save() elif answer == 'cancel': return code = self.editor.get("1.0",END) self.root.withdraw() try: exec code in vanilla except: print "There has been an error during execution" time.sleep(5) self.root.deiconify() #!!! on PPC you can close the console and idleCE remains running so raise #!!! so we need to deiconify on error or you will have an invisable program... time.sleep(5) self.root.deiconify() def options(self): """Present the user with a dialog for customizing editor features.""" top = Toplevel() top.root = self top.title("Options") top.resizable(0,0) top.focus_set() top.sb = Spinbox(top, from_=1, to=8) top.sb.pack() def setTabs(): num = int(top.sb.get()) top.root.editor.set_tabwidth(num) file = open('idle.cfg', 'w') cPickle.dump(num, file) file.close() top.destroy() ok = Button(top, text="Ok", command=setTabs) ok.pack(fill=X) def about_dialog(self): """Sillyness""" top = Toplevel() top.title("about") top.resizable(0,0) top.focus_set() about = """ IdleCE v1.0a 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(filetypes=[("Python files",".py"),("All files","*")]) if OS == 'ce': # Just passing filename fails... self.filename = self.filename.replace('/','\\') try: file = open(self.filename) self.editor.delete("1.0",END) row = 1 for line in file: self.editor.insert(END,line) self.editor.colorize(row,len(line)) #colorize(textline,lastcolumn) row += 1 file.close() self.root.title('IdleCE - ' + os.path.basename(self.filename)) self.editor.mark_set(INSERT, '1.0') self.editor.see(INSERT) except IOError, info: tkMessageBox.showerror('Exception!',info) 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() self.root.title('IdleCE - ' + os.path.basename(self.filename)) 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.editor.insert(INSERT,'\n') self.filename = "" self.root.title('IdleCE') 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) if answer == 'yes': self.save() elif answer == 'cancel': return # End the program firmly. root.destroy() root.quit() class Finder: """UI and methods for finding and replacing text. This class mainly makes use of the search capabilities already built into the Tkinter Text widget. """ def __init__(self, root): self.root = root self.top = Toplevel() top = self.top self.top.title("Finder") self.top.resizable(0,0) self.top.transient(self.root.root) self.top.focus_set() self.top.protocol("WM_DELETE_WINDOW", self.hide) #doesn't seem to work l = Label(top, text='Find:') l.grid(row=0, column=0, sticky=W) self.find = Entry(top) self.find.grid(row=0, column=1, columnspan=3) l = Label(top, text='Replace:') l.grid(row=1, column=0) self.replace = Entry(top) self.replace.grid(row=1, column=1, columnspan=3) btn = Button(top, text='Find', command=self.findit) btn.grid(row=0, column=4, sticky=NSEW) btn = Button(top, text='Replace', command=self.replaceit) btn.grid(row=1, column=4, sticky=NSEW) btn = Button(top, text='Replace all', command=self.replaceAll) btn.grid(row=2, column=4, sticky=NSEW) self.find.bind('', self.findit) # Variables for the check boxes self.up = IntVar() self.re = IntVar() self.match = IntVar() self.match.set(1) # The up check box f = Frame(top) f.grid(row=2, column=0, sticky=NSEW) l = Label(f, text='Up') l.pack(side=LEFT) btn = Checkbutton(f, variable=self.up) btn.pack(side=LEFT) # The re check box f = Frame(top) f.grid(row=2, column=1, sticky=NSEW) l = Label(f, text='Re') l.pack(side=LEFT) btn = Checkbutton(f, variable=self.re) btn.pack(side=LEFT) # The match check box f = Frame(top) f.grid(row=2, column=2, sticky=NSEW) l = Label(f, text='No case') l.pack(side=LEFT) btn = Checkbutton(f, variable=self.match) btn.pack(side=LEFT) # This makes sure the finder is visable top.update() geo = top.geometry() shape = geo[:geo.index("+")] top.geometry(shape + "+20+100") top.resizable(0,0) def findit(self, evt=None): text = self.find.get() self.find.select_range(0, END) if text: self.root.editor.find(text, self.up.get(), self.re.get(), self.match.get()) self.root.editor.focus() def replaceit(self): text = self.replace.get() self.root.editor.replace(text) def replaceAll(self): done = False ftext = self.find.get() rtext = self.replace.get() if ftext and rtext: while not done: done = self.root.editor.find(ftext, up.get()) if not done:self.root.editor.replace(rtext) def hide(self): self.top.withdraw() def show(self): self.top.deiconify() 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 = 15 else: w = 80 h = 25 Text.__init__(self,root,wrap=NONE,bd=0,width=w,height=h,padx=8,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 try: file = open('idle.cfg','r') num = cPickle.load(file) self.indentwidth = num except: self.indentwidth = 4 # Should perhaps be 2 due to the small screen?? self.set_tabwidth(self.indentwidth) # IDLE... self.sel_store = [] # Used for passing selections around self.tstart = '' # Used in colorizing tri-quotes # 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('', self.key_press) # For scanning input self.bind('',self.autoindent) # Overides default binding self.bind('',self.autoindent) # increments self.indention self.bind('',self.autoindent)# decrements self.indention self.bind_all('',self.copy) self.bind_all('',self.cut) self.bind_all('',self.paste) self.bind_all('',self.edit_undo) self.bind_all('',self.edit_redo) self.bind('',self.paste)# pastes text self.tag_bind(SEL,'',self.popup) self.bind('',self.indent_region) self.bind('',self.dedent_region) def popup(self, event): """Edit popup menu with a special attribute for the selection.""" self.sel_store = self.get_selection_indices() #print self.sel_store 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.%s'%(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.%s'%(row, column) def get_tabwidth(self): # From IDLE current = self['tabs'] or 5000 return int(current) def set_tabwidth(self, newtabwidth): """Note: the tabs this was meant to set are no longer used. Now the variable self.indentwidth stores the tab width. This function should change the existing tab widths in the document when called.""" # From IDLE text = self if self.get_tabwidth() != newtabwidth: pixels = text.tk.call("font", "measure", text["font"], "-displayof", text.master, "n" * newtabwidth) #print "setting tab width to %s" %pixels self.indentwidth = newtabwidth # this is added since we use spaces for tabs 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,pop=0): """Code directly from IDLE, should help with my indent/dedent problems.""" text = self.text if self.sel_store: first, last = self.sel_store self.sel_store = [] else: 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): """From IDLE. mate to get_region, should be very helpful.""" text = self.text penult = text.index(END) penult = penult.split('.') if not '\n' in text.get(self.to_index(int(penult[0])-1,penult[1])): # Kludge for the last line indent error text.insert(END,'\n') # Now there is always one line more. 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 find(self, text, up=False, re=False, match=True): length = IntVar() #Search for the given text if not up: # The stopindex needs to be different start = self.search(text, INSERT, stopindex=END, backwards=up, regexp=re, nocase=match, count=length) else: start = self.search(text, INSERT, stopindex='1.0', backwards=up, regexp=re, nocase=match, count=length) if start: coords = start.split('.') end = coords[0] + '.' + str(int(coords[-1])+length.get()) # move the cursor to the right place if not up: self.mark_set(INSERT, end) else: self.mark_set(INSERT, start) # Highlight the text self.tag_remove(SEL, 1.0, start) self.tag_add(SEL, start, end) self.tag_remove(SEL, end, END) # and show it to the user self.see(INSERT) return False else: return True def replace(self, text): first, last = self.get_selection_indices() if text and first and last: self.delete(first,last) self.insert(INSERT, text) def cut(self,event=0): self.clipboard_clear() if self.sel_store: # Sent by the popup first, last = self.sel_store self.sel_store = [] else: # Sent by menu first, last = self.get_selection_indices() if first and last: SelectedText = self.get(first,last) self.delete(first,last) self.clipboard_append(SelectedText) self.clipper(SelectedText) return "break" def copy(self,event=0): self.clipboard_clear() if self.sel_store: # Sent by the popup first, last = self.sel_store self.sel_store = [] else: # Sent by menu first, last = self.get_selection_indices() if first and last: SelectedText = self.get(first, last) self.clipboard_append(SelectedText) self.clipper(SelectedText) return "break" def paste(self,event=0): SelectedText = self.root.selection_get(selection='CLIPBOARD') if self.sel_store: # Sent by the popup first, last = self.sel_store self.sel_store = [] else: # Sent by menu first, last = self.get_selection_indices() if first and last: self.delete(first,last) first = int(float(self.index(INSERT))) # int doesn't accept a float in a string (e.g. "3.5") last = first last += SelectedText.count('\n') self.insert(INSERT, SelectedText) for line in range(first,last): lastcol = self.getline(line)[1] #print 'colorizing ' + self.to_index(line,lastcol) self.colorize(line,lastcol) return "break" def autoindent(self,event): """Key bindings for return, tab, and backspace. These overide the standard key bindings in an attempt to be indent aware. """ if event.keysym == 'Return': """if the current line has indent then the new line should have the same indent. The exception is when the line ends with a colon. Note: comments and white space should be stripped before looking for a colon.""" self.edit_separator() # For undo/redo index = self.index(INSERT).split('.') line = int(index[0]) column = int(index[1]) indention = 0 sp = 0 str,lastcol = self.getline(line) for char in str: if char == ' ': #print 'found a space...' sp += 1 if sp%self.indentwidth == 0: #print 'found a tab...' indention += 1 else: break if '#' in str: str = str[:str.index('#')] str.rstrip() if self.get(self.to_index(line, column-1)) == ':': indention += 1 self.insert(INSERT,'\n') self.insert(INSERT,(' '*self.indentwidth)*indention) return 'break' # Overides standard bindings elif event.keysym == 'Tab': # When using graffiti style input tab is not recognized.(key code is zero) """If there is a selection then indent/dedent region else tab should add spaces until INSERT.column%indentwidth is 0.""" self.edit_separator() first,last = self.get_selection_indices() if first and last: if event.state == 9: # note: this doesn't work on PPC, event.state is 0 for all. #this is a , a dedent self.dedent_region() else: self.indent_region() return "break" index = self.index(INSERT).split('.') line = int(index[0]) column = int(index[1]) #prefix = self.get("insert linestart", "insert") self.insert(INSERT,' ') # This is a little clumsy, column += 1 # I need to add one space while column%self.indentwidth != 0: # before the while loop in self.insert(INSERT,' ') # case I started at a tab- column += 1 # stop. return "break" elif event.keysym == 'BackSpace': """If there is a selection delete it otherwise if INSERT.column%indentwidth equals 0 and INSERT.column is preceeded by tabwidth*space then delete tabwidth spaces.""" self.edit_separator() index = self.index(INSERT).split('.') line = int(index[0]) column = int(index[1]) if column%self.indentwidth == 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))) if prev == ' '*self.indentwidth: self.delete(self.to_index(line,(column)-self.indentwidth), self.to_index(line,column)) 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...""" 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.""" 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.""" 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.""" 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)) squote = False # single dquote = False # double tquote = False # triple dstart = 0 sstart = 0 for i in range(len(buffer)): """First scan for an opening triple quote, if that isn't found look for a single or a double quote. If an open triple quote is found add a color tag with no closing tag. Then when/if another triple quote is found close the tag. For now I'm going to ignore multiline strings which are not tri-quoted. """ if buffer[i-3:i] == '"""': #print 'found tri-quote' if not tquote: tquote = True self.tstart = self.to_index(cline, i) self.tag_add('str', self.tstart, END) else: #print 'closing tri-quote' self.tag_remove('str', tstart, END) tquote = False self.tag_add('str', self.tstart, self.to_index(cline, i)) self.tstart = '' elif buffer[i] == '"': if dquote: self.tag_add('str', self.to_index(cline, dstart), self.to_index(cline, i+1)) dquote = False else: dstart = i dquote = True elif buffer[i] == "'": if squote: self.tag_add('str', self.to_index(cline, sstart), self.to_index(cline, i+1)) squote = False else: sstart = i squote = True 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)) # Deep magic by Sebastian, fixes text selection issues. b1motion = root.bind_class('Text','') root.bind_class('Text','','if {![info exists ::tk::Priv(ignoreB1Motion)]} {%s}'%b1motion) root.bind_class('Text','','set ::tk::Priv(ignoreB1Motion) 1') root.bind_class('Text','','array unset ::tk::Priv ignoreB1Motion') app = Idle(root) root.mainloop()