jpayne@68
|
1 """
|
jpayne@68
|
2 MultiCall - a class which inherits its methods from a Tkinter widget (Text, for
|
jpayne@68
|
3 example), but enables multiple calls of functions per virtual event - all
|
jpayne@68
|
4 matching events will be called, not only the most specific one. This is done
|
jpayne@68
|
5 by wrapping the event functions - event_add, event_delete and event_info.
|
jpayne@68
|
6 MultiCall recognizes only a subset of legal event sequences. Sequences which
|
jpayne@68
|
7 are not recognized are treated by the original Tk handling mechanism. A
|
jpayne@68
|
8 more-specific event will be called before a less-specific event.
|
jpayne@68
|
9
|
jpayne@68
|
10 The recognized sequences are complete one-event sequences (no emacs-style
|
jpayne@68
|
11 Ctrl-X Ctrl-C, no shortcuts like <3>), for all types of events.
|
jpayne@68
|
12 Key/Button Press/Release events can have modifiers.
|
jpayne@68
|
13 The recognized modifiers are Shift, Control, Option and Command for Mac, and
|
jpayne@68
|
14 Control, Alt, Shift, Meta/M for other platforms.
|
jpayne@68
|
15
|
jpayne@68
|
16 For all events which were handled by MultiCall, a new member is added to the
|
jpayne@68
|
17 event instance passed to the binded functions - mc_type. This is one of the
|
jpayne@68
|
18 event type constants defined in this module (such as MC_KEYPRESS).
|
jpayne@68
|
19 For Key/Button events (which are handled by MultiCall and may receive
|
jpayne@68
|
20 modifiers), another member is added - mc_state. This member gives the state
|
jpayne@68
|
21 of the recognized modifiers, as a combination of the modifier constants
|
jpayne@68
|
22 also defined in this module (for example, MC_SHIFT).
|
jpayne@68
|
23 Using these members is absolutely portable.
|
jpayne@68
|
24
|
jpayne@68
|
25 The order by which events are called is defined by these rules:
|
jpayne@68
|
26 1. A more-specific event will be called before a less-specific event.
|
jpayne@68
|
27 2. A recently-binded event will be called before a previously-binded event,
|
jpayne@68
|
28 unless this conflicts with the first rule.
|
jpayne@68
|
29 Each function will be called at most once for each event.
|
jpayne@68
|
30 """
|
jpayne@68
|
31 import re
|
jpayne@68
|
32 import sys
|
jpayne@68
|
33
|
jpayne@68
|
34 import tkinter
|
jpayne@68
|
35
|
jpayne@68
|
36 # the event type constants, which define the meaning of mc_type
|
jpayne@68
|
37 MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
|
jpayne@68
|
38 MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
|
jpayne@68
|
39 MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
|
jpayne@68
|
40 MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
|
jpayne@68
|
41 MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
|
jpayne@68
|
42 # the modifier state constants, which define the meaning of mc_state
|
jpayne@68
|
43 MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
|
jpayne@68
|
44 MC_OPTION = 1<<6; MC_COMMAND = 1<<7
|
jpayne@68
|
45
|
jpayne@68
|
46 # define the list of modifiers, to be used in complex event types.
|
jpayne@68
|
47 if sys.platform == "darwin":
|
jpayne@68
|
48 _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
|
jpayne@68
|
49 _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
|
jpayne@68
|
50 else:
|
jpayne@68
|
51 _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
|
jpayne@68
|
52 _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
|
jpayne@68
|
53
|
jpayne@68
|
54 # a dictionary to map a modifier name into its number
|
jpayne@68
|
55 _modifier_names = dict([(name, number)
|
jpayne@68
|
56 for number in range(len(_modifiers))
|
jpayne@68
|
57 for name in _modifiers[number]])
|
jpayne@68
|
58
|
jpayne@68
|
59 # In 3.4, if no shell window is ever open, the underlying Tk widget is
|
jpayne@68
|
60 # destroyed before .__del__ methods here are called. The following
|
jpayne@68
|
61 # is used to selectively ignore shutdown exceptions to avoid
|
jpayne@68
|
62 # 'Exception ignored' messages. See http://bugs.python.org/issue20167
|
jpayne@68
|
63 APPLICATION_GONE = "application has been destroyed"
|
jpayne@68
|
64
|
jpayne@68
|
65 # A binder is a class which binds functions to one type of event. It has two
|
jpayne@68
|
66 # methods: bind and unbind, which get a function and a parsed sequence, as
|
jpayne@68
|
67 # returned by _parse_sequence(). There are two types of binders:
|
jpayne@68
|
68 # _SimpleBinder handles event types with no modifiers and no detail.
|
jpayne@68
|
69 # No Python functions are called when no events are binded.
|
jpayne@68
|
70 # _ComplexBinder handles event types with modifiers and a detail.
|
jpayne@68
|
71 # A Python function is called each time an event is generated.
|
jpayne@68
|
72
|
jpayne@68
|
73 class _SimpleBinder:
|
jpayne@68
|
74 def __init__(self, type, widget, widgetinst):
|
jpayne@68
|
75 self.type = type
|
jpayne@68
|
76 self.sequence = '<'+_types[type][0]+'>'
|
jpayne@68
|
77 self.widget = widget
|
jpayne@68
|
78 self.widgetinst = widgetinst
|
jpayne@68
|
79 self.bindedfuncs = []
|
jpayne@68
|
80 self.handlerid = None
|
jpayne@68
|
81
|
jpayne@68
|
82 def bind(self, triplet, func):
|
jpayne@68
|
83 if not self.handlerid:
|
jpayne@68
|
84 def handler(event, l = self.bindedfuncs, mc_type = self.type):
|
jpayne@68
|
85 event.mc_type = mc_type
|
jpayne@68
|
86 wascalled = {}
|
jpayne@68
|
87 for i in range(len(l)-1, -1, -1):
|
jpayne@68
|
88 func = l[i]
|
jpayne@68
|
89 if func not in wascalled:
|
jpayne@68
|
90 wascalled[func] = True
|
jpayne@68
|
91 r = func(event)
|
jpayne@68
|
92 if r:
|
jpayne@68
|
93 return r
|
jpayne@68
|
94 self.handlerid = self.widget.bind(self.widgetinst,
|
jpayne@68
|
95 self.sequence, handler)
|
jpayne@68
|
96 self.bindedfuncs.append(func)
|
jpayne@68
|
97
|
jpayne@68
|
98 def unbind(self, triplet, func):
|
jpayne@68
|
99 self.bindedfuncs.remove(func)
|
jpayne@68
|
100 if not self.bindedfuncs:
|
jpayne@68
|
101 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
|
jpayne@68
|
102 self.handlerid = None
|
jpayne@68
|
103
|
jpayne@68
|
104 def __del__(self):
|
jpayne@68
|
105 if self.handlerid:
|
jpayne@68
|
106 try:
|
jpayne@68
|
107 self.widget.unbind(self.widgetinst, self.sequence,
|
jpayne@68
|
108 self.handlerid)
|
jpayne@68
|
109 except tkinter.TclError as e:
|
jpayne@68
|
110 if not APPLICATION_GONE in e.args[0]:
|
jpayne@68
|
111 raise
|
jpayne@68
|
112
|
jpayne@68
|
113 # An int in range(1 << len(_modifiers)) represents a combination of modifiers
|
jpayne@68
|
114 # (if the least significant bit is on, _modifiers[0] is on, and so on).
|
jpayne@68
|
115 # _state_subsets gives for each combination of modifiers, or *state*,
|
jpayne@68
|
116 # a list of the states which are a subset of it. This list is ordered by the
|
jpayne@68
|
117 # number of modifiers is the state - the most specific state comes first.
|
jpayne@68
|
118 _states = range(1 << len(_modifiers))
|
jpayne@68
|
119 _state_names = [''.join(m[0]+'-'
|
jpayne@68
|
120 for i, m in enumerate(_modifiers)
|
jpayne@68
|
121 if (1 << i) & s)
|
jpayne@68
|
122 for s in _states]
|
jpayne@68
|
123
|
jpayne@68
|
124 def expand_substates(states):
|
jpayne@68
|
125 '''For each item of states return a list containing all combinations of
|
jpayne@68
|
126 that item with individual bits reset, sorted by the number of set bits.
|
jpayne@68
|
127 '''
|
jpayne@68
|
128 def nbits(n):
|
jpayne@68
|
129 "number of bits set in n base 2"
|
jpayne@68
|
130 nb = 0
|
jpayne@68
|
131 while n:
|
jpayne@68
|
132 n, rem = divmod(n, 2)
|
jpayne@68
|
133 nb += rem
|
jpayne@68
|
134 return nb
|
jpayne@68
|
135 statelist = []
|
jpayne@68
|
136 for state in states:
|
jpayne@68
|
137 substates = list(set(state & x for x in states))
|
jpayne@68
|
138 substates.sort(key=nbits, reverse=True)
|
jpayne@68
|
139 statelist.append(substates)
|
jpayne@68
|
140 return statelist
|
jpayne@68
|
141
|
jpayne@68
|
142 _state_subsets = expand_substates(_states)
|
jpayne@68
|
143
|
jpayne@68
|
144 # _state_codes gives for each state, the portable code to be passed as mc_state
|
jpayne@68
|
145 _state_codes = []
|
jpayne@68
|
146 for s in _states:
|
jpayne@68
|
147 r = 0
|
jpayne@68
|
148 for i in range(len(_modifiers)):
|
jpayne@68
|
149 if (1 << i) & s:
|
jpayne@68
|
150 r |= _modifier_masks[i]
|
jpayne@68
|
151 _state_codes.append(r)
|
jpayne@68
|
152
|
jpayne@68
|
153 class _ComplexBinder:
|
jpayne@68
|
154 # This class binds many functions, and only unbinds them when it is deleted.
|
jpayne@68
|
155 # self.handlerids is the list of seqs and ids of binded handler functions.
|
jpayne@68
|
156 # The binded functions sit in a dictionary of lists of lists, which maps
|
jpayne@68
|
157 # a detail (or None) and a state into a list of functions.
|
jpayne@68
|
158 # When a new detail is discovered, handlers for all the possible states
|
jpayne@68
|
159 # are binded.
|
jpayne@68
|
160
|
jpayne@68
|
161 def __create_handler(self, lists, mc_type, mc_state):
|
jpayne@68
|
162 def handler(event, lists = lists,
|
jpayne@68
|
163 mc_type = mc_type, mc_state = mc_state,
|
jpayne@68
|
164 ishandlerrunning = self.ishandlerrunning,
|
jpayne@68
|
165 doafterhandler = self.doafterhandler):
|
jpayne@68
|
166 ishandlerrunning[:] = [True]
|
jpayne@68
|
167 event.mc_type = mc_type
|
jpayne@68
|
168 event.mc_state = mc_state
|
jpayne@68
|
169 wascalled = {}
|
jpayne@68
|
170 r = None
|
jpayne@68
|
171 for l in lists:
|
jpayne@68
|
172 for i in range(len(l)-1, -1, -1):
|
jpayne@68
|
173 func = l[i]
|
jpayne@68
|
174 if func not in wascalled:
|
jpayne@68
|
175 wascalled[func] = True
|
jpayne@68
|
176 r = l[i](event)
|
jpayne@68
|
177 if r:
|
jpayne@68
|
178 break
|
jpayne@68
|
179 if r:
|
jpayne@68
|
180 break
|
jpayne@68
|
181 ishandlerrunning[:] = []
|
jpayne@68
|
182 # Call all functions in doafterhandler and remove them from list
|
jpayne@68
|
183 for f in doafterhandler:
|
jpayne@68
|
184 f()
|
jpayne@68
|
185 doafterhandler[:] = []
|
jpayne@68
|
186 if r:
|
jpayne@68
|
187 return r
|
jpayne@68
|
188 return handler
|
jpayne@68
|
189
|
jpayne@68
|
190 def __init__(self, type, widget, widgetinst):
|
jpayne@68
|
191 self.type = type
|
jpayne@68
|
192 self.typename = _types[type][0]
|
jpayne@68
|
193 self.widget = widget
|
jpayne@68
|
194 self.widgetinst = widgetinst
|
jpayne@68
|
195 self.bindedfuncs = {None: [[] for s in _states]}
|
jpayne@68
|
196 self.handlerids = []
|
jpayne@68
|
197 # we don't want to change the lists of functions while a handler is
|
jpayne@68
|
198 # running - it will mess up the loop and anyway, we usually want the
|
jpayne@68
|
199 # change to happen from the next event. So we have a list of functions
|
jpayne@68
|
200 # for the handler to run after it finishes calling the binded functions.
|
jpayne@68
|
201 # It calls them only once.
|
jpayne@68
|
202 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
|
jpayne@68
|
203 # this is done so that it would be mutable.
|
jpayne@68
|
204 self.ishandlerrunning = []
|
jpayne@68
|
205 self.doafterhandler = []
|
jpayne@68
|
206 for s in _states:
|
jpayne@68
|
207 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
|
jpayne@68
|
208 handler = self.__create_handler(lists, type, _state_codes[s])
|
jpayne@68
|
209 seq = '<'+_state_names[s]+self.typename+'>'
|
jpayne@68
|
210 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
|
jpayne@68
|
211 seq, handler)))
|
jpayne@68
|
212
|
jpayne@68
|
213 def bind(self, triplet, func):
|
jpayne@68
|
214 if triplet[2] not in self.bindedfuncs:
|
jpayne@68
|
215 self.bindedfuncs[triplet[2]] = [[] for s in _states]
|
jpayne@68
|
216 for s in _states:
|
jpayne@68
|
217 lists = [ self.bindedfuncs[detail][i]
|
jpayne@68
|
218 for detail in (triplet[2], None)
|
jpayne@68
|
219 for i in _state_subsets[s] ]
|
jpayne@68
|
220 handler = self.__create_handler(lists, self.type,
|
jpayne@68
|
221 _state_codes[s])
|
jpayne@68
|
222 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
|
jpayne@68
|
223 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
|
jpayne@68
|
224 seq, handler)))
|
jpayne@68
|
225 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
|
jpayne@68
|
226 if not self.ishandlerrunning:
|
jpayne@68
|
227 doit()
|
jpayne@68
|
228 else:
|
jpayne@68
|
229 self.doafterhandler.append(doit)
|
jpayne@68
|
230
|
jpayne@68
|
231 def unbind(self, triplet, func):
|
jpayne@68
|
232 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
|
jpayne@68
|
233 if not self.ishandlerrunning:
|
jpayne@68
|
234 doit()
|
jpayne@68
|
235 else:
|
jpayne@68
|
236 self.doafterhandler.append(doit)
|
jpayne@68
|
237
|
jpayne@68
|
238 def __del__(self):
|
jpayne@68
|
239 for seq, id in self.handlerids:
|
jpayne@68
|
240 try:
|
jpayne@68
|
241 self.widget.unbind(self.widgetinst, seq, id)
|
jpayne@68
|
242 except tkinter.TclError as e:
|
jpayne@68
|
243 if not APPLICATION_GONE in e.args[0]:
|
jpayne@68
|
244 raise
|
jpayne@68
|
245
|
jpayne@68
|
246 # define the list of event types to be handled by MultiEvent. the order is
|
jpayne@68
|
247 # compatible with the definition of event type constants.
|
jpayne@68
|
248 _types = (
|
jpayne@68
|
249 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
|
jpayne@68
|
250 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
|
jpayne@68
|
251 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
|
jpayne@68
|
252 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
|
jpayne@68
|
253 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
|
jpayne@68
|
254 ("Visibility",),
|
jpayne@68
|
255 )
|
jpayne@68
|
256
|
jpayne@68
|
257 # which binder should be used for every event type?
|
jpayne@68
|
258 _binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
|
jpayne@68
|
259
|
jpayne@68
|
260 # A dictionary to map a type name into its number
|
jpayne@68
|
261 _type_names = dict([(name, number)
|
jpayne@68
|
262 for number in range(len(_types))
|
jpayne@68
|
263 for name in _types[number]])
|
jpayne@68
|
264
|
jpayne@68
|
265 _keysym_re = re.compile(r"^\w+$")
|
jpayne@68
|
266 _button_re = re.compile(r"^[1-5]$")
|
jpayne@68
|
267 def _parse_sequence(sequence):
|
jpayne@68
|
268 """Get a string which should describe an event sequence. If it is
|
jpayne@68
|
269 successfully parsed as one, return a tuple containing the state (as an int),
|
jpayne@68
|
270 the event type (as an index of _types), and the detail - None if none, or a
|
jpayne@68
|
271 string if there is one. If the parsing is unsuccessful, return None.
|
jpayne@68
|
272 """
|
jpayne@68
|
273 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
|
jpayne@68
|
274 return None
|
jpayne@68
|
275 words = sequence[1:-1].split('-')
|
jpayne@68
|
276 modifiers = 0
|
jpayne@68
|
277 while words and words[0] in _modifier_names:
|
jpayne@68
|
278 modifiers |= 1 << _modifier_names[words[0]]
|
jpayne@68
|
279 del words[0]
|
jpayne@68
|
280 if words and words[0] in _type_names:
|
jpayne@68
|
281 type = _type_names[words[0]]
|
jpayne@68
|
282 del words[0]
|
jpayne@68
|
283 else:
|
jpayne@68
|
284 return None
|
jpayne@68
|
285 if _binder_classes[type] is _SimpleBinder:
|
jpayne@68
|
286 if modifiers or words:
|
jpayne@68
|
287 return None
|
jpayne@68
|
288 else:
|
jpayne@68
|
289 detail = None
|
jpayne@68
|
290 else:
|
jpayne@68
|
291 # _ComplexBinder
|
jpayne@68
|
292 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
|
jpayne@68
|
293 type_re = _keysym_re
|
jpayne@68
|
294 else:
|
jpayne@68
|
295 type_re = _button_re
|
jpayne@68
|
296
|
jpayne@68
|
297 if not words:
|
jpayne@68
|
298 detail = None
|
jpayne@68
|
299 elif len(words) == 1 and type_re.match(words[0]):
|
jpayne@68
|
300 detail = words[0]
|
jpayne@68
|
301 else:
|
jpayne@68
|
302 return None
|
jpayne@68
|
303
|
jpayne@68
|
304 return modifiers, type, detail
|
jpayne@68
|
305
|
jpayne@68
|
306 def _triplet_to_sequence(triplet):
|
jpayne@68
|
307 if triplet[2]:
|
jpayne@68
|
308 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
|
jpayne@68
|
309 triplet[2]+'>'
|
jpayne@68
|
310 else:
|
jpayne@68
|
311 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
|
jpayne@68
|
312
|
jpayne@68
|
313 _multicall_dict = {}
|
jpayne@68
|
314 def MultiCallCreator(widget):
|
jpayne@68
|
315 """Return a MultiCall class which inherits its methods from the
|
jpayne@68
|
316 given widget class (for example, Tkinter.Text). This is used
|
jpayne@68
|
317 instead of a templating mechanism.
|
jpayne@68
|
318 """
|
jpayne@68
|
319 if widget in _multicall_dict:
|
jpayne@68
|
320 return _multicall_dict[widget]
|
jpayne@68
|
321
|
jpayne@68
|
322 class MultiCall (widget):
|
jpayne@68
|
323 assert issubclass(widget, tkinter.Misc)
|
jpayne@68
|
324
|
jpayne@68
|
325 def __init__(self, *args, **kwargs):
|
jpayne@68
|
326 widget.__init__(self, *args, **kwargs)
|
jpayne@68
|
327 # a dictionary which maps a virtual event to a tuple with:
|
jpayne@68
|
328 # 0. the function binded
|
jpayne@68
|
329 # 1. a list of triplets - the sequences it is binded to
|
jpayne@68
|
330 self.__eventinfo = {}
|
jpayne@68
|
331 self.__binders = [_binder_classes[i](i, widget, self)
|
jpayne@68
|
332 for i in range(len(_types))]
|
jpayne@68
|
333
|
jpayne@68
|
334 def bind(self, sequence=None, func=None, add=None):
|
jpayne@68
|
335 #print("bind(%s, %s, %s)" % (sequence, func, add),
|
jpayne@68
|
336 # file=sys.__stderr__)
|
jpayne@68
|
337 if type(sequence) is str and len(sequence) > 2 and \
|
jpayne@68
|
338 sequence[:2] == "<<" and sequence[-2:] == ">>":
|
jpayne@68
|
339 if sequence in self.__eventinfo:
|
jpayne@68
|
340 ei = self.__eventinfo[sequence]
|
jpayne@68
|
341 if ei[0] is not None:
|
jpayne@68
|
342 for triplet in ei[1]:
|
jpayne@68
|
343 self.__binders[triplet[1]].unbind(triplet, ei[0])
|
jpayne@68
|
344 ei[0] = func
|
jpayne@68
|
345 if ei[0] is not None:
|
jpayne@68
|
346 for triplet in ei[1]:
|
jpayne@68
|
347 self.__binders[triplet[1]].bind(triplet, func)
|
jpayne@68
|
348 else:
|
jpayne@68
|
349 self.__eventinfo[sequence] = [func, []]
|
jpayne@68
|
350 return widget.bind(self, sequence, func, add)
|
jpayne@68
|
351
|
jpayne@68
|
352 def unbind(self, sequence, funcid=None):
|
jpayne@68
|
353 if type(sequence) is str and len(sequence) > 2 and \
|
jpayne@68
|
354 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
|
jpayne@68
|
355 sequence in self.__eventinfo:
|
jpayne@68
|
356 func, triplets = self.__eventinfo[sequence]
|
jpayne@68
|
357 if func is not None:
|
jpayne@68
|
358 for triplet in triplets:
|
jpayne@68
|
359 self.__binders[triplet[1]].unbind(triplet, func)
|
jpayne@68
|
360 self.__eventinfo[sequence][0] = None
|
jpayne@68
|
361 return widget.unbind(self, sequence, funcid)
|
jpayne@68
|
362
|
jpayne@68
|
363 def event_add(self, virtual, *sequences):
|
jpayne@68
|
364 #print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
|
jpayne@68
|
365 # file=sys.__stderr__)
|
jpayne@68
|
366 if virtual not in self.__eventinfo:
|
jpayne@68
|
367 self.__eventinfo[virtual] = [None, []]
|
jpayne@68
|
368
|
jpayne@68
|
369 func, triplets = self.__eventinfo[virtual]
|
jpayne@68
|
370 for seq in sequences:
|
jpayne@68
|
371 triplet = _parse_sequence(seq)
|
jpayne@68
|
372 if triplet is None:
|
jpayne@68
|
373 #print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
|
jpayne@68
|
374 widget.event_add(self, virtual, seq)
|
jpayne@68
|
375 else:
|
jpayne@68
|
376 if func is not None:
|
jpayne@68
|
377 self.__binders[triplet[1]].bind(triplet, func)
|
jpayne@68
|
378 triplets.append(triplet)
|
jpayne@68
|
379
|
jpayne@68
|
380 def event_delete(self, virtual, *sequences):
|
jpayne@68
|
381 if virtual not in self.__eventinfo:
|
jpayne@68
|
382 return
|
jpayne@68
|
383 func, triplets = self.__eventinfo[virtual]
|
jpayne@68
|
384 for seq in sequences:
|
jpayne@68
|
385 triplet = _parse_sequence(seq)
|
jpayne@68
|
386 if triplet is None:
|
jpayne@68
|
387 #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
|
jpayne@68
|
388 widget.event_delete(self, virtual, seq)
|
jpayne@68
|
389 else:
|
jpayne@68
|
390 if func is not None:
|
jpayne@68
|
391 self.__binders[triplet[1]].unbind(triplet, func)
|
jpayne@68
|
392 triplets.remove(triplet)
|
jpayne@68
|
393
|
jpayne@68
|
394 def event_info(self, virtual=None):
|
jpayne@68
|
395 if virtual is None or virtual not in self.__eventinfo:
|
jpayne@68
|
396 return widget.event_info(self, virtual)
|
jpayne@68
|
397 else:
|
jpayne@68
|
398 return tuple(map(_triplet_to_sequence,
|
jpayne@68
|
399 self.__eventinfo[virtual][1])) + \
|
jpayne@68
|
400 widget.event_info(self, virtual)
|
jpayne@68
|
401
|
jpayne@68
|
402 def __del__(self):
|
jpayne@68
|
403 for virtual in self.__eventinfo:
|
jpayne@68
|
404 func, triplets = self.__eventinfo[virtual]
|
jpayne@68
|
405 if func:
|
jpayne@68
|
406 for triplet in triplets:
|
jpayne@68
|
407 try:
|
jpayne@68
|
408 self.__binders[triplet[1]].unbind(triplet, func)
|
jpayne@68
|
409 except tkinter.TclError as e:
|
jpayne@68
|
410 if not APPLICATION_GONE in e.args[0]:
|
jpayne@68
|
411 raise
|
jpayne@68
|
412
|
jpayne@68
|
413 _multicall_dict[widget] = MultiCall
|
jpayne@68
|
414 return MultiCall
|
jpayne@68
|
415
|
jpayne@68
|
416
|
jpayne@68
|
417 def _multi_call(parent): # htest #
|
jpayne@68
|
418 top = tkinter.Toplevel(parent)
|
jpayne@68
|
419 top.title("Test MultiCall")
|
jpayne@68
|
420 x, y = map(int, parent.geometry().split('+')[1:])
|
jpayne@68
|
421 top.geometry("+%d+%d" % (x, y + 175))
|
jpayne@68
|
422 text = MultiCallCreator(tkinter.Text)(top)
|
jpayne@68
|
423 text.pack()
|
jpayne@68
|
424 def bindseq(seq, n=[0]):
|
jpayne@68
|
425 def handler(event):
|
jpayne@68
|
426 print(seq)
|
jpayne@68
|
427 text.bind("<<handler%d>>"%n[0], handler)
|
jpayne@68
|
428 text.event_add("<<handler%d>>"%n[0], seq)
|
jpayne@68
|
429 n[0] += 1
|
jpayne@68
|
430 bindseq("<Key>")
|
jpayne@68
|
431 bindseq("<Control-Key>")
|
jpayne@68
|
432 bindseq("<Alt-Key-a>")
|
jpayne@68
|
433 bindseq("<Control-Key-a>")
|
jpayne@68
|
434 bindseq("<Alt-Control-Key-a>")
|
jpayne@68
|
435 bindseq("<Key-b>")
|
jpayne@68
|
436 bindseq("<Control-Button-1>")
|
jpayne@68
|
437 bindseq("<Button-2>")
|
jpayne@68
|
438 bindseq("<Alt-Button-1>")
|
jpayne@68
|
439 bindseq("<FocusOut>")
|
jpayne@68
|
440 bindseq("<Enter>")
|
jpayne@68
|
441 bindseq("<Leave>")
|
jpayne@68
|
442
|
jpayne@68
|
443 if __name__ == "__main__":
|
jpayne@68
|
444 from unittest import main
|
jpayne@68
|
445 main('idlelib.idle_test.test_mainmenu', verbosity=2, exit=False)
|
jpayne@68
|
446
|
jpayne@68
|
447 from idlelib.idle_test.htest import run
|
jpayne@68
|
448 run(_multi_call)
|