WidgetRedirector.py 4.37 KB
Newer Older
1
from tkinter import *
David Scherer's avatar
David Scherer committed
2 3 4

class WidgetRedirector:

5 6 7 8 9 10 11 12 13
    """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.
David Scherer's avatar
David Scherer committed
14

15 16 17 18 19 20 21 22 23 24 25
    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.

    """
David Scherer's avatar
David Scherer committed
26
    def __init__(self, widget):
27 28 29 30
        self._operations = {}
        self.widget = widget            # widget instance
        self.tk = tk = widget.tk        # widget's root
        w = widget._w                   # widget's (full) Tk pathname
David Scherer's avatar
David Scherer committed
31
        self.orig = w + "_orig"
32
        # Rename the Tcl command within Tcl:
David Scherer's avatar
David Scherer committed
33
        tk.call("rename", w, self.orig)
34 35
        # 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:
David Scherer's avatar
David Scherer committed
36 37 38 39 40 41 42
        tk.createcommand(w, self.dispatch)

    def __repr__(self):
        return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__,
                                             self.widget._w)

    def close(self):
43 44
        for operation in list(self._operations):
            self.unregister(operation)
David Scherer's avatar
David Scherer committed
45 46 47 48 49
        widget = self.widget; del self.widget
        orig = self.orig; del self.orig
        tk = widget.tk
        w = widget._w
        tk.deletecommand(w)
50
        # restore the original widget Tcl command:
David Scherer's avatar
David Scherer committed
51 52
        tk.call("rename", orig, w)

53 54 55 56 57 58 59 60 61 62 63
    def register(self, operation, function):
        self._operations[operation] = function
        setattr(self.widget, operation, function)
        return OriginalCommand(self, operation)

    def unregister(self, operation):
        if operation in self._operations:
            function = self._operations[operation]
            del self._operations[operation]
            if hasattr(self.widget, operation):
                delattr(self.widget, operation)
David Scherer's avatar
David Scherer committed
64 65 66 67
            return function
        else:
            return None

68 69 70 71 72 73 74 75 76 77 78 79 80
    def dispatch(self, operation, *args):
        '''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)
David Scherer's avatar
David Scherer committed
81 82
        try:
            if m:
83
                return m(*args)
David Scherer's avatar
David Scherer committed
84
            else:
85
                return self.tk.call((self.orig, operation) + args)
David Scherer's avatar
David Scherer committed
86 87 88 89 90 91
        except TclError:
            return ""


class OriginalCommand:

92
    def __init__(self, redir, operation):
David Scherer's avatar
David Scherer committed
93
        self.redir = redir
94
        self.operation = operation
David Scherer's avatar
David Scherer committed
95 96 97
        self.tk = redir.tk
        self.orig = redir.orig
        self.tk_call = self.tk.call
98
        self.orig_and_operation = (self.orig, self.operation)
David Scherer's avatar
David Scherer committed
99 100

    def __repr__(self):
101
        return "OriginalCommand(%r, %r)" % (self.redir, self.operation)
David Scherer's avatar
David Scherer committed
102 103

    def __call__(self, *args):
104
        return self.tk_call(self.orig_and_operation + args)
David Scherer's avatar
David Scherer committed
105 106 107 108


def main():
    root = Tk()
109
    root.wm_protocol("WM_DELETE_WINDOW", root.quit)
David Scherer's avatar
David Scherer committed
110 111 112 113
    text = Text()
    text.pack()
    text.focus_set()
    redir = WidgetRedirector(text)
114
    global previous_tcl_fcn
David Scherer's avatar
David Scherer committed
115
    def my_insert(*args):
116
        print("insert", args)
117 118 119 120 121
        previous_tcl_fcn(*args)
    previous_tcl_fcn = redir.register("insert", my_insert)
    root.mainloop()
    redir.unregister("insert")  # runs after first 'close window'
    redir.close()
David Scherer's avatar
David Scherer committed
122
    root.mainloop()
123
    root.destroy()
David Scherer's avatar
David Scherer committed
124 125 126

if __name__ == "__main__":
    main()