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