tuikit/events.py
changeset 45 43b2279b06e1
parent 43 369c8ef5070a
child 46 2b43a7f38c34
equal deleted inserted replaced
44:d77f1ae3786c 45:43b2279b06e1
     7 This module implements universal mechanism for event emitting and handling.
     7 This module implements universal mechanism for event emitting and handling.
     8 
     8 
     9 """
     9 """
    10 
    10 
    11 import logging
    11 import logging
       
    12 import inspect
    12 
    13 
    13 
    14 
    14 class Event:
    15 class Event:
    15     def __init__(self):
    16     def __init__(self):
    16         self.originator = None
    17         self.originator = None
    86 class Emitter:
    87 class Emitter:
    87 
    88 
    88     """Event emitter mixin class."""
    89     """Event emitter mixin class."""
    89 
    90 
    90     def add_events(self, *events):
    91     def add_events(self, *events):
    91         """Add events which may be registered by user.
    92         """Add events which may be emitted on instances of this class.
    92 
    93 
    93         This should be called only by subclasses.
    94         This should be called only by subclasses.
    94         This serves also as initializer, other methods of Emitter
    95         It also serves as initializer, other methods of Emitter
    95         will not work if add_events was not called.
    96         will not work if add_events was not called.
    96 
    97 
    97         *events -- Arguments must be given in pairs.
    98         *events -- Arguments must be given in pairs.
    98 
    99 
    99         Each pair consists of event_name, event_class:
   100         Each pair consists of event_name, event_class:
   100             event_name -- a string used in connect(), emit()
   101             event_name -- a string used in add_handler(), emit() etc.
   101             event_class -- class of event payload
   102             event_class -- class of event payload
   102 
   103 
   103         """
   104         It will also inspect base classes of instance on which add_events()
       
   105         is called, adding all methods meeting naming convention to handler list.
       
   106         The convention for method names is "on_event_name", "after_event_name".
       
   107         Prefix on/after determines whether the handler is added as first or last.
       
   108 
       
   109         """
       
   110         if len(events) % 2:
       
   111             raise ValueError('add_events(): Event names and classes must be passed in pairs.')
   104         if not hasattr(self, '_event_handlers'):
   112         if not hasattr(self, '_event_handlers'):
   105             self._event_handlers = dict()
   113             self._event_handlers = dict()
   106             self._event_class = dict()
   114             self._event_class = dict()
   107         for event_name, event_class in zip(events[::2], events[1::2]):
   115         for event_name, event_class in zip(events[::2], events[1::2]):
       
   116             if not isinstance(event_name, str):
       
   117                 raise TypeError('add_events(): Event name must be a string: %r.' % event_name)
       
   118             if not issubclass(event_class, Event):
       
   119                 raise TypeError('add_events(): Class %s does not inherit from Event.' % event_class)
   108             self._event_handlers[event_name] = []
   120             self._event_handlers[event_name] = []
   109             self._event_class[event_name] = event_class
   121             self._event_class[event_name] = event_class
   110             # add default dummy handler if no handler exists for this event
   122             self._add_default_handlers(event_name)
   111             handler_name = '_handle_' + event_name
   123 
   112             if not hasattr(Emitter, handler_name):
   124     def add_handler(self, event_name, handler, last=False):
   113                 setattr(Emitter, handler_name, lambda self, ev: False)
   125         """Add handler to event name.
   114 
   126 
   115     def connect(self, event_name, handler):
   127         last=False -- Add handler as first in handler list.
   116         """Connect event handler to event name.
   128         last=True -- Add handler to the end of handler list.
   117 
   129 
   118         Add handler to the end of handler list.
   130         """
   119 
   131         if event_name in self._event_handlers:
   120         """
   132             if last:
   121         if event_name in self._event_handlers:
   133                 self._event_handlers[event_name].append(handler)
   122             self._event_handlers[event_name].append(handler)
       
   123         else:
       
   124             raise KeyError('Unknown event: %s', event_name)
       
   125 
       
   126     def disconnect(self, event_name, handler=None):
       
   127         """Remove event handler from the list.
       
   128 
       
   129         If no handler is given, remove all handlers.
       
   130 
       
   131         """
       
   132         if event_name in self._event_handlers:
       
   133             if handler:
       
   134                 self._event_handlers[event_name].remove(handler)
       
   135             else:
   134             else:
   136                 self._event_handlers[event_name][:] = []
   135                 self._event_handlers[event_name].insert(0, handler)
   137         else:
   136         else:
   138             raise KeyError('Unknown event: %s', event_name)
   137             raise KeyError('Unknown event: %s', event_name)
   139 
   138 
   140     def is_connected(self, event_name):
   139     def remove_handler(self, event_name, handler):
   141         """Test if any handlers are connected to event name.
   140         """Remove event handler from the list."""
       
   141         if event_name in self._event_handlers:
       
   142             self._event_handlers[event_name].remove(handler)
       
   143         else:
       
   144             raise KeyError('Unknown event: %s', event_name)
       
   145 
       
   146     def remove_all_handlers(self, event_name):
       
   147         """Remove all handlers for event."""
       
   148         if event_name in self._event_handlers:
       
   149             self._event_handlers[event_name][:] = []
       
   150         else:
       
   151             raise KeyError('Unknown event: %s', event_name)
       
   152 
       
   153     def has_handlers(self, event_name):
       
   154         """Test if any handlers are attached to event name.
   142 
   155 
   143         Return True if event handler list is not empty,
   156         Return True if event handler list is not empty,
   144         False otherwise.
   157         False otherwise.
   145 
   158 
   146         """
   159         """
   151 
   164 
   152     def emit(self, event_name, *args, **kwargs):
   165     def emit(self, event_name, *args, **kwargs):
   153         """Emit the event.
   166         """Emit the event.
   154 
   167 
   155         Call all handlers from event's handler list,
   168         Call all handlers from event's handler list,
   156         starting from first added handler.
   169         starting from last added handler, going to those added
       
   170         before, ending with those added with last=True.
       
   171 
       
   172         Stop if any handler returns True.
   157 
   173 
   158         Return True when one of the handlers returns True,
   174         Return True when one of the handlers returns True,
   159         False otherwise.
   175         False otherwise.
   160 
   176 
   161         This creates new instance of event_class given to
   177         Creates new instance of event_class given to
   162         add_events() and passes all arguments after event_name
   178         add_events() and passes all arguments after event_name
   163         to its __init__ method.
   179         to its __init__ method.
   164 
   180 
   165         Unless first of these arguments is Event instance
   181         Unless first of these arguments is Event instance
   166         in which case no object is created and the instance
   182         in which case no object is created and the Event instance
   167         is passed to handlers.
   183         is passed to handlers.
   168 
   184 
   169         """
   185         """
   170         logging.getLogger('tuikit').debug('Emit "%s" on %s %s',
   186         logging.getLogger('tuikit').debug('Emit "%s" on %s %s',
   171             event_name,
   187             event_name,
   172             self.__class__.__name__,
   188             self.__class__.__name__,
   173             getattr(self, 'name', None) or id(self))
   189             getattr(self, 'name', None) or id(self))
   174         # create event from specified event class, or use first argument
   190         # create event from specified event class, or use first argument
   175         if len(args) and isinstance(args[0], Event):
   191         if len(args) == 1 and isinstance(args[0], Event):
   176             event = args[0]
   192             event = args[0]
   177         else:
   193         else:
   178             event = self._event_class[event_name](*args, **kwargs)
   194             event = self._event_class[event_name](*args, **kwargs)
       
   195         # set originator to instance on which emit() was called
   179         event.originator = self
   196         event.originator = self
   180         # try default handler, stop if satisfied
   197         # call handlers from first to last, stop if satisfied
   181         handled = getattr(self, '_handle_' + event_name)(event)
       
   182         if handled:
       
   183             return True
       
   184         # try custom handlers, stop if satisfied
       
   185         for handler in self._event_handlers[event_name]:
   198         for handler in self._event_handlers[event_name]:
   186             handled = handler(event)
   199             handled = handler(event)
   187             if handled:
   200             if handled:
   188                 return True
   201                 return True
   189 
   202         return False
       
   203 
       
   204     def _add_default_handlers(self, event_name):
       
   205         """Add default handlers from the instance and its base classes.
       
   206 
       
   207         Handlers are looked up in methods by their names.
       
   208         Method name is composited from prefix and event_name.
       
   209         Prefix is one of "on_" or "after_".
       
   210         Depending on prefix the handler is added to top or bottom of handler stack.
       
   211 
       
   212         See _add_default_handler method for more information.
       
   213 
       
   214         """
       
   215         for cls in reversed(inspect.getmro(self.__class__)):
       
   216             self._add_default_handler(cls, 'on_', event_name, last=False)
       
   217             self._add_default_handler(cls, 'after_', event_name, last=True)
       
   218 
       
   219     def _add_default_handler(self, cls, prefix, event_name, last):
       
   220         """Add handler from one of base classes to handler list.
       
   221 
       
   222         cls -- one of self's base classes
       
   223         prefix -- method name prefix
       
   224         event_name -- event name
       
   225         last -- if True, add handler to end of handler list (will be called last)
       
   226 
       
   227         Look for method of name prefix + event_name in class 'cls',
       
   228         if exists, add it to handler list.
       
   229 
       
   230         """
       
   231         method_name = prefix + event_name
       
   232         if method_name in cls.__dict__:
       
   233             unbound_handler = cls.__dict__[method_name]
       
   234             if callable(unbound_handler):
       
   235                 handler = unbound_handler.__get__(self, cls)
       
   236                 self.add_handler(event_name, handler, last)
       
   237