blob: 9ab34c5acfb22c6683882c80954ab503b40d9e47 [file] [log] [blame]
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -04001from tkinter import TclError
David Scherer7aced172000-08-15 01:13:23 +00002
David Scherer7aced172000-08-15 01:13:23 +00003class WidgetRedirector:
Guido van Rossum8ce8a782007-11-01 19:42:39 +00004 """Support for redirecting arbitrary widget subcommands.
David Scherer7aced172000-08-15 01:13:23 +00005
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -04006 Some Tk operations don't normally pass through tkinter. For example, if a
Guido van Rossum8ce8a782007-11-01 19:42:39 +00007 character is inserted into a Text widget by pressing a key, a default Tk
8 binding to the widget's 'insert' operation is activated, and the Tk library
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -04009 processes the insert without calling back into tkinter.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000010
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -040011 Although a binding to <Key> could be made via tkinter, what we really want
12 to do is to hook the Tk 'insert' operation itself. For one thing, we want
13 a text.insert call in idle code to have the same effect as a key press.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000014
15 When a widget is instantiated, a Tcl command is created whose name is the
16 same as the pathname widget._w. This command is used to invoke the various
17 widget operations, e.g. insert (for a Text widget). We are going to hook
18 this command and provide a facility ('register') to intercept the widget
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -040019 operation. We will also intercept method calls on the tkinter class
20 instance that represents the tk widget.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000021
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040022 In IDLE, WidgetRedirector is used in Percolator to intercept Text
23 commands. The function being registered provides access to the top
24 of a Percolator chain. At the bottom of the chain is a call to the
25 original Tk widget operation.
Guido van Rossum8ce8a782007-11-01 19:42:39 +000026 """
David Scherer7aced172000-08-15 01:13:23 +000027 def __init__(self, widget):
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040028 '''Initialize attributes and setup redirection.
29
30 _operations: dict mapping operation name to new function.
31 widget: the widget whose tcl command is to be intercepted.
32 tk: widget.tk, a convenience attribute, probably not needed.
33 orig: new name of the original tcl command.
34
35 Since renaming to orig fails with TclError when orig already
36 exists, only one WidgetDirector can exist for a given widget.
37 '''
Guido van Rossum8ce8a782007-11-01 19:42:39 +000038 self._operations = {}
39 self.widget = widget # widget instance
40 self.tk = tk = widget.tk # widget's root
41 w = widget._w # widget's (full) Tk pathname
David Scherer7aced172000-08-15 01:13:23 +000042 self.orig = w + "_orig"
Guido van Rossum8ce8a782007-11-01 19:42:39 +000043 # Rename the Tcl command within Tcl:
David Scherer7aced172000-08-15 01:13:23 +000044 tk.call("rename", w, self.orig)
Guido van Rossum8ce8a782007-11-01 19:42:39 +000045 # Create a new Tcl command whose name is the widget's pathname, and
46 # whose action is to dispatch on the operation passed to the widget:
David Scherer7aced172000-08-15 01:13:23 +000047 tk.createcommand(w, self.dispatch)
48
49 def __repr__(self):
Serhiy Storchaka465e60e2014-07-25 23:36:00 +030050 return "%s(%s<%s>)" % (self.__class__.__name__,
51 self.widget.__class__.__name__,
52 self.widget._w)
David Scherer7aced172000-08-15 01:13:23 +000053
54 def close(self):
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040055 "Unregister operations and revert redirection created by .__init__."
Guido van Rossum8ce8a782007-11-01 19:42:39 +000056 for operation in list(self._operations):
57 self.unregister(operation)
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040058 widget = self.widget
David Scherer7aced172000-08-15 01:13:23 +000059 tk = widget.tk
60 w = widget._w
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040061 # Restore the original widget Tcl command.
David Scherer7aced172000-08-15 01:13:23 +000062 tk.deletecommand(w)
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040063 tk.call("rename", self.orig, w)
64 del self.widget, self.tk # Should not be needed
65 # if instance is deleted after close, as in Percolator.
David Scherer7aced172000-08-15 01:13:23 +000066
Guido van Rossum8ce8a782007-11-01 19:42:39 +000067 def register(self, operation, function):
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040068 '''Return OriginalCommand(operation) after registering function.
69
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -040070 Registration adds an operation: function pair to ._operations.
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +030071 It also adds a widget function attribute that masks the tkinter
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -040072 class instance method. Method masking operates independently
73 from command dispatch.
74
75 If a second function is registered for the same operation, the
76 first function is replaced in both places.
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040077 '''
Guido van Rossum8ce8a782007-11-01 19:42:39 +000078 self._operations[operation] = function
79 setattr(self.widget, operation, function)
80 return OriginalCommand(self, operation)
David Scherer7aced172000-08-15 01:13:23 +000081
Guido van Rossum8ce8a782007-11-01 19:42:39 +000082 def unregister(self, operation):
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -040083 '''Return the function for the operation, or None.
84
85 Deleting the instance attribute unmasks the class attribute.
86 '''
Guido van Rossum8ce8a782007-11-01 19:42:39 +000087 if operation in self._operations:
88 function = self._operations[operation]
89 del self._operations[operation]
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -040090 try:
Guido van Rossum8ce8a782007-11-01 19:42:39 +000091 delattr(self.widget, operation)
Terry Jan Reedy223dd8d2014-07-11 00:16:00 -040092 except AttributeError:
93 pass
David Scherer7aced172000-08-15 01:13:23 +000094 return function
95 else:
96 return None
97
Guido van Rossum8ce8a782007-11-01 19:42:39 +000098 def dispatch(self, operation, *args):
99 '''Callback from Tcl which runs when the widget is referenced.
100
101 If an operation has been registered in self._operations, apply the
102 associated function to the args passed into Tcl. Otherwise, pass the
103 operation through to Tk via the original Tcl function.
104
105 Note that if a registered function is called, the operation is not
106 passed through to Tk. Apply the function returned by self.register()
Terry Jan Reedy6fa5bdc2016-05-28 13:22:31 -0400107 to *args to accomplish that. For an example, see colorizer.py.
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000108
109 '''
110 m = self._operations.get(operation)
David Scherer7aced172000-08-15 01:13:23 +0000111 try:
112 if m:
Raymond Hettinger931237e2003-07-09 18:48:24 +0000113 return m(*args)
David Scherer7aced172000-08-15 01:13:23 +0000114 else:
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000115 return self.tk.call((self.orig, operation) + args)
David Scherer7aced172000-08-15 01:13:23 +0000116 except TclError:
117 return ""
118
119
120class OriginalCommand:
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -0400121 '''Callable for original tk command that has been redirected.
122
123 Returned by .register; can be used in the function registered.
124 redir = WidgetRedirector(text)
125 def my_insert(*args):
126 print("insert", args)
127 original_insert(*args)
128 original_insert = redir.register("insert", my_insert)
129 '''
David Scherer7aced172000-08-15 01:13:23 +0000130
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000131 def __init__(self, redir, operation):
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -0400132 '''Create .tk_call and .orig_and_operation for .__call__ method.
133
134 .redir and .operation store the input args for __repr__.
135 .tk and .orig copy attributes of .redir (probably not needed).
136 '''
David Scherer7aced172000-08-15 01:13:23 +0000137 self.redir = redir
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000138 self.operation = operation
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -0400139 self.tk = redir.tk # redundant with self.redir
140 self.orig = redir.orig # redundant with self.redir
141 # These two could be deleted after checking recipient code.
142 self.tk_call = redir.tk.call
143 self.orig_and_operation = (redir.orig, operation)
David Scherer7aced172000-08-15 01:13:23 +0000144
145 def __repr__(self):
Serhiy Storchaka465e60e2014-07-25 23:36:00 +0300146 return "%s(%r, %r)" % (self.__class__.__name__,
147 self.redir, self.operation)
David Scherer7aced172000-08-15 01:13:23 +0000148
149 def __call__(self, *args):
Guido van Rossum8ce8a782007-11-01 19:42:39 +0000150 return self.tk_call(self.orig_and_operation + args)
David Scherer7aced172000-08-15 01:13:23 +0000151
152
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -0400153def _widget_redirector(parent): # htest #
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400154 from tkinter import Toplevel, Text
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -0400155
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400156 top = Toplevel(parent)
157 top.title("Test WidgetRedirector")
Terry Jan Reedya7480322016-07-10 17:28:10 -0400158 x, y = map(int, parent.geometry().split('+')[1:])
159 top.geometry("+%d+%d" % (x, y + 175))
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400160 text = Text(top)
David Scherer7aced172000-08-15 01:13:23 +0000161 text.pack()
162 text.focus_set()
163 redir = WidgetRedirector(text)
David Scherer7aced172000-08-15 01:13:23 +0000164 def my_insert(*args):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000165 print("insert", args)
Terry Jan Reedy7cca28f2014-07-10 01:16:49 -0400166 original_insert(*args)
167 original_insert = redir.register("insert", my_insert)
David Scherer7aced172000-08-15 01:13:23 +0000168
169if __name__ == "__main__":
Terry Jan Reedyea3dc802018-06-18 04:47:59 -0400170 from unittest import main
171 main('idlelib.idle_test.test_redirector', verbosity=2, exit=False)
Terry Jan Reedya7480322016-07-10 17:28:10 -0400172
Terry Jan Reedy1b392ff2014-05-24 18:48:18 -0400173 from idlelib.idle_test.htest import run
174 run(_widget_redirector)