1
# -*- test-case-name: twisted.words.test.test_xishutil -*-
3
# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
4
# See LICENSE for details.
7
""" Internal method to determine if an object is a string """
8
return isinstance(s, type('')) or isinstance(s, type(u''))
10
class _MethodWrapper(object):
11
""" Internal class for tracking method calls """
12
def __init__(self, method, *args, **kwargs):
17
def __call__(self, *args, **kwargs):
18
nargs = self.args + args
19
nkwargs = self.kwargs.copy()
20
nkwargs.update(kwargs)
21
self.method(*nargs, **nkwargs)
27
def addCallback(self, onetime, method, *args, **kwargs):
28
if not method in self.callbacks:
29
self.callbacks[method] = (_MethodWrapper(method, *args, **kwargs), onetime)
31
def removeCallback(self, method):
32
if method in self.callbacks:
33
del self.callbacks[method]
35
def callback(self, *args, **kwargs):
36
for key, (methodwrapper, onetime) in self.callbacks.items():
37
methodwrapper(*args, **kwargs)
39
del self.callbacks[key]
41
from twisted.words.xish import xpath, domish
43
class EventDispatcher:
44
""" Event dispatching service.
46
The C{EventDispatcher} allows observers to be registered for certain events
47
that are dispatched. There are two types of events: XPath events and Named
50
Every dispatch is triggered by calling L{dispatch} with a data object and,
51
for named events, the name of the event.
53
When an XPath type event is dispatched, the associated object is assumed
54
to be a L{domish.Element} object, which is matched against all registered
55
XPath queries. For every match, the respective observer will be called with
58
A named event will simply call each registered observer for that particular
59
event name, with the data object. Unlike XPath type events, the data object
60
is not restricted to L{domish.Element}, but can be anything.
62
When registering observers, the event that is to be observed is specified
63
using an L{xpath.XPathQuery} object or a string. In the latter case, the
64
string can also contain the string representation of an XPath expression.
65
To distinguish these from named events, each named event should start with
66
a special prefix that is stored in C{self.prefix}. It defaults to
69
Observers registered using L{addObserver} are persistent: after the
70
observer has been triggered by a dispatch, it remains registered for a
71
possible next dispatch. If instead L{addOnetimeObserver} was used to
72
observe an event, the observer is removed from the list of observers after
73
the first observed event.
75
Obsevers can also prioritized, by providing an optional C{priority}
76
parameter to the L{addObserver} and L{addOnetimeObserver} methods. Higher
77
priority observers are then called before lower priority observers.
79
Finally, observers can be unregistered by using L{removeObserver}.
83
def __init__(self, eventprefix = "//event/"):
84
self.prefix = eventprefix
85
self._eventObservers = {}
86
self._xpathObservers = {}
87
self._orderedEventObserverKeys = []
88
self._orderedXpathObserverKeys = []
89
self._dispatchDepth = 0 # Flag indicating levels of dispatching in progress
90
self._updateQueue = [] # Queued updates for observer ops
92
def _isEvent(self, event):
93
return _isStr(event) and self.prefix == event[0:len(self.prefix)]
95
def addOnetimeObserver(self, event, observerfn, priority=0, *args, **kwargs):
96
""" Register a one-time observer for an event.
98
Like L{addObserver}, but is only triggered at most once. See there
99
for a description of the parameters.
101
self._addObserver(True, event, observerfn, priority, *args, **kwargs)
103
def addObserver(self, event, observerfn, priority=0, *args, **kwargs):
104
""" Register an observer for an event.
106
Each observer will be registered with a certain priority. Higher
107
priority observers get called before lower priority observers.
109
@param event: Name or XPath query for the event to be monitored.
110
@type event: L{str} or L{xpath.XPathQuery}.
111
@param observerfn: Function to be called when the specified event
112
has been triggered. This function takes
113
one parameter: the data object that triggered
114
the event. When specified, the C{*args} and
115
C{**kwargs} parameters to addObserver are being used
116
as additional parameters to the registered observer
118
@param priority: (Optional) priority of this observer in relation to
119
other observer that match the same event. Defaults to
121
@type priority: L{int}
123
self._addObserver(False, event, observerfn, priority, *args, **kwargs)
125
def _addObserver(self, onetime, event, observerfn, priority, *args, **kwargs):
126
# If this is happening in the middle of the dispatch, queue
127
# it up for processing after the dispatch completes
128
if self._dispatchDepth > 0:
129
self._updateQueue.append(lambda:self.addObserver(event, observerfn, priority, *args, **kwargs))
135
if self.prefix == event[0:len(self.prefix)]:
137
observers = self._eventObservers
140
event = xpath.internQuery(event)
141
observers = self._xpathObservers
144
observers = self._xpathObservers
146
key = (priority, event)
147
if not key in observers:
149
cbl.addCallback(onetime, observerfn, *args, **kwargs)
152
observers[key].addCallback(onetime, observerfn, *args, **kwargs)
154
# Update the priority ordered list of xpath keys --
155
# This really oughta be rethought for efficiency
156
self._orderedEventObserverKeys = self._eventObservers.keys()
157
self._orderedEventObserverKeys.sort()
158
self._orderedEventObserverKeys.reverse()
159
self._orderedXpathObserverKeys = self._xpathObservers.keys()
160
self._orderedXpathObserverKeys.sort()
161
self._orderedXpathObserverKeys.reverse()
163
def removeObserver(self, event, observerfn):
164
""" Remove function as observer for an event.
166
The observer function is removed for all priority levels for the
169
@param event: Event for which the observer function was registered.
170
@type event: L{str} or L{xpath.XPathQuery}
171
@param observerfn: Observer function to be unregistered.
174
# If this is happening in the middle of the dispatch, queue
175
# it up for processing after the dispatch completes
176
if self._dispatchDepth > 0:
177
self._updateQueue.append(lambda:self.removeObserver(event, observerfn))
183
if self.prefix == event[0:len(self.prefix)]:
184
observers = self._eventObservers
186
event = xpath.internQuery(event)
187
observers = self._xpathObservers
189
observers = self._xpathObservers
191
for priority, query in observers:
193
observers[(priority, query)].removeCallback(observerfn)
195
# Update the priority ordered list of xpath keys --
196
# This really oughta be rethought for efficiency
197
self._orderedEventObserverKeys = self._eventObservers.keys()
198
self._orderedEventObserverKeys.sort()
199
self._orderedEventObserverKeys.reverse()
200
self._orderedXpathObserverKeys = self._xpathObservers.keys()
201
self._orderedXpathObserverKeys.sort()
202
self._orderedXpathObserverKeys.reverse()
204
def dispatch(self, object, event = None):
205
""" Dispatch an event.
207
When C{event} is C{None}, an XPath type event is triggered, and
208
C{object} is assumed to be an instance of {domish.Element}. Otherwise,
209
C{event} holds the name of the named event being triggered. In the
210
latter case, C{object} can be anything.
212
@param object: The object to be dispatched.
213
@param event: Optional event name.
219
# Aiyiyi! If this dispatch occurs within a dispatch
220
# we need to preserve the original dispatching flag
221
# and not mess up the updateQueue
222
self._dispatchDepth = self._dispatchDepth + 1
225
for priority, query in self._orderedEventObserverKeys:
227
self._eventObservers[(priority, event)].callback(object)
230
for priority, query in self._orderedXpathObserverKeys:
231
callbacklist = self._xpathObservers[(priority, query)]
232
if query.matches(object):
233
callbacklist.callback(object)
236
self._dispatchDepth = self._dispatchDepth -1
238
# If this is a dispatch within a dispatch, don't
239
# do anything with the updateQueue -- it needs to
240
# wait until we've back all the way out of the stack
241
if self._dispatchDepth == 0:
242
# Deal with pending update operations
243
for f in self._updateQueue:
245
self._updateQueue = []