Tkinter Text widget reading input (how to do overwrite mode?)
Jeff Epler
jepler at unpythonic.net
Tue Apr 30 12:09:21 EDT 2002
Here's another version of the code, with a small enhancement plus
a number of comments.
import Tkinter, sys
def comparable_index(index):
""" Convert a Text index like '37.15' to a tuple that can be compared
with Python's normal comparison operators """
return map(int, index.split("."))
class OverwriteText(Tkinter.Text):
def __init__(self, *args, **kw):
Tkinter.Text.__init__(self, *args, **kw) # Create the widget
# Insert the OverwriteText bindings in the widget tags.
# When an event happens, Tk starts with the most specific tag
# and unless processing is stopped, it moves to the next
# less specific tag. At each step, the best match for the
# event is determined, and that binding is executed.
# Usually a widget's tags look like
# .full.path WidgetClass .toplevel all
# where .full.path is the Tk name for the widget, and .toplevel
# is the name for the toplevel window containing it (often ".").
# Because we want to modify the behavior of the Text widget,
# we insert this new tag, OverwriteText just before Text.
# It *should* always be between indices 0 and 1, but we go through
# this activity anyway.
tags = list(self.bindtags())
idx = tags.index("Text")
tags[idx:idx] = ["OverwriteText"]
self.bindtags(tuple(tags))
self.is_overwrite = 0
# bind_class() with one argument returns all the bindings for that
# class. If none exist, then create them. This notion of the "class"
# is distinct from Python classes
if not self.bind_class("OverwriteText"):
self._init_bindings()
def _init_bindings(self):
# Create two bindings, one that will change the way <KeyPress> works,
# and one that will replace the old behavior of the Insert key with
# a new one (toggle overwrite mode)
# Just writing self.bind_class(..., OverwriteText.KeyPress) would not
# work, because the first (only) arg would be "evt" not an instance
# of OverwriteText
# The following bindings are necessary due to the Tk event matching
# machinery. We don't want to treat Alt-X the same as X, and the way
# Tk does this is by creating a more specific binding which performs
# no action. The whole thing is complicated, and explained in the
# Tk manual page for "bind" under the "MULTIPLE MATCHES" section.
# http://tcl.activestate.com/man/tcl8.2.3/TkCmd/bind.htm#M41
nop = "# nothing"
self.bind_class("OverwriteText", "<Alt-KeyPress>", nop)
self.bind_class("OverwriteText", "<Meta-KeyPress>", nop)
self.bind_class("OverwriteText", "<Control-KeyPress>", nop)
self.bind_class("OverwriteText", "<Escape>", nop)
self.bind_class("OverwriteText", "<KP_Enter>", nop)
if sys.platform == "mac":
self.bind_class("OverwriteText", "<Command-KeyPress>", nop)
self.bind_class("OverwriteText", "<KeyPress>",
lambda evt: OverwriteText.KeyPress(evt.widget, evt))
self.bind_class("OverwriteText", "<Insert>",
lambda evt: OverwriteText.Insert(evt.widget, evt))
def toggle_overwrite(self):
self.is_overwrite = not self.is_overwrite
def KeyPress(self, evt):
# This is called when a key has been pressed, whether or not
# overwrite mode is enabled
if self.is_overwrite:
# We find where the end-of-line is
eol = self.index("insert lineend")
# And we also find the index for the characters we would ovwrrite
nextchar = self.index("insert + %dc" % len(evt.char))
eol_num = comparable_index(eol)
nextchar_num = comparable_index(nextchar)
if eol_num < nextchar_num:
# If the EOL is closer to the insert point, then delete only
# that far
index = eol
else:
# Otherwise, delete as many chars as we'll insert
index = nextchar
# Actually perform the deletion, from the insertion point to
# the index determined above
self.delete("insert", index)
def Insert(self, evt):
self.toggle_overwrite()
# Returning "break" from the binding function means that
# Tk will not pass this event on to the next tag. Otherwise,
# pressing Insert would toggle between insert and overwrite modes,
# and then insert the text from the clipboard
return "break"
def _test():
import sys
t = OverwriteText()
t.pack()
t.insert(0.0, open(sys.argv[0]).read())
t.mainloop()
# If you run this as a script (as opposed to importing it), create a sample
if __name__ == '__main__': _test()
More information about the Python-list
mailing list