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