blob: 47f402d32611ec14f002f4129cf08a63b2ed8a97 [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
Ronald Oussoren827822e2009-02-12 15:01:44 +000035from idlelib import macosxSupport
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.
Ronald Oussoren827822e2009-02-12 15:01:44 +000048if macosxSupport.runningAsOSXApp():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000049 _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))
Guido van Rossum89da5d72006-08-22 00:21:25 +0000109_state_names = [''.join(m[0]+'-'
110 for i, m in enumerate(_modifiers)
111 if (1 << i) & s)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000112 for s in _states]
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000113
114def expand_substates(states):
115 '''For each item of states return a list containing all combinations of
116 that item with individual bits reset, sorted by the number of set bits.
117 '''
118 def nbits(n):
119 "number of bits set in n base 2"
120 nb = 0
121 while n:
122 n, rem = divmod(n, 2)
123 nb += rem
124 return nb
125 statelist = []
126 for state in states:
127 substates = list(set(state & x for x in states))
Raymond Hettingerd4cb56d2008-01-30 02:55:10 +0000128 substates.sort(key=nbits, reverse=True)
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000129 statelist.append(substates)
130 return statelist
131
132_state_subsets = expand_substates(_states)
133
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000134# _state_codes gives for each state, the portable code to be passed as mc_state
Guido van Rossum89da5d72006-08-22 00:21:25 +0000135_state_codes = []
136for s in _states:
137 r = 0
138 for i in range(len(_modifiers)):
139 if (1 << i) & s:
140 r |= _modifier_masks[i]
141 _state_codes.append(r)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000142
143class _ComplexBinder:
144 # This class binds many functions, and only unbinds them when it is deleted.
145 # self.handlerids is the list of seqs and ids of binded handler functions.
146 # The binded functions sit in a dictionary of lists of lists, which maps
147 # a detail (or None) and a state into a list of functions.
148 # When a new detail is discovered, handlers for all the possible states
149 # are binded.
150
151 def __create_handler(self, lists, mc_type, mc_state):
152 def handler(event, lists = lists,
153 mc_type = mc_type, mc_state = mc_state,
154 ishandlerrunning = self.ishandlerrunning,
155 doafterhandler = self.doafterhandler):
156 ishandlerrunning[:] = [True]
157 event.mc_type = mc_type
158 event.mc_state = mc_state
159 wascalled = {}
160 r = None
161 for l in lists:
162 for i in range(len(l)-1, -1, -1):
163 func = l[i]
164 if func not in wascalled:
165 wascalled[func] = True
166 r = l[i](event)
167 if r:
168 break
169 if r:
170 break
171 ishandlerrunning[:] = []
172 # Call all functions in doafterhandler and remove them from list
173 while doafterhandler:
174 doafterhandler.pop()()
175 if r:
176 return r
177 return handler
178
179 def __init__(self, type, widget, widgetinst):
180 self.type = type
181 self.typename = _types[type][0]
182 self.widget = widget
183 self.widgetinst = widgetinst
184 self.bindedfuncs = {None: [[] for s in _states]}
185 self.handlerids = []
186 # we don't want to change the lists of functions while a handler is
187 # running - it will mess up the loop and anyway, we usually want the
188 # change to happen from the next event. So we have a list of functions
189 # for the handler to run after it finishes calling the binded functions.
190 # It calls them only once.
191 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
192 # this is done so that it would be mutable.
193 self.ishandlerrunning = []
194 self.doafterhandler = []
195 for s in _states:
196 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
197 handler = self.__create_handler(lists, type, _state_codes[s])
198 seq = '<'+_state_names[s]+self.typename+'>'
199 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
200 seq, handler)))
201
202 def bind(self, triplet, func):
Guido van Rossum811c4e02006-08-22 15:45:46 +0000203 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000204 self.bindedfuncs[triplet[2]] = [[] for s in _states]
205 for s in _states:
206 lists = [ self.bindedfuncs[detail][i]
207 for detail in (triplet[2], None)
208 for i in _state_subsets[s] ]
209 handler = self.__create_handler(lists, self.type,
210 _state_codes[s])
211 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
212 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
213 seq, handler)))
214 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
215 if not self.ishandlerrunning:
216 doit()
217 else:
218 self.doafterhandler.append(doit)
219
220 def unbind(self, triplet, func):
221 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
222 if not self.ishandlerrunning:
223 doit()
224 else:
225 self.doafterhandler.append(doit)
226
227 def __del__(self):
228 for seq, id in self.handlerids:
229 self.widget.unbind(self.widgetinst, seq, id)
230
231# define the list of event types to be handled by MultiEvent. the order is
232# compatible with the definition of event type constants.
233_types = (
234 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
235 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
236 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
237 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
238 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
239 ("Visibility",),
240)
241
242# which binder should be used for every event type?
243_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
244
245# A dictionary to map a type name into its number
246_type_names = dict([(name, number)
247 for number in range(len(_types))
248 for name in _types[number]])
249
250_keysym_re = re.compile(r"^\w+$")
251_button_re = re.compile(r"^[1-5]$")
252def _parse_sequence(sequence):
253 """Get a string which should describe an event sequence. If it is
254 successfully parsed as one, return a tuple containing the state (as an int),
255 the event type (as an index of _types), and the detail - None if none, or a
256 string if there is one. If the parsing is unsuccessful, return None.
257 """
258 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
259 return None
Kurt B. Kaiser4d9620a2007-08-22 19:41:43 +0000260 words = sequence[1:-1].split('-')
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000261 modifiers = 0
262 while words and words[0] in _modifier_names:
263 modifiers |= 1 << _modifier_names[words[0]]
264 del words[0]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000265 if words and words[0] in _type_names:
266 type = _type_names[words[0]]
267 del words[0]
268 else:
269 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000270 if _binder_classes[type] is _SimpleBinder:
271 if modifiers or words:
272 return None
273 else:
274 detail = None
275 else:
276 # _ComplexBinder
277 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
278 type_re = _keysym_re
279 else:
280 type_re = _button_re
281
282 if not words:
283 detail = None
284 elif len(words) == 1 and type_re.match(words[0]):
285 detail = words[0]
286 else:
287 return None
288
289 return modifiers, type, detail
290
291def _triplet_to_sequence(triplet):
292 if triplet[2]:
293 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
294 triplet[2]+'>'
295 else:
296 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
297
298_multicall_dict = {}
299def MultiCallCreator(widget):
300 """Return a MultiCall class which inherits its methods from the
301 given widget class (for example, Tkinter.Text). This is used
302 instead of a templating mechanism.
303 """
304 if widget in _multicall_dict:
305 return _multicall_dict[widget]
306
307 class MultiCall (widget):
Georg Brandl14fc4272008-05-17 18:39:55 +0000308 assert issubclass(widget, tkinter.Misc)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000309
310 def __init__(self, *args, **kwargs):
Neal Norwitzd9108552006-03-17 08:00:19 +0000311 widget.__init__(self, *args, **kwargs)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000312 # a dictionary which maps a virtual event to a tuple with:
313 # 0. the function binded
314 # 1. a list of triplets - the sequences it is binded to
315 self.__eventinfo = {}
316 self.__binders = [_binder_classes[i](i, widget, self)
317 for i in range(len(_types))]
318
319 def bind(self, sequence=None, func=None, add=None):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000320 #print("bind(%s, %s, %s)" % (sequence, func, add),
321 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000322 if type(sequence) is str and len(sequence) > 2 and \
323 sequence[:2] == "<<" and sequence[-2:] == ">>":
324 if sequence in self.__eventinfo:
325 ei = self.__eventinfo[sequence]
326 if ei[0] is not None:
327 for triplet in ei[1]:
328 self.__binders[triplet[1]].unbind(triplet, ei[0])
329 ei[0] = func
330 if ei[0] is not None:
331 for triplet in ei[1]:
332 self.__binders[triplet[1]].bind(triplet, func)
333 else:
334 self.__eventinfo[sequence] = [func, []]
335 return widget.bind(self, sequence, func, add)
336
337 def unbind(self, sequence, funcid=None):
338 if type(sequence) is str and len(sequence) > 2 and \
339 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
340 sequence in self.__eventinfo:
341 func, triplets = self.__eventinfo[sequence]
342 if func is not None:
343 for triplet in triplets:
344 self.__binders[triplet[1]].unbind(triplet, func)
345 self.__eventinfo[sequence][0] = None
346 return widget.unbind(self, sequence, funcid)
347
348 def event_add(self, virtual, *sequences):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000349 #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
350 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000351 if virtual not in self.__eventinfo:
352 self.__eventinfo[virtual] = [None, []]
353
354 func, triplets = self.__eventinfo[virtual]
355 for seq in sequences:
356 triplet = _parse_sequence(seq)
357 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000358 #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000359 widget.event_add(self, virtual, seq)
360 else:
361 if func is not None:
362 self.__binders[triplet[1]].bind(triplet, func)
363 triplets.append(triplet)
364
365 def event_delete(self, virtual, *sequences):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000366 if virtual not in self.__eventinfo:
367 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000368 func, triplets = self.__eventinfo[virtual]
369 for seq in sequences:
370 triplet = _parse_sequence(seq)
371 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000372 #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000373 widget.event_delete(self, virtual, seq)
374 else:
375 if func is not None:
376 self.__binders[triplet[1]].unbind(triplet, func)
377 triplets.remove(triplet)
378
379 def event_info(self, virtual=None):
380 if virtual is None or virtual not in self.__eventinfo:
381 return widget.event_info(self, virtual)
382 else:
383 return tuple(map(_triplet_to_sequence,
384 self.__eventinfo[virtual][1])) + \
385 widget.event_info(self, virtual)
386
387 def __del__(self):
388 for virtual in self.__eventinfo:
389 func, triplets = self.__eventinfo[virtual]
390 if func:
391 for triplet in triplets:
392 self.__binders[triplet[1]].unbind(triplet, func)
393
394
395 _multicall_dict[widget] = MultiCall
396 return MultiCall
397
398if __name__ == "__main__":
399 # Test
Georg Brandl14fc4272008-05-17 18:39:55 +0000400 root = tkinter.Tk()
401 text = MultiCallCreator(tkinter.Text)(root)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000402 text.pack()
403 def bindseq(seq, n=[0]):
404 def handler(event):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000405 print(seq)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000406 text.bind("<<handler%d>>"%n[0], handler)
407 text.event_add("<<handler%d>>"%n[0], seq)
408 n[0] += 1
409 bindseq("<Key>")
410 bindseq("<Control-Key>")
411 bindseq("<Alt-Key-a>")
412 bindseq("<Control-Key-a>")
413 bindseq("<Alt-Control-Key-a>")
414 bindseq("<Key-b>")
415 bindseq("<Control-Button-1>")
416 bindseq("<Alt-Button-1>")
417 bindseq("<FocusOut>")
418 bindseq("<Enter>")
419 bindseq("<Leave>")
420 root.mainloop()