blob: 1c40638402afd5e56d358484d132ec6b575a38de [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 re
Georg Brandl14fc4272008-05-17 18:39:55 +000034import tkinter
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000035
36# the event type constants, which define the meaning of mc_type
37MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
38MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
39MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
40MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
41MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
42# the modifier state constants, which define the meaning of mc_state
43MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
44MC_OPTION = 1<<6; MC_COMMAND = 1<<7
45
46# define the list of modifiers, to be used in complex event types.
47if sys.platform == "darwin" and sys.executable.count(".app"):
48 _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
49 _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
50else:
51 _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
52 _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
53
54# a dictionary to map a modifier name into its number
55_modifier_names = dict([(name, number)
56 for number in range(len(_modifiers))
57 for name in _modifiers[number]])
58
59# A binder is a class which binds functions to one type of event. It has two
60# methods: bind and unbind, which get a function and a parsed sequence, as
61# returned by _parse_sequence(). There are two types of binders:
62# _SimpleBinder handles event types with no modifiers and no detail.
63# No Python functions are called when no events are binded.
64# _ComplexBinder handles event types with modifiers and a detail.
65# A Python function is called each time an event is generated.
66
67class _SimpleBinder:
68 def __init__(self, type, widget, widgetinst):
69 self.type = type
70 self.sequence = '<'+_types[type][0]+'>'
71 self.widget = widget
72 self.widgetinst = widgetinst
73 self.bindedfuncs = []
74 self.handlerid = None
75
76 def bind(self, triplet, func):
77 if not self.handlerid:
78 def handler(event, l = self.bindedfuncs, mc_type = self.type):
79 event.mc_type = mc_type
80 wascalled = {}
81 for i in range(len(l)-1, -1, -1):
82 func = l[i]
83 if func not in wascalled:
84 wascalled[func] = True
85 r = func(event)
86 if r:
87 return r
88 self.handlerid = self.widget.bind(self.widgetinst,
89 self.sequence, handler)
90 self.bindedfuncs.append(func)
91
92 def unbind(self, triplet, func):
93 self.bindedfuncs.remove(func)
94 if not self.bindedfuncs:
95 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
96 self.handlerid = None
97
98 def __del__(self):
99 if self.handlerid:
100 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
101
102# An int in range(1 << len(_modifiers)) represents a combination of modifiers
103# (if the least significent bit is on, _modifiers[0] is on, and so on).
104# _state_subsets gives for each combination of modifiers, or *state*,
105# a list of the states which are a subset of it. This list is ordered by the
106# number of modifiers is the state - the most specific state comes first.
107_states = range(1 << len(_modifiers))
Guido van Rossum89da5d72006-08-22 00:21:25 +0000108_state_names = [''.join(m[0]+'-'
109 for i, m in enumerate(_modifiers)
110 if (1 << i) & s)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000111 for s in _states]
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000112
113def expand_substates(states):
114 '''For each item of states return a list containing all combinations of
115 that item with individual bits reset, sorted by the number of set bits.
116 '''
117 def nbits(n):
118 "number of bits set in n base 2"
119 nb = 0
120 while n:
121 n, rem = divmod(n, 2)
122 nb += rem
123 return nb
124 statelist = []
125 for state in states:
126 substates = list(set(state & x for x in states))
Raymond Hettingerd4cb56d2008-01-30 02:55:10 +0000127 substates.sort(key=nbits, reverse=True)
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000128 statelist.append(substates)
129 return statelist
130
131_state_subsets = expand_substates(_states)
132
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000133# _state_codes gives for each state, the portable code to be passed as mc_state
Guido van Rossum89da5d72006-08-22 00:21:25 +0000134_state_codes = []
135for s in _states:
136 r = 0
137 for i in range(len(_modifiers)):
138 if (1 << i) & s:
139 r |= _modifier_masks[i]
140 _state_codes.append(r)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000141
142class _ComplexBinder:
143 # This class binds many functions, and only unbinds them when it is deleted.
144 # self.handlerids is the list of seqs and ids of binded handler functions.
145 # The binded functions sit in a dictionary of lists of lists, which maps
146 # a detail (or None) and a state into a list of functions.
147 # When a new detail is discovered, handlers for all the possible states
148 # are binded.
149
150 def __create_handler(self, lists, mc_type, mc_state):
151 def handler(event, lists = lists,
152 mc_type = mc_type, mc_state = mc_state,
153 ishandlerrunning = self.ishandlerrunning,
154 doafterhandler = self.doafterhandler):
155 ishandlerrunning[:] = [True]
156 event.mc_type = mc_type
157 event.mc_state = mc_state
158 wascalled = {}
159 r = None
160 for l in lists:
161 for i in range(len(l)-1, -1, -1):
162 func = l[i]
163 if func not in wascalled:
164 wascalled[func] = True
165 r = l[i](event)
166 if r:
167 break
168 if r:
169 break
170 ishandlerrunning[:] = []
171 # Call all functions in doafterhandler and remove them from list
172 while doafterhandler:
173 doafterhandler.pop()()
174 if r:
175 return r
176 return handler
177
178 def __init__(self, type, widget, widgetinst):
179 self.type = type
180 self.typename = _types[type][0]
181 self.widget = widget
182 self.widgetinst = widgetinst
183 self.bindedfuncs = {None: [[] for s in _states]}
184 self.handlerids = []
185 # we don't want to change the lists of functions while a handler is
186 # running - it will mess up the loop and anyway, we usually want the
187 # change to happen from the next event. So we have a list of functions
188 # for the handler to run after it finishes calling the binded functions.
189 # It calls them only once.
190 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
191 # this is done so that it would be mutable.
192 self.ishandlerrunning = []
193 self.doafterhandler = []
194 for s in _states:
195 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
196 handler = self.__create_handler(lists, type, _state_codes[s])
197 seq = '<'+_state_names[s]+self.typename+'>'
198 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
199 seq, handler)))
200
201 def bind(self, triplet, func):
Guido van Rossum811c4e02006-08-22 15:45:46 +0000202 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000203 self.bindedfuncs[triplet[2]] = [[] for s in _states]
204 for s in _states:
205 lists = [ self.bindedfuncs[detail][i]
206 for detail in (triplet[2], None)
207 for i in _state_subsets[s] ]
208 handler = self.__create_handler(lists, self.type,
209 _state_codes[s])
210 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
211 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
212 seq, handler)))
213 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
214 if not self.ishandlerrunning:
215 doit()
216 else:
217 self.doafterhandler.append(doit)
218
219 def unbind(self, triplet, func):
220 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
221 if not self.ishandlerrunning:
222 doit()
223 else:
224 self.doafterhandler.append(doit)
225
226 def __del__(self):
227 for seq, id in self.handlerids:
228 self.widget.unbind(self.widgetinst, seq, id)
229
230# define the list of event types to be handled by MultiEvent. the order is
231# compatible with the definition of event type constants.
232_types = (
233 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
234 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
235 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
236 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
237 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
238 ("Visibility",),
239)
240
241# which binder should be used for every event type?
242_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
243
244# A dictionary to map a type name into its number
245_type_names = dict([(name, number)
246 for number in range(len(_types))
247 for name in _types[number]])
248
249_keysym_re = re.compile(r"^\w+$")
250_button_re = re.compile(r"^[1-5]$")
251def _parse_sequence(sequence):
252 """Get a string which should describe an event sequence. If it is
253 successfully parsed as one, return a tuple containing the state (as an int),
254 the event type (as an index of _types), and the detail - None if none, or a
255 string if there is one. If the parsing is unsuccessful, return None.
256 """
257 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
258 return None
Kurt B. Kaiser4d9620a2007-08-22 19:41:43 +0000259 words = sequence[1:-1].split('-')
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000260 modifiers = 0
261 while words and words[0] in _modifier_names:
262 modifiers |= 1 << _modifier_names[words[0]]
263 del words[0]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000264 if words and words[0] in _type_names:
265 type = _type_names[words[0]]
266 del words[0]
267 else:
268 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000269 if _binder_classes[type] is _SimpleBinder:
270 if modifiers or words:
271 return None
272 else:
273 detail = None
274 else:
275 # _ComplexBinder
276 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
277 type_re = _keysym_re
278 else:
279 type_re = _button_re
280
281 if not words:
282 detail = None
283 elif len(words) == 1 and type_re.match(words[0]):
284 detail = words[0]
285 else:
286 return None
287
288 return modifiers, type, detail
289
290def _triplet_to_sequence(triplet):
291 if triplet[2]:
292 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
293 triplet[2]+'>'
294 else:
295 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
296
297_multicall_dict = {}
298def MultiCallCreator(widget):
299 """Return a MultiCall class which inherits its methods from the
300 given widget class (for example, Tkinter.Text). This is used
301 instead of a templating mechanism.
302 """
303 if widget in _multicall_dict:
304 return _multicall_dict[widget]
305
306 class MultiCall (widget):
Georg Brandl14fc4272008-05-17 18:39:55 +0000307 assert issubclass(widget, tkinter.Misc)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000308
309 def __init__(self, *args, **kwargs):
Neal Norwitzd9108552006-03-17 08:00:19 +0000310 widget.__init__(self, *args, **kwargs)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000311 # a dictionary which maps a virtual event to a tuple with:
312 # 0. the function binded
313 # 1. a list of triplets - the sequences it is binded to
314 self.__eventinfo = {}
315 self.__binders = [_binder_classes[i](i, widget, self)
316 for i in range(len(_types))]
317
318 def bind(self, sequence=None, func=None, add=None):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000319 #print("bind(%s, %s, %s)" % (sequence, func, add),
320 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000321 if type(sequence) is str and len(sequence) > 2 and \
322 sequence[:2] == "<<" and sequence[-2:] == ">>":
323 if sequence in self.__eventinfo:
324 ei = self.__eventinfo[sequence]
325 if ei[0] is not None:
326 for triplet in ei[1]:
327 self.__binders[triplet[1]].unbind(triplet, ei[0])
328 ei[0] = func
329 if ei[0] is not None:
330 for triplet in ei[1]:
331 self.__binders[triplet[1]].bind(triplet, func)
332 else:
333 self.__eventinfo[sequence] = [func, []]
334 return widget.bind(self, sequence, func, add)
335
336 def unbind(self, sequence, funcid=None):
337 if type(sequence) is str and len(sequence) > 2 and \
338 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
339 sequence in self.__eventinfo:
340 func, triplets = self.__eventinfo[sequence]
341 if func is not None:
342 for triplet in triplets:
343 self.__binders[triplet[1]].unbind(triplet, func)
344 self.__eventinfo[sequence][0] = None
345 return widget.unbind(self, sequence, funcid)
346
347 def event_add(self, virtual, *sequences):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000348 #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
349 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000350 if virtual not in self.__eventinfo:
351 self.__eventinfo[virtual] = [None, []]
352
353 func, triplets = self.__eventinfo[virtual]
354 for seq in sequences:
355 triplet = _parse_sequence(seq)
356 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000357 #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000358 widget.event_add(self, virtual, seq)
359 else:
360 if func is not None:
361 self.__binders[triplet[1]].bind(triplet, func)
362 triplets.append(triplet)
363
364 def event_delete(self, virtual, *sequences):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000365 if virtual not in self.__eventinfo:
366 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000367 func, triplets = self.__eventinfo[virtual]
368 for seq in sequences:
369 triplet = _parse_sequence(seq)
370 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000371 #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000372 widget.event_delete(self, virtual, seq)
373 else:
374 if func is not None:
375 self.__binders[triplet[1]].unbind(triplet, func)
376 triplets.remove(triplet)
377
378 def event_info(self, virtual=None):
379 if virtual is None or virtual not in self.__eventinfo:
380 return widget.event_info(self, virtual)
381 else:
382 return tuple(map(_triplet_to_sequence,
383 self.__eventinfo[virtual][1])) + \
384 widget.event_info(self, virtual)
385
386 def __del__(self):
387 for virtual in self.__eventinfo:
388 func, triplets = self.__eventinfo[virtual]
389 if func:
390 for triplet in triplets:
391 self.__binders[triplet[1]].unbind(triplet, func)
392
393
394 _multicall_dict[widget] = MultiCall
395 return MultiCall
396
397if __name__ == "__main__":
398 # Test
Georg Brandl14fc4272008-05-17 18:39:55 +0000399 root = tkinter.Tk()
400 text = MultiCallCreator(tkinter.Text)(root)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000401 text.pack()
402 def bindseq(seq, n=[0]):
403 def handler(event):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000404 print(seq)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000405 text.bind("<<handler%d>>"%n[0], handler)
406 text.event_add("<<handler%d>>"%n[0], seq)
407 n[0] += 1
408 bindseq("<Key>")
409 bindseq("<Control-Key>")
410 bindseq("<Alt-Key-a>")
411 bindseq("<Control-Key-a>")
412 bindseq("<Alt-Control-Key-a>")
413 bindseq("<Key-b>")
414 bindseq("<Control-Button-1>")
415 bindseq("<Alt-Button-1>")
416 bindseq("<FocusOut>")
417 bindseq("<Enter>")
418 bindseq("<Leave>")
419 root.mainloop()