1""" 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 33import string 34import re 35import Tkinter 36 37# the event type constants, which define the meaning of mc_type 38MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3; 39MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7; 40MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12; 41MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17; 42MC_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 44MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5 45MC_OPTION = 1<<6; MC_COMMAND = 1<<7 46 47# define the list of modifiers, to be used in complex event types. 48if sys.platform == "darwin": 49 _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",)) 50 _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND) 51else: 52 _modifiers = (("Control",), ("Alt",), ("Shift",), ("Meta", "M")) 53 _modifier_masks = (MC_CONTROL, MC_ALT, MC_SHIFT, MC_META) 54 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]]) 59 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. 67 68class _SimpleBinder: 69 def __init__(self, type, widget, widgetinst): 70 self.type = type 71 self.sequence = '<'+_types[type][0]+'>' 72 self.widget = widget 73 self.widgetinst = widgetinst 74 self.bindedfuncs = [] 75 self.handlerid = None 76 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 81 wascalled = {} 82 for i in range(len(l)-1, -1, -1): 83 func = l[i] 84 if func not in wascalled: 85 wascalled[func] = True 86 r = func(event) 87 if r: 88 return r 89 self.handlerid = self.widget.bind(self.widgetinst, 90 self.sequence, handler) 91 self.bindedfuncs.append(func) 92 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) 97 self.handlerid = None 98 99 def __del__(self): 100 if self.handlerid: 101 self.widget.unbind(self.widgetinst, self.sequence, self.handlerid) 102 103# An int in range(1 << len(_modifiers)) represents a combination of modifiers 104# (if the least significant 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) 111 if (1 << i) & s) 112 for s in _states] 113 114def 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. 117 ''' 118 def nbits(n): 119 "number of bits set in n base 2" 120 nb = 0 121 while n: 122 n, rem = divmod(n, 2) 123 nb += rem 124 return nb 125 statelist = [] 126 for state in states: 127 substates = list(set(state & x for x in states)) 128 substates.sort(key=nbits, reverse=True) 129 statelist.append(substates) 130 return statelist 131 132_state_subsets = expand_substates(_states) 133 134# _state_codes gives for each state, the portable code to be passed as mc_state 135_state_codes = [] 136for s in _states: 137 r = 0 138 for i in range(len(_modifiers)): 139 if (1 << i) & s: 140 r |= _modifier_masks[i] 141 _state_codes.append(r) 142 143class _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 149 # are binded. 150 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 159 wascalled = {} 160 r = None 161 for l in lists: 162 for i in range(len(l)-1, -1, -1): 163 func = l[i] 164 if func not in wascalled: 165 wascalled[func] = True 166 r = l[i](event) 167 if r: 168 break 169 if r: 170 break 171 ishandlerrunning[:] = [] 172 # Call all functions in doafterhandler and remove them from list 173 for f in doafterhandler: 174 f() 175 doafterhandler[:] = [] 176 if r: 177 return r 178 return handler 179 180 def __init__(self, type, widget, widgetinst): 181 self.type = type 182 self.typename = _types[type][0] 183 self.widget = widget 184 self.widgetinst = widgetinst 185 self.bindedfuncs = {None: [[] for s in _states]} 186 self.handlerids = [] 187 # we don't want to change the lists of functions while a handler is 188 # running - it will mess up the loop and anyway, we usually want the 189 # change to happen from the next event. So we have a list of functions 190 # for the handler to run after it finishes calling the binded functions. 191 # It calls them only once. 192 # ishandlerrunning is a list. An empty one means no, otherwise - yes. 193 # this is done so that it would be mutable. 194 self.ishandlerrunning = [] 195 self.doafterhandler = [] 196 for s in _states: 197 lists = [self.bindedfuncs[None][i] for i in _state_subsets[s]] 198 handler = self.__create_handler(lists, type, _state_codes[s]) 199 seq = '<'+_state_names[s]+self.typename+'>' 200 self.handlerids.append((seq, self.widget.bind(self.widgetinst, 201 seq, handler))) 202 203 def bind(self, triplet, func): 204 if triplet[2] not in self.bindedfuncs: 205 self.bindedfuncs[triplet[2]] = [[] for s in _states] 206 for s in _states: 207 lists = [ self.bindedfuncs[detail][i] 208 for detail in (triplet[2], None) 209 for i in _state_subsets[s] ] 210 handler = self.__create_handler(lists, self.type, 211 _state_codes[s]) 212 seq = "<%s%s-%s>"% (_state_names[s], self.typename, triplet[2]) 213 self.handlerids.append((seq, self.widget.bind(self.widgetinst, 214 seq, handler))) 215 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].append(func) 216 if not self.ishandlerrunning: 217 doit() 218 else: 219 self.doafterhandler.append(doit) 220 221 def unbind(self, triplet, func): 222 doit = lambda: self.bindedfuncs[triplet[2]][triplet[0]].remove(func) 223 if not self.ishandlerrunning: 224 doit() 225 else: 226 self.doafterhandler.append(doit) 227 228 def __del__(self): 229 for seq, id in self.handlerids: 230 self.widget.unbind(self.widgetinst, seq, id) 231 232# define the list of event types to be handled by MultiEvent. the order is 233# compatible with the definition of event type constants. 234_types = ( 235 ("KeyPress", "Key"), ("KeyRelease",), ("ButtonPress", "Button"), 236 ("ButtonRelease",), ("Activate",), ("Circulate",), ("Colormap",), 237 ("Configure",), ("Deactivate",), ("Destroy",), ("Enter",), ("Expose",), 238 ("FocusIn",), ("FocusOut",), ("Gravity",), ("Leave",), ("Map",), 239 ("Motion",), ("MouseWheel",), ("Property",), ("Reparent",), ("Unmap",), 240 ("Visibility",), 241) 242 243# which binder should be used for every event type? 244_binder_classes = (_ComplexBinder,) * 4 + (_SimpleBinder,) * (len(_types)-4) 245 246# A dictionary to map a type name into its number 247_type_names = dict([(name, number) 248 for number in range(len(_types)) 249 for name in _types[number]]) 250 251_keysym_re = re.compile(r"^\w+$") 252_button_re = re.compile(r"^[1-5]$") 253def _parse_sequence(sequence): 254 """Get a string which should describe an event sequence. If it is 255 successfully parsed as one, return a tuple containing the state (as an int), 256 the event type (as an index of _types), and the detail - None if none, or a 257 string if there is one. If the parsing is unsuccessful, return None. 258 """ 259 if not sequence or sequence[0] != '<' or sequence[-1] != '>': 260 return None 261 words = string.split(sequence[1:-1], '-') 262 263 modifiers = 0 264 while words and words[0] in _modifier_names: 265 modifiers |= 1 << _modifier_names[words[0]] 266 del words[0] 267 268 if words and words[0] in _type_names: 269 type = _type_names[words[0]] 270 del words[0] 271 else: 272 return None 273 274 if _binder_classes[type] is _SimpleBinder: 275 if modifiers or words: 276 return None 277 else: 278 detail = None 279 else: 280 # _ComplexBinder 281 if type in [_type_names[s] for s in ("KeyPress", "KeyRelease")]: 282 type_re = _keysym_re 283 else: 284 type_re = _button_re 285 286 if not words: 287 detail = None 288 elif len(words) == 1 and type_re.match(words[0]): 289 detail = words[0] 290 else: 291 return None 292 293 return modifiers, type, detail 294 295def _triplet_to_sequence(triplet): 296 if triplet[2]: 297 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'-'+ \ 298 triplet[2]+'>' 299 else: 300 return '<'+_state_names[triplet[0]]+_types[triplet[1]][0]+'>' 301 302_multicall_dict = {} 303def MultiCallCreator(widget): 304 """Return a MultiCall class which inherits its methods from the 305 given widget class (for example, Tkinter.Text). This is used 306 instead of a templating mechanism. 307 """ 308 if widget in _multicall_dict: 309 return _multicall_dict[widget] 310 311 class MultiCall (widget): 312 assert issubclass(widget, Tkinter.Misc) 313 314 def __init__(self, *args, **kwargs): 315 widget.__init__(self, *args, **kwargs) 316 # a dictionary which maps a virtual event to a tuple with: 317 # 0. the function binded 318 # 1. a list of triplets - the sequences it is binded to 319 self.__eventinfo = {} 320 self.__binders = [_binder_classes[i](i, widget, self) 321 for i in range(len(_types))] 322 323 def bind(self, sequence=None, func=None, add=None): 324 #print "bind(%s, %s, %s) called." % (sequence, func, add) 325 if type(sequence) is str and len(sequence) > 2 and \ 326 sequence[:2] == "<<" and sequence[-2:] == ">>": 327 if sequence in self.__eventinfo: 328 ei = self.__eventinfo[sequence] 329 if ei[0] is not None: 330 for triplet in ei[1]: 331 self.__binders[triplet[1]].unbind(triplet, ei[0]) 332 ei[0] = func 333 if ei[0] is not None: 334 for triplet in ei[1]: 335 self.__binders[triplet[1]].bind(triplet, func) 336 else: 337 self.__eventinfo[sequence] = [func, []] 338 return widget.bind(self, sequence, func, add) 339 340 def unbind(self, sequence, funcid=None): 341 if type(sequence) is str and len(sequence) > 2 and \ 342 sequence[:2] == "<<" and sequence[-2:] == ">>" and \ 343 sequence in self.__eventinfo: 344 func, triplets = self.__eventinfo[sequence] 345 if func is not None: 346 for triplet in triplets: 347 self.__binders[triplet[1]].unbind(triplet, func) 348 self.__eventinfo[sequence][0] = None 349 return widget.unbind(self, sequence, funcid) 350 351 def event_add(self, virtual, *sequences): 352 #print "event_add(%s,%s) was called"%(repr(virtual),repr(sequences)) 353 if virtual not in self.__eventinfo: 354 self.__eventinfo[virtual] = [None, []] 355 356 func, triplets = self.__eventinfo[virtual] 357 for seq in sequences: 358 triplet = _parse_sequence(seq) 359 if triplet is None: 360 #print >> sys.stderr, "Seq. %s was added by Tkinter."%seq 361 widget.event_add(self, virtual, seq) 362 else: 363 if func is not None: 364 self.__binders[triplet[1]].bind(triplet, func) 365 triplets.append(triplet) 366 367 def event_delete(self, virtual, *sequences): 368 if virtual not in self.__eventinfo: 369 return 370 func, triplets = self.__eventinfo[virtual] 371 for seq in sequences: 372 triplet = _parse_sequence(seq) 373 if triplet is None: 374 #print >> sys.stderr, "Seq. %s was deleted by Tkinter."%seq 375 widget.event_delete(self, virtual, seq) 376 else: 377 if func is not None: 378 self.__binders[triplet[1]].unbind(triplet, func) 379 triplets.remove(triplet) 380 381 def event_info(self, virtual=None): 382 if virtual is None or virtual not in self.__eventinfo: 383 return widget.event_info(self, virtual) 384 else: 385 return tuple(map(_triplet_to_sequence, 386 self.__eventinfo[virtual][1])) + \ 387 widget.event_info(self, virtual) 388 389 def __del__(self): 390 for virtual in self.__eventinfo: 391 func, triplets = self.__eventinfo[virtual] 392 if func: 393 for triplet in triplets: 394 self.__binders[triplet[1]].unbind(triplet, func) 395 396 397 _multicall_dict[widget] = MultiCall 398 return MultiCall 399 400 401def _multi_call(parent): 402 root = Tkinter.Tk() 403 root.title("Test MultiCall") 404 width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) 405 root.geometry("+%d+%d"%(x, y + 150)) 406 text = MultiCallCreator(Tkinter.Text)(root) 407 text.pack() 408 def bindseq(seq, n=[0]): 409 def handler(event): 410 print seq 411 text.bind("<<handler%d>>"%n[0], handler) 412 text.event_add("<<handler%d>>"%n[0], seq) 413 n[0] += 1 414 bindseq("<Key>") 415 bindseq("<Control-Key>") 416 bindseq("<Alt-Key-a>") 417 bindseq("<Control-Key-a>") 418 bindseq("<Alt-Control-Key-a>") 419 bindseq("<Key-b>") 420 bindseq("<Control-Button-1>") 421 bindseq("<Button-2>") 422 bindseq("<Alt-Button-1>") 423 bindseq("<FocusOut>") 424 bindseq("<Enter>") 425 bindseq("<Leave>") 426 root.mainloop() 427 428if __name__ == "__main__": 429 from idlelib.idle_test.htest import run 430 run(_multi_call) 431