30ff541a5f080554f672c4dbcd6c37c6edd91ecf
[platform/upstream/pygobject2.git] / gi / _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 #   gi/_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, see <http://www.gnu.org/licenses/>.
19
20 import sys
21
22 from ._gi import _gobject
23
24 # Callable went away in python 3.0 and came back in 3.2.
25 # Use versioning to figure out when to define it, otherwise we have to deal with
26 # the complexity of using __builtin__ or builtin between python versions to
27 # check if callable exists which PyFlakes will also complain about.
28 if (3, 0) <= sys.version_info < (3, 2):
29     def callable(fn):
30         return hasattr(fn, '__call__')
31
32
33 class Signal(str):
34     """Object which gives a nice API for creating and binding signals.
35
36     :param name:
37         Name of signal or callable closure when used as a decorator.
38     :type name: str or callable
39     :param callable func:
40         Callable closure method.
41     :param GObject.SignalFlags flags:
42         Flags specifying when to run closure.
43     :param type return_type:
44         Return type of the Signal.
45     :param list arg_types:
46         List of argument types specifying the signals function signature
47     :param str doc:
48         Documentation of signal object.
49     :param callable accumulator:
50         Accumulator method with the signature:
51         func(ihint, return_accu, handler_return, accu_data) -> boolean
52     :param object accu_data:
53         User data passed to the accumulator.
54
55     :Example:
56
57     .. code-block:: python
58
59         class Spam(GObject.Object):
60             velocity = 0
61
62             @GObject.Signal
63             def pushed(self):
64                 self.velocity += 1
65
66             @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
67             def pulled(self):
68                 self.velocity -= 1
69
70             stomped = GObject.Signal('stomped', arg_types=(int,))
71
72             @GObject.Signal
73             def annotated_signal(self, a:int, b:str):
74                 "Python3 annotation support for parameter types.
75
76         def on_pushed(obj):
77             print(obj)
78
79         spam = Spam()
80         spam.pushed.connect(on_pushed)
81         spam.pushed.emit()
82     """
83     class BoundSignal(str):
84         """
85         Temporary binding object which can be used for connecting signals
86         without specifying the signal name string to connect.
87         """
88         def __new__(cls, name, *args, **kargs):
89             return str.__new__(cls, name)
90
91         def __init__(self, signal, gobj):
92             str.__init__(self)
93             self.signal = signal
94             self.gobj = gobj
95
96         def __repr__(self):
97             return 'BoundSignal("%s")' % self
98
99         def __call__(self, *args, **kargs):
100             """Call the signals closure."""
101             return self.signal.func(self.gobj, *args, **kargs)
102
103         def connect(self, callback, *args, **kargs):
104             """Same as GObject.Object.connect except there is no need to specify
105             the signal name."""
106             return self.gobj.connect(self, callback, *args, **kargs)
107
108         def connect_detailed(self, callback, detail, *args, **kargs):
109             """Same as GObject.Object.connect except there is no need to specify
110             the signal name. In addition concats "::<detail>" to the signal name
111             when connecting; for use with notifications like "notify" when a property
112             changes.
113             """
114             return self.gobj.connect(self + '::' + detail, callback, *args, **kargs)
115
116         def disconnect(self, handler_id):
117             """Same as GObject.Object.disconnect."""
118             self.instance.disconnect(handler_id)
119
120         def emit(self, *args, **kargs):
121             """Same as GObject.Object.emit except there is no need to specify
122             the signal name."""
123             return self.gobj.emit(str(self), *args, **kargs)
124
125     def __new__(cls, name='', *args, **kargs):
126         if callable(name):
127             name = name.__name__
128         return str.__new__(cls, name)
129
130     def __init__(self, name='', func=None, flags=_gobject.SIGNAL_RUN_FIRST,
131                  return_type=None, arg_types=None, doc='', accumulator=None, accu_data=None):
132         if func and not name:
133             name = func.__name__
134         elif callable(name):
135             func = name
136             name = func.__name__
137         if func and not doc:
138             doc = func.__doc__
139
140         str.__init__(self)
141
142         if func and not (return_type or arg_types):
143             return_type, arg_types = get_signal_annotations(func)
144         if arg_types is None:
145             arg_types = tuple()
146
147         self.func = func
148         self.flags = flags
149         self.return_type = return_type
150         self.arg_types = arg_types
151         self.__doc__ = doc
152         self.accumulator = accumulator
153         self.accu_data = accu_data
154
155     def __get__(self, instance, owner=None):
156         """Returns a BoundSignal when accessed on an object instance."""
157         if instance is None:
158             return self
159         return self.BoundSignal(self, instance)
160
161     def __call__(self, obj, *args, **kargs):
162         """Allows for instantiated Signals to be used as a decorator or calling
163         of the underlying signal method."""
164
165         # If obj is a GObject, than we call this signal as a closure otherwise
166         # it is used as a re-application of a decorator.
167         if isinstance(obj, _gobject.GObject):
168             self.func(obj, *args, **kargs)
169         else:
170             # If self is already an allocated name, use it otherwise create a new named
171             # signal using the closure name as the name.
172             if str(self):
173                 name = str(self)
174             else:
175                 name = obj.__name__
176             # Return a new value of this type since it is based on an immutable string.
177             return type(self)(name=name, func=obj, flags=self.flags,
178                               return_type=self.return_type, arg_types=self.arg_types,
179                               doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
180
181     def copy(self, newName=None):
182         """Returns a renamed copy of the Signal."""
183         if newName is None:
184             newName = self.name
185         return type(self)(name=newName, func=self.func, flags=self.flags,
186                           return_type=self.return_type, arg_types=self.arg_types,
187                           doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data)
188
189     def get_signal_args(self):
190         """Returns a tuple of: (flags, return_type, arg_types, accumulator, accu_data)"""
191         return (self.flags, self.return_type, self.arg_types, self.accumulator, self.accu_data)
192
193
194 class SignalOverride(Signal):
195     """Specialized sub-class of Signal which can be used as a decorator for overriding
196     existing signals on GObjects.
197
198     :Example:
199
200     .. code-block:: python
201
202         class MyWidget(Gtk.Widget):
203             @GObject.SignalOverride
204             def configure_event(self):
205                 pass
206     """
207     def get_signal_args(self):
208         """Returns the string 'override'."""
209         return 'override'
210
211
212 def get_signal_annotations(func):
213     """Attempt pulling python 3 function annotations off of 'func' for
214     use as a signals type information. Returns an ordered nested tuple
215     of (return_type, (arg_type1, arg_type2, ...)). If the given function
216     does not have annotations then (None, tuple()) is returned.
217     """
218     arg_types = tuple()
219     return_type = None
220
221     if hasattr(func, '__annotations__'):
222         # import inspect only when needed because it takes ~10 msec to load
223         import inspect
224         spec = inspect.getfullargspec(func)
225         arg_types = tuple(spec.annotations[arg] for arg in spec.args
226                           if arg in spec.annotations)
227         if 'return' in spec.annotations:
228             return_type = spec.annotations['return']
229
230     return return_type, arg_types
231
232
233 def install_signals(cls):
234     """Adds Signal instances on a GObject derived class into the '__gsignals__'
235     dictionary to be picked up and registered as real GObject signals.
236     """
237     gsignals = cls.__dict__.get('__gsignals__', {})
238     newsignals = {}
239     for name, signal in cls.__dict__.items():
240         if isinstance(signal, Signal):
241             signalName = str(signal)
242             # Fixup a signal which is unnamed by using the class variable name.
243             # Since Signal is based on string which immutable,
244             # we must copy and replace the class variable.
245             if not signalName:
246                 signalName = name
247                 signal = signal.copy(name)
248                 setattr(cls, name, signal)
249             if signalName in gsignals:
250                 raise ValueError('Signal "%s" has already been registered.' % name)
251             newsignals[signalName] = signal
252             gsignals[signalName] = signal.get_signal_args()
253
254     cls.__gsignals__ = gsignals
255
256     # Setup signal closures by adding the specially named
257     # method to the class in the form of "do_<signal_name>".
258     for name, signal in newsignals.items():
259         if signal.func is not None:
260             funcName = 'do_' + name.replace('-', '_')
261             if not hasattr(cls, funcName):
262                 setattr(cls, funcName, signal.func)