A little bug fix
[jcd.git] / SignalSlot.py
bloba5cbfc50b4dea017868606f60b4ec6a9883ba9d2
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 # vim: expandtab:shiftwidth=4:fileencoding=utf-8 :
5 # Copyright ® 2008 Fulvio Satta
7 # If you want contact me, send an email to Yota_VGA@users.sf.net
9 # This file is part of jcd
11 # jcd is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # jcd is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 #TODO: Make the default implementation warning-based
26 #TODO: Test, test, test
27 #TODO: Polishing
29 ##########################
30 ##### IMPORT SECTION #####
31 ##########################
33 from inspect import getargspec as _getargspec
35 from threading import Lock as _Lock
37 from Syncronized import Syncronized as _Syncronized
38 from Syncronized import AutoLock as _AutoLock
40 #########################################
41 ##### EXCEPTIONS DEFINITION SECTION #####
42 #########################################
44 #Exception always ignored in the exception handling
45 class SlotIgnore(Exception):
46 pass
48 ############################################
49 ##### PUBLIC SUPPORT FUNCTIONS SECTION #####
50 ############################################
52 #See if a prototype function match with the arguments
53 def matchingFunction(f, args, kwargs):
54 #specs for call the function
55 specs = _getargspec(f)
57 #normalize specs[3]
58 defp = specs[3]
59 if not specs[3]:
60 defp = ()
62 #Get some usefull parameter for later
63 min_params = len(specs[0]) - len(defp)
64 poskey_params = min(len(specs[0]), len(args))
66 #This have True for the declared positional elements, and false for the other positional key elements
67 positional_params = [True] * poskey_params + [False] * (len(specs[0]) - poskey_params)
69 #Counter for the key params contained in positional key params
70 c = 0
71 #Enumerate the positional key params
72 for i, p in enumerate(specs[0]):
73 #For each key param contained in the positional params
74 if p in kwargs:
75 #If is already declared continue to the next function
76 if positional_params[i]:
77 return False
78 #Count it
79 c += 1
81 #If the function have too many positional parameters continue to the next function
82 if not specs[1] and len(args) > len(specs[0]):
83 return False
85 #If the function have unknown keywords parameters continue to the next function
86 if not specs[2] and len(kwargs) != c:
87 return False
89 return True
91 #Make a delayed callable from a SlotBase derived function.
92 #The function will be called immediately if possible, elsewere will be queued
93 def delayCaller(function):
94 def call(*args, **kwargs):
95 if not matchingFunction(function, args, kwargs):
96 raise SlotIgnore
98 self = function.im_self
99 secondLock = False
101 try:
102 for i in xrange(2):
103 if self.lock(blocking = False):
104 try:
105 function(self, *args, **kwargs)
106 finally:
107 self.unlock()
108 return
109 if not secondLock:
110 secondLock = True
111 self.slotList.lock()
112 self.slotList += function
113 finally:
114 if secondLock:
115 self.slotList.unlock()
117 return call()
119 ############################################
120 ##### SIGNAL/SLOT BASE CLASSES SECTION #####
121 ############################################
123 #Class that one must be inherit for send signals
124 class SignalBase(_Syncronized):
125 #Init the class
126 def __init__(self):
127 _Syncronized.__init__(self)
129 self.slots = {}
130 self.setupSignals()
132 #Init the signals
133 def setupSignals(self):
134 pass
136 #Add a signal
137 @_AutoLock
138 def addSignal(self, signal):
139 if signal in self.slots:
140 raise ValueError
142 self.slots[signal] = []
144 #Remove a signal
145 @_AutoLock
146 def removeSignal(self, signal):
147 try:
148 del self.slots[signal]
149 except KeyError:
150 raise ValueError
152 #Emit a function
153 @_AutoLock
154 def emitOne(self, signal, function, args, kwargs):
155 #If the function don't match skip to the next
156 f = delayCaller(function)
157 if not matchingFunction(f, args, kwargs):
158 return
160 #Try the function call and append the result in r
161 try:
162 r.append(f(*args, **kwargs))
164 #If the function send SlotIgnore ignore it
165 except SlotIgnore:
166 pass
168 #For exception management
169 def emitOneProxy(self, signal, function, n, args, kwargs):
170 try:
171 self.emitOne(signal, function, args, kwargs)
172 except:
173 pass
175 #Try all the functions (slots) associated with a signal
176 @_AutoLock
177 def emit(self, signal, *args, **kwargs):
178 #If the signal don't exist send an exception
179 try:
180 l = self.slots[signal]
181 except KeyError:
182 raise ValueError
184 #For each function
185 for n, f in enumerate(l):
186 self.emitOneProxy(signal, f, n, args, kwargs)
188 #Class that one must be inherit for receive signals
189 class SlotBase(_Syncronized):
190 def __init__(self):
191 #Support class for the list of slots
192 class slotListClass(list, _Syncronized):
193 def __init__(self):
194 list.__init__(self)
195 _Syncronized.__init__(self)
196 self.__mutex = _Lock()
198 _Syncronized.__init__(self)
199 self.slotList = slotListClass()
200 self.__locknums = 0
202 #Rederinition for __locknums
203 def lock(self, blocking = 1):
204 r = _Syncronized.lock(self, blocking)
205 self.__locknums += 1
206 return r
208 #For exception management
209 def proxyUnlock(self, f, args, kwargs):
210 try:
211 f(self, *args, **kwargs)
212 except:
213 pass
215 #Unlock the class executing the waiting slots
216 def unlock(self):
217 slotlock = False
218 self.__locknums -= 1
219 try:
220 if not self.__locknums:
221 self.slotList.lock()
222 slotLock = True
224 for f, args, kwargs in self.slotList:
225 self.proxyUnlock(f, self, *args, **kwargs)
227 finally:
228 _Syncronized.unlock(self)
229 self.slotList.unlock()
231 #Class that generate and capture signals
232 class SignalSlotBase(SignalBase, SlotBase):
233 def __init__(self):
234 import PluginLoader
236 SignalBase.__init__(self)
237 SlotBase.__init__(self)
238 PluginLoader.Loader().registerObject(self)
240 #A slot for regen the signals
241 @_AutoLock
242 def renewSlots(self):
243 self.slots = {}
244 self.setupSignals()
246 #A slot called when the plugins are reloaded
247 @_AutoLock
248 def reloadPlugins(self):
249 self.renewSlots()
251 ########################
252 ##### TEST SECTION #####
253 ########################
255 if __name__ == '__main__':
256 class Try1(SignalSlotBase):
257 def setupSignals(self):
258 self.addSignal('ba')
260 class Try2(SlotBase):
261 def fun1(a, b):
262 print '1'
264 def fun2(a, b, c = None):
265 print '2'
267 t1 = Try1()
268 t2 = Try2()
269 t1.slots['ba'] += [t2.fun1, t2.fun2]
271 t1.emit('ba', 1, b = 2)