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""" 31import re 32import sys 33 34import tkinter 35 36# the event type constants, which define the meaning of mc_type 37MC_KEYPRESS=0; MC_KEYRELEASE=1; MC_BUTTONPRESS=2; MC_BUTTONRELEASE=3; 38MC_ACTIVATE=4; MC_CIRCULATE=5; MC_COLORMAP=6; MC_CONFIGURE=7; 39MC_DEACTIVATE=8; MC_DESTROY=9; MC_ENTER=10; MC_EXPOSE=11; MC_FOCUSIN=12; 40MC_FOCUSOUT=13; MC_GRAVITY=14; MC_LEAVE=15; MC_MAP=16; MC_MOTION=17; 41MC_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 43MC_SHIFT = 1<<0; MC_CONTROL = 1<<2; MC_ALT = 1<<3; MC_META = 1<<5 44MC_OPTION = 1<<6; MC_COMMAND = 1<<7 45 46# define the list of modifiers, to be used in complex event types. 47if sys.platform == "darwin": 48 _modifiers = (("Shift",), ("Control",), ("Option",), ("Command",)) 49 _modifier_masks = (MC_SHIFT, MC_CONTROL, MC_OPTION, MC_COMMAND) 50else: 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 63APPLICATION_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 73class _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 124def 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 = [] 146for 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 153class _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]$") 267def _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 306def _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 = {} 314def 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 417def _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 443if __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) 449