1 # -*- Mode: Python; py-indent-offset: 4 -*-
2 # pygobject - Python bindings for the GObject library
3 # Copyright (C) 2012 Simon Feltman
5 # gobject/signalhelper.py: GObject signal binding decorator object
7 # This library is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU Lesser General Public
9 # License as published by the Free Software Foundation; either
10 # version 2.1 of the License, or (at your option) any later version.
12 # This library is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 # Lesser General Public License for more details.
17 # You should have received a copy of the GNU Lesser General Public
18 # License along with this library; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
25 from . import _gobject
27 # Callable went away in python 3.0 and came back in 3.2.
28 # Use versioning to figure out when to define it, otherwise we have to deal with
29 # the complexity of using __builtin__ or builtin between python versions to
30 # check if callable exists which PyFlakes will also complain about.
31 if (3, 0) <= sys.version_info < (3, 2):
33 return hasattr(fn, '__call__')
38 Object which gives a nice API for creating and binding signals.
41 class Spam(GObject.GObject):
48 @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
52 stomped = GObject.Signal('stomped', arg_types=(int,))
55 def annotated_signal(self, a:int, b:str):
56 "Python3 annotation support for parameter types.
62 spam.pushed.connect(on_pushed)
65 class BoundSignal(str):
67 Temporary binding object which can be used for connecting signals
68 without specifying the signal name string to connect.
70 def __new__(cls, name, *args, **kargs):
71 return str.__new__(cls, name)
73 def __init__(self, signal, gobj):
79 return 'BoundSignal("%s")' % self
81 def __call__(self, *args, **kargs):
82 """Call the signals closure."""
83 return self.signal.func(self.gobj, *args, **kargs)
85 def connect(self, callback, *args, **kargs):
86 """Same as GObject.GObject.connect except there is no need to specify
88 return self.gobj.connect(self, callback, *args, **kargs)
90 def connect_detailed(self, callback, detail, *args, **kargs):
91 """Same as GObject.GObject.connect except there is no need to specify
92 the signal name. In addition concats "::<detail>" to the signal name
93 when connecting; for use with notifications like "notify" when a property
96 return self.gobj.connect(self + '::' + detail, callback, *args, **kargs)
98 def disconnect(self, handler_id):
99 """Same as GObject.GObject.disconnect."""
100 self.instance.disconnect(handler_id)
102 def emit(self, *args, **kargs):
103 """Same as GObject.GObject.emit except there is no need to specify
105 self.gobj.emit(str(self), *args, **kargs)
107 def __new__(cls, name='', *args, **kargs):
110 return str.__new__(cls, name)
112 def __init__(self, name='', func=None, flags=_gobject.SIGNAL_RUN_FIRST,
113 return_type=None, arg_types=None, doc=''):
115 @param name: name of signal or closure method when used as direct decorator.
116 @type name: string or callable
117 @param func: closure method.
119 @param flags: flags specifying when to run closure
120 @type flags: GObject.SignalFlags
121 @param return_type: return type
122 @type return_type: type
123 @param arg_types: list of argument types specifying the signals function signature
124 @type arg_types: None
125 @param doc: documentation of signal object
128 if func and not name:
138 if func and not (return_type or arg_types):
139 return_type, arg_types = get_signal_annotations(func)
140 if arg_types is None:
145 self.return_type = return_type
146 self.arg_types = arg_types
149 def __get__(self, instance, owner=None):
150 """Returns a BoundSignal when accessed on an object instance."""
153 return self.BoundSignal(self, instance)
155 def __call__(self, obj, *args, **kargs):
156 """Allows for instantiated Signals to be used as a decorator or calling
157 of the underlying signal method."""
159 # If obj is a GObject, than we call this signal as a closure otherwise
160 # it is used as a re-application of a decorator.
161 if isinstance(obj, _gobject.GObject):
162 self.func(obj, *args, **kargs)
164 # If self is already an allocated name, use it otherwise create a new named
165 # signal using the closure name as the name.
170 # Return a new value of this type since it is based on an immutable string.
171 return type(self)(name=name, func=obj, flags=self.flags,
172 return_type=self.return_type, arg_types=self.arg_types,
175 def copy(self, newName=None):
176 """Returns a renamed copy of the Signal."""
179 return type(self)(name=newName, func=self.func, flags=self.flags,
180 return_type=self.return_type, arg_types=self.arg_types,
183 def get_signal_args(self):
184 """Returns a tuple of: (flags, return_type, arg_types)"""
185 return (self.flags, self.return_type, self.arg_types)
188 class SignalOverride(Signal):
189 """Specialized sub-class of signal which can be used as a decorator for overriding
190 existing signals on GObjects.
193 class MyWidget(Gtk.Widget):
194 @GObject.SignalOverride
195 def configure_event(self):
198 def get_signal_args(self):
199 """Returns the string 'override'."""
203 def get_signal_annotations(func):
204 """Attempt pulling python 3 function annotations off of 'func' for
205 use as a signals type information. Returns an ordered nested tuple
206 of (return_type, (arg_type1, arg_type2, ...)). If the given function
207 does not have annotations then (None, tuple()) is returned.
212 if hasattr(func, '__annotations__'):
213 spec = inspect.getfullargspec(func)
214 arg_types = tuple(spec.annotations[arg] for arg in spec.args
215 if arg in spec.annotations)
216 if 'return' in spec.annotations:
217 return_type = spec.annotations['return']
219 return return_type, arg_types
222 def install_signals(cls):
223 """Adds Signal instances on a GObject derived class into the '__gsignals__'
224 dictionary to be picked up and registered as real GObject signals.
226 gsignals = cls.__dict__.get('__gsignals__', {})
228 for name, signal in cls.__dict__.items():
229 if isinstance(signal, Signal):
230 signalName = str(signal)
231 # Fixup a signal which is unnamed by using the class variable name.
232 # Since Signal is based on string which immutable,
233 # we must copy and replace the class variable.
236 signal = signal.copy(name)
237 setattr(cls, name, signal)
238 if signalName in gsignals:
239 raise ValueError('Signal "%s" has already been registered.' % name)
240 newsignals[signalName] = signal
241 gsignals[signalName] = signal.get_signal_args()
243 cls.__gsignals__ = gsignals
245 # Setup signal closures by adding the specially named
246 # method to the class in the form of "do_<signal_name>".
247 for name, signal in newsignals.items():
248 if signal.func is not None:
249 funcName = 'do_' + name.replace('-', '_')
250 if not hasattr(cls, funcName):
251 setattr(cls, funcName, signal.func)