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