~ubuntu-branches/ubuntu/vivid/frescobaldi/vivid

« back to all changes in this revision

Viewing changes to frescobaldi_app/signals.py

  • Committer: Package Import Robot
  • Author(s): Ryan Kavanagh
  • Date: 2012-01-03 16:20:11 UTC
  • mfrom: (1.4.1)
  • Revision ID: package-import@ubuntu.com-20120103162011-tsjkwl4sntwmprea
Tags: 2.0.0-1
* New upstream release 
* Drop the following uneeded patches:
  + 01_checkmodules_no_python-kde4_build-dep.diff
  + 02_no_pyc.diff
  + 04_no_binary_lilypond_upgrades.diff
* Needs new dependency python-poppler-qt4
* Update debian/watch for new download path
* Update copyright file with new holders and years

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
 
2
#
 
3
# Copyright (c) 2008 - 2011 by Wilbert Berendsen
 
4
#
 
5
# This program is free software; you can redistribute it and/or
 
6
# modify it under the terms of the GNU General Public License
 
7
# as published by the Free Software Foundation; either version 2
 
8
# of the License, or (at your option) any later version.
 
9
#
 
10
# This program is distributed in the hope that it will be useful,
 
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
# GNU General Public License for more details.
 
14
#
 
15
# You should have received a copy of the GNU General Public License
 
16
# along with this program; if not, write to the Free Software
 
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
18
# See http://www.gnu.org/licenses/ for more information.
 
19
 
 
20
"""
 
21
A simple signal/slot implementation.
 
22
"""
 
23
 
 
24
import bisect
 
25
import contextlib
 
26
import types
 
27
import weakref
 
28
 
 
29
 
 
30
__all__ = ["Signal"]
 
31
 
 
32
 
 
33
class Signal(object):
 
34
    """A Signal can be emitted and receivers (slots) can be connected to it.
 
35
    
 
36
    An example:
 
37
    
 
38
    class MyObject(object):
 
39
    
 
40
        somethingChanged = Signal()
 
41
        
 
42
        def __init__(self):
 
43
            pass # etc
 
44
            
 
45
        def doSomething(self):
 
46
            ... do things ...
 
47
            self.somethingChanged("Hi there!")     # emit the signal
 
48
    
 
49
    def receiver(arg):
 
50
        print "Received message:", arg
 
51
    
 
52
    
 
53
    >>> o = MyObject()
 
54
    >>> o.somethingChanged.connect(receiver)
 
55
    >>> o.doSomething()
 
56
    Hi there!
 
57
    
 
58
    A Signal() can be used directly or as a class attribute, but can also be
 
59
    accessed as an attribute of an instance, in which case it creates a Signal
 
60
    instance for that instance.
 
61
 
 
62
    The signal is emitted by the emit() method or by simply invoking it.
 
63
    
 
64
    It is currently not possible to enforce argument types that should be used
 
65
    when emitting the signal. But if called methods or functions expect fewer
 
66
    arguments than were given on emit(), the superfluous arguments are left out.
 
67
    
 
68
    Methods or functions are connected using connect() and disconnected using
 
69
    disconnect(). It is no problem to call connect() or disconnect() more than
 
70
    once for the same function or method. Only one connection to the same method
 
71
    or function can exist.
 
72
    
 
73
    """
 
74
    
 
75
    def __init__(self, owner=None):
 
76
        """Creates the Signal.
 
77
        
 
78
        If owner is given (must be a keyword argument) a weak reference to it is
 
79
        kept, and this allows a Signal to be connected to another Signal. When
 
80
        the owner dies, the connection is removed.
 
81
        
 
82
        """
 
83
        self.listeners = []
 
84
        self._blocked = False
 
85
        self._owner = weakref.ref(owner) if owner else lambda: None
 
86
        
 
87
    def __get__(self, instance, cls):
 
88
        """Called when accessing as a descriptor: returns another instance."""
 
89
        if instance is None:
 
90
            return self
 
91
        try:
 
92
            return self._instances[instance]
 
93
        except AttributeError:
 
94
            self._instances = weakref.WeakKeyDictionary()
 
95
        except KeyError:
 
96
            pass
 
97
        ret = self._instances[instance] = Signal(owner=instance)
 
98
        return ret
 
99
    
 
100
    def owner(self):
 
101
        """Returns the owner of this Signal, if any."""
 
102
        return self._owner()
 
103
        
 
104
    def connect(self, slot, priority=0, owner=None):
 
