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.
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.
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.
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.
35
from idlelib import macosxSupport
37
# the event type constants, which define the meaning of mc_type
38
MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3;
39
MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7;
40
MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12;
41
MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17;
42
MC_MOUSEWHEEL=18; MC_PROPERTY=19; MC_REPARENT=20; MC_UNMAP=21; MC_VISIBILITY=22;
43
# the modifier state constants, which define the meaning of mc_state
44
MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5
45
MC_OPTION = 1<<6; MC_COMMAND = 1<<7
47
# define the list of modifiers, to be used in complex event types.
48
if macosxSupport.runningAsOSXApp():
49
_modifiers = (("Shift",), ("Control",), ("Option",), ("Command",))
50
_modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND)
52
_modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M"))
53
_modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META)
55
# a dictionary to map a modifier name into its number
56
_modifier_names = dict([(name, number)
57
for number in range(len(_modifiers))
58
for name in _modifiers[number]])
60
# A binder is a class which binds functions to one type of event. It has two
61
# methods: bind and unbind, which get a function and a parsed sequence, as
62
# returned by _parse_sequence(). There are two types of binders:
63
# _SimpleBinder handles event types with no modifiers and no detail.
64
# No Python functions are called when no events are binded.
65
# _ComplexBinder handles event types with modifiers and a detail.
66
# A Python function is called each time an event is generated.
69
def __init__(self, type, widget, widgetinst):
71
self.sequence = '<'+_types[type][0]+'>'
73
self.widgetinst = widgetinst
77
def bind(self, triplet, func):
78
if not self.handlerid:
79
def handler(event, l = self.bindedfuncs, mc_type = self.type):
80
event.mc_type = mc_type
82
for i in range(len(l)-1, -1, -1):
84
if func not in wascalled:
85
wascalled[func] = True
89
self.handlerid = self.widget.bind(self.widgetinst,
90
self.sequence, handler)
91
self.bindedfuncs.append(func)
93
def unbind(self, triplet, func):
94
self.bindedfuncs.remove(func)
95
if not self.bindedfuncs:
96
self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
101
self.widget.unbind(self.widgetinst, self.sequence, self.handlerid)
103
# An int in range(1 << len(_modifiers)) represents a combination of modifiers
104
# (if the least significent bit is on, _modifiers[0] is on, and so on).
105
# _state_subsets gives for each combination of modifiers, or *state*,
106
# a list of the states which are a subset of it. This list is ordered by the
107
# number of modifiers is the state - the most specific state comes first.
108
_states = range(1 << len(_modifiers))
109
_state_names = [''.join(m[0]+'-'
110
for i, m in enumerate(_modifiers)
114
def expand_substates(states):
115
'''For each item of states return a list containing all combinations of
116
that item with individual bits reset, sorted by the number of set bits.
119
"number of bits set in n base 2"
122
n, rem = divmod(n, 2)
127
substates = list(set(state & x for x in states))
128
substates.sort(key=nbits, reverse=True)
129
statelist.append(substates)
132
_state_subsets = expand_substates(_states)
134
# _state_codes gives for each state, the portable code to be passed as mc_state
138
for i in range(len(_modifiers)):
140
r |= _modifier_masks[i]
141
_state_codes.append(r)
143
class _ComplexBinder:
144
# This class binds many functions, and only unbinds them when it is deleted.
145
# self.handlerids is the list of seqs and ids of binded handler functions.
146
# The binded functions sit in a dictionary of lists of lists, which maps
147
# a detail (or None) and a state into a list of functions.
148
# When a new detail is discovered, handlers for all the possible states
151
def __create_handler(self, lists, mc_type, mc_state):
152
def handler(event, lists = lists,
153
mc_type = mc_type, mc_state = mc_state,
154
ishandlerrunning = self.ishandlerrunning,
155
doafterhandler = self.doafterhandler):
156
ishandlerrunning[:] = [True]
157
event.mc_type = mc_type
158
event.mc_state = mc_state
162
for i in range(len(l)-1, -1, -1):
164
if func not in wascalled:
165
wascalled[func] = True
171
ishandlerrunning[:] = []
172
# Call all functions in doafterhandler and remove them from list
173
while doafterhandler:
174
doafterhandler.pop()()
179
def __init__(self, type, widget, widgetinst):
181
self.typename = _types[type][0]
183
self.widgetinst = widgetinst
184
self.bindedfuncs = {None: [[] for s in _states]}
186
# we don't want to change the lists of functions while a handler is
187
# running - it will mess up the loop and anyway, we usually want the
188
# change to happen from the next event. So we have a list of functions
189
# for the handler to run after it finishes calling the binded functions.
190
# It calls them only once.
191
# ishandlerrunning is a list. An empty one means no, otherwise - yes.
192
# this is done so that it would be mutable.
193
self.ishandlerrunning = []
194
self.doafterhandler = []
196
lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]]
197
handler = self.__create_handler(lists, type, _state_codes[s])
198
seq = '<'+_state_names[s]+self.typename+'>'
199
self.handlerids.append((seq, self.widget.bind(self.widgetinst,
202
def bind(self, triplet, func):
203
if triplet[2] not in self.bindedfuncs:
204
self.bindedfuncs[triplet[2]] = [[] for s in _states]
206
lists = [ self.bindedfuncs[detail][i]
207
for detail in (triplet[2], None)
208
for i in _state_subsets[s] ]
209
handler = self.__create_handler(lists, self.type,
211
seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2])
212
self.handlerids.append((seq, self.widget.bind(self.widgetinst,
214
doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func)
215
if not self.ishandlerrunning:
218
self.doafterhandler.append(doit)
220
def unbind(self, triplet, func):
221
doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func)
222
if not self.ishandlerrunning:
225
self.doafterhandler.append(doit)
228
for seq, id in self.handlerids:
229
self.widget.unbind(self.widgetinst, seq, id)
231
# define the list of event types to be handled by MultiEvent. the order is
232
# compatible with the definition of event type constants.
234
("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"),
235
("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",),
236
("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",),
237
("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",),
238
("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",),
242
# which binder should be used for every event type?
243
_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4)
245
# A dictionary to map a type name into its number
246
_type_names = dict([(name, number)
247
for number in range(len(_types))
248
for name in _types[number]])
250
_keysym_re = re.compile(r"^\w+$")
251
_button_re = re.compile(r"^[1-5]$")
252
def _parse_sequence(sequence):
253
"""Get a string which should describe an event sequence. If it is
254
successfully parsed as one, return a tuple containing the state (as an int),
255
the event type (as an index of _types), and the detail - None if none, or a
256
string if there is one. If the parsing is unsuccessful, return None.
258
if not sequence or sequence[0] != '<' or sequence[-1] != '>':
260
words = sequence[1:-1].split('-')
262
while words and words[0] in _modifier_names:
263
modifiers |= 1 << _modifier_names[words[0]]
265
if words and words[0] in _type_names:
266
type = _type_names[words[0]]
270
if _binder_classes[type] is _SimpleBinder:
271
if modifiers or words:
277
if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]:
284
elif len(words) == 1 and type_re.match(words[0]):
289
return modifiers, type, detail
291
def _triplet_to_sequence(triplet):
293
return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \
296
return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>'
299
def MultiCallCreator(widget):
300
"""Return a MultiCall class which inherits its methods from the
301
given widget class (for example, Tkinter.Text). This is used
302
instead of a templating mechanism.
304
if widget in _multicall_dict:
305
return _multicall_dict[widget]
307
class MultiCall (widget):
308
assert issubclass(widget, tkinter.Misc)
310
def __init__(self, *args, **kwargs):
311
widget.__init__(self, *args, **kwargs)
312
# a dictionary which maps a virtual event to a tuple with:
313
# 0. the function binded
314
# 1. a list of triplets - the sequences it is binded to
315
self.__eventinfo = {}
316
self.__binders = [_binder_classes[i](i, widget, self)
317
for i in range(len(_types))]
319
def bind(self, sequence=None, func=None, add=None):
320
#print("bind(%s, %s, %s)" % (sequence, func, add),
321
# file=sys.__stderr__)
322
if type(sequence) is str and len(sequence) > 2 and \
323
sequence[:2] == "<<" and sequence[-2:] == ">>":
324
if sequence in self.__eventinfo:
325
ei = self.__eventinfo[sequence]
326
if ei[0] is not None:
327
for triplet in ei[1]:
328
self.__binders[triplet[1]].unbind(triplet, ei[0])
330
if ei[0] is not None:
331
for triplet in ei[1]:
332
self.__binders[triplet[1]].bind(triplet, func)
334
self.__eventinfo[sequence] = [func, []]
335
return widget.bind(self, sequence, func, add)
337
def unbind(self, sequence, funcid=None):
338
if type(sequence) is str and len(sequence) > 2 and \
339
sequence[:2] == "<<" and sequence[-2:] == ">>" and \
340
sequence in self.__eventinfo:
341
func, triplets = self.__eventinfo[sequence]
343
for triplet in triplets:
344
self.__binders[triplet[1]].unbind(triplet, func)
345
self.__eventinfo[sequence][0] = None
346
return widget.unbind(self, sequence, funcid)
348
def event_add(self, virtual, *sequences):
349
#print("event_add(%s, %s)" % (repr(virtual), repr(sequences)),
350
# file=sys.__stderr__)
351
if virtual not in self.__eventinfo:
352
self.__eventinfo[virtual] = [None, []]
354
func, triplets = self.__eventinfo[virtual]
355
for seq in sequences:
356
triplet = _parse_sequence(seq)
358
#print("Tkinter event_add(%s)" % seq, file=sys.__stderr__)
359
widget.event_add(self, virtual, seq)
362
self.__binders[triplet[1]].bind(triplet, func)
363
triplets.append(triplet)
365
def event_delete(self, virtual, *sequences):
366
if virtual not in self.__eventinfo:
368
func, triplets = self.__eventinfo[virtual]
369
for seq in sequences:
370
triplet = _parse_sequence(seq)
372
#print("Tkinter event_delete: %s" % seq, file=sys.__stderr__)
373
widget.event_delete(self, virtual, seq)
376
self.__binders[triplet[1]].unbind(triplet, func)
377
triplets.remove(triplet)
379
def event_info(self, virtual=None):
380
if virtual is None or virtual not in self.__eventinfo:
381
return widget.event_info(self, virtual)
383
return tuple(map(_triplet_to_sequence,
384
self.__eventinfo[virtual][1])) + \
385
widget.event_info(self, virtual)
388
for virtual in self.__eventinfo:
389
func, triplets = self.__eventinfo[virtual]
391
for triplet in triplets:
392
self.__binders[triplet[1]].unbind(triplet, func)
395
_multicall_dict[widget] = MultiCall
398
if __name__ == "__main__":
401
text = MultiCallCreator(tkinter.Text)(root)
403
def bindseq(seq, n=[0]):
406
text.bind("<<handler%d>>"%n[0], handler)
407
text.event_add("<<handler%d>>"%n[0], seq)
410
bindseq("<Control-Key>")
411
bindseq("<Alt-Key-a>")
412
bindseq("<Control-Key-a>")
413
bindseq("<Alt-Control-Key-a>")
415
bindseq("<Control-Button-1>")
416
bindseq("<Alt-Button-1>")
417
bindseq("<FocusOut>")