(root)/
Python-3.11.7/
Lib/
idlelib/
multicall.py
       1  """
       2  MultiCall - a class which inherits its methods from a Tkinter widget (Text, for
       3  example), but enables multiple calls of functions per virtual event - all
       4  matching events will be called, not only the most specific one. This is done
       5  by wrapping the event functions - event_add, event_delete and event_info.
       6  MultiCall recognizes only a subset of legal event sequences. Sequences which
       7  are not recognized are treated by the original Tk handling mechanism. A
       8  more-specific event will be called before a less-specific event.
       9  
      10  The recognized sequences are complete one-event sequences (no emacs-style
      11  Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
      12  Key/Button Press/Release events can have modifiers.
      13  The recognized modifiers are Shift, Control, Option and Command for Mac, and
      14  Control, Alt, Shift, Meta/M for other platforms.
      15  
      16  For all events which were handled by MultiCall, a new member is added to the
      17  event instance passed to the binded functions - mc_type. This is one of the
      18  event type constants defined in this module (such as MC_KEYPRESS).
      19  For Key/Button events (which are handled by MultiCall and may receive
      20  modifiers), another member is added - mc_state. This member gives the state
      21  of the recognized modifiers, as a combination of the modifier constants
      22  also defined in this module (for example, MC_SHIFT).
      23  Using these members is absolutely portable.
      24  
      25  The order by which events are called is defined by these rules:
      26  1. A more-specific event will be called before a less-specific event.
      27  2. A recently-binded event will be called before a previously-binded event,
      28     unless this conflicts with the first rule.
      29  Each function will be called at most once for each event.
      30  """
      31  import re
      32  import sys
      33  
      34  import tkinter
      35  
      36  # the event type constants, which define the meaning of mc_type
      37  MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
      38  MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
      39  MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
      40  MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
      41  MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
      42  # the modifier state constants, which define the meaning of mc_state
      43  MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
      44  MC_OPTION = 1<<6; MC_COMMAND = 1<<7
      45  
      46  # define the list of modifiers, to be used in complex event types.
      47  if sys.platform == "darwin":
      48      _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
      49      _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
      50  else:
      51      _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
      52      _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
      53  
      54  # a dictionary to map a modifier name into its number
      55  _modifier_names = {name: number
      56                           for number in range(len(_modifiers))
      57                           for name in _modifiers[number]}
      58  
      59  # In 3.4, if no shell window is ever open, the underlying Tk widget is
      60  # destroyed before .__del__ methods here are called.  The following
      61  # is used to selectively ignore shutdown exceptions to avoid
      62  # 'Exception ignored' messages.  See http://bugs.python.org/issue20167
      63  APPLICATION_GONE = "application has been destroyed"
      64  
      65  # A binder is a class which binds functions to one type of event. It has two
      66  # methods: bind and unbind, which get a function and a parsed sequence, as
      67  # returned by _parse_sequence(). There are two types of binders:
      68  # _SimpleBinder handles event types with no modifiers and no detail.
      69  # No Python functions are called when no events are binded.
      70  # _ComplexBinder handles event types with modifiers and a detail.
      71  # A Python function is called each time an event is generated.
      72  
      73  class ESC[4;38;5;81m_SimpleBinder:
      74      def __init__(self, type, widget, widgetinst):
      75          self.type = type
      76          self.sequence = '<'+_types[type][0]+'>'
      77          self.widget = widget
      78          self.widgetinst = widgetinst
      79          self.bindedfuncs = []
      80          self.handlerid = None
      81  
      82      def bind(self, triplet, func):
      83          if not self.handlerid:
      84              def handler(event, l = self.bindedfuncs, mc_type = self.type):
      85                  event.mc_type = mc_type
      86                  wascalled = {}
      87                  for i in range(len(l)-1, -1, -1):
      88                      func = l[i]
      89                      if func not in wascalled:
      90                          wascalled[func] = True
      91                          r = func(event)
      92                          if r:
      93                              return r
      94              self.handlerid = self.widget.bind(self.widgetinst,
      95                                                self.sequence, handler)
      96          self.bindedfuncs.append(func)
      97  
      98      def unbind(self, triplet, func):
      99          self.bindedfuncs.remove(func)
     100          if not self.bindedfuncs:
     101              self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
     102              self.handlerid = None
     103  
     104      def __del__(self):
     105          if self.handlerid:
     106              try:
     107                  self.widget.unbind(self.widgetinst, self.sequence,
     108                          self.handlerid)
     109              except tkinter.TclError as e:
     110                  if not APPLICATION_GONE in e.args[0]:
     111                      raise
     112  
     113  # An int in range(1 << len(_modifiers)) represents a combination of modifiers
     114  # (if the least significant bit is on, _modifiers[0] is on, and so on).
     115  # _state_subsets gives for each combination of modifiers, or *state*,
     116  # a list of the states which are a subset of it. This list is ordered by the
     117  # number of modifiers is the state - the most specific state comes first.
     118  _states = range(1 << len(_modifiers))
     119  _state_names = [''.join(m[0]+'-'
     120                          for i, m in enumerate(_modifiers)
     121                          if (1 << i) & s)
     122                  for s in _states]
     123  
     124  def expand_substates(states):
     125      '''For each item of states return a list containing all combinations of
     126      that item with individual bits reset, sorted by the number of set bits.
     127      '''
     128      def nbits(n):
     129          "number of bits set in n base 2"
     130          nb = 0
     131          while n:
     132              n, rem = divmod(n, 2)
     133              nb += rem
     134          return nb
     135      statelist = []
     136      for state in states:
     137          substates = list({state & x for x in states})
     138          substates.sort(key=nbits, reverse=True)
     139          statelist.append(substates)
     140      return statelist
     141  
     142  _state_subsets = expand_substates(_states)
     143  
     144  # _state_codes gives for each state, the portable code to be passed as mc_state
     145  _state_codes = []
     146  for s in _states:
     147      r = 0
     148      for i in range(len(_modifiers)):
     149          if (1 << i) & s:
     150              r |= _modifier_masks[i]
     151      _state_codes.append(r)
     152  
     153  class ESC[4;38;5;81m_ComplexBinder:
     154      # This class binds many functions, and only unbinds them when it is deleted.
     155      # self.handlerids is the list of seqs and ids of binded handler functions.
     156      # The binded functions sit in a dictionary of lists of lists, which maps
     157      # a detail (or None) and a state into a list of functions.
     158      # When a new detail is discovered, handlers for all the possible states
     159      # are binded.
     160  
     161      def __create_handler(self, lists, mc_type, mc_state):
     162          def handler(event, lists = lists,
     163                      mc_type = mc_type, mc_state = mc_state,
     164                      ishandlerrunning = self.ishandlerrunning,
     165                      doafterhandler = self.doafterhandler):
     166              ishandlerrunning[:] = [True]
     167              event.mc_type = mc_type
     168              event.mc_state = mc_state
     169              wascalled = {}
     170              r = None
     171              for l in lists:
     172                  for i in range(len(l)-1, -1, -1):
     173                      func = l[i]
     174                      if func not in wascalled:
     175                          wascalled[func] = True
     176                          r = l[i](event)
     177                          if r:
     178                              break
     179                  if r:
     180                      break
     181              ishandlerrunning[:] = []
     182              # Call all functions in doafterhandler and remove them from list
     183              for f in doafterhandler:
     184                  f()
     185              doafterhandler[:] = []
     186              if r:
     187                  return r
     188          return handler
     189  
     190      def __init__(self, type, widget, widgetinst):
     191          self.type = type
     192          self.typename = _types[type][0]
     193          self.widget = widget
     194          self.widgetinst = widgetinst
     195          self.bindedfuncs = {None: [[] for s in _states]}
     196          self.handlerids = []
     197          # we don't want to change the lists of functions while a handler is
     198          # running - it will mess up the loop and anyway, we usually want the
     199          # change to happen from the next event. So we have a list of functions
     200          # for the handler to run after it finishes calling the binded functions.
     201          # It calls them only once.
     202          # ishandlerrunning is a list. An empty one means no, otherwise - yes.
     203          # this is done so that it would be mutable.
     204          self.ishandlerrunning = []
     205          self.doafterhandler = []
     206          for s in _states:
     207              lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
     208              handler = self.__create_handler(lists, type, _state_codes[s])
     209              seq = '<'+_state_names[s]+self.typename+'>'
     210              self.handlerids.append((seq, self.widget.bind(self.widgetinst,
     211                                                            seq, handler)))
     212  
     213      def bind(self, triplet, func):
     214          if triplet[2] not in self.bindedfuncs:
     215              self.bindedfuncs[triplet[2]] = [[] for s in _states]
     216              for s in _states:
     217                  lists = [ self.bindedfuncs[detail][i]
     218                            for detail in (triplet[2], None)
     219                            for i in _state_subsets[s]       ]
     220                  handler = self.__create_handler(lists, self.type,
     221                                                  _state_codes[s])
     222                  seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
     223                  self.handlerids.append((seq, self.widget.bind(self.widgetinst,
     224                                                                seq, handler)))
     225          doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
     226          if not self.ishandlerrunning:
     227              doit()
     228          else:
     229              self.doafterhandler.append(doit)
     230  
     231      def unbind(self, triplet, func):
     232          doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
     233          if not self.ishandlerrunning:
     234              doit()
     235          else:
     236              self.doafterhandler.append(doit)
     237  
     238      def __del__(self):
     239          for seq, id in self.handlerids:
     240              try:
     241                  self.widget.unbind(self.widgetinst, seq, id)
     242              except tkinter.TclError as e:
     243                  if not APPLICATION_GONE in e.args[0]:
     244                      raise
     245  
     246  # define the list of event types to be handled by MultiEvent. the order is
     247  # compatible with the definition of event type constants.
     248  _types = (
     249      ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
     250      ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
     251      ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
     252      ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
     253      ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
     254      ("Visibility",),
     255  )
     256  
     257  # which binder should be used for every event type?
     258  _binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
     259  
     260  # A dictionary to map a type name into its number
     261  _type_names = {name: number
     262                       for number in range(len(_types))
     263                       for name in _types[number]}
     264  
     265  _keysym_re = re.compile(r"^\w+$")
     266  _button_re = re.compile(r"^[1-5]$")
     267  def _parse_sequence(sequence):
     268      """Get a string which should describe an event sequence. If it is
     269      successfully parsed as one, return a tuple containing the state (as an int),
     270      the event type (as an index of _types), and the detail - None if none, or a
     271      string if there is one. If the parsing is unsuccessful, return None.
     272      """
     273      if not sequence or sequence[0] != '<' or sequence[-1] != '>':
     274          return None
     275      words = sequence[1:-1].split('-')
     276      modifiers = 0
     277      while words and words[0] in _modifier_names:
     278          modifiers |= 1 << _modifier_names[words[0]]
     279          del words[0]
     280      if words and words[0] in _type_names:
     281          type = _type_names[words[0]]
     282          del words[0]
     283      else:
     284          return None
     285      if _binder_classes[type] is _SimpleBinder:
     286          if modifiers or words:
     287              return None
     288          else:
     289              detail = None
     290      else:
     291          # _ComplexBinder
     292          if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
     293              type_re = _keysym_re
     294          else:
     295              type_re = _button_re
     296  
     297          if not words:
     298              detail = None
     299          elif len(words) == 1 and type_re.match(words[0]):
     300              detail = words[0]
     301          else:
     302              return None
     303  
     304      return modifiers, type, detail
     305  
     306  def _triplet_to_sequence(triplet):
     307      if triplet[2]:
     308          return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
     309                 triplet[2]+'>'
     310      else:
     311          return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
     312  
     313  _multicall_dict = {}
     314  def MultiCallCreator(widget):
     315      """Return a MultiCall class which inherits its methods from the
     316      given widget class (for example, Tkinter.Text). This is used
     317      instead of a templating mechanism.
     318      """
     319      if widget in _multicall_dict:
     320          return _multicall_dict[widget]
     321  
     322      class ESC[4;38;5;81mMultiCall (ESC[4;38;5;149mwidget):
     323          assert issubclass(widget, tkinter.Misc)
     324  
     325          def __init__(self, *args, **kwargs):
     326              widget.__init__(self, *args, **kwargs)
     327              # a dictionary which maps a virtual event to a tuple with:
     328              #  0. the function binded
     329              #  1. a list of triplets - the sequences it is binded to
     330              self.__eventinfo = {}
     331              self.__binders = [_binder_classes[i](i, widget, self)
     332                                for i in range(len(_types))]
     333  
     334          def bind(self, sequence=None, func=None, add=None):
     335              #print("bind(%s, %s, %s)" % (sequence, func, add),
     336              #      file=sys.__stderr__)
     337              if type(sequence) is str and len(sequence) > 2 and \
     338                 sequence[:2] == "<<" and sequence[-2:] == ">>":
     339                  if sequence in self.__eventinfo:
     340                      ei = self.__eventinfo[sequence]
     341                      if ei[0] is not None:
     342                          for triplet in ei[1]:
     343                              self.__binders[triplet[1]].unbind(triplet, ei[0])
     344                      ei[0] = func
     345                      if ei[0] is not None:
     346                          for triplet in ei[1]:
     347                              self.__binders[triplet[1]].bind(triplet, func)
     348                  else:
     349                      self.__eventinfo[sequence] = [func, []]
     350              return widget.bind(self, sequence, func, add)
     351  
     352          def unbind(self, sequence, funcid=None):
     353              if type(sequence) is str and len(sequence) > 2 and \
     354                 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
     355                 sequence in self.__eventinfo:
     356                  func, triplets = self.__eventinfo[sequence]
     357                  if func is not None:
     358                      for triplet in triplets:
     359                          self.__binders[triplet[1]].unbind(triplet, func)
     360                      self.__eventinfo[sequence][0] = None
     361              return widget.unbind(self, sequence, funcid)
     362  
     363          def event_add(self, virtual, *sequences):
     364              #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
     365              #      file=sys.__stderr__)
     366              if virtual not in self.__eventinfo:
     367                  self.__eventinfo[virtual] = [None, []]
     368  
     369              func, triplets = self.__eventinfo[virtual]
     370              for seq in sequences:
     371                  triplet = _parse_sequence(seq)
     372                  if triplet is None:
     373                      #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
     374                      widget.event_add(self, virtual, seq)
     375                  else:
     376                      if func is not None:
     377                          self.__binders[triplet[1]].bind(triplet, func)
     378                      triplets.append(triplet)
     379  
     380          def event_delete(self, virtual, *sequences):
     381              if virtual not in self.__eventinfo:
     382                  return
     383              func, triplets = self.__eventinfo[virtual]
     384              for seq in sequences:
     385                  triplet = _parse_sequence(seq)
     386                  if triplet is None:
     387                      #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
     388                      widget.event_delete(self, virtual, seq)
     389                  else:
     390                      if func is not None:
     391                          self.__binders[triplet[1]].unbind(triplet, func)
     392                      triplets.remove(triplet)
     393  
     394          def event_info(self, virtual=None):
     395              if virtual is None or virtual not in self.__eventinfo:
     396                  return widget.event_info(self, virtual)
     397              else:
     398                  return tuple(map(_triplet_to_sequence,
     399                                   self.__eventinfo[virtual][1])) + \
     400                         widget.event_info(self, virtual)
     401  
     402          def __del__(self):
     403              for virtual in self.__eventinfo:
     404                  func, triplets = self.__eventinfo[virtual]
     405                  if func:
     406                      for triplet in triplets:
     407                          try:
     408                              self.__binders[triplet[1]].unbind(triplet, func)
     409                          except tkinter.TclError as e:
     410                              if not APPLICATION_GONE in e.args[0]:
     411                                  raise
     412  
     413      _multicall_dict[widget] = MultiCall
     414      return MultiCall
     415  
     416  
     417  def _multi_call(parent):  # htest #
     418      top = tkinter.Toplevel(parent)
     419      top.title("Test MultiCall")
     420      x, y = map(int, parent.geometry().split('+')[1:])
     421      top.geometry("+%d+%d" % (x, y + 175))
     422      text = MultiCallCreator(tkinter.Text)(top)
     423      text.pack()
     424      text.focus_set()
     425  
     426      def bindseq(seq, n=[0]):
     427          def handler(event):
     428              print(seq)
     429          text.bind("<<handler%d>>"%n[0], handler)
     430          text.event_add("<<handler%d>>"%n[0], seq)
     431          n[0] += 1
     432      bindseq("<Key>")
     433      bindseq("<Control-Key>")
     434      bindseq("<Alt-Key-a>")
     435      bindseq("<Control-Key-a>")
     436      bindseq("<Alt-Control-Key-a>")
     437      bindseq("<Key-b>")
     438      bindseq("<Control-Button-1>")
     439      bindseq("<Button-2>")
     440      bindseq("<Alt-Button-1>")
     441      bindseq("<FocusOut>")
     442      bindseq("<Enter>")
     443      bindseq("<Leave>")
     444  
     445  
     446  if __name__ == "__main__":
     447      from unittest import main
     448      main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False)
     449  
     450      from idlelib.idle_test.htest import run
     451      run(_multi_call)