1. Add comments to provide top-level documentation.

2. Refactor to use more descriptive names.
3. Enhance tests in main().
This commit is contained in:
Kurt B. Kaiser 2007-10-26 00:10:09 +00:00
parent aa8a96a159
commit 1e45f80b22
1 changed files with 67 additions and 29 deletions

View File

@ -1,17 +1,38 @@
from Tkinter import * from Tkinter import *
class WidgetRedirector: class WidgetRedirector:
"""Support for redirecting arbitrary widget subcommands.""" """Support for redirecting arbitrary widget subcommands.
Some Tk operations don't normally pass through Tkinter. For example, if a
character is inserted into a Text widget by pressing a key, a default Tk
binding to the widget's 'insert' operation is activated, and the Tk library
processes the insert without calling back into Tkinter.
Although a binding to <Key> could be made via Tkinter, what we really want
to do is to hook the Tk 'insert' operation itself.
When a widget is instantiated, a Tcl command is created whose name is the
same as the pathname widget._w. This command is used to invoke the various
widget operations, e.g. insert (for a Text widget). We are going to hook
this command and provide a facility ('register') to intercept the widget
operation.
In IDLE, the function being registered provides access to the top of a
Percolator chain. At the bottom of the chain is a call to the original
Tk widget operation.
"""
def __init__(self, widget): def __init__(self, widget):
self.dict = {} self._operations = {}
self.widget = widget self.widget = widget # widget instance
self.tk = tk = widget.tk self.tk = tk = widget.tk # widget's root
w = widget._w w = widget._w # widget's (full) Tk pathname
self.orig = w + "_orig" self.orig = w + "_orig"
# Rename the Tcl command within Tcl:
tk.call("rename", w, self.orig) tk.call("rename", w, self.orig)
# Create a new Tcl command whose name is the widget's pathname, and
# whose action is to dispatch on the operation passed to the widget:
tk.createcommand(w, self.dispatch) tk.createcommand(w, self.dispatch)
def __repr__(self): def __repr__(self):
@ -19,70 +40,87 @@ class WidgetRedirector:
self.widget._w) self.widget._w)
def close(self): def close(self):
for name in self.dict.keys(): for operation in self._operations:
self.unregister(name) self.unregister(operation)
widget = self.widget; del self.widget widget = self.widget; del self.widget
orig = self.orig; del self.orig orig = self.orig; del self.orig
tk = widget.tk tk = widget.tk
w = widget._w w = widget._w
tk.deletecommand(w) tk.deletecommand(w)
# restore the original widget Tcl command:
tk.call("rename", orig, w) tk.call("rename", orig, w)
def register(self, name, function): def register(self, operation, function):
self.dict[name] = function self._operations[operation] = function
setattr(self.widget, name, function) setattr(self.widget, operation, function)
return OriginalCommand(self, name) return OriginalCommand(self, operation)
def unregister(self, name): def unregister(self, operation):
if self.dict.has_key(name): if operation in self._operations:
function = self.dict[name] function = self._operations[operation]
del self.dict[name] del self._operations[operation]
if hasattr(self.widget, name): if hasattr(self.widget, operation):
delattr(self.widget, name) delattr(self.widget, operation)
return function return function
else: else:
return None return None
def dispatch(self, cmd, *args): def dispatch(self, operation, *args):
m = self.dict.get(cmd) '''Callback from Tcl which runs when the widget is referenced.
If an operation has been registered in self._operations, apply the
associated function to the args passed into Tcl. Otherwise, pass the
operation through to Tk via the original Tcl function.
Note that if a registered function is called, the operation is not
passed through to Tk. Apply the function returned by self.register()
to *args to accomplish that. For an example, see ColorDelegator.py.
'''
m = self._operations.get(operation)
try: try:
if m: if m:
return m(*args) return m(*args)
else: else:
return self.tk.call((self.orig, cmd) + args) return self.tk.call((self.orig, operation) + args)
except TclError: except TclError:
return "" return ""
class OriginalCommand: class OriginalCommand:
def __init__(self, redir, name): def __init__(self, redir, operation):
self.redir = redir self.redir = redir
self.name = name self.operation = operation
self.tk = redir.tk self.tk = redir.tk
self.orig = redir.orig self.orig = redir.orig
self.tk_call = self.tk.call self.tk_call = self.tk.call
self.orig_and_name = (self.orig, self.name) self.orig_and_operation = (self.orig, self.operation)
def __repr__(self): def __repr__(self):
return "OriginalCommand(%r, %r)" % (self.redir, self.name) return "OriginalCommand(%r, %r)" % (self.redir, self.operation)
def __call__(self, *args): def __call__(self, *args):
return self.tk_call(self.orig_and_name + args) return self.tk_call(self.orig_and_operation + args)
def main(): def main():
root = Tk() root = Tk()
root.wm_protocol("WM_DELETE_WINDOW", root.quit)
text = Text() text = Text()
text.pack() text.pack()
text.focus_set() text.focus_set()
redir = WidgetRedirector(text) redir = WidgetRedirector(text)
global orig_insert global previous_tcl_fcn
def my_insert(*args): def my_insert(*args):
print "insert", args print "insert", args
orig_insert(*args) previous_tcl_fcn(*args)
orig_insert = redir.register("insert", my_insert) previous_tcl_fcn = redir.register("insert", my_insert)
root.mainloop() root.mainloop()
redir.unregister("insert") # runs after first 'close window'
redir.close()
root.mainloop()
root.destroy()
if __name__ == "__main__": if __name__ == "__main__":
main() main()