105
        """Connects a method or function ('slot') to this Signal.
 
106
        
 
107
        The priority argument determines the order the connected slots are
 
108
        called. A lower value calls the slot earlier.
 
109
        If owner is given, the connection will be removed if owner is garbage
 
110
        collected.
 
111
        
 
112
        A slot that is already connected will not be connected twice.
 
113
        
 
114
        If slot is an instance method (bound method), the Signal keeps no
 
115
        reference to the object the method belongs to. So if the object is
 
116
        garbage collected, the signal is automatically disconnected.
 
117
        
 
118
        If slot is a (normal or lambda) function, the Signal will keep a
 
119
        reference to the function. If you want to have the function disconnected
 
120
        automatically when some object dies, you should provide that object
 
121
        through the owner argument. Be sure that the connected function does not
 
122
        keep a reference to that object in that case!
 
123
        
 
124
        """
 
125
        key = makeListener(slot, owner)
 
126
        if key not in self.listeners:
 
127
            key.add(self, priority)
 
128
            
 
129
    def disconnect(self, func):
 
130
        """Disconnects the method or function.
 
131
        
 
132
        No exception is raised if there wasn't a connection.
 
133
        
 
134
        """
 
135
        key = makeListener(func)
 
136
        try:
 
137
            self.listeners.remove(key)
 
138
        except ValueError:
 
139
            pass
 
140
    
 
141
    def clear(self):
 
142
        """Removes all connected slots."""
 
143
        del self.listeners[:]
 
144
    
 
145
    @contextlib.contextmanager
 
146
    def blocked(self):
 
147
        """Returns a contextmanager that suppresses the signal.
 
148
        
 
149
        An example (continued from the class documentation):
 
150
        
 
151
        >>> o = MyObject()
 
152
        >>> o.somethingChanged.connect(receiver)
 
153
        >>> with o.somethingChanged.blocked():
 
154
        ...     o.doSomething()
 
155
        (no output)
 
156
        
 
157
        The doSomething() method will emit the signal but the connected slots
 
158
        will not be called.
 
159
        
 
160
        """
 
161
        blocked, self._blocked = self._blocked, True
 
162
        try:
 
163
            yield
 
164
        finally:
 
165
            self._blocked = blocked
 
166
 
 
167
    def emit(self, *args, **kwargs):
 
168
        """Emits the signal.
 
169
        
 
170
        Unless blocked, all slots will be called with the supplied arguments.
 
171
        
 
172
        """
 
173
        if not self._blocked:
 
174
            for l in self.listeners[:]:
 
175
                l.call(args, kwargs)
 
176
    
 
177
    __call__ = emit
 
178
 
 
179
 
 
180
def makeListener(func, owner=None):
 
181
    if isinstance(func, (types.MethodType, types.BuiltinMethodType)):
 
182
        return MethodListener(func)
 
183
    elif isinstance(func, Signal):
 
184
        return FunctionListener(func, owner or func.owner())
 
185
    else:
 
186
        return FunctionListener(func, owner)
 
187
 
 
188
 
 
189
class ListenerBase(object):
 
190
    def __lt__(self, other):
 
191
        return self.priority < other.priority
 
192
    
 
193
    def add(self, signal, priority):
 
194
        self.priority = priority
 
195
        bisect.insort_right(signal.listeners, self)
 
196
        if self.obj is not None:
 
197
            def remove(wr, selfref=weakref.ref(self), sigref=weakref.ref(signal)):
 
198
                self, signal = selfref(), sigref()
 
199
                if self and signal:
 
200
                    signal.listeners.remove(self)
 
201
            self.obj = weakref.ref(self.obj, remove)
 
202
        try:
 
203
            nargs = self.func.func_code.co_argcount
 
204
        except AttributeError:
 
205
            self.argslice = slice(0, None)
 
206
        else:
 
207
            self.argslice = slice(0, nargs - self.removeargs)
 
208
 
 
209
 
 
210
class MethodListener(ListenerBase):
 
211
    removeargs = 1
 
212
    def __init__(self, meth):
 
213
        self.obj = meth.__self__
 
214
        self.objid = id(meth.__self__)
 
215
        try:
 
216
            self.func = meth.__func__
 
217
        except AttributeError:
 
218
            # c++ methods from PyQt4 object sometimes do not have the __func__ attribute
 
219
            self.func = getattr(meth.__self__.__class__, meth.__name__)
 
220
    
 
221
    def __eq__(self, other):
 
222
        return self.__class__ is other.__class__ and self.objid == other.objid and self.func is other.func
 
223
 
 
224
    def call(self, args, kwargs):
 
225
        obj = self.obj()
 
226
        if obj is not None:
 
227
            self.func(obj, *args[self.argslice], **kwargs)
 
228
 
 
229
 
 
230
class FunctionListener(ListenerBase):
 
231
    removeargs = 0
 
232
    def __init__(self, func, owner=None):
 
233
        self.obj = owner
 
234
        self.func = func
 
235
 
 
236
    def __eq__(self, other):
 
237
        return self.__class__ is other.__class__ and self.func is other.func
 
238
 
 
239
    def call(self, args, kwargs):
 
240
        self.func(*args[self.argslice], **kwargs)
 
241
        
 
242