source: SHX/trunk/src/SeismicHandler/core/modules/Messages.py @ 153

Revision 153, 6.4 KB checked in by marcus, 14 years ago (diff)
  • final renaming Events to Messages
  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id Author Revision Date
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2008-2010 Marcus Walther (walther@szgrf.bgr.de)
4#
5# This file is part of Seismic Handler eXtended (SHX)
6# Full details can be found at project website http://www.seismic-handler.org/
7#
8# SHX is free software; you can redistribute it and/or modify
9# it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
10# the Free Software Foundation; either version 3 of the License, or
11# (at your option) any later version.
12#
13# SHX is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with SHX (see license.txt); if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
21
22"""Events class
23
24    - simple event manager
25"""
26
27from SeismicHandler.core.error import Error
28from SeismicHandler.core.log import logging
29
30class NotifierNotCallableError(Error):
31    def __init__(self):
32        Error.__init__(self)
33
34    def __str__(self):
35        return "notifier must be callable!"
36
37class Message(object):
38    """Event class.
39
40    Implemented as multiton, so that equal id's are represented by identical
41    instances.
42    """
43
44    _instances = {}
45
46    def __new__(cls, id):
47        if not id in cls._instances.keys():
48            cls._instances[id] = object.__new__(cls, id)
49
50        return cls._instances[id]
51
52    def __init__(self, id):
53        self.name = id
54        self.done = False
55
56class MessageController(object):
57    """Events manager
58
59    Subscribing to events:
60    >>> def x(e, x): print x*2
61    >>> e = MessageController()
62    >>> foo = Message("foo")
63    >>> bar = Message("bar")
64    >>> e.subscribe(foo, x)
65    >>> e.subscribe(bar, x)
66    >>> sorted([i.name for i in e.events])
67    ['bar', 'foo']
68    >>> e.events[bar] #doctest: +ELLIPSIS
69    [<function x at 0x...]
70    >>> e.events[foo] #doctest: +ELLIPSIS
71    [<function x at 0x...]
72
73    Now trigger an event:
74    >>> e.trigger(foo, 2)
75    4
76    >>> e.trigger(bar, 7)
77    14
78
79    Events can be blocked to prevent propagation:
80    >>> e.block(foo)
81    >>> e.trigger(foo, 2)
82    >>> e.trigger(foo, 4)
83
84    If an event is unblocked, pending events can be processed then or discarded.
85    Running only the last one is default, None skips processing in total. Here
86    we run all pending events with their associated data:
87    >>> e.unblock(foo, process="all")
88    4
89    8
90
91    Now only the last event queued:
92    >>> e.block(foo)
93    >>> e.trigger(foo, 3)
94    >>> e.trigger(foo, 5)
95    >>> e.unblock(foo)
96    10
97
98    A function can be unsubscribed from an event:
99    >>> e.unsubscribe(x, foo)
100    >>> e.trigger(foo, 111)
101    >>> e.trigger(bar, 6)
102    12
103
104    To unsubscribe a function in total, you don't have to know which
105    subscriptions are active:
106    >>> e.unsubscribe(x)
107    >>> e.trigger(foo, 99)
108    >>> e.trigger(bar, 6)
109    """
110
111    # standard events
112    TRACEUPDATE = "traceupdate"
113    REDRAW = "redraw"
114    COMMANDRUN = "cmdrun"
115    INFOIDXUPDATE = "infoindexupdate"
116
117    events = {}
118    blocked = set()
119    queue = []
120
121    def __init__(self):
122        self.logger = logging.getLogger("modules.events")
123
124    def subscribe(self, events, notify):
125        """subscribe to events list
126
127            - notify must contain a call back function
128        """
129
130        if not callable(notify):
131            raise NotifierNotCallableError
132
133        # change type to list if not iterable
134        if not hasattr(events, "__iter__"):
135            events = [events, ]
136
137        for e in events:
138            try:
139                self.events[e].append(notify)
140            except KeyError:
141                self.events[e] = [notify]
142
143            self.logger.debug("'%s' subscribed to event '%s'." % (notify, e.name))
144
145    def trigger(self, event, *data):
146        """trigger event """
147
148        if event in self.blocked:
149            # save blocked event for later use
150            self.queue.append([event, data])
151            self.logger.debug("Event '%s' triggered (but blocked)." % event.name)
152            return
153
154        self.logger.debug("Event '%s' triggered." % event.name)
155
156        if not self.events.has_key(event):
157            self.logger.debug("Event '%s' not found." % event.name)
158            return False
159
160        for i in self.events[event]:
161            i(event, *data)
162
163            # notified function has the possibility to stop further processing
164            if event.done:
165                self.logger.debug("Event '%s' propagation stopped by %s." % (event.name, i))
166                break
167
168    def unsubscribe(self, notify, events=None):
169        """unsubscribe notifier from events """
170
171        if not events:
172            events = self.events
173
174        if not hasattr(events, "__iter__"):
175            events = [events, ]
176
177        for e in events:
178            if notify in self.events[e]:
179                try:
180                    self.logger.debug("'%s' unsubscribed from event '%s'." % (notify, e.name))
181                    self.events[e].remove(notify)
182                except (ValueError):
183                    pass
184
185    def block(self, event):
186        """set event on block stage, this means, that the event won't be propagated
187        """
188
189        self.blocked.update([event])
190
191    def unblock(self, event, process="last"):
192        """unblock event propagation
193
194            @param process:
195                control what to do with pending events:
196                None   - discard
197                "last" - only trigger last one
198                "all"  - trigger all
199        """
200
201        # unblock event
202        try:
203            self.blocked.remove(event)
204        except KeyError:
205            return
206
207        # check if event occurred and maybe trigger it
208        # work on a copy of queue
209        if process == "last":
210            # reverse order
211            cqueue = self.queue[::-1]
212        else:
213            # normal copy
214            cqueue = self.queue[:]
215
216        for pending in cqueue:
217            if event != pending[0]:
218                continue
219
220            self.queue.remove(pending)
221
222            # skip if requested
223            if not process:
224                continue
225
226            # alter state if only last one should be processed
227            if process == "last":
228                process = None
229
230            self.trigger(pending[0], *pending[1])
231
232MessageService = MessageController()
233
234if __name__ == "__main__":
235    import doctest
236    doctest.testmod()
Note: See TracBrowser for help on using the repository browser.