blob: 2cf931c99606987d40a852d8860d6377cd8a3c74 [file] [log] [blame]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +00001"""
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
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000033import string
34import re
Georg Brandl6634bf22008-05-20 07:13:37 +000035import Tkinter
Ronald Oussorena97063a2009-03-04 21:35:05 +000036import macosxSupport
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000037
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.
Ronald Oussorena97063a2009-03-04 21:35:05 +000049if macosxSupport.runningAsOSXApp():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000050 _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):
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000188 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000189 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):
Georg Brandl6634bf22008-05-20 07:13:37 +0000296 assert issubclass(widget, Tkinter.Misc)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000297
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):
Georg Brandld53d9512007-03-06 11:52:24 +0000352 if virtual not in self.__eventinfo:
353 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000354 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
Georg Brandl6634bf22008-05-20 07:13:37 +0000386 root = Tkinter.Tk()
387 text = MultiCallCreator(Tkinter.Text)(root)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000388 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()