OSDN Git Service

Merge 2013.1.
[nvdajp/nvdajp.git] / source / baseObject.py
1 #baseObject.py\r
2 #A part of NonVisual Desktop Access (NVDA)\r
3 #Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/>\r
4 #This file is covered by the GNU General Public License.\r
5 #See the file COPYING for more details.\r
6 \r
7 """Contains the base classes that many of NVDA's classes such as NVDAObjects, virtualBuffers, appModules, synthDrivers inherit from. These base classes provide such things as auto properties, and methods and properties for scripting and key binding.\r
8 """\r
9 \r
10 import weakref\r
11 from logHandler import log\r
12 \r
13 class Getter(object):\r
14 \r
15         def __init__(self,fget):\r
16                 self.fget=fget\r
17 \r
18         def __get__(self,instance,owner):\r
19                 if not instance:\r
20                         return self\r
21                 return self.fget(instance)\r
22 \r
23         def setter(self,func):\r
24                 return property(fget=self._func,fset=func)\r
25 \r
26         def deleter(self,func):\r
27                 return property(fget=self._func,fdel=func)\r
28 \r
29 class CachingGetter(Getter):\r
30 \r
31         def __get__(self, instance, owner):\r
32                 if not instance:\r
33                         return self\r
34                 return instance._getPropertyViaCache(self.fget)\r
35 \r
36 class AutoPropertyType(type):\r
37 \r
38         def __init__(self,name,bases,dict):\r
39                 super(AutoPropertyType,self).__init__(name,bases,dict)\r
40 \r
41                 cacheByDefault=False\r
42                 try:\r
43                         cacheByDefault=dict["cachePropertiesByDefault"]\r
44                 except KeyError:\r
45                         cacheByDefault=any(getattr(base, "cachePropertiesByDefault", False) for base in bases)\r
46 \r
47                 props=(x[5:] for x in dict.keys() if x[0:5] in ('_get_','_set_','_del_'))\r
48                 for x in props:\r
49                         g=dict.get('_get_%s'%x,None)\r
50                         s=dict.get('_set_%s'%x,None)\r
51                         d=dict.get('_del_%s'%x,None)\r
52                         if x in dict:\r
53                                 methodsString=",".join([str(i) for i in g,s,d if i])\r
54                                 raise TypeError("%s is already a class attribute, cannot create descriptor with methods %s"%(x,methodsString))\r
55                         if not g:\r
56                                 # There's a setter or deleter, but no getter.\r
57                                 # This means it could be in one of the base classes.\r
58                                 for base in bases:\r
59                                         g = getattr(base,'_get_%s'%x,None)\r
60                                         if g:\r
61                                                 break\r
62 \r
63                         cache=dict.get('_cache_%s'%x,None)\r
64                         if cache is None:\r
65                                 # The cache setting hasn't been specified in this class, but it could be in one of the bases.\r
66                                 for base in bases:\r
67                                         cache = getattr(base,'_cache_%s'%x,None)\r
68                                         if cache is not None:\r
69                                                 break\r
70                                 else:\r
71                                         cache=cacheByDefault\r
72 \r
73                         if g and not s and not d:\r
74                                 setattr(self,x,(CachingGetter if cache else Getter)(g))\r
75                         else:\r
76                                 setattr(self,x,property(fget=g,fset=s,fdel=d))\r
77 \r
78 class AutoPropertyObject(object):\r
79         """A class that dynamicly supports properties, by looking up _get_* and _set_* methods at runtime.\r
80         _get_x will make property x with a getter (you can get its value).\r
81         _set_x will make a property x with a setter (you can set its value).\r
82         If there is a _get_x but no _set_x then setting x will override the property completely.\r
83         Properties can also be cached for the duration of one core pump cycle.\r
84         This is useful if the same property is likely to be fetched multiple times in one cycle. For example, several NVDAObject properties are fetched by both braille and speech.\r
85         Setting _cache_x to C{True} specifies that x should be cached. Setting it to C{False} specifies that it should not be cached.\r
86         If _cache_x is not set, L{cachePropertiesByDefault} is used.\r
87         """\r
88         __metaclass__=AutoPropertyType\r
89 \r
90         #: Tracks the instances of this class; used by L{invalidateCaches}.\r
91         #: @type: weakref.WeakKeyDictionary\r
92         __instances=weakref.WeakKeyDictionary()\r
93         #: Specifies whether properties are cached by default;\r
94         #: can be overridden for individual properties by setting _cache_propertyName.\r
95         #: @type: bool\r
96         cachePropertiesByDefault = False\r
97 \r
98         def __init__(self):\r
99                 #: Maps properties to cached values.\r
100                 #: @type: dict\r
101                 self._propertyCache={}\r
102                 self.__instances[self]=None\r
103 \r
104         def _getPropertyViaCache(self,getterMethod=None):\r
105                 if not getterMethod:\r
106                         raise ValueError("getterMethod is None")\r
107                 try:\r
108                         val=self._propertyCache[getterMethod]\r
109                 except KeyError:\r
110                         val=getterMethod(self)\r
111                         self._propertyCache[getterMethod]=val\r
112                 return val\r
113 \r
114         def invalidateCache(self):\r
115                 self._propertyCache.clear()\r
116 \r
117         @classmethod\r
118         def invalidateCaches(cls):\r
119                 """Invalidate the caches for all current instances.\r
120                 """\r
121                 # We use keys() here instead of iterkeys(), as invalidating the cache on an object may cause instances to disappear,\r
122                 # which would in turn cause an exception due to the dictionary changing size during iteration.\r
123                 for instance in cls.__instances.keys():\r
124                         instance.invalidateCache()\r
125 \r
126 class ScriptableObject(AutoPropertyObject):\r
127         """A class that implements NVDA's scripting interface.\r
128         Input gestures are bound to scripts such that the script will be executed when the appropriate input gesture is received.\r
129         Scripts are methods named with a prefix of C{script_}; e.g. C{script_foo}.\r
130         They accept an L{inputCore.InputGesture} as their single argument.\r
131         Gesture bindings can be specified on the class by creating a C{__gestures} dict which maps gesture identifiers to script names.\r
132         They can also be bound on an instance using the L{bindGesture} method.\r
133         """\r
134 \r
135         def __init__(self):\r
136                 #: Maps input gestures to script functions.\r
137                 #: @type: dict\r
138                 self._gestureMap = {}\r
139                 # Bind gestures specified on the class.\r
140                 for cls in self.__class__.__mro__:\r
141                         try:\r
142                                 self.bindGestures(getattr(cls, "_%s__gestures" % cls.__name__))\r
143                         except AttributeError:\r
144                                 pass\r
145                 super(ScriptableObject, self).__init__()\r
146 \r
147         def bindGesture(self, gestureIdentifier, scriptName):\r
148                 """Bind an input gesture to a script.\r
149                 @param gestureIdentifier: The identifier of the input gesture.\r
150                 @type gestureIdentifier: str\r
151                 @param scriptName: The name of the script, which is the name of the method excluding the C{script_} prefix.\r
152                 @type scriptName: str\r
153                 @raise LookupError: If there is no script with the provided name.\r
154                 """\r
155                 # Don't store the instance method, as this causes a circular reference\r
156                 # and instance methods are meant to be generated on retrieval anyway.\r
157                 func = getattr(self.__class__, "script_%s" % scriptName, None)\r
158                 if not func:\r
159                         raise LookupError("No such script: %s" % func)\r
160                 # Import late to avoid circular import.\r
161                 import inputCore\r
162                 self._gestureMap[inputCore.normalizeGestureIdentifier(gestureIdentifier)] = func\r
163 \r
164         def clearGestureBindings(self):\r
165                 """Remove all input gesture bindings from this object.\r
166                 """\r
167                 self._gestureMap.clear()\r
168 \r
169         def bindGestures(self, gestureMap):\r
170                 """Bind multiple input gestures to scripts.\r
171                 This is a convenience method which simply calls L{bindGesture} for each gesture and script pair, logging any errors.\r
172                 @param gestureMap: A mapping of gesture identifiers to script names.\r
173                 @type gestureMap: dict of str to str\r
174                 """\r
175                 for gestureIdentifier, scriptName in gestureMap.iteritems():\r
176                         try:\r
177                                 self.bindGesture(gestureIdentifier, scriptName)\r
178                         except LookupError:\r
179                                 log.error("Error binding script %s in %r" % (scriptName, self))\r
180 \r
181         def getScript(self,gesture):\r
182                 """Retrieve the script bound to a given gesture.\r
183                 @param gesture: The input gesture in question.\r
184                 @type gesture: L{inputCore.InputGesture}\r
185                 @return: The script function or C{None} if none was found.\r
186                 @rtype: script function\r
187                 """ \r
188                 for identifier in gesture.identifiers:\r
189                         try:\r
190                                 # Convert to instance method.\r
191                                 return self._gestureMap[identifier].__get__(self, self.__class__)\r
192                         except KeyError:\r
193                                 continue\r
194                 else:\r
195                         return None\r
196 \r
197         #: A value for sleepMode which indicates that NVDA should fully sleep for this object;\r
198         #: i.e. braille and speech via NVDA controller client is disabled and the user cannot disable sleep mode.\r
199         SLEEP_FULL = "full"\r