root/pyxcb/pyxcb/eventsystem.py

Revision db8c5e103011783b6d0173f0df1163149c2d59a8, 16.2 KB (checked in by Friedrich Weber <fred@…>, 20 months ago)

Moved xcb to an own package pyxcb.

  • pyxcb: less dependency cycles
  • sx-wm, sx-event: use pyxcb
  • Property mode set to 100644
Line 
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
37All objects that produce events in pyglet implement `EventDispatcher`,
38providing a consistent interface for registering and manipulating event
39handlers.  A commonly used event dispatcher is `pyglet.window.Window`.
40
41Event types
42===========
43
44For each event dispatcher there is a set of events that it dispatches; these
45correspond with the type of event handlers you can attach.  Event types are
46identified by their name, for example, ''on_resize''.  If you are creating a
47new class which implements `EventDispatcher`, you must call
48`EventDispatcher.register_event_type` for each event type.
49
50Attaching event handlers
51========================
52
53An event handler is simply a function or method.  You can attach an event
54handler by setting the appropriate function on the instance::
55
56    def on_resize(width, height):
57        # ...
58    dispatcher.on_resize = on_resize
59
60There is also a convenience decorator that reduces typing::
61
62    @dispatcher.event
63    def on_resize(width, height):
64        # ...
65
66You may prefer to subclass and override the event handlers instead::
67
68    class MyDispatcher(DispatcherClass):
69        def on_resize(self, width, height):
70            # ...
71
72Event handler stack
73===================
74
75When attaching an event handler to a dispatcher using the above methods, it
76replaces any existing handler (causing the original handler to no longer be
77called).  Each dispatcher maintains a stack of event handlers, allowing you to
78insert an event handler "above" the existing one rather than replacing it.
79
80There 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
89Use `EventDispatcher.push_handlers` to create a new level in the stack and
90attach handlers to it.  You can push several handlers at once::
91
92    dispatcher.push_handlers(on_resize, on_key_press)
93
94If your function handlers have different names to the events they handle, use
95keyword arguments::
96
97    dispatcher.push_handlers(on_resize=my_resize,
98                             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 propogation.
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 continously
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'''
138
139__docformat__ = 'restructuredtext'
140__version__ = '$Id$'
141
142import inspect
143
144EVENT_HANDLED = True 
145EVENT_UNHANDLED = None
146
147class EventException(Exception):
148    '''An exception raised when an event handler could not be attached.
149    '''
150    pass
151
152class 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
Note: See TracBrowser for help on using the browser.