blob: 1bb1576853684b56ce3eafda0eaf9604d2d68aee [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
33import os
34import string
35import re
36import Tkinter
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 sys.platform == "darwin" and sys.executable.count(".app"):
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.
Guido van Rossum89da5d72006-08-22 00:21:25 +0000109# XXX rewrite without overusing functional primitives :-)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000110_states = range(1 << len(_modifiers))
Guido van Rossum89da5d72006-08-22 00:21:25 +0000111_state_names = [''.join(m[0]+'-'
112 for i, m in enumerate(_modifiers)
113 if (1 << i) & s)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000114 for s in _states]
115_state_subsets = map(lambda i: filter(lambda j: not (j & (~i)), _states),
Guido van Rossum89da5d72006-08-22 00:21:25 +0000116 _states)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000117for 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
Guido van Rossum89da5d72006-08-22 00:21:25 +0000122_state_codes = []
123for s in _states:
124 r = 0
125 for i in range(len(_modifiers)):
126 if (1 << i) & s:
127 r |= _modifier_masks[i]
128 _state_codes.append(r)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000129
130class _ComplexBinder:
131 # This class binds many functions, and only unbinds them when it is deleted.
132 # self.handlerids is the list of seqs and ids of binded handler functions.
133 # The binded functions sit in a dictionary of lists of lists, which maps
134 # a detail (or None) and a state into a list of functions.
135 # When a new detail is discovered, handlers for all the possible states
136 # are binded.
137
138 def __create_handler(self, lists, mc_type, mc_state):
139 def handler(event, lists = lists,
140 mc_type = mc_type, mc_state = mc_state,
141 ishandlerrunning = self.ishandlerrunning,
142 doafterhandler = self.doafterhandler):
143 ishandlerrunning[:] = [True]
144 event.mc_type = mc_type
145 event.mc_state = mc_state
146 wascalled = {}
147 r = None
148 for l in lists:
149 for i in range(len(l)-1, -1, -1):
150 func = l[i]
151 if func not in wascalled:
152 wascalled[func] = True
153 r = l[i](event)
154 if r:
155 break
156 if r:
157 break
158 ishandlerrunning[:] = []
159 # Call all functions in doafterhandler and remove them from list
160 while doafterhandler:
161 doafterhandler.pop()()
162 if r:
163 return r
164 return handler
165
166 def __init__(self, type, widget, widgetinst):
167 self.type = type
168 self.typename = _types[type][0]
169 self.widget = widget
170 self.widgetinst = widgetinst
171 self.bindedfuncs = {None: [[] for s in _states]}
172 self.handlerids = []
173 # we don't want to change the lists of functions while a handler is
174 # running - it will mess up the loop and anyway, we usually want the
175 # change to happen from the next event. So we have a list of functions
176 # for the handler to run after it finishes calling the binded functions.
177 # It calls them only once.
178 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
179 # this is done so that it would be mutable.
180 self.ishandlerrunning = []
181 self.doafterhandler = []
182 for s in _states:
183 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
184 handler = self.__create_handler(lists, type, _state_codes[s])
185 seq = '<'+_state_names[s]+self.typename+'>'
186 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
187 seq, handler)))
188
189 def bind(self, triplet, func):
Guido van Rossum811c4e02006-08-22 15:45:46 +0000190 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000191 self.bindedfuncs[triplet[2]] = [[] for s in _states]
192 for s in _states:
193 lists = [ self.bindedfuncs[detail][i]
194 for detail in (triplet[2], None)
195 for i in _state_subsets[s] ]
196 handler = self.__create_handler(lists, self.type,
197 _state_codes[s])
198 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
199 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
200 seq, handler)))
201 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
202 if not self.ishandlerrunning:
203 doit()
204 else:
205 self.doafterhandler.append(doit)
206
207 def unbind(self, triplet, func):
208 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
209 if not self.ishandlerrunning:
210 doit()
211 else:
212 self.doafterhandler.append(doit)
213
214 def __del__(self):
215 for seq, id in self.handlerids:
216 self.widget.unbind(self.widgetinst, seq, id)
217
218# define the list of event types to be handled by MultiEvent. the order is
219# compatible with the definition of event type constants.
220_types = (
221 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
222 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
223 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
224 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
225 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
226 ("Visibility",),
227)
228
229# which binder should be used for every event type?
230_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
231
232# A dictionary to map a type name into its number
233_type_names = dict([(name, number)
234 for number in range(len(_types))
235 for name in _types[number]])
236
237_keysym_re = re.compile(r"^\w+$")
238_button_re = re.compile(r"^[1-5]$")
239def _parse_sequence(sequence):
240 """Get a string which should describe an event sequence. If it is
241 successfully parsed as one, return a tuple containing the state (as an int),
242 the event type (as an index of _types), and the detail - None if none, or a
243 string if there is one. If the parsing is unsuccessful, return None.
244 """
245 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
246 return None
247 words = string.split(sequence[1:-1], '-')
248
249 modifiers = 0
250 while words and words[0] in _modifier_names:
251 modifiers |= 1 << _modifier_names[words[0]]
252 del words[0]
253
254 if words and words[0] in _type_names:
255 type = _type_names[words[0]]
256 del words[0]
257 else:
258 return None
259
260 if _binder_classes[type] is _SimpleBinder:
261 if modifiers or words:
262 return None
263 else:
264 detail = None
265 else:
266 # _ComplexBinder
267 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
268 type_re = _keysym_re
269 else:
270 type_re = _button_re
271
272 if not words:
273 detail = None
274 elif len(words) == 1 and type_re.match(words[0]):
275 detail = words[0]
276 else:
277 return None
278
279 return modifiers, type, detail
280
281def _triplet_to_sequence(triplet):
282 if triplet[2]:
283 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
284 triplet[2]+'>'
285 else:
286 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
287
288_multicall_dict = {}
289def MultiCallCreator(widget):
290 """Return a MultiCall class which inherits its methods from the
291 given widget class (for example, Tkinter.Text). This is used
292 instead of a templating mechanism.
293 """
294 if widget in _multicall_dict:
295 return _multicall_dict[widget]
296
297 class MultiCall (widget):
298 assert issubclass(widget, Tkinter.Misc)
299
300 def __init__(self, *args, **kwargs):
Neal Norwitzd9108552006-03-17 08:00:19 +0000301 widget.__init__(self, *args, **kwargs)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000302 # a dictionary which maps a virtual event to a tuple with:
303 # 0. the function binded
304 # 1. a list of triplets - the sequences it is binded to
305 self.__eventinfo = {}
306 self.__binders = [_binder_classes[i](i, widget, self)
307 for i in range(len(_types))]
308
309 def bind(self, sequence=None, func=None, add=None):
310 #print "bind(%s, %s, %s) called." % (sequence, func, add)
311 if type(sequence) is str and len(sequence) > 2 and \
312 sequence[:2] == "<<" and sequence[-2:] == ">>":
313 if sequence in self.__eventinfo:
314 ei = self.__eventinfo[sequence]
315 if ei[0] is not None:
316 for triplet in ei[1]:
317 self.__binders[triplet[1]].unbind(triplet, ei[0])
318 ei[0] = func
319 if ei[0] is not None:
320 for triplet in ei[1]:
321 self.__binders[triplet[1]].bind(triplet, func)
322 else:
323 self.__eventinfo[sequence] = [func, []]
324 return widget.bind(self, sequence, func, add)
325
326 def unbind(self, sequence, funcid=None):
327 if type(sequence) is str and len(sequence) > 2 and \
328 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
329 sequence in self.__eventinfo:
330 func, triplets = self.__eventinfo[sequence]
331 if func is not None:
332 for triplet in triplets:
333 self.__binders[triplet[1]].unbind(triplet, func)
334 self.__eventinfo[sequence][0] = None
335 return widget.unbind(self, sequence, funcid)
336
337 def event_add(self, virtual, *sequences):
338 #print "event_add(%s,%s) was called"%(repr(virtual),repr(sequences))
339 if virtual not in self.__eventinfo:
340 self.__eventinfo[virtual] = [None, []]
341
342 func, triplets = self.__eventinfo[virtual]
343 for seq in sequences:
344 triplet = _parse_sequence(seq)
345 if triplet is None:
346 #print >> sys.stderr, "Seq. %s was added by Tkinter."%seq
347 widget.event_add(self, virtual, seq)
348 else:
349 if func is not None:
350 self.__binders[triplet[1]].bind(triplet, func)
351 triplets.append(triplet)
352
353 def event_delete(self, virtual, *sequences):
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):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000391 print(seq)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000392 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()