Imported Upstream version 3.7.3
[platform/upstream/python-gobject.git] / gi / _gobject / signalhelper.py
1 # -*- Mode: Python; py-indent-offset: 4 -*-
2 # pygobject - Python bindings for the GObject library
3 # Copyright (C) 2012 Simon Feltman
4 #
5 #   gobject/signalhelper.py: GObject signal binding decorator object
6 #
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.
11 #
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.
16 #
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
20 # USA
21
22 import sys
23 import inspect
24
25 from . import _gobject
26
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):
32     def callable(fn):
33         return hasattr(fn, '__call__')
34
35
36 class Signal(str):
37     """
38     Object which gives a nice API for creating and binding signals.
39
40     Example:
41     class Spam(GObject.GObject):
42         velocity = 0
43
44         @GObject.Signal
45         def pushed(self):
46             self.velocity += 1
47
48         @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
49         def pulled(self):
50             self.velocity -= 1
51
52         stomped = GObject.Signal('stomped', arg_types=(int,))
53
54         @GObject.Signal
55         def annotated_signal(self, a:int, b:str):
56             "Python3 annotation support for parameter types.
57
58     def on_pushed(obj):
59         print(obj)
60
61     spam = Spam()
62     spam.pushed.connect(on_pushed)
63     spam.pushed.emit()
64     """
65     class BoundSignal(str):
66         """
67         Temporary binding object which can be used for connecting signals
68         without specifying the signal name string to connect.
69         """
70         def __new__(cls, name, *args, **kargs):
71             return str.__new__(cls, name)
72
73         def __init__(self, signal, gobj):
74             str.__init__(self)
75             self.signal = signal
76             self.gobj = gobj
77
78         def __repr__(self):
79             return 'BoundSignal("%s")' % self
80
81         def __call__(self, *args, **kargs):
82             """Call the signals closure."""
83             return self.signal.func(self.gobj, *args, **kargs)
84
85         def connect(self, callback, *args, **kargs):
86             """Same as GObject.GObject.connect except there is no need to specify
87             the signal name."""
88             return self.gobj.connect(self, callback, *args, **kargs)
89
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
94             changes.
95             """
96             return self.gobj.connect(self + '::' + detail, callback, *args, **kargs)
97
98         def disconnect(self, handler_id):
99             """Same as GObject.GObject.disconnect."""
100             self.instance.disconnect(handler_id)
101
102         def emit(self, *args, **kargs):
103             """Same as GObject.GObject.emit except there is no need to specify
104             the signal name."""
105             self.gobj.emit(str(self), *args, **kargs)
106
107     def __new__(cls, name='', *args, **kargs):
108         if callable(name):
109             name = name.__name__
110         return str.__new__(cls, name)
111
112     def __init__(self, name='', func=None, flags=_gobject.SIGNAL_RUN_FIRST,
113                  return_type=None, arg_types=None, doc=''):
114         """
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.
118         @type   func: callable
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
126         @type   doc: string
127         """
128         if func and not name:
129             name = func.__name__
130         elif callable(name):
131             func = name
132             name = func.__name__
133         if func and not doc:
134             doc = func.__doc__
135
136         str.__init__(self)
137
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:
141             arg_types = tuple()
142
143         self.func = func
144         self.flags = flags
145         self.return_type = return_type
146         self.arg_types = arg_types
147         self.__doc__ = doc
148
149     def __get__(self, instance, owner=None):
150         """Returns a BoundSignal when accessed on an object instance."""
151         if instance is None:
152             return self
153         return self.BoundSignal(self, instance)
154
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."""
158
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)
163         else:
164             # If self is already an allocated name, use it otherwise create a new named
165             # signal using the closure name as the name.
166             if str(self):
167                 name = str(self)
168             else:
169                 name = obj.__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,
173                               doc=self.__doc__)
174
175     def copy(self, newName=None):
176         """Returns a renamed copy of the Signal."""
177         if newName is None:
178             newName = self.name
179         return type(self)(name=newName, func=self.func, flags=self.flags,
180                           return_type=self.return_type, arg_types=self.arg_types,
181                           doc=self.__doc__)
182
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)
186
187
188 class SignalOverride(Signal):
189     """Specialized sub-class of signal which can be used as a decorator for overriding
190     existing signals on GObjects.
191
192     Example:
193     class MyWidget(Gtk.Widget):
194         @GObject.SignalOverride
195         def configure_event(self):
196             pass
197     """
198     def get_signal_args(self):
199         """Returns the string 'override'."""
200         return 'override'
201
202
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.
208     """
209     arg_types = tuple()
210     return_type = None
211
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']
218
219     return return_type, arg_types
220
221
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.
225     """
226     gsignals = cls.__dict__.get('__gsignals__', {})
227     newsignals = {}
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.
234             if not signalName:
235                 signalName = name
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()
242
243     cls.__gsignals__ = gsignals
244
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)