Mercurial > repos > rliterman > csp2
comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/multicall.py @ 68:5028fdace37b
planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author | jpayne |
---|---|
date | Tue, 18 Mar 2025 16:23:26 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 68:5028fdace37b |
---|---|
1 """ | |
2 MultiCall - a class which inherits its methods from a Tkinter widget (Text, for | |
3 example), but enables multiple calls of functions per virtual event - all | |
4 matching events will be called, not only the most specific one. This is done | |
5 by wrapping the event functions - event_add, event_delete and event_info. | |
6 MultiCall recognizes only a subset of legal event sequences. Sequences which | |
7 are not recognized are treated by the original Tk handling mechanism. A | |
8 more-specific event will be called before a less-specific event. | |
9 | |
10 The recognized sequences are complete one-event sequences (no emacs-style | |
11 Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events. | |
12 Key/Button Press/Release events can have modifiers. | |
13 The recognized modifiers are Shift, Control, Option and Command for Mac, and | |
14 Control, Alt, Shift, Meta/M for other platforms. | |
15 | |
16 For all events which were handled by MultiCall, a new member is added to the | |
17 event instance passed to the binded functions - mc_type. This is one of the | |
18 event type constants defined in this module (such as MC_KEYPRESS). | |
19 For Key/Button events (which are handled by MultiCall and may receive | |
20 modifiers), another member is added - mc_state. This member gives the state | |
21 of the recognized modifiers, as a combination of the modifier constants | |
22 also defined in this module (for example, MC_SHIFT). | |
23 Using these members is absolutely portable. | |
24 | |
25 The order by which events are called is defined by these rules: | |
26 1. A more-specific event will be called before a less-specific event. | |
27 2. A recently-binded event will be called before a previously-binded event, | |
28 unless this conflicts with the first rule. | |
29 Each function will be called at most once for each event. | |
30 """ | |
31 import re | |
32 import sys | |
33 | |
34 import tkinter | |
35 | |
36 # the event type constants, which define the meaning of mc_type | |
37 MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3; | |
38 MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7; | |
39 MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12; | |
40 MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17; | |
41 MC_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 | |
43 MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5 | |
44 MC_OPTION = 1<<6; MC_COMMAND = 1<<7 | |
45 | |
46 # define the list of modifiers, to be used in complex event types. | |
47 if sys.platform == "darwin": | |
48 _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",)) | |
49 _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND) | |
50 else: | |
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 # 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 | |
63 APPLICATION_GONE = "application has been destroyed" | |
64 | |
65 # 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 | |
73 class _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: | |
106 try: | |
107 self.widget.unbind(self.widgetinst, self.sequence, | |
108 self.handlerid) | |
109 except tkinter.TclError as e: | |
110 if not APPLICATION_GONE in e.args[0]: | |
111 raise | |
112 | |
113 # An int in range(1 << len(_modifiers)) represents a combination of modifiers | |
114 # (if the least significant bit is on, _modifiers[0] is on, and so on). | |
115 # _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)) | |
119 _state_names = [''.join(m[0]+'-' | |
120 for i, m in enumerate(_modifiers) | |
121 if (1 << i) & s) | |
122 for s in _states] | |
123 | |
124 def 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)) | |
138 substates.sort(key=nbits, reverse=True) | |
139 statelist.append(substates) | |
140 return statelist | |
141 | |
142 _state_subsets = expand_substates(_states) | |
143 | |
144 # _state_codes gives for each state, the portable code to be passed as mc_state | |
145 _state_codes = [] | |
146 for 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) | |
152 | |
153 class _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 | |
183 for f in doafterhandler: | |
184 f() | |
185 doafterhandler[:] = [] | |
186 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): | |
214 if triplet[2] not in self.bindedfuncs: | |
215 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: | |
240 try: | |
241 self.widget.unbind(self.widgetinst, seq, id) | |
242 except tkinter.TclError as e: | |
243 if not APPLICATION_GONE in e.args[0]: | |
244 raise | |
245 | |
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]$") | |
267 def _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 | |
275 words = sequence[1:-1].split('-') | |
276 modifiers = 0 | |
277 while words and words[0] in _modifier_names: | |
278 modifiers |= 1 << _modifier_names[words[0]] | |
279 del words[0] | |
280 if words and words[0] in _type_names: | |
281 type = _type_names[words[0]] | |
282 del words[0] | |
283 else: | |
284 return None | |
285 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 | |
306 def _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 = {} | |
314 def 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): | |
323 assert issubclass(widget, tkinter.Misc) | |
324 | |
325 def __init__(self, *args, **kwargs): | |
326 widget.__init__(self, *args, **kwargs) | |
327 # 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): | |
335 #print("bind(%s, %s, %s)" % (sequence, func, add), | |
336 # file=sys.__stderr__) | |
337 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): | |
364 #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)), | |
365 # file=sys.__stderr__) | |
366 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: | |
373 #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__) | |
374 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): | |
381 if virtual not in self.__eventinfo: | |
382 return | |
383 func, triplets = self.__eventinfo[virtual] | |
384 for seq in sequences: | |
385 triplet = _parse_sequence(seq) | |
386 if triplet is None: | |
387 #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__) | |
388 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: | |
407 try: | |
408 self.__binders[triplet[1]].unbind(triplet, func) | |
409 except tkinter.TclError as e: | |
410 if not APPLICATION_GONE in e.args[0]: | |
411 raise | |
412 | |
413 _multicall_dict[widget] = MultiCall | |
414 return MultiCall | |
415 | |
416 | |
417 def _multi_call(parent): # htest # | |
418 top = tkinter.Toplevel(parent) | |
419 top.title("Test MultiCall") | |
420 x, y = map(int, parent.geometry().split('+')[1:]) | |
421 top.geometry("+%d+%d" % (x, y + 175)) | |
422 text = MultiCallCreator(tkinter.Text)(top) | |
423 text.pack() | |
424 def bindseq(seq, n=[0]): | |
425 def handler(event): | |
426 print(seq) | |
427 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>") | |
437 bindseq("<Button-2>") | |
438 bindseq("<Alt-Button-1>") | |
439 bindseq("<FocusOut>") | |
440 bindseq("<Enter>") | |
441 bindseq("<Leave>") | |
442 | |
443 if __name__ == "__main__": | |
444 from unittest import main | |
445 main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False) | |
446 | |
447 from idlelib.idle_test.htest import run | |
448 run(_multi_call) |