11
12
logger = logging.getLogger('zim')
15
if os.environ.get('ZIM_TEST_RUNNING'):
14
21
# Constants for signal order
27
class SignalHandler(object):
28
'''Wrapper for a signal handler method that allows blocking the
29
handler for incoming signals. To be used as function decorator.
31
The method will be replaced by a L{BoundSignalHandler} object that
32
supports a C{blocked()} method which returns a context manager
33
to temporarily block a callback.
35
Intended to be used as::
44
with self.on_changed.blocked():
45
... # do something that results in a "changed" signal
49
def __init__(self, func):
52
def __get__(self, instance, klass):
57
# instance acces, return bound version
58
name = '_bound_' + self._func.__name__
59
if not hasattr(instance, name) \
60
or getattr(instance, name) is None:
61
bound_obj = BoundSignalHandler(instance, self._func)
62
setattr(instance, name, bound_obj)
64
return getattr(instance, name)
67
class BoundSignalHandler(object):
69
def __init__(self, instance, func):
70
self._instance = instance
74
def __call__(self, *args, **kwargs):
75
if self._blocked == 0:
76
return self._func(self._instance, *args, **kwargs)
86
'''Returns a context manager that can be used to temporarily
89
return SignalHandlerBlockContextManager(self)
92
class SignalHandlerBlockContextManager(object):
94
def __init__(self, handler):
95
self.handler = handler
100
def __exit__(self, exc_type, exc_val, exc_tb):
101
self.handler._unblock()
20
106
class ConnectorMixin(object):
21
107
'''Mixin class that has convenience methods for objects that
22
108
want to connect to signals of other objects.
135
221
name = 'do_' + signal.replace('-', '_')
136
222
if hasattr(obj, name):
137
223
method = getattr(obj, name)
141
229
def call_handlers(obj, signal, handlers, args):
156
244
r = callback(obj, *myargs)
158
# TODO in case of test mode, re-raise the error
159
246
logger.exception('Exception in signal handler for %s on %s', signal, obj)
253
class BlockSignalsContextManager(object):
255
def __init__(self, obj, signals):
257
self.signals = signals
260
for signal in self.signals:
261
self.obj.block_signal(signal)
263
def __exit__(self, exc_type, exc_val, exc_tb):
264
for signal in self.signals:
265
self.obj.unblock_signal(signal)
164
268
class SignalEmitter(object):
165
269
'''Replacement for C{GObject} to make objects emit signals.
166
270
API should be backward compatible with API offered by GObject.
214
318
def _connect(self, order, signal, callback, userdata):
215
319
#if self._get_signal(signal) is None:
216
320
# raise ValueError, 'No such signal: %s' % signal
321
assert not '_' in signal, 'Signal names use "-"'
218
323
if not hasattr(self, '_signal_handlers'):
219
324
self._signal_handlers = {}
263
368
return_first = signal in self.__hooks__ # XXX
370
if hasattr(self, '_blocked_signals') \
371
and self._blocked_signals.get(signal):
265
374
if not hasattr(self, '_signal_handlers') \
266
375
or not signal in self._signal_handlers:
267
call_default(self, signal, args)
376
return call_default(self, signal, args)
269
378
before = [h for h in self._signal_handlers[signal] if h[0] & SIGNAL_NORMAL]
270
379
for r in call_handlers(self, signal, before, args):
275
384
if return_first and r is not None:
387
if not signal in self._signal_handlers:
388
return None # Yes I have seen a case where default resulted in all handlers disconnected here ...
278
390
after = [h for h in self._signal_handlers[signal] if h[0] & SIGNAL_AFTER]
279
391
for r in call_handlers(self, signal, after, args):
280
392
if return_first and r is not None:
395
def blocked_signals(self, *signals):
396
'''Returns a context manager for blocking one or more signals'''
397
return BlockSignalsContextManager(self, signals)
399
def block_signal(self, signal):
400
'''Block signal emition by signal name'''
401
assert signal not in self.__hooks__, 'Cannot block a hook'
402
#if self._get_signal(signal) is None:
403
# raise ValueError, 'No such signal: %s' % signal
405
if not hasattr(self, '_blocked_signals'):
406
self._blocked_signals = {}
408
self._blocked_signals.setdefault(signal, 0)
409
self._blocked_signals[signal] += 1
411
def unblock_signal(self, signal):
412
'''Unblock signal emition by signal name'''
413
#if self._get_signal(signal) is None:
414
# raise ValueError, 'No such signal: %s' % signal
416
if hasattr(self, '_blocked_signals') \
417
and signal in self._blocked_signals \
418
and self._blocked_signals[signal] > 0:
419
self._blocked_signals[signal] -= 1
284
422
class DelayedCallback(object):
285
423
'''Wrapper for callbacks that need to be delayed after a signal