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