test_normalization should skip and not crash when the resource isn't available
[python.git] / Lib / idlelib / MultiCall.py
blob2cf931c99606987d40a852d8860d6377cd8a3c74
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.
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.
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.
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 """
32 import sys
33 import string
34 import re
35 import Tkinter
36 import macosxSupport
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
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)
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]])
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.
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
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)
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
100 def __del__(self):
101 if self.handlerid:
102 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
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],
114 for s in _states]
115 _state_subsets = map(lambda i: filter(lambda j: not (j & (~i)), _states),
116 _states)
117 for 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],
126 for s in _states]
128 class _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.
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
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)))
187 def bind(self, triplet, func):
188 if triplet[2] not in self.bindedfuncs:
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)
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)
212 def __del__(self):
213 for seq, id in self.handlerids:
214 self.widget.unbind(self.widgetinst, seq, id)
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",),
227 # which binder should be used for every event type?
228 _binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
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]])
235 _keysym_re = re.compile(r"^\w+$")
236 _button_re = re.compile(r"^[1-5]$")
237 def _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.
243 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
244 return None
245 words = string.split(sequence[1:-1], '-')
247 modifiers = 0
248 while words and words[0] in _modifier_names:
249 modifiers |= 1 << _modifier_names[words[0]]
250 del words[0]
252 if words and words[0] in _type_names:
253 type = _type_names[words[0]]
254 del words[0]
255 else:
256 return None
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
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
277 return modifiers, type, detail
279 def _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]+'>'
286 _multicall_dict = {}
287 def 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.
292 if widget in _multicall_dict:
293 return _multicall_dict[widget]
295 class MultiCall (widget):
296 assert issubclass(widget, Tkinter.Misc)
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))]
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)
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)
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, []]
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)
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)
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)
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)
381 _multicall_dict[widget] = MultiCall
382 return MultiCall
384 if __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()