1"""
2MultiCall - a class which inherits its methods from a Tkinter widget (Text, for
3example), but enables multiple calls of functions per virtual event - all
4matching events will be called, not only the most specific one. This is done
5by wrapping the event functions - event_add, event_delete and event_info.
6MultiCall recognizes only a subset of legal event sequences. Sequences which
7are not recognized are treated by the original Tk handling mechanism. A
8more-specific event will be called before a less-specific event.
9
10The recognized sequences are complete one-event sequences (no emacs-style
11Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
12Key/Button Press/Release events can have modifiers.
13The recognized modifiers are Shift, Control, Option and Command for Mac, and
14Control, Alt, Shift, Meta/M for other platforms.
15
16For all events which were handled by MultiCall, a new member is added to the
17event instance passed to the binded functions - mc_type. This is one of the
18event type constants defined in this module (such as MC_KEYPRESS).
19For Key/Button events (which are handled by MultiCall and may receive
20modifiers), another member is added - mc_state. This member gives the state
21of the recognized modifiers, as a combination of the modifier constants
22also defined in this module (for example, MC_SHIFT).
23Using these members is absolutely portable.
24
25The order by which events are called is defined by these rules:
261. A more-specific event will be called before a less-specific event.
272. A recently-binded event will be called before a previously-binded event,
28   unless this conflicts with the first rule.
29Each function will be called at most once for each event.
30"""
31
32import sys
33import string
34import re
35import Tkinter
36from idlelib import macosxSupport
37
38# the event type constants, which define the meaning of mc_type
39MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
40MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
41MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
42MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
43MC_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
45MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
46MC_OPTION = 1<<6; MC_COMMAND = 1<<7
47
48# define the list of modifiers, to be used in complex event types.
49if macosxSupport.runningAsOSXApp():
50    _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
51    _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
52else:
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
69class _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
115def 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 = []
137for 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
144class _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]$")
254def _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
296def _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 = {}
304def 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
401if __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