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