blob: b032a653d3f4306c4fb90f5b2ac7f7a159b0e349 [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 string
34import re
Georg Brandl6634bf22008-05-20 07:13:37 +000035import Tkinter
Florent Xiclunad630c042010-04-02 07:24:52 +000036from idlelib import macosxSupport
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000037
38# the event type constants, which define the meaning of mc_type
39MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
40MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
41MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
42MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
43MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
44# the modifier state constants, which define the meaning of mc_state
45MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
46MC_OPTION = 1<<6; MC_COMMAND = 1<<7
47
48# define the list of modifiers, to be used in complex event types.
Ronald Oussorena97063a2009-03-04 21:35:05 +000049if macosxSupport.runningAsOSXApp():
Kurt B. Kaiserb1754452005-11-18 22:05:48 +000050 _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
51 _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
52else:
53 _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
54 _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
55
56# a dictionary to map a modifier name into its number
57_modifier_names = dict([(name, number)
58 for number in range(len(_modifiers))
59 for name in _modifiers[number]])
60
61# A binder is a class which binds functions to one type of event. It has two
62# methods: bind and unbind, which get a function and a parsed sequence, as
63# returned by _parse_sequence(). There are two types of binders:
64# _SimpleBinder handles event types with no modifiers and no detail.
65# No Python functions are called when no events are binded.
66# _ComplexBinder handles event types with modifiers and a detail.
67# A Python function is called each time an event is generated.
68
69class _SimpleBinder:
70 def __init__(self, type, widget, widgetinst):
71 self.type = type
72 self.sequence = '<'+_types[type][0]+'>'
73 self.widget = widget
74 self.widgetinst = widgetinst
75 self.bindedfuncs = []
76 self.handlerid = None
77
78 def bind(self, triplet, func):
79 if not self.handlerid:
80 def handler(event, l = self.bindedfuncs, mc_type = self.type):
81 event.mc_type = mc_type
82 wascalled = {}
83 for i in range(len(l)-1, -1, -1):
84 func = l[i]
85 if func not in wascalled:
86 wascalled[func] = True
87 r = func(event)
88 if r:
89 return r
90 self.handlerid = self.widget.bind(self.widgetinst,
91 self.sequence, handler)
92 self.bindedfuncs.append(func)
93
94 def unbind(self, triplet, func):
95 self.bindedfuncs.remove(func)
96 if not self.bindedfuncs:
97 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
98 self.handlerid = None
99
100 def __del__(self):
101 if self.handlerid:
102 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
103
104# An int in range(1 << len(_modifiers)) represents a combination of modifiers
105# (if the least significent bit is on, _modifiers[0] is on, and so on).
106# _state_subsets gives for each combination of modifiers, or *state*,
107# a list of the states which are a subset of it. This list is ordered by the
108# number of modifiers is the state - the most specific state comes first.
109_states = range(1 << len(_modifiers))
Florent Xicluna8d1da0f2010-04-01 18:17:09 +0000110_state_names = [''.join(m[0]+'-'
111 for i, m in enumerate(_modifiers)
112 if (1 << i) & s)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000113 for s in _states]
Florent Xiclunad630c042010-04-02 07:24:52 +0000114
115def expand_substates(states):
116 '''For each item of states return a list containing all combinations of
117 that item with individual bits reset, sorted by the number of set bits.
118 '''
119 def nbits(n):
120 "number of bits set in n base 2"
121 nb = 0
122 while n:
123 n, rem = divmod(n, 2)
124 nb += rem
125 return nb
126 statelist = []
127 for state in states:
128 substates = list(set(state & x for x in states))
129 substates.sort(key=nbits, reverse=True)
130 statelist.append(substates)
131 return statelist
132
133_state_subsets = expand_substates(_states)
134
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000135# _state_codes gives for each state, the portable code to be passed as mc_state
Florent Xicluna8d1da0f2010-04-01 18:17:09 +0000136_state_codes = []
137for s in _states:
138 r = 0
139 for i in range(len(_modifiers)):
140 if (1 << i) & s:
141 r |= _modifier_masks[i]
142 _state_codes.append(r)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000143
144class _ComplexBinder:
145 # This class binds many functions, and only unbinds them when it is deleted.
146 # self.handlerids is the list of seqs and ids of binded handler functions.
147 # The binded functions sit in a dictionary of lists of lists, which maps
148 # a detail (or None) and a state into a list of functions.
149 # When a new detail is discovered, handlers for all the possible states
150 # are binded.
151
152 def __create_handler(self, lists, mc_type, mc_state):
153 def handler(event, lists = lists,
154 mc_type = mc_type, mc_state = mc_state,
155 ishandlerrunning = self.ishandlerrunning,
156 doafterhandler = self.doafterhandler):
157 ishandlerrunning[:] = [True]
158 event.mc_type = mc_type
159 event.mc_state = mc_state
160 wascalled = {}
161 r = None
162 for l in lists:
163 for i in range(len(l)-1, -1, -1):
164 func = l[i]
165 if func not in wascalled:
166 wascalled[func] = True
167 r = l[i](event)
168 if r:
169 break
170 if r:
171 break
172 ishandlerrunning[:] = []
173 # Call all functions in doafterhandler and remove them from list
Roger Serwycadd7862013-03-31 15:53:08 -0500174 for f in doafterhandler:
175 f()
176 doafterhandler[:] = []
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000177 if r:
178 return r
179 return handler
180
181 def __init__(self, type, widget, widgetinst):
182 self.type = type
183 self.typename = _types[type][0]
184 self.widget = widget
185 self.widgetinst = widgetinst
186 self.bindedfuncs = {None: [[] for s in _states]}
187 self.handlerids = []
188 # we don't want to change the lists of functions while a handler is
189 # running - it will mess up the loop and anyway, we usually want the
190 # change to happen from the next event. So we have a list of functions
191 # for the handler to run after it finishes calling the binded functions.
192 # It calls them only once.
193 # ishandlerrunning is a list. An empty one means no, otherwise - yes.
194 # this is done so that it would be mutable.
195 self.ishandlerrunning = []
196 self.doafterhandler = []
197 for s in _states:
198 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
199 handler = self.__create_handler(lists, type, _state_codes[s])
200 seq = '<'+_state_names[s]+self.typename+'>'
201 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
202 seq, handler)))
203
204 def bind(self, triplet, func):
Benjamin Peterson6e3dbbd2009-10-09 22:15:50 +0000205 if triplet[2] not in self.bindedfuncs:
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000206 self.bindedfuncs[triplet[2]] = [[] for s in _states]
207 for s in _states:
208 lists = [ self.bindedfuncs[detail][i]
209 for detail in (triplet[2], None)
210 for i in _state_subsets[s] ]
211 handler = self.__create_handler(lists, self.type,
212 _state_codes[s])
213 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
214 self.handlerids.append((seq, self.widget.bind(self.widgetinst,
215 seq, handler)))
216 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
217 if not self.ishandlerrunning:
218 doit()
219 else:
220 self.doafterhandler.append(doit)
221
222 def unbind(self, triplet, func):
223 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
224 if not self.ishandlerrunning:
225 doit()
226 else:
227 self.doafterhandler.append(doit)
228
229 def __del__(self):
230 for seq, id in self.handlerids:
231 self.widget.unbind(self.widgetinst, seq, id)
232
233# define the list of event types to be handled by MultiEvent. the order is
234# compatible with the definition of event type constants.
235_types = (
236 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
237 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
238 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
239 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
240 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
241 ("Visibility",),
242)
243
244# which binder should be used for every event type?
245_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
246
247# A dictionary to map a type name into its number
248_type_names = dict([(name, number)
249 for number in range(len(_types))
250 for name in _types[number]])
251
252_keysym_re = re.compile(r"^\w+$")
253_button_re = re.compile(r"^[1-5]$")
254def _parse_sequence(sequence):
255 """Get a string which should describe an event sequence. If it is
256 successfully parsed as one, return a tuple containing the state (as an int),
257 the event type (as an index of _types), and the detail - None if none, or a
258 string if there is one. If the parsing is unsuccessful, return None.
259 """
260 if not sequence or sequence[0] != '<' or sequence[-1] != '>':
261 return None
262 words = string.split(sequence[1:-1], '-')
263
264 modifiers = 0
265 while words and words[0] in _modifier_names:
266 modifiers |= 1 << _modifier_names[words[0]]
267 del words[0]
268
269 if words and words[0] in _type_names:
270 type = _type_names[words[0]]
271 del words[0]
272 else:
273 return None
274
275 if _binder_classes[type] is _SimpleBinder:
276 if modifiers or words:
277 return None
278 else:
279 detail = None
280 else:
281 # _ComplexBinder
282 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
283 type_re = _keysym_re
284 else:
285 type_re = _button_re
286
287 if not words:
288 detail = None
289 elif len(words) == 1 and type_re.match(words[0]):
290 detail = words[0]
291 else:
292 return None
293
294 return modifiers, type, detail
295
296def _triplet_to_sequence(triplet):
297 if triplet[2]:
298 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
299 triplet[2]+'>'
300 else:
301 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
302
303_multicall_dict = {}
304def MultiCallCreator(widget):
305 """Return a MultiCall class which inherits its methods from the
306 given widget class (for example, Tkinter.Text). This is used
307 instead of a templating mechanism.
308 """
309 if widget in _multicall_dict:
310 return _multicall_dict[widget]
311
312 class MultiCall (widget):
Georg Brandl6634bf22008-05-20 07:13:37 +0000313 assert issubclass(widget, Tkinter.Misc)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000314
315 def __init__(self, *args, **kwargs):
Florent Xiclunad630c042010-04-02 07:24:52 +0000316 widget.__init__(self, *args, **kwargs)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000317 # a dictionary which maps a virtual event to a tuple with:
318 # 0. the function binded
319 # 1. a list of triplets - the sequences it is binded to
320 self.__eventinfo = {}
321 self.__binders = [_binder_classes[i](i, widget, self)
322 for i in range(len(_types))]
323
324 def bind(self, sequence=None, func=None, add=None):
325 #print "bind(%s, %s, %s) called." % (sequence, func, add)
326 if type(sequence) is str and len(sequence) > 2 and \
327 sequence[:2] == "<<" and sequence[-2:] == ">>":
328 if sequence in self.__eventinfo:
329 ei = self.__eventinfo[sequence]
330 if ei[0] is not None:
331 for triplet in ei[1]:
332 self.__binders[triplet[1]].unbind(triplet, ei[0])
333 ei[0] = func
334 if ei[0] is not None:
335 for triplet in ei[1]:
336 self.__binders[triplet[1]].bind(triplet, func)
337 else:
338 self.__eventinfo[sequence] = [func, []]
339 return widget.bind(self, sequence, func, add)
340
341 def unbind(self, sequence, funcid=None):
342 if type(sequence) is str and len(sequence) > 2 and \
343 sequence[:2] == "<<" and sequence[-2:] == ">>" and \
344 sequence in self.__eventinfo:
345 func, triplets = self.__eventinfo[sequence]
346 if func is not None:
347 for triplet in triplets:
348 self.__binders[triplet[1]].unbind(triplet, func)
349 self.__eventinfo[sequence][0] = None
350 return widget.unbind(self, sequence, funcid)
351
352 def event_add(self, virtual, *sequences):
353 #print "event_add(%s,%s) was called"%(repr(virtual),repr(sequences))
354 if virtual not in self.__eventinfo:
355 self.__eventinfo[virtual] = [None, []]
356
357 func, triplets = self.__eventinfo[virtual]
358 for seq in sequences:
359 triplet = _parse_sequence(seq)
360 if triplet is None:
361 #print >> sys.stderr, "Seq. %s was added by Tkinter."%seq
362 widget.event_add(self, virtual, seq)
363 else:
364 if func is not None:
365 self.__binders[triplet[1]].bind(triplet, func)
366 triplets.append(triplet)
367
368 def event_delete(self, virtual, *sequences):
Georg Brandld53d9512007-03-06 11:52:24 +0000369 if virtual not in self.__eventinfo:
370 return
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000371 func, triplets = self.__eventinfo[virtual]
372 for seq in sequences:
373 triplet = _parse_sequence(seq)
374 if triplet is None:
375 #print >> sys.stderr, "Seq. %s was deleted by Tkinter."%seq
376 widget.event_delete(self, virtual, seq)
377 else:
378 if func is not None:
379 self.__binders[triplet[1]].unbind(triplet, func)
380 triplets.remove(triplet)
381
382 def event_info(self, virtual=None):
383 if virtual is None or virtual not in self.__eventinfo:
384 return widget.event_info(self, virtual)
385 else:
386 return tuple(map(_triplet_to_sequence,
387 self.__eventinfo[virtual][1])) + \
388 widget.event_info(self, virtual)
389
390 def __del__(self):
391 for virtual in self.__eventinfo:
392 func, triplets = self.__eventinfo[virtual]
393 if func:
394 for triplet in triplets:
395 self.__binders[triplet[1]].unbind(triplet, func)
396
397
398 _multicall_dict[widget] = MultiCall
399 return MultiCall
400
401if __name__ == "__main__":
402 # Test
Georg Brandl6634bf22008-05-20 07:13:37 +0000403 root = Tkinter.Tk()
404 text = MultiCallCreator(Tkinter.Text)(root)
Kurt B. Kaiserb1754452005-11-18 22:05:48 +0000405 text.pack()
406 def bindseq(seq, n=[0]):
407 def handler(event):
408 print seq
409 text.bind("<<handler%d>>"%n[0], handler)
410 text.event_add("<<handler%d>>"%n[0], seq)
411 n[0] += 1
412 bindseq("<Key>")
413 bindseq("<Control-Key>")
414 bindseq("<Alt-Key-a>")
415 bindseq("<Control-Key-a>")
416 bindseq("<Alt-Control-Key-a>")
417 bindseq("<Key-b>")
418 bindseq("<Control-Button-1>")
419 bindseq("<Alt-Button-1>")
420 bindseq("<FocusOut>")
421 bindseq("<Enter>")
422 bindseq("<Leave>")
423 root.mainloop()