• 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"""
31import re
32import sys
33
34import tkinter
35
36# the event type constants, which define the meaning of mc_type
37MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
38MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
39MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
40MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
41MC_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
43MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
44MC_OPTION = 1<<6; MC_COMMAND = 1<<7
45
46# define the list of modifiers, to be used in complex event types.
47if sys.platform == "darwin":
48    _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
49    _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
50else:
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 = dict([(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
63APPLICATION_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
73class _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
124def 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(set(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 = []
146for 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
153class _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 = dict([(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]$")
267def _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
306def _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 = {}
314def 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 MultiCall (widget):
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
417def _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    def bindseq(seq, n=[0]):
425        def handler(event):
426            print(seq)
427        text.bind("<<handler%d>>"%n[0], handler)
428        text.event_add("<<handler%d>>"%n[0], seq)
429        n[0] += 1
430    bindseq("<Key>")
431    bindseq("<Control-Key>")
432    bindseq("<Alt-Key-a>")
433    bindseq("<Control-Key-a>")
434    bindseq("<Alt-Control-Key-a>")
435    bindseq("<Key-b>")
436    bindseq("<Control-Button-1>")
437    bindseq("<Button-2>")
438    bindseq("<Alt-Button-1>")
439    bindseq("<FocusOut>")
440    bindseq("<Enter>")
441    bindseq("<Leave>")
442
443if __name__ == "__main__":
444    from unittest import main
445    main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False)
446
447    from idlelib.idle_test.htest import run
448    run(_multi_call)
449