blob: dc02001292fc14e812db4b292c14fa427800276a [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"""
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000031import re
Terry Jan Reedybfbaa6b2016-08-31 00:50:55 -040032import sys
33
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
Terry Jan Reedy4fe69ba2014-10-09 23:13:36 -040063APPLICATION_GONE = "application has been destroyed"
Terry Jan Reedy57e41272014-02-08 04:47:29 -050064
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000065# A binder is a class which binds functions to one type of event. It has two
66# methods: bind and unbind, which get a function and a parsed sequence, as
67# returned by _parse_sequence(). There are two types of binders:
68# _SimpleBinder handles event types with no modifiers and no detail.
69# No Python functions are called when no events are binded.
70# _ComplexBinder handles event types with modifiers and a detail.
71# A Python function is called each time an event is generated.
72
73class _SimpleBinder:
74 def __init__(self, type, widget, widgetinst):
75 self.type = type
76 self.sequence = '<'+_types[type][0]+'>'
77 self.widget = widget
78 self.widgetinst = widgetinst
79 self.bindedfuncs = []
80 self.handlerid = None
81
82 def bind(self, triplet, func):
83 if not self.handlerid:
84 def handler(event, l = self.bindedfuncs, mc_type = self.type):
85 event.mc_type = mc_type
86 wascalled = {}
87 for i in range(len(l)-1, -1, -1):
88 func = l[i]
89 if func not in wascalled:
90 wascalled[func] = True
91 r = func(event)
92 if r:
93 return r
94 self.handlerid = self.widget.bind(self.widgetinst,
95 self.sequence, handler)
96 self.bindedfuncs.append(func)
97
98 def unbind(self, triplet, func):
99 self.bindedfuncs.remove(func)
100 if not self.bindedfuncs:
101 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
102 self.handlerid = None
103
104 def __del__(self):
105 if self.handlerid:
Terry Jan Reedy57e41272014-02-08 04:47:29 -0500106 try:
107 self.widget.unbind(self.widgetinst, self.sequence,
108 self.handlerid)
109 except tkinter.TclError as e:
Terry Jan Reedy4fe69ba2014-10-09 23:13:36 -0400110 if not APPLICATION_GONE in e.args[0]:
Terry Jan Reedyb825a392014-02-10 16:46:28 -0500111 raise
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000112
113# An int in range(1 << len(_modifiers)) represents a combination of modifiers
Martin Pantere26da7c2016-06-02 10:07:09 +0000114# (if the least significant bit is on, _modifiers[0] is on, and so on).
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000115# _state_subsets gives for each combination of modifiers, or *state*,
116# a list of the states which are a subset of it. This list is ordered by the
117# number of modifiers is the state - the most specific state comes first.
118_states = range(1 << len(_modifiers))
Guido van Rossum89da5d72006-08-22 00:21:25 +0000119_state_names = [''.join(m[0]+'-'
120 for i, m in enumerate(_modifiers)
121 if (1 << i) & s)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000122 for s in _states]
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000123
124def expand_substates(states):
125 '''For each item of states return a list containing all combinations of
126 that item with individual bits reset, sorted by the number of set bits.
127 '''
128 def nbits(n):
129 "number of bits set in n base 2"
130 nb = 0
131 while n:
132 n, rem = divmod(n, 2)
133 nb += rem
134 return nb
135 statelist = []
136 for state in states:
137 substates = list(set(state & x for x in states))
Raymond Hettingerd4cb56d2008-01-30 02:55:10 +0000138 substates.sort(key=nbits, reverse=True)
Guido van Rossumb5a755e2007-07-18 18:15:48 +0000139 statelist.append(substates)
140 return statelist
141
142_state_subsets = expand_substates(_states)
143
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000144# _state_codes gives for each state, the portable code to be passed as mc_state
Guido van Rossum89da5d72006-08-22 00:21:25 +0000145_state_codes = []
146for s in _states:
147 r = 0
148 for i in range(len(_modifiers)):
149 if (1 << i) & s:
150 r |= _modifier_masks[i]
151 _state_codes.append(r)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000152
153class _ComplexBinder:
154 # This class binds many functions, and only unbinds them when it is deleted.
155 # self.handlerids is the list of seqs and ids of binded handler functions.
156 # The binded functions sit in a dictionary of lists of lists, which maps
157 # a detail (or None) and a state into a list of functions.
158 # When a new detail is discovered, handlers for all the possible states
159 # are binded.
160
161 def __create_handler(self, lists, mc_type, mc_state):
162 def handler(event, lists = lists,
163 mc_type = mc_type, mc_state = mc_state,
164 ishandlerrunning = self.ishandlerrunning,
165 doafterhandler = self.doafterhandler):
166 ishandlerrunning[:] = [True]
167 event.mc_type = mc_type
168 event.mc_state = mc_state
169 wascalled = {}
170 r = None
171 for l in lists:
172 for i in range(len(l)-1, -1, -1):
173 func = l[i]
174 if func not in wascalled:
175 wascalled[func] = True
176 r = l[i](event)
177 if r:
178 break
179 if r:
180 break
181 ishandlerrunning[:] = []
182 # Call all functions in doafterhandler and remove them from list
Roger Serwy420e2d82013-03-31 15:53:08 -0500183 for f in doafterhandler:
184 f()
185 doafterhandler[:] = []
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000186 if r:
187 return r
188 return handler
189
190 def __init__(self, type, widget, widgetinst):
191 self.type = type
192 self.typename = _types[type][0]
193 self.widget = widget
194 self.widgetinst = widgetinst
195 self.bindedfuncs = {None: [[] for s in _states]}
196 self.handlerids = []
197 # we don't want to change the lists of functions while a handler is
198 # running - it will mess up the loop and anyway, we usually want the
199 # change to happen from the next event. So we have a list of functions
200 # for the handler to run after it finishes calling the binded functions.
201 # It calls them only once.
202 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
203 # this is done so that it would be mutable.
204 self.ishandlerrunning = []
205 self.doafterhandler = []
206 for s in _states:
207 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
208 handler = self.__create_handler(lists, type, _state_codes[s])
209 seq = '<'+_state_names[s]+self.typename+'>'
210 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
211 seq, handler)))
212
213 def bind(self, triplet, func):
Guido van Rossum811c4e02006-08-22 15:45:46 +0000214 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000215 self.bindedfuncs[triplet[2]] = [[] for s in _states]
216 for s in _states:
217 lists = [ self.bindedfuncs[detail][i]
218 for detail in (triplet[2], None)
219 for i in _state_subsets[s] ]
220 handler = self.__create_handler(lists, self.type,
221 _state_codes[s])
222 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
223 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
224 seq, handler)))
225 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
226 if not self.ishandlerrunning:
227 doit()
228 else:
229 self.doafterhandler.append(doit)
230
231 def unbind(self, triplet, func):
232 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
233 if not self.ishandlerrunning:
234 doit()
235 else:
236 self.doafterhandler.append(doit)
237
238 def __del__(self):
239 for seq, id in self.handlerids:
Terry Jan Reedy57e41272014-02-08 04:47:29 -0500240 try:
241 self.widget.unbind(self.widgetinst, seq, id)
242 except tkinter.TclError as e:
Terry Jan Reedy4fe69ba2014-10-09 23:13:36 -0400243 if not APPLICATION_GONE in e.args[0]:
Terry Jan Reedyb825a392014-02-10 16:46:28 -0500244 raise
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000245
246# define the list of event types to be handled by MultiEvent. the order is
247# compatible with the definition of event type constants.
248_types = (
249 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
250 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
251 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
252 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
253 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
254 ("Visibility",),
255)
256
257# which binder should be used for every event type?
258_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
259
260# A dictionary to map a type name into its number
261_type_names = dict([(name, number)
262 for number in range(len(_types))
263 for name in _types[number]])
264
265_keysym_re = re.compile(r"^\w+$")
266_button_re = re.compile(r"^[1-5]$")
267def _parse_sequence(sequence):
268 """Get a string which should describe an event sequence. If it is
269 successfully parsed as one, return a tuple containing the state (as an int),
270 the event type (as an index of _types), and the detail - None if none, or a
271 string if there is one. If the parsing is unsuccessful, return None.
272 """
273 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
274 return None
Kurt B. Kaiser4d9620a2007-08-22 19:41:43 +0000275 words = sequence[1:-1].split('-')
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000276 modifiers = 0
277 while words and words[0] in _modifier_names:
278 modifiers |= 1 << _modifier_names[words[0]]
279 del words[0]
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000280 if words and words[0] in _type_names:
281 type = _type_names[words[0]]
282 del words[0]
283 else:
284 return None
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000285 if _binder_classes[type] is _SimpleBinder:
286 if modifiers or words:
287 return None
288 else:
289 detail = None
290 else:
291 # _ComplexBinder
292 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
293 type_re = _keysym_re
294 else:
295 type_re = _button_re
296
297 if not words:
298 detail = None
299 elif len(words) == 1 and type_re.match(words[0]):
300 detail = words[0]
301 else:
302 return None
303
304 return modifiers, type, detail
305
306def _triplet_to_sequence(triplet):
307 if triplet[2]:
308 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
309 triplet[2]+'>'
310 else:
311 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
312
313_multicall_dict = {}
314def MultiCallCreator(widget):
315 """Return a MultiCall class which inherits its methods from the
316 given widget class (for example, Tkinter.Text). This is used
317 instead of a templating mechanism.
318 """
319 if widget in _multicall_dict:
320 return _multicall_dict[widget]
321
322 class MultiCall (widget):
Georg Brandl14fc4272008-05-17 18:39:55 +0000323 assert issubclass(widget, tkinter.Misc)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000324
325 def __init__(self, *args, **kwargs):
Neal Norwitzd9108552006-03-17 08:00:19 +0000326 widget.__init__(self, *args, **kwargs)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000327 # a dictionary which maps a virtual event to a tuple with:
328 # 0. the function binded
329 # 1. a list of triplets - the sequences it is binded to
330 self.__eventinfo = {}
331 self.__binders = [_binder_classes[i](i, widget, self)
332 for i in range(len(_types))]
333
334 def bind(self, sequence=None, func=None, add=None):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000335 #print("bind(%s, %s, %s)" % (sequence, func, add),
336 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000337 if type(sequence) is str and len(sequence) > 2 and \
338 sequence[:2] == "<<" and sequence[-2:] == ">>":
339 if sequence in self.__eventinfo:
340 ei = self.__eventinfo[sequence]
341 if ei[0] is not None:
342 for triplet in ei[1]:
343 self.__binders[triplet[1]].unbind(triplet, ei[0])
344 ei[0] = func
345 if ei[0] is not None:
346 for triplet in ei[1]:
347 self.__binders[triplet[1]].bind(triplet, func)
348 else:
349 self.__eventinfo[sequence] = [func, []]
350 return widget.bind(self, sequence, func, add)
351
352 def unbind(self, sequence, funcid=None):
353 if type(sequence) is str and len(sequence) > 2 and \
354 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
355 sequence in self.__eventinfo:
356 func, triplets = self.__eventinfo[sequence]
357 if func is not None:
358 for triplet in triplets:
359 self.__binders[triplet[1]].unbind(triplet, func)
360 self.__eventinfo[sequence][0] = None
361 return widget.unbind(self, sequence, funcid)
362
363 def event_add(self, virtual, *sequences):
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000364 #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
365 # file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000366 if virtual not in self.__eventinfo:
367 self.__eventinfo[virtual] = [None, []]
368
369 func, triplets = self.__eventinfo[virtual]
370 for seq in sequences:
371 triplet = _parse_sequence(seq)
372 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000373 #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000374 widget.event_add(self, virtual, seq)
375 else:
376 if func is not None:
377 self.__binders[triplet[1]].bind(triplet, func)
378 triplets.append(triplet)
379
380 def event_delete(self, virtual, *sequences):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000381 if virtual not in self.__eventinfo:
382 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000383 func, triplets = self.__eventinfo[virtual]
384 for seq in sequences:
385 triplet = _parse_sequence(seq)
386 if triplet is None:
Kurt B. Kaiserea03c112007-08-22 18:57:50 +0000387 #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000388 widget.event_delete(self, virtual, seq)
389 else:
390 if func is not None:
391 self.__binders[triplet[1]].unbind(triplet, func)
392 triplets.remove(triplet)
393
394 def event_info(self, virtual=None):
395 if virtual is None or virtual not in self.__eventinfo:
396 return widget.event_info(self, virtual)
397 else:
398 return tuple(map(_triplet_to_sequence,
399 self.__eventinfo[virtual][1])) + \
400 widget.event_info(self, virtual)
401
402 def __del__(self):
403 for virtual in self.__eventinfo:
404 func, triplets = self.__eventinfo[virtual]
405 if func:
406 for triplet in triplets:
Terry Jan Reedy57e41272014-02-08 04:47:29 -0500407 try:
408 self.__binders[triplet[1]].unbind(triplet, func)
409 except tkinter.TclError as e:
Terry Jan Reedy4fe69ba2014-10-09 23:13:36 -0400410 if not APPLICATION_GONE in e.args[0]:
Terry Jan Reedyb825a392014-02-10 16:46:28 -0500411 raise
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000412
413 _multicall_dict[widget] = MultiCall
414 return MultiCall
415
Terry Jan Reedy1b392ff2014-05-24 18:48:18 -0400416
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400417def _multi_call(parent): # htest #
418 top = tkinter.Toplevel(parent)
419 top.title("Test MultiCall")
Terry Jan Reedya7480322016-07-10 17:28:10 -0400420 x, y = map(int, parent.geometry().split('+')[1:])
421 top.geometry("+%d+%d" % (x, y + 175))
Terry Jan Reedyb60adc52016-06-21 18:41:38 -0400422 text = MultiCallCreator(tkinter.Text)(top)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000423 text.pack()
424 def bindseq(seq, n=[0]):
425 def handler(event):
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000426 print(seq)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000427 text.bind("<<handler%d>>"%n[0], handler)
428 text.event_add("<<handler%d>>"%n[0], seq)
429 n[0] += 1
430 bindseq("<Key>")
431 bindseq("<Control-Key>")
432 bindseq("<Alt-Key-a>")
433 bindseq("<Control-Key-a>")
434 bindseq("<Alt-Control-Key-a>")
435 bindseq("<Key-b>")
436 bindseq("<Control-Button-1>")
Terry Jan Reedy1b392ff2014-05-24 18:48:18 -0400437 bindseq("<Button-2>")
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000438 bindseq("<Alt-Button-1>")
439 bindseq("<FocusOut>")
440 bindseq("<Enter>")
441 bindseq("<Leave>")
Terry Jan Reedy1b392ff2014-05-24 18:48:18 -0400442
443if __name__ == "__main__":
Terry Jan Reedyea3dc802018-06-18 04:47:59 -0400444 from unittest import main
445 main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False)
446
Terry Jan Reedy1b392ff2014-05-24 18:48:18 -0400447 from idlelib.idle_test.htest import run
448 run(_multi_call)