Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 299 lines (221 sloc) 10.028 kB
c2935b7 @baverman removed gsignals dependency
authored
1 import gobject
2 import gobject.constants
3 import weakref
da35d8b @baverman connect signals to base class handlers
authored
4 from inspect import getmembers, ismethod
c2935b7 @baverman removed gsignals dependency
authored
5
6 from weak import weak_connect
1e15924 @baverman again massive signal system refactoring
authored
7 from util import append_attr
c2935b7 @baverman removed gsignals dependency
authored
8
9 SSIGNAL = gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_NO_RECURSE | gobject.SIGNAL_ACTION
10 SACTION = gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION
11
1e15924 @baverman again massive signal system refactoring
authored
12 def attach_signal_connect_info(attr, obj, func, after, idle):
13 """
14 Adds signal connection info to function
15
16 Used by signal and trigger decorators
17 """
18 connect_params = dict(after=after, idle=idle)
19
20 if func:
21 if not getattr(func, '__call__'):
22 raise Exception('Signal decorator accept callable or connect params')
23
24 append_attr(func, attr, (obj, connect_params))
25 return func
26 else:
27 def inner(func):
28 append_attr(func, attr, (obj, connect_params))
29 return func
30
31 return inner
c2935b7 @baverman removed gsignals dependency
authored
32
33 class Signal(object):
1e15924 @baverman again massive signal system refactoring
authored
34 """
35 Unbounded signal
36
37 Class holds signal parameters which used to construct correct GObject later.
38
39 Instantiating signals::
40
41 Signal() # Signal without arguments
42
43 Signal(object, int) # Signal with two arguments
44
45 Signal(object, return_type=int) # Signal with return type
46
47 Signal(type=gobject.SIGNAL_RUN_FIRST) # default signal type is gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_NO_RECURSE | gobject.SIGNAL_ACTION
48
49
50 Unbounded signal instances can be used to mark callbacks for automatic signal connecting::
51
52 signal = Signal()
53
54 class Handler(object):
55 @signal
56 def callback(...): pass # Usual (in gobject terms) signal connection
57
58 @signal(idle=True)
59 def callback(...): pass # Connects signal with idle wrapper
60
61 @signal(after=True)
62 def callback(...): pass # sender.connect_after(callback) analog
63
64 @signal(idle=9999)
65 def callback(...): pass # idle wrapper will start callback with specified priority
66 """
67
c2935b7 @baverman removed gsignals dependency
authored
68 def __init__(self, *signal_args, **kwargs):
69 allowed_named_arguments = set(('type', 'return_type'))
70 if not all(r in allowed_named_arguments for r in kwargs.keys()):
71 raise Exception('Signal constructor takes only `type` and `return_type` named arguments')
72
73 self.signal_type = kwargs.get('type', SSIGNAL)
74 self.return_type = kwargs.get('return_type', None)
75 self.arg_types = tuple(signal_args)
76 self.name = None
77
78 def __call__(self, func=None, after=False, idle=False):
79 return attach_signal_connect_info('signals_to_connect', self, func, after, idle)
80
81 def emit(self):
1e15924 @baverman again massive signal system refactoring
authored
82 """
83 Only hint for IDE
84 """
c2935b7 @baverman removed gsignals dependency
authored
85 raise Exception('You cannot emit unbounded signals')
86
87
88 class SignalManager(object):
1e15924 @baverman again massive signal system refactoring
authored
89 """
90 Wrapper for inner GObject with signals
91
92 Example::
93
94 class Manager(SignalManager):
95 show = Signal()
96 hide = Signal()
97
98 ``Manager.show`` and ``Manager.hide`` is unbounded signals and can be used as
99 decorators to callbacks. Whereas ``instance.show`` and ``instance.hide`` is bounded and
100 can be used to emit signals::
101
102 class Plugin(object):
103 def __init__(self):
104 self.signals = Manager()
105 self.signals.connect_signals()
106
107 self.signals.hide.emit()
108
109 @Manager.show
110 def show(self, sender):
111 pass
112
113 Inner GObject with necessary __gsignals__ is constructed during instance initialization
114 """
c2935b7 @baverman removed gsignals dependency
authored
115 registered_classes = {}
116
117 def __new__(cls, *args, **kwargs):
118 try:
119 newcls = SignalManager.registered_classes[cls]
120 obj = newcls.__new__(newcls, *args, **kwargs)
121 gobject.GObject.__init__(obj)
122 newcls.__init__(obj, *args, **kwargs)
123
124 return obj
125 except KeyError:
126 pass
127
1e15924 @baverman again massive signal system refactoring
authored
128 def make_signal_prop(signal):
129 def inner(self):
130 return BoundedSignal(self, signal)
131
132 return property(inner)
133
134 newdict = dict(cls.__dict__)
c2935b7 @baverman removed gsignals dependency
authored
135 signals = {}
136 for sname, signal in cls.__dict__.iteritems():
137 if isinstance(signal, Signal):
138 signal.name = sname.replace('_', '-')
139 signals[signal.name] = (signal.signal_type,
140 signal.return_type, signal.arg_types)
1e15924 @baverman again massive signal system refactoring
authored
141
142 newdict[sname] = make_signal_prop(signal)
143
c2935b7 @baverman removed gsignals dependency
authored
144 if not signals:
145 return super(SignalManager, cls).__new__(cls, *args, **kwargs)
146
147 newdict['__gsignals__'] = signals
148 newdict['weak_connect'] = SignalManager.weak_connect
1e15924 @baverman again massive signal system refactoring
authored
149 newdict['connect_signals'] = SignalManager.connect_signals
c2935b7 @baverman removed gsignals dependency
authored
150
151 for k, v in newdict.iteritems():
152 if hasattr(v, 'im_func'):
153 newdict[k] = v.im_func
154
155 newcls = type(cls.__name__, (gobject.GObject,), newdict)
156 gobject.type_register(newcls)
157 SignalManager.registered_classes[cls] = newcls
1e15924 @baverman again massive signal system refactoring
authored
158
c2935b7 @baverman removed gsignals dependency
authored
159 obj = newcls.__new__(newcls, *args, **kwargs)
160 gobject.GObject.__init__(obj)
161 newcls.__init__(obj, *args, **kwargs)
162
163 return obj
1e15924 @baverman again massive signal system refactoring
authored
164
165 def connect_signals(self, obj):
166 """
167 Connects marked object methods
168 """
40b553b @baverman inspect only class members not object during signal wiring
authored
169 for attr, value in getmembers(obj.__class__, ismethod):
1e15924 @baverman again massive signal system refactoring
authored
170 for signal, connect_params in getattr(value, 'signals_to_connect', ()):
da35d8b @baverman connect signals to base class handlers
authored
171 id = self.weak_connect(signal, obj, attr, **connect_params)
1e15924 @baverman again massive signal system refactoring
authored
172 append_handler_to_object(obj, attr, id, self, signal.name)
c2935b7 @baverman removed gsignals dependency
authored
173
1e15924 @baverman again massive signal system refactoring
authored
174 def weak_connect(self, signal, obj, attr, after, idle):
175 """
176 Connects unbounded signal
177
178 @param signal: Unbounded signal
179 """
180 return weak_connect(self, signal.name, obj, attr, after=after, idle=idle)
c2935b7 @baverman removed gsignals dependency
authored
181
182
183 class BoundedSignal(object):
1e15924 @baverman again massive signal system refactoring
authored
184 """
185 This class knows about its GObject wrapper and unbounded signal name
186
187 This allows it to emit signals. Bounded signal weakly connected to its manager so
188 you can safely use it in any context
189 """
c2935b7 @baverman removed gsignals dependency
authored
190 def __init__(self, manager, signal):
191 self.manager = weakref.ref(manager)
192 self.signal = signal
193
1e15924 @baverman again massive signal system refactoring
authored
194 def connect(self, obj, attr, after=False, idle=False):
195 manager = self.manager()
196 if manager:
197 manager.weak_connect(self.signal, obj, attr, after=after, idle=idle)
198
c2935b7 @baverman removed gsignals dependency
authored
199 def emit(self, *args):
200 manager = self.manager()
201 if manager:
1e15924 @baverman again massive signal system refactoring
authored
202 return manager.emit(self.signal.name, *args)
c2935b7 @baverman removed gsignals dependency
authored
203
204
1e15924 @baverman again massive signal system refactoring
authored
205 def connect_external(sender_name, signal_name, after=False, idle=False):
206 def inner(func):
207 return attach_signal_connect_info('external_signals_to_connect',
208 (sender_name, signal_name), func, after, idle)
209
210 return inner
211
212 def connect_external_signals(obj, **kwargs):
40b553b @baverman inspect only class members not object during signal wiring
authored
213 for attr, value in getmembers(obj.__class__, ismethod):
1e15924 @baverman again massive signal system refactoring
authored
214 for (sender_name, signal_name), connect_params in getattr(value, 'external_signals_to_connect', ()):
215 sender = kwargs[sender_name]
216 id = weak_connect(sender, signal_name, obj, attr, **connect_params)
217 append_handler_to_object(obj, attr, id, sender, signal_name, sender_name)
218
219 def append_handler_to_object(obj, attr, handler_id, sender, signal_name, sender_name=None):
220 name = attr + '_handler'
221 if not hasattr(obj, name):
222 setattr(obj, name, HandlerHolder())
223
224 getattr(obj, name).add(handler_id, sender, signal_name, sender_name)
225
226 def connect_all(obj, *signal_managers, **external_senders):
227 [s.connect_signals(obj) for s in signal_managers]
228
229 if external_senders:
230 connect_external_signals(obj, **external_senders)
231
c2935b7 @baverman removed gsignals dependency
authored
232 class Handler(object):
1e15924 @baverman again massive signal system refactoring
authored
233 def __init__(self, handler_id, sender, signal_name, sender_name):
c2935b7 @baverman removed gsignals dependency
authored
234 self.id = handler_id
235 self.sender = weakref.ref(sender)
1e15924 @baverman again massive signal system refactoring
authored
236 self.signal_name = signal_name
237 self.sender_name = sender_name
238
239 def is_match(self, sender, sender_name, signal_name):
240 result = True
241
242 result = result and (sender == None or self.sender() is sender)
243 result = result and (sender_name == None or self.sender_name == sender_name)
244 result = result and (signal_name == None or self.signal_name == signal_name)
245
246 return result
c2935b7 @baverman removed gsignals dependency
authored
247
248 def block(self):
249 sender = self.sender()
250 if sender:
251 sender.handler_block(self.id)
252
253 def unblock(self):
254 sender = self.sender()
255 if sender:
256 sender.handler_unblock(self.id)
1e15924 @baverman again massive signal system refactoring
authored
257
258
259 class HandlerHolder(object):
260
261 def __init__(self):
262 self.handlers = []
263
264 def add(self, id, sender, signal_name, sender_name=None):
265 self.handlers.append(Handler(id, sender, signal_name, sender_name))
266
267 def block(self):
268 try:
269 (handler,) = self.handlers
270 handler.block()
271 except ValueError:
272 raise Exception('There are several signals connected to callback')
273
274 def unblock(self):
275 try:
276 (handler,) = self.handlers
277 handler.unblock()
278 except ValueError:
279 raise Exception('There are several signals connected to callback')
280
281 @property
282 def id(self):
283 try:
284 (handler,) = self.handlers
285 return handler.id
286 except ValueError:
287 raise Exception('There are several signals connected to callback')
288
289 def __call__(self, sender=None, sender_name=None, signal_name=None):
290 handler = None
291 for h in self.handlers:
292 if h.is_match(sender=sender, sender_name=sender_name, signal_name=signal_name):
293 if handler:
294 raise Exception('Match returns several handlers')
295 else:
296 handler = h
297
298 return handler
Something went wrong with that request. Please try again.