(root)/
Python-3.11.7/
Lib/
idlelib/
redirector.py
       1  from tkinter import TclError
       2  
       3  class ESC[4;38;5;81mWidgetRedirector:
       4      """Support for redirecting arbitrary widget subcommands.
       5  
       6      Some Tk operations don't normally pass through tkinter.  For example, if a
       7      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
       9      processes the insert without calling back into tkinter.
      10  
      11      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.
      14  
      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
      19      operation.  We will also intercept method calls on the tkinter class
      20      instance that represents the tk widget.
      21  
      22      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.
      26      """
      27      def __init__(self, widget):
      28          '''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          '''
      38          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
      42          self.orig = w + "_orig"
      43          # Rename the Tcl command within Tcl:
      44          tk.call("rename", w, self.orig)
      45          # 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:
      47          tk.createcommand(w, self.dispatch)
      48  
      49      def __repr__(self):
      50          w = self.widget
      51          return f"{self.__class__.__name__,}({w.__class__.__name__}<{w._w}>)"
      52  
      53      def close(self):
      54          "Unregister operations and revert redirection created by .__init__."
      55          for operation in list(self._operations):
      56              self.unregister(operation)
      57          widget = self.widget
      58          tk = widget.tk
      59          w = widget._w
      60          # Restore the original widget Tcl command.
      61          tk.deletecommand(w)
      62          tk.call("rename", self.orig, w)
      63          del self.widget, self.tk  # Should not be needed
      64          # if instance is deleted after close, as in Percolator.
      65  
      66      def register(self, operation, function):
      67          '''Return OriginalCommand(operation) after registering function.
      68  
      69          Registration adds an operation: function pair to ._operations.
      70          It also adds a widget function attribute that masks the tkinter
      71          class instance method.  Method masking operates independently
      72          from command dispatch.
      73  
      74          If a second function is registered for the same operation, the
      75          first function is replaced in both places.
      76          '''
      77          self._operations[operation] = function
      78          setattr(self.widget, operation, function)
      79          return OriginalCommand(self, operation)
      80  
      81      def unregister(self, operation):
      82          '''Return the function for the operation, or None.
      83  
      84          Deleting the instance attribute unmasks the class attribute.
      85          '''
      86          if operation in self._operations:
      87              function = self._operations[operation]
      88              del self._operations[operation]
      89              try:
      90                  delattr(self.widget, operation)
      91              except AttributeError:
      92                  pass
      93              return function
      94          else:
      95              return None
      96  
      97      def dispatch(self, operation, *args):
      98          '''Callback from Tcl which runs when the widget is referenced.
      99  
     100          If an operation has been registered in self._operations, apply the
     101          associated function to the args passed into Tcl. Otherwise, pass the
     102          operation through to Tk via the original Tcl function.
     103  
     104          Note that if a registered function is called, the operation is not
     105          passed through to Tk.  Apply the function returned by self.register()
     106          to *args to accomplish that.  For an example, see colorizer.py.
     107  
     108          '''
     109          m = self._operations.get(operation)
     110          try:
     111              if m:
     112                  return m(*args)
     113              else:
     114                  return self.tk.call((self.orig, operation) + args)
     115          except TclError:
     116              return ""
     117  
     118  
     119  class ESC[4;38;5;81mOriginalCommand:
     120      '''Callable for original tk command that has been redirected.
     121  
     122      Returned by .register; can be used in the function registered.
     123      redir = WidgetRedirector(text)
     124      def my_insert(*args):
     125          print("insert", args)
     126          original_insert(*args)
     127      original_insert = redir.register("insert", my_insert)
     128      '''
     129  
     130      def __init__(self, redir, operation):
     131          '''Create .tk_call and .orig_and_operation for .__call__ method.
     132  
     133          .redir and .operation store the input args for __repr__.
     134          .tk and .orig copy attributes of .redir (probably not needed).
     135          '''
     136          self.redir = redir
     137          self.operation = operation
     138          self.tk = redir.tk  # redundant with self.redir
     139          self.orig = redir.orig  # redundant with self.redir
     140          # These two could be deleted after checking recipient code.
     141          self.tk_call = redir.tk.call
     142          self.orig_and_operation = (redir.orig, operation)
     143  
     144      def __repr__(self):
     145          return f"{self.__class__.__name__,}({self.redir!r}, {self.operation!r})"
     146  
     147      def __call__(self, *args):
     148          return self.tk_call(self.orig_and_operation + args)
     149  
     150  
     151  def _widget_redirector(parent):  # htest #
     152      from tkinter import Toplevel, Text
     153  
     154      top = Toplevel(parent)
     155      top.title("Test WidgetRedirector")
     156      x, y = map(int, parent.geometry().split('+')[1:])
     157      top.geometry("+%d+%d" % (x, y + 175))
     158      text = Text(top)
     159      text.pack()
     160      text.focus_set()
     161      redir = WidgetRedirector(text)
     162      def my_insert(*args):
     163          print("insert", args)
     164          original_insert(*args)
     165      original_insert = redir.register("insert", my_insert)
     166  
     167  
     168  if __name__ == "__main__":
     169      from unittest import main
     170      main('idlelib.idle_test.test_redirector', verbosity=2, exit=False)
     171  
     172      from idlelib.idle_test.htest import run
     173      run(_widget_redirector)