1# ---------------------------------------------------------------------------- 2# pyglet 3# Copyright (c) 2006-2008 Alex Holkner 4# Copyright (c) 2008-2021 pyglet contributors 5# All rights reserved. 6# 7# Redistribution and use in source and binary forms, with or without 8# modification, are permitted provided that the following conditions 9# are met: 10# 11# * Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# * Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in 15# the documentation and/or other materials provided with the 16# distribution. 17# * Neither the name of pyglet nor the names of its 18# contributors may be used to endorse or promote products 19# derived from this software without specific prior written 20# permission. 21# 22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33# POSSIBILITY OF SUCH DAMAGE. 34# ---------------------------------------------------------------------------- 35 36"""Event dispatch framework. 37 38All objects that produce events in pyglet implement :py:class:`~pyglet.event.EventDispatcher`, 39providing a consistent interface for registering and manipulating event 40handlers. A commonly used event dispatcher is `pyglet.window.Window`. 41 42Event types 43=========== 44 45For each event dispatcher there is a set of events that it dispatches; these 46correspond with the type of event handlers you can attach. Event types are 47identified by their name, for example, ''on_resize''. If you are creating a 48new class which implements :py:class:`~pyglet.event.EventDispatcher`, you must call 49`EventDispatcher.register_event_type` for each event type. 50 51Attaching event handlers 52======================== 53 54An event handler is simply a function or method. You can attach an event 55handler by setting the appropriate function on the instance:: 56 57 def on_resize(width, height): 58 # ... 59 dispatcher.on_resize = on_resize 60 61There is also a convenience decorator that reduces typing:: 62 63 @dispatcher.event 64 def on_resize(width, height): 65 # ... 66 67You may prefer to subclass and override the event handlers instead:: 68 69 class MyDispatcher(DispatcherClass): 70 def on_resize(self, width, height): 71 # ... 72 73Event handler stack 74=================== 75 76When attaching an event handler to a dispatcher using the above methods, it 77replaces any existing handler (causing the original handler to no longer be 78called). Each dispatcher maintains a stack of event handlers, allowing you to 79insert an event handler "above" the existing one rather than replacing it. 80 81There are two main use cases for "pushing" event handlers: 82 83* Temporarily intercepting the events coming from the dispatcher by pushing a 84 custom set of handlers onto the dispatcher, then later "popping" them all 85 off at once. 86* Creating "chains" of event handlers, where the event propagates from the 87 top-most (most recently added) handler to the bottom, until a handler 88 takes care of it. 89 90Use `EventDispatcher.push_handlers` to create a new level in the stack and 91attach handlers to it. You can push several handlers at once:: 92 93 dispatcher.push_handlers(on_resize, on_key_press) 94 95If your function handlers have different names to the events they handle, use 96keyword arguments:: 97 98 dispatcher.push_handlers(on_resize=my_resize, on_key_press=my_key_press) 99 100After an event handler has processed an event, it is passed on to the 101next-lowest event handler, unless the handler returns `EVENT_HANDLED`, which 102prevents further propagation. 103 104To remove all handlers on the top stack level, use 105`EventDispatcher.pop_handlers`. 106 107Note that any handlers pushed onto the stack have precedence over the 108handlers set directly on the instance (for example, using the methods 109described in the previous section), regardless of when they were set. 110For example, handler ``foo`` is called before handler ``bar`` in the following 111example:: 112 113 dispatcher.push_handlers(on_resize=foo) 114 dispatcher.on_resize = bar 115 116Dispatching events 117================== 118 119pyglet uses a single-threaded model for all application code. Event 120handlers are only ever invoked as a result of calling 121EventDispatcher.dispatch_events`. 122 123It is up to the specific event dispatcher to queue relevant events until they 124can be dispatched, at which point the handlers are called in the order the 125events were originally generated. 126 127This implies that your application runs with a main loop that continuously 128updates the application state and checks for new events:: 129 130 while True: 131 dispatcher.dispatch_events() 132 # ... additional per-frame processing 133 134Not all event dispatchers require the call to ``dispatch_events``; check with 135the particular class documentation. 136 137.. note:: 138 139 In order to prevent issues with garbage collection, the 140 :py:class:`~pyglet.event.EventDispatcher` class only holds weak 141 references to pushed event handlers. That means the following example 142 will not work, because the pushed object will fall out of scope and be 143 collected:: 144 145 dispatcher.push_handlers(MyHandlerClass()) 146 147 Instead, you must make sure to keep a reference to the object before pushing 148 it. For example:: 149 150 my_handler_instance = MyHandlerClass() 151 dispatcher.push_handlers(my_handler_instance) 152 153""" 154 155import inspect 156 157from functools import partial 158from weakref import WeakMethod 159 160EVENT_HANDLED = True 161EVENT_UNHANDLED = None 162 163 164class EventException(Exception): 165 """An exception raised when an event handler could not be attached. 166 """ 167 pass 168 169 170class EventDispatcher: 171 """Generic event dispatcher interface. 172 173 See the module docstring for usage. 174 """ 175 # Placeholder empty stack; real stack is created only if needed 176 _event_stack = () 177 178 @classmethod 179 def register_event_type(cls, name): 180 """Register an event type with the dispatcher. 181 182 Registering event types allows the dispatcher to validate event 183 handler names as they are attached, and to search attached objects for 184 suitable handlers. 185 186 :Parameters: 187 `name` : str 188 Name of the event to register. 189 190 """ 191 if not hasattr(cls, 'event_types'): 192 cls.event_types = [] 193 cls.event_types.append(name) 194 return name 195 196 def push_handlers(self, *args, **kwargs): 197 """Push a level onto the top of the handler stack, then attach zero or 198 more event handlers. 199 200 If keyword arguments are given, they name the event type to attach. 201 Otherwise, a callable's `__name__` attribute will be used. Any other 202 object may also be specified, in which case it will be searched for 203 callables with event names. 204 """ 205 # Create event stack if necessary 206 if type(self._event_stack) is tuple: 207 self._event_stack = [] 208 209 # Place dict full of new handlers at beginning of stack 210 self._event_stack.insert(0, {}) 211 self.set_handlers(*args, **kwargs) 212 213 def _get_handlers(self, args, kwargs): 214 """Implement handler matching on arguments for set_handlers and 215 remove_handlers. 216 """ 217 for obj in args: 218 if inspect.isroutine(obj): 219 # Single magically named function 220 name = obj.__name__ 221 if name not in self.event_types: 222 raise EventException('Unknown event "%s"' % name) 223 if inspect.ismethod(obj): 224 yield name, WeakMethod(obj, partial(self._remove_handler, name)) 225 else: 226 yield name, obj 227 else: 228 # Single instance with magically named methods 229 for name in dir(obj): 230 if name in self.event_types: 231 meth = getattr(obj, name) 232 yield name, WeakMethod(meth, partial(self._remove_handler, name)) 233 234 for name, handler in kwargs.items(): 235 # Function for handling given event (no magic) 236 if name not in self.event_types: 237 raise EventException('Unknown event "%s"' % name) 238 if inspect.ismethod(handler): 239 yield name, WeakMethod(handler, partial(self._remove_handler, name)) 240 else: 241 yield name, handler 242 243 def set_handlers(self, *args, **kwargs): 244 """Attach one or more event handlers to the top level of the handler 245 stack. 246 247 See :py:meth:`~pyglet.event.EventDispatcher.push_handlers` for the accepted argument types. 248 """ 249 # Create event stack if necessary 250 if type(self._event_stack) is tuple: 251 self._event_stack = [{}] 252 253 for name, handler in self._get_handlers(args, kwargs): 254 self.set_handler(name, handler) 255 256 def set_handler(self, name, handler): 257 """Attach a single event handler. 258 259 :Parameters: 260 `name` : str 261 Name of the event type to attach to. 262 `handler` : callable 263 Event handler to attach. 264 265 """ 266 # Create event stack if necessary 267 if type(self._event_stack) is tuple: 268 self._event_stack = [{}] 269 270 self._event_stack[0][name] = handler 271 272 def pop_handlers(self): 273 """Pop the top level of event handlers off the stack. 274 """ 275 assert self._event_stack and 'No handlers pushed' 276 277 del self._event_stack[0] 278 279 def remove_handlers(self, *args, **kwargs): 280 """Remove event handlers from the event stack. 281 282 See :py:meth:`~pyglet.event.EventDispatcher.push_handlers` for the 283 accepted argument types. All handlers are removed from the first stack 284 frame that contains any of the given handlers. No error is raised if 285 any handler does not appear in that frame, or if no stack frame 286 contains any of the given handlers. 287 288 If the stack frame is empty after removing the handlers, it is 289 removed from the stack. Note that this interferes with the expected 290 symmetry of :py:meth:`~pyglet.event.EventDispatcher.push_handlers` and 291 :py:meth:`~pyglet.event.EventDispatcher.pop_handlers`. 292 """ 293 handlers = list(self._get_handlers(args, kwargs)) 294 295 # Find the first stack frame containing any of the handlers 296 def find_frame(): 297 for frame in self._event_stack: 298 for name, handler in handlers: 299 try: 300 if frame[name] == handler: 301 return frame 302 except KeyError: 303 pass 304 305 frame = find_frame() 306 307 # No frame matched; no error. 308 if not frame: 309 return 310 311 # Remove each handler from the frame. 312 for name, handler in handlers: 313 try: 314 if frame[name] == handler: 315 del frame[name] 316 except KeyError: 317 pass 318 319 # Remove the frame if it's empty. 320 if not frame: 321 self._event_stack.remove(frame) 322 323 def remove_handler(self, name, handler): 324 """Remove a single event handler. 325 326 The given event handler is removed from the first handler stack frame 327 it appears in. The handler must be the exact same callable as passed 328 to `set_handler`, `set_handlers` or 329 :py:meth:`~pyglet.event.EventDispatcher.push_handlers`; and the name 330 must match the event type it is bound to. 331 332 No error is raised if the event handler is not set. 333 334 :Parameters: 335 `name` : str 336 Name of the event type to remove. 337 `handler` : callable 338 Event handler to remove. 339 """ 340 for frame in self._event_stack: 341 try: 342 if frame[name] == handler: 343 del frame[name] 344 break 345 except KeyError: 346 pass 347 348 def _remove_handler(self, name, handler): 349 """Used internally to remove all handler instances for the given event name. 350 351 This is normally called from a dead ``WeakMethod`` to remove itself from the 352 event stack. 353 """ 354 # Iterate over a copy as we might mutate the list 355 for frame in list(self._event_stack): 356 if name in frame and frame[name] == handler: 357 del frame[name] 358 if not frame: 359 self._event_stack.remove(frame) 360 361 def dispatch_event(self, event_type, *args): 362 """Dispatch a single event to the attached handlers. 363 364 The event is propagated to all handlers from from the top of the stack 365 until one returns `EVENT_HANDLED`. This method should be used only by 366 :py:class:`~pyglet.event.EventDispatcher` implementors; applications should call 367 the ``dispatch_events`` method. 368 369 Since pyglet 1.2, the method returns `EVENT_HANDLED` if an event 370 handler returned `EVENT_HANDLED` or `EVENT_UNHANDLED` if all events 371 returned `EVENT_UNHANDLED`. If no matching event handlers are in the 372 stack, ``False`` is returned. 373 374 :Parameters: 375 `event_type` : str 376 Name of the event. 377 `args` : sequence 378 Arguments to pass to the event handler. 379 380 :rtype: bool or None 381 :return: (Since pyglet 1.2) `EVENT_HANDLED` if an event handler 382 returned `EVENT_HANDLED`; `EVENT_UNHANDLED` if one or more event 383 handlers were invoked but returned only `EVENT_UNHANDLED`; 384 otherwise ``False``. In pyglet 1.1 and earlier, the return value 385 is always ``None``. 386 387 """ 388 assert hasattr(self, 'event_types'), ( 389 "No events registered on this EventDispatcher. " 390 "You need to register events with the class method " 391 "EventDispatcher.register_event_type('event_name')." 392 ) 393 assert event_type in self.event_types, \ 394 "%r not found in %r.event_types == %r" % (event_type, self, self.event_types) 395 396 invoked = False 397 398 # Search handler stack for matching event handlers 399 for frame in list(self._event_stack): 400 handler = frame.get(event_type, None) 401 if not handler: 402 continue 403 if isinstance(handler, WeakMethod): 404 handler = handler() 405 assert handler is not None 406 try: 407 invoked = True 408 if handler(*args): 409 return EVENT_HANDLED 410 except TypeError as exception: 411 self._raise_dispatch_exception(event_type, args, handler, exception) 412 413 # Check instance for an event handler 414 try: 415 if getattr(self, event_type)(*args): 416 return EVENT_HANDLED 417 except AttributeError as e: 418 event_op = getattr(self, event_type, None) 419 if callable(event_op): 420 raise e 421 except TypeError as exception: 422 self._raise_dispatch_exception(event_type, args, getattr(self, event_type), exception) 423 else: 424 invoked = True 425 426 if invoked: 427 return EVENT_UNHANDLED 428 429 return False 430 431 @staticmethod 432 def _raise_dispatch_exception(event_type, args, handler, exception): 433 # A common problem in applications is having the wrong number of 434 # arguments in an event handler. This is caught as a TypeError in 435 # dispatch_event but the error message is obfuscated. 436 # 437 # Here we check if there is indeed a mismatch in argument count, 438 # and construct a more useful exception message if so. If this method 439 # doesn't find a problem with the number of arguments, the error 440 # is re-raised as if we weren't here. 441 442 n_args = len(args) 443 444 # Inspect the handler 445 argspecs = inspect.getfullargspec(handler) 446 handler_args = argspecs.args 447 handler_varargs = argspecs.varargs 448 handler_defaults = argspecs.defaults 449 450 n_handler_args = len(handler_args) 451 452 # Remove "self" arg from handler if it's a bound method 453 if inspect.ismethod(handler) and handler.__self__: 454 n_handler_args -= 1 455 456 # Allow *args varargs to overspecify arguments 457 if handler_varargs: 458 n_handler_args = max(n_handler_args, n_args) 459 460 # Allow default values to overspecify arguments 461 if n_handler_args > n_args >= n_handler_args - len(handler_defaults) and handler_defaults: 462 n_handler_args = n_args 463 464 if n_handler_args != n_args: 465 if inspect.isfunction(handler) or inspect.ismethod(handler): 466 descr = f"'{handler.__name__}' at {handler.__code__.co_filename}:{handler.__code__.co_firstlineno}" 467 else: 468 descr = repr(handler) 469 470 raise TypeError(f"The '{event_type}' event was dispatched with {len(args)} arguments,\n" 471 f"but your handler {descr} accepts only {n_handler_args} arguments.") 472 473 else: 474 raise exception 475 476 def event(self, *args): 477 """Function decorator for an event handler. 478 479 Usage:: 480 481 win = window.Window() 482 483 @win.event 484 def on_resize(self, width, height): 485 # ... 486 487 or:: 488 489 @win.event('on_resize') 490 def foo(self, width, height): 491 # ... 492 493 """ 494 if len(args) == 0: # @window.event() 495 def decorator(func): 496 func_name = func.__name__ 497 self.set_handler(func_name, func) 498 return func 499 500 return decorator 501 elif inspect.isroutine(args[0]): # @window.event 502 func = args[0] 503 name = func.__name__ 504 self.set_handler(name, func) 505 return args[0] 506 elif isinstance(args[0], str): # @window.event('on_resize') 507 name = args[0] 508 509 def decorator(func): 510 self.set_handler(name, func) 511 return func 512 513 return decorator 514