blob: 25105dc93ecb99754a28ce8359e97621ee56affb [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
Terry Jan Reedy57e41272014-02-08 04:47:29 -050060# In 3.4, if no shell window is ever open, the underlying Tk widget is
61# destroyed before .__del__ methods here are called. The following
62# is used to selectively ignore shutdown exceptions to avoid
63# 'Exception ignored' messages. See http://bugs.python.org/issue20167
64APPLICATION_GONE = '''\
65can't invoke "bind" command: application has been destroyed'''
66
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000067# A binder is a class which binds functions to one type of event. It has two
68# methods: bind and unbind, which get a function and a parsed sequence, as
69# returned by _parse_sequence(). There are two types of binders:
70# _SimpleBinder handles event types with no modifiers and no detail.
71# No Python functions are called when no events are binded.
72# _ComplexBinder handles event types with modifiers and a detail.
73# A Python function is called each time an event is generated.
74
75class _SimpleBinder:
76 def __init__(self, type, widget, widgetinst):
77 self.type = type
78 self.sequence = '<'+_types[type][0]+'>'
79 self.widget = widget
80 self.widgetinst = widgetinst
81 self.bindedfuncs = []
82 self.handlerid = None
83
84 def bind(self, triplet, func):
85 if not self.handlerid:
86 def handler(event, l = self.bindedfuncs, mc_type = self.type):
87 event.mc_type = mc_type
88 wascalled = {}
89 for i in range(len(l)-1, -1, -1):
90 func = l[i]
91 if func not in wascalled:
92 wascalled[func] = True
93 r = func(event)
94 if r:
95 return r
96 self.handlerid = self.widget.bind(self.widgetinst,
97 self.sequence, handler)
98 self.bindedfuncs.append(func)
99
100 def unbind(self, triplet, func):
101 self.bindedfuncs.remove(func)
102 if not self.bindedfuncs:
103 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
104 self.handlerid = None
105
106 def __del__(self):
107 if self.handlerid:
Terry Jan Reedy57e41272014-02-08 04:47:29 -0500108 try:
109 self.widget.unbind(self.widgetinst, self.sequence,
110 self.handlerid)
111 except tkinter.TclError as e:
112 if e.args[0] == APPLICATION_GONE:
113 pass
Terry Jan Reedyb825a392014-02-10 16:46:28 -0500114 else:
115 raise
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000116
117# An int in range(1 << len(_modifiers)) represents a combination of modifiers
118# (if the least significent bit is on, _modifiers[0] is on, and so on).
119# _state_subsets gives for each combination of modifiers, or *state*,
120# a list of the states which are a subset of it. This list is ordered by the
121# number of modifiers is the state - the most specific state comes first.
122_states = range(1 << len(_modifiers))
Guido van Rossum89da5d72006-08-22 00:21:25 +0000123_state_names = [''.join(m[0]+'-'
124 for i, m in enumerate(_modifiers)
125 if (1 << i) & s)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000126 for s in _states]
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000127
128def expand_substates(states):
129 '''For each item of states return a list containing all combinations of
130 that item with individual bits reset, sorted by the number of set bits.
131 '''
132 def nbits(n):
133 "number of bits set in n base 2"
134 nb = 0
135 while n:
136 n, rem = divmod(n, 2)
137 nb += rem
138 return nb
139 statelist = []
140 for state in states:
141 substates = list(set(state & x for x in states))
Raymond Hettingerd4cb56d2008-01-30 02:55:10 +0000142 substates.sort(key=nbits, reverse=True)
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000143 statelist.append(substates)
144 return statelist
145
146_state_subsets = expand_substates(_states)
147
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000148# _state_codes gives for each state, the portable code to be passed as mc_state
Guido van Rossum89da5d72006-08-22 00:21:25 +0000149_state_codes = []
150for s in _states:
151 r = 0
152 for i in range(len(_modifiers)):
153 if (1 << i) & s:
154 r |= _modifier_masks[i]
155 _state_codes.append(r)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000156
157class _ComplexBinder:
158 # This class binds many functions, and only unbinds them when it is deleted.
159 # self.handlerids is the list of seqs and ids of binded handler functions.
160 # The binded functions sit in a dictionary of lists of lists, which maps
161 # a detail (or None) and a state into a list of functions.
162 # When a new detail is discovered, handlers for all the possible states
163 # are binded.
164
165 def __create_handler(self, lists, mc_type, mc_state):
166 def handler(event, lists = lists,
167 mc_type = mc_type, mc_state = mc_state,
168 ishandlerrunning = self.ishandlerrunning,
169 doafterhandler = self.doafterhandler):
170 ishandlerrunning[:] = [True]
171 event.mc_type = mc_type
172 event.mc_state = mc_state
173 wascalled = {}
174 r = None
175 for l in lists:
176 for i in range(len(l)-1, -1, -1):
177 func = l[i]
178 if func not in wascalled:
179 wascalled[func] = True
180 r = l[i](event)
181 if r:
182 break
183 if r:
184 break
185 ishandlerrunning[:] = []
186 # Call all functions in doafterhandler and remove them from list
Roger Serwy420e2d82013-03-31 15:53:08 -0500187 for f in doafterhandler:
188 f()
189 doafterhandler[:] = []
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000190 if r:
191 return r
192 return handler
193
194 def __init__(self, type, widget, widgetinst):
195 self.type = type
196 self.typename = _types[type][0]
197 self.widget = widget
198 self.widgetinst = widgetinst
199 self.bindedfuncs = {None: [[] for s in _states]}
200 self.handlerids = []
201 # we don't want to change the lists of functions while a handler is
202 # running - it will mess up the loop and anyway, we usually want the
203 # change to happen from the next event. So we have a list of functions
204 # for the handler to run after it finishes calling the binded functions.
205 # It calls them only once.
206 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
207 # this is done so that it would be mutable.
208 self.ishandlerrunning = []
209 self.doafterhandler = []
210 for s in _states:
211 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
212 handler = self.__create_handler(lists, type, _state_codes[s])
213 seq = '<'+_state_names[s]+self.typename+'>'
214 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
215 seq, handler)))
216
217 def bind(self, triplet, func):
Guido van Rossum811c4e02006-08-22 15:45:46 +0000218 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000219 self.bindedfuncs[triplet[2]] = [[] for s in _states]
220 for s in _states:
221 lists = [ self.bindedfuncs[detail][i]
222 for detail in (triplet[2], None)
223 for i in _state_subsets[s] ]
224 handler = self.__create_handler(lists, self.type,
225 _state_codes[s])
226 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
227 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
228 seq, handler)))
229 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
230 if not self.ishandlerrunning:
231 doit()
232 else:
233 self.doafterhandler.append(doit)
234
235 def unbind(self, triplet, func):
236 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
237 if not self.ishandlerrunning:
238 doit()
239 else:
240 self.doafterhandler.append(doit)
241
242 def __del__(self):
243 for seq, id in self.handlerids:
Terry Jan Reedy57e41272014-02-08 04:47:29 -0500244 try:
245 self.widget.unbind(self.widgetinst, seq, id)
246 except tkinter.TclError as e:
247 if e.args[0] == APPLICATION_GONE:
248 break
Terry Jan Reedyb825a392014-02-10 16:46:28 -0500249 else:
250 raise
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000251
252# define the list of event types to be handled by MultiEvent. the order is
253# compatible with the definition of event type constants.
254_types = (
255 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
256 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
257 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
258 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
259 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
260 ("Visibility",),
261)
262
263# which binder should be used for every event type?
264_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
265
266# A dictionary to map a type name into its number
267_type_names = dict([(name, number)
268 for number in range(len(_types))
269 for name in _types[number]])
270
271_keysym_re = re.compile(r"^\w+$")
272_button_re = re.compile(r"^[1-5]$")
273def _parse_sequence(sequence):
274 """Get a string which should describe an event sequence. If it is
275 successfully parsed as one, return a tuple containing the state (as an int),
276 the event type (as an index of _types), and the detail - None if none, or a
277 string if there is one. If the parsing is unsuccessful, return None.
278 """
279 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
280 return None
Kurt B. Kaiser4d9620a2007-08-22 19:41:43 +0000281 words = sequence[1:-1].split('-')
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000282 modifiers = 0
283 while words and words[0] in _modifier_names:
284 modifiers |= 1 << _modifier_names[words[0]]
285 del words[0]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000286 if words and words[0] in _type_names:
287 type = _type_names[words[0]]
288 del words[0]
289 else:
290 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000291 if _binder_classes[type] is _SimpleBinder:
292 if modifiers or words:
293 return None
294 else:
295 detail = None
296 else:
297 # _ComplexBinder
298 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
299 type_re = _keysym_re
300 else:
301 type_re = _button_re
302
303 if not words:
304 detail = None
305 elif len(words) == 1 and type_re.match(words[0]):
306 detail = words[0]
307 else:
308 return None
309
310 return modifiers, type, detail
311
312def _triplet_to_sequence(triplet):
313 if triplet[2]:
314 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
315 triplet[2]+'>'
316 else:
317 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
318
319_multicall_dict = {}
320def MultiCallCreator(widget):
321 """Return a MultiCall class which inherits its methods from the
322 given widget class (for example, Tkinter.Text). This is used
323 instead of a templating mechanism.
324 """
325 if widget in _multicall_dict:
326 return _multicall_dict[widget]
327
328 class MultiCall (widget):
Georg Brandl14fc4272008-05-17 18:39:55 +0000329 assert issubclass(widget, tkinter.Misc)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000330
331 def __init__(self, *args, **kwargs):
Neal Norwitzd9108552006-03-17 08:00:19 +0000332 widget.__init__(self, *args, **kwargs)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000333 # a dictionary which maps a virtual event to a tuple with:
334 # 0. the function binded
335 # 1. a list of triplets - the sequences it is binded to
336 self.__eventinfo = {}
337 self.__binders = [_binder_classes[i](i, widget, self)
338 for i in range(len(_types))]
339
340 def bind(self, sequence=None, func=None, add=None):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000341 #print("bind(%s, %s, %s)" % (sequence, func, add),
342 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000343 if type(sequence) is str and len(sequence) > 2 and \
344 sequence[:2] == "<<" and sequence[-2:] == ">>":
345 if sequence in self.__eventinfo:
346 ei = self.__eventinfo[sequence]
347 if ei[0] is not None:
348 for triplet in ei[1]:
349 self.__binders[triplet[1]].unbind(triplet, ei[0])
350 ei[0] = func
351 if ei[0] is not None:
352 for triplet in ei[1]:
353 self.__binders[triplet[1]].bind(triplet, func)
354 else:
355 self.__eventinfo[sequence] = [func, []]
356 return widget.bind(self, sequence, func, add)
357
358 def unbind(self, sequence, funcid=None):
359 if type(sequence) is str and len(sequence) > 2 and \
360 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
361 sequence in self.__eventinfo:
362 func, triplets = self.__eventinfo[sequence]
363 if func is not None:
364 for triplet in triplets:
365 self.__binders[triplet[1]].unbind(triplet, func)
366 self.__eventinfo[sequence][0] = None
367 return widget.unbind(self, sequence, funcid)
368
369 def event_add(self, virtual, *sequences):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000370 #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
371 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000372 if virtual not in self.__eventinfo:
373 self.__eventinfo[virtual] = [None, []]
374
375 func, triplets = self.__eventinfo[virtual]
376 for seq in sequences:
377 triplet = _parse_sequence(seq)
378 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000379 #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000380 widget.event_add(self, virtual, seq)
381 else:
382 if func is not None:
383 self.__binders[triplet[1]].bind(triplet, func)
384 triplets.append(triplet)
385
386 def event_delete(self, virtual, *sequences):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000387 if virtual not in self.__eventinfo:
388 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000389 func, triplets = self.__eventinfo[virtual]
390 for seq in sequences:
391 triplet = _parse_sequence(seq)
392 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000393 #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000394 widget.event_delete(self, virtual, seq)
395 else:
396 if func is not None:
397 self.__binders[triplet[1]].unbind(triplet, func)
398 triplets.remove(triplet)
399
400 def event_info(self, virtual=None):
401 if virtual is None or virtual not in self.__eventinfo:
402 return widget.event_info(self, virtual)
403 else:
404 return tuple(map(_triplet_to_sequence,
405 self.__eventinfo[virtual][1])) + \
406 widget.event_info(self, virtual)
407
408 def __del__(self):
409 for virtual in self.__eventinfo:
410 func, triplets = self.__eventinfo[virtual]
411 if func:
412 for triplet in triplets:
Terry Jan Reedy57e41272014-02-08 04:47:29 -0500413 try:
414 self.__binders[triplet[1]].unbind(triplet, func)
415 except tkinter.TclError as e:
416 if e.args[0] == APPLICATION_GONE:
417 break
Terry Jan Reedyb825a392014-02-10 16:46:28 -0500418 else:
419 raise
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000420
421 _multicall_dict[widget] = MultiCall
422 return MultiCall
423
424if __name__ == "__main__":
425 # Test
Georg Brandl14fc4272008-05-17 18:39:55 +0000426 root = tkinter.Tk()
427 text = MultiCallCreator(tkinter.Text)(root)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000428 text.pack()
429 def bindseq(seq, n=[0]):
430 def handler(event):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000431 print(seq)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000432 text.bind("<<handler%d>>"%n[0], handler)
433 text.event_add("<<handler%d>>"%n[0], seq)
434 n[0] += 1
435 bindseq("<Key>")
436 bindseq("<Control-Key>")
437 bindseq("<Alt-Key-a>")
438 bindseq("<Control-Key-a>")
439 bindseq("<Alt-Control-Key-a>")
440 bindseq("<Key-b>")
441 bindseq("<Control-Button-1>")
442 bindseq("<Alt-Button-1>")
443 bindseq("<FocusOut>")
444 bindseq("<Enter>")
445 bindseq("<Leave>")
446 root.mainloop()