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