PageRenderTime 299ms CodeModel.GetById 81ms app.highlight 136ms RepoModel.GetById 76ms app.codeStats 0ms

/Lib/idlelib/MultiCall.py

http://unladen-swallow.googlecode.com/
Python | 406 lines | 350 code | 10 blank | 46 comment | 19 complexity | 6fe8c8bd338d255f71816ca848e039d2 MD5 | raw file
  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
 36import 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 = [reduce(lambda x, y: x + y,
111                       [_modifiers[i][0]+'-' for i in range(len(_modifiers))
112                        if (1 << i) & s],
113                       "")
114                for s in _states]
115_state_subsets = map(lambda i: filter(lambda j: not (j & (~i)), _states),
116                      _states)
117for l in _state_subsets:
118    l.sort(lambda a, b, nummod = lambda x: len(filter(lambda i: (1<<i) & x,
119                                                      range(len(_modifiers)))):
120           nummod(b) - nummod(a))
121# _state_codes gives for each state, the portable code to be passed as mc_state
122_state_codes = [reduce(lambda x, y: x | y,
123                       [_modifier_masks[i] for i in range(len(_modifiers))
124                        if (1 << i) & s],
125                       0)
126                for s in _states]
127
128class _ComplexBinder:
129    # This class binds many functions, and only unbinds them when it is deleted.
130    # self.handlerids is the list of seqs and ids of binded handler functions.
131    # The binded functions sit in a dictionary of lists of lists, which maps
132    # a detail (or None) and a state into a list of functions.
133    # When a new detail is discovered, handlers for all the possible states
134    # are binded.
135
136    def __create_handler(self, lists, mc_type, mc_state):
137        def handler(event, lists = lists,
138                    mc_type = mc_type, mc_state = mc_state,
139                    ishandlerrunning = self.ishandlerrunning,
140                    doafterhandler = self.doafterhandler):
141            ishandlerrunning[:] = [True]
142            event.mc_type = mc_type
143            event.mc_state = mc_state
144            wascalled = {}
145            r = None
146            for l in lists:
147                for i in range(len(l)-1, -1, -1):
148                    func = l[i]
149                    if func not in wascalled:
150                        wascalled[func] = True
151                        r = l[i](event)
152                        if r:
153                            break
154                if r:
155                    break
156            ishandlerrunning[:] = []
157            # Call all functions in doafterhandler and remove them from list
158            while doafterhandler:
159                doafterhandler.pop()()
160            if r:
161                return r
162        return handler
163
164    def __init__(self, type, widget, widgetinst):
165        self.type = type
166        self.typename = _types[type][0]
167        self.widget = widget
168        self.widgetinst = widgetinst
169        self.bindedfuncs = {None: [[] for s in _states]}
170        self.handlerids = []
171        # we don't want to change the lists of functions while a handler is
172        # running - it will mess up the loop and anyway, we usually want the
173        # change to happen from the next event. So we have a list of functions
174        # for the handler to run after it finishes calling the binded functions.
175        # It calls them only once.
176        # ishandlerrunning is a list. An empty one means no, otherwise - yes.
177        # this is done so that it would be mutable.
178        self.ishandlerrunning = []
179        self.doafterhandler = []
180        for s in _states:
181            lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
182            handler = self.__create_handler(lists, type, _state_codes[s])
183            seq = '<'+_state_names[s]+self.typename+'>'
184            self.handlerids.append((seq, self.widget.bind(self.widgetinst,
185                                                          seq, handler)))
186
187    def bind(self, triplet, func):
188        if not self.bindedfuncs.has_key(triplet[2]):
189            self.bindedfuncs[triplet[2]] = [[] for s in _states]
190            for s in _states:
191                lists = [ self.bindedfuncs[detail][i]
192                          for detail in (triplet[2], None)
193                          for i in _state_subsets[s]       ]
194                handler = self.__create_handler(lists, self.type,
195                                                _state_codes[s])
196                seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
197                self.handlerids.append((seq, self.widget.bind(self.widgetinst,
198                                                              seq, handler)))
199        doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
200        if not self.ishandlerrunning:
201            doit()
202        else:
203            self.doafterhandler.append(doit)
204
205    def unbind(self, triplet, func):
206        doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
207        if not self.ishandlerrunning:
208            doit()
209        else:
210            self.doafterhandler.append(doit)
211
212    def __del__(self):
213        for seq, id in self.handlerids:
214            self.widget.unbind(self.widgetinst, seq, id)
215
216# define the list of event types to be handled by MultiEvent. the order is
217# compatible with the definition of event type constants.
218_types = (
219    ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
220    ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
221    ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
222    ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
223    ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
224    ("Visibility",),
225)
226
227# which binder should be used for every event type?
228_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
229
230# A dictionary to map a type name into its number
231_type_names = dict([(name, number)
232                     for number in range(len(_types))
233                     for name in _types[number]])
234
235_keysym_re = re.compile(r"^\w+$")
236_button_re = re.compile(r"^[1-5]$")
237def _parse_sequence(sequence):
238    """Get a string which should describe an event sequence. If it is
239    successfully parsed as one, return a tuple containing the state (as an int),
240    the event type (as an index of _types), and the detail - None if none, or a
241    string if there is one. If the parsing is unsuccessful, return None.
242    """
243    if not sequence or sequence[0] != '<' or sequence[-1] != '>':
244        return None
245    words = string.split(sequence[1:-1], '-')
246
247    modifiers = 0
248    while words and words[0] in _modifier_names:
249        modifiers |= 1 << _modifier_names[words[0]]
250        del words[0]
251
252    if words and words[0] in _type_names:
253        type = _type_names[words[0]]
254        del words[0]
255    else:
256        return None
257
258    if _binder_classes[type] is _SimpleBinder:
259        if modifiers or words:
260            return None
261        else:
262            detail = None
263    else:
264        # _ComplexBinder
265        if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
266            type_re = _keysym_re
267        else:
268            type_re = _button_re
269
270        if not words:
271            detail = None
272        elif len(words) == 1 and type_re.match(words[0]):
273            detail = words[0]
274        else:
275            return None
276
277    return modifiers, type, detail
278
279def _triplet_to_sequence(triplet):
280    if triplet[2]:
281        return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
282               triplet[2]+'>'
283    else:
284        return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
285
286_multicall_dict = {}
287def MultiCallCreator(widget):
288    """Return a MultiCall class which inherits its methods from the
289    given widget class (for example, Tkinter.Text). This is used
290    instead of a templating mechanism.
291    """
292    if widget in _multicall_dict:
293        return _multicall_dict[widget]
294
295    class MultiCall (widget):
296        assert issubclass(widget, Tkinter.Misc)
297
298        def __init__(self, *args, **kwargs):
299            apply(widget.__init__, (self,)+args, kwargs)
300            # a dictionary which maps a virtual event to a tuple with:
301            #  0. the function binded
302            #  1. a list of triplets - the sequences it is binded to
303            self.__eventinfo = {}
304            self.__binders = [_binder_classes[i](i, widget, self)
305                              for i in range(len(_types))]
306
307        def bind(self, sequence=None, func=None, add=None):
308            #print "bind(%s, %s, %s) called." % (sequence, func, add)
309            if type(sequence) is str and len(sequence) > 2 and \
310               sequence[:2] == "<<" and sequence[-2:] == ">>":
311                if sequence in self.__eventinfo:
312                    ei = self.__eventinfo[sequence]
313                    if ei[0] is not None:
314                        for triplet in ei[1]:
315                            self.__binders[triplet[1]].unbind(triplet, ei[0])
316                    ei[0] = func
317                    if ei[0] is not None:
318                        for triplet in ei[1]:
319                            self.__binders[triplet[1]].bind(triplet, func)
320                else:
321                    self.__eventinfo[sequence] = [func, []]
322            return widget.bind(self, sequence, func, add)
323
324        def unbind(self, sequence, funcid=None):
325            if type(sequence) is str and len(sequence) > 2 and \
326               sequence[:2] == "<<" and sequence[-2:] == ">>" and \
327               sequence in self.__eventinfo:
328                func, triplets = self.__eventinfo[sequence]
329                if func is not None:
330                    for triplet in triplets:
331                        self.__binders[triplet[1]].unbind(triplet, func)
332                    self.__eventinfo[sequence][0] = None
333            return widget.unbind(self, sequence, funcid)
334
335        def event_add(self, virtual, *sequences):
336            #print "event_add(%s,%s) was called"%(repr(virtual),repr(sequences))
337            if virtual not in self.__eventinfo:
338                self.__eventinfo[virtual] = [None, []]
339
340            func, triplets = self.__eventinfo[virtual]
341            for seq in sequences:
342                triplet = _parse_sequence(seq)
343                if triplet is None:
344                    #print >> sys.stderr, "Seq. %s was added by Tkinter."%seq
345                    widget.event_add(self, virtual, seq)
346                else:
347                    if func is not None:
348                        self.__binders[triplet[1]].bind(triplet, func)
349                    triplets.append(triplet)
350
351        def event_delete(self, virtual, *sequences):
352            if virtual not in self.__eventinfo:
353                return
354            func, triplets = self.__eventinfo[virtual]
355            for seq in sequences:
356                triplet = _parse_sequence(seq)
357                if triplet is None:
358                    #print >> sys.stderr, "Seq. %s was deleted by Tkinter."%seq
359                    widget.event_delete(self, virtual, seq)
360                else:
361                    if func is not None:
362                        self.__binders[triplet[1]].unbind(triplet, func)
363                    triplets.remove(triplet)
364
365        def event_info(self, virtual=None):
366            if virtual is None or virtual not in self.__eventinfo:
367                return widget.event_info(self, virtual)
368            else:
369                return tuple(map(_triplet_to_sequence,
370                                 self.__eventinfo[virtual][1])) + \
371                       widget.event_info(self, virtual)
372
373        def __del__(self):
374            for virtual in self.__eventinfo:
375                func, triplets = self.__eventinfo[virtual]
376                if func:
377                    for triplet in triplets:
378                        self.__binders[triplet[1]].unbind(triplet, func)
379
380
381    _multicall_dict[widget] = MultiCall
382    return MultiCall
383
384if __name__ == "__main__":
385    # Test
386    root = Tkinter.Tk()
387    text = MultiCallCreator(Tkinter.Text)(root)
388    text.pack()
389    def bindseq(seq, n=[0]):
390        def handler(event):
391            print seq
392        text.bind("<<handler%d>>"%n[0], handler)
393        text.event_add("<<handler%d>>"%n[0], seq)
394        n[0] += 1
395    bindseq("<Key>")
396    bindseq("<Control-Key>")
397    bindseq("<Alt-Key-a>")
398    bindseq("<Control-Key-a>")
399    bindseq("<Alt-Control-Key-a>")
400    bindseq("<Key-b>")
401    bindseq("<Control-Button-1>")
402    bindseq("<Alt-Button-1>")
403    bindseq("<FocusOut>")
404    bindseq("<Enter>")
405    bindseq("<Leave>")
406    root.mainloop()