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