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
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):
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
)
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
71 #Enumerate the positional key params
72 for i
, p
in enumerate(specs
[0]):
73 #For each key param contained in the positional params
75 #If is already declared continue to the next function
76 if positional_params
[i
]:
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]):
85 #If the function have unknown keywords parameters continue to the next function
86 if not specs
[2] and len(kwargs
) != c
:
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
):
98 self
= function
.im_self
103 if self
.lock(blocking
= False):
105 function(self
, *args
, **kwargs
)
112 self
.slotList
+= function
115 self
.slotList
.unlock()
119 ############################################
120 ##### SIGNAL/SLOT BASE CLASSES SECTION #####
121 ############################################
123 #Class that one must be inherit for send signals
124 class SignalBase(_Syncronized
):
127 _Syncronized
.__init
__(self
)
133 def setupSignals(self
):
138 def addSignal(self
, signal
):
139 if signal
in self
.slots
:
142 self
.slots
[signal
] = []
146 def removeSignal(self
, signal
):
148 del self
.slots
[signal
]
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
):
160 #Try the function call and append the result in r
162 r
.append(f(*args
, **kwargs
))
164 #If the function send SlotIgnore ignore it
168 #For exception management
169 def emitOneProxy(self
, signal
, function
, n
, args
, kwargs
):
171 self
.emitOne(signal
, function
, args
, kwargs
)
175 #Try all the functions (slots) associated with a signal
177 def emit(self
, signal
, *args
, **kwargs
):
178 #If the signal don't exist send an exception
180 l
= self
.slots
[signal
]
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
):
191 #Support class for the list of slots
192 class slotListClass(list, _Syncronized
):
195 _Syncronized
.__init
__(self
)
196 self
.__mutex
= _Lock()
198 _Syncronized
.__init
__(self
)
199 self
.slotList
= slotListClass()
202 #Rederinition for __locknums
203 def lock(self
, blocking
= 1):
204 r
= _Syncronized
.lock(self
, blocking
)
208 #For exception management
209 def proxyUnlock(self
, f
, args
, kwargs
):
211 f(self
, *args
, **kwargs
)
215 #Unlock the class executing the waiting slots
220 if not self
.__locknums
:
224 for f
, args
, kwargs
in self
.slotList
:
225 self
.proxyUnlock(f
, self
, *args
, **kwargs
)
228 _Syncronized
.unlock(self
)
229 self
.slotList
.unlock()
231 #Class that generate and capture signals
232 class SignalSlotBase(SignalBase
, SlotBase
):
236 SignalBase
.__init
__(self
)
237 SlotBase
.__init
__(self
)
238 PluginLoader
.Loader().registerObject(self
)
240 #A slot for regen the signals
242 def renewSlots(self
):
246 #A slot called when the plugins are reloaded
248 def reloadPlugins(self
):
251 ########################
252 ##### TEST SECTION #####
253 ########################
255 if __name__
== '__main__':
256 class Try1(SignalSlotBase
):
257 def setupSignals(self
):
260 class Try2(SlotBase
):
264 def fun2(a
, b
, c
= None):
269 t1
.slots
['ba'] += [t2
.fun1
, t2
.fun2
]
271 t1
.emit('ba', 1, b
= 2)