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