* registry.py: Fixed bug #435282, Wrong instance variable name
[platform/core/uifw/at-spi2-atk.git] / pyatspi / utils.py
1 '''
2 Utility functions for AT-SPI for querying interfaces, searching the hierarchy,
3 converting constants to strings, and so forth.
4
5 @author: Peter Parente
6 @organization: IBM Corporation
7 @copyright: Copyright (c) 2005, 2007 IBM Corporation
8 @license: LGPL
9
10 This library is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public
12 License as published by the Free Software Foundation; either
13 version 2 of the License, or (at your option) any later version.
14
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 Library General Public License for more details.
19
20 You should have received a copy of the GNU Library General Public
21 License along with this library; if not, write to the
22 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 Boston, MA 02111-1307, USA.
24
25 Portions of this code originally licensed and copyright (c) 2005, 2007
26 IBM Corporation under the BSD license, available at
27 U{http://www.opensource.org/licenses/bsd-license.php}
28 '''
29 import Accessibility__POA
30
31 def getInterfaceIID(cls):
32   '''
33   Gets the ID of an interface class in string format for use in queryInterface.
34   
35   @param cls: Class representing an AT-SPI interface
36   @type cls: class
37   @return: IID for the interface
38   @rtype: string
39   @raise AttributeError: When the parameter does not provide typecode info
40   '''
41   return cls.__typecode__.repo_id
42
43 def getInterfaceName(cls):
44   '''
45   Gets the human readable name of an interface class in string format.
46   
47   @param cls: Class representing an AT-SPI interface
48   @type cls: class
49   @return: Name of the interface
50   @rtype: string
51   @raise AttributeError: When the parameter does not provide typecode info
52   '''
53   return cls.__typecode__.name
54
55 # we're importing here to avoid cyclic importants; constants relies on the
56 # two functions above
57 import constants
58
59 def stringToConst(prefix, suffix):
60   '''
61   Maps a string name to an AT-SPI constant. The rules for the mapping are as 
62   follows:
63     - The prefix is captalized and has an _ appended to it.
64     - All spaces in the suffix are mapped to the _ character. 
65     - All alpha characters in the suffix are mapped to their uppercase.
66     
67   The resulting name is used with getattr to look up a constant with that name
68   in the L{constants} module. If such a constant does not exist, the string
69   suffix is returned instead.
70
71   This method allows strings to be used to refer to roles, relations, etc. 
72   without direct access to the constants. It also supports the future expansion
73   of roles, relations, etc. by allowing arbitrary strings which may or may not
74   map to the current standard set of roles, relations, etc., but may still match
75   some non-standard role, relation, etc. being reported by an application.
76   
77   @param prefix: Prefix of the constant name such as role, relation, state, 
78     text, modifier, key
79   @type prefix: string
80   @param suffix: Name of the role, relation, etc. to use to lookup the constant
81   @type suffix: string
82   @return: The matching constant value
83   @rtype: object
84   '''
85   name = prefix.upper()+'_'+suffix.upper().replace(' ', '_')
86   return getattr(constants, name, suffix)
87
88 def stateToString(value):
89   '''
90   Converts a state value to a string based on the name of the state constant in 
91   the L{constants} module that has the given value.
92   
93   @param value: An AT-SPI state
94   @type value: Accessibility.StateType
95   @return: Human readable, untranslated name of the state
96   @rtype: string
97   '''
98   return constants.STATE_VALUE_TO_NAME.get(value)
99
100 def relationToString(value):
101   '''
102   Converts a relation value to a string based on the name of the state constant
103   in the L{constants} module that has the given value.
104   
105   @param value: An AT-SPI relation
106   @type value: Accessibility.RelationType
107   @return: Human readable, untranslated name of the relation
108   @rtype: string
109   '''
110   return constants.RELATION_VALUE_TO_NAME.get(value)
111
112 def allModifiers():
113   '''
114   Generates all possible keyboard modifiers for use with 
115   L{registry.Registry.registerKeystrokeListener}.
116   '''
117   mask = 0
118   while mask <= (1 << constants.MODIFIER_NUMLOCK):
119     yield mask
120     mask += 1
121
122 def findDescendant(acc, pred, breadth_first=False):
123   '''
124   Searches for a descendant node satisfying the given predicate starting at 
125   this node. The search is performed in depth-first order by default or
126   in breadth first order if breadth_first is True. For example,
127   
128   my_win = findDescendant(lambda x: x.name == 'My Window')
129   
130   will search all descendants of node until one is located with the name 'My
131   Window' or all nodes are exausted. Calls L{_findDescendantDepth} or
132   L{_findDescendantBreadth} to start the recursive search.
133   
134   @param acc: Root accessible of the search
135   @type acc: Accessibility.Accessible
136   @param pred: Search predicate returning True if accessible matches the 
137       search criteria or False otherwise
138   @type pred: callable
139   @param breadth_first: Search breadth first (True) or depth first (False)?
140   @type breadth_first: boolean
141   @return: Accessible matching the criteria or None if not found
142   @rtype: Accessibility.Accessible or None
143   '''
144   if breadth_first:
145     return _findDescendantBreadth(acc, pred)
146
147   for child in acc:
148     try:
149       ret = _findDescendantDepth(acc, pred)
150     except Exception:
151       ret = None
152     if ret is not None: return ret
153
154 def _findDescendantBreadth(acc, pred):
155   '''    
156   Internal function for locating one descendant. Called by L{findDescendant} to
157   start the search.
158   
159   @param acc: Root accessible of the search
160   @type acc: Accessibility.Accessible
161   @param pred: Search predicate returning True if accessible matches the 
162       search criteria or False otherwise
163   @type pred: callable
164   @return: Matching node or None to keep searching
165   @rtype: Accessibility.Accessible or None
166   '''
167   for child in acc:
168     try:
169       if pred(child): return child
170     except Exception:
171       pass
172   for child in acc:
173     try:
174       ret = _findDescedantBreadth(child, pred)
175     except Exception:
176       ret = None
177     if ret is not None: return ret
178
179 def _findDescendantDepth(acc, pred):
180   '''
181   Internal function for locating one descendant. Called by L{findDescendant} to
182   start the search.
183
184   @param acc: Root accessible of the search
185   @type acc: Accessibility.Accessible
186   @param pred: Search predicate returning True if accessible matches the 
187     search criteria or False otherwise
188   @type pred: callable
189   @return: Matching node or None to keep searching
190   @rtype: Accessibility.Accessible or None
191   '''
192   try:
193     if pred(acc): return acc
194   except Exception:
195     pass
196   for child in acc:
197     try:
198       ret = _findDescendantDepth(child, pred)
199     except Exception:
200       ret = None
201     if ret is not None: return ret
202     
203 def findAllDescendants(acc, pred):
204   '''
205   Searches for all descendant nodes satisfying the given predicate starting at 
206   this node. Does an in-order traversal. For example,
207   
208   pred = lambda x: x.getRole() == pyatspi.ROLE_PUSH_BUTTON
209   buttons = pyatspi.findAllDescendants(node, pred)
210   
211   will locate all push button descendants of node.
212   
213   @param acc: Root accessible of the search
214   @type acc: Accessibility.Accessible
215   @param pred: Search predicate returning True if accessible matches the 
216       search criteria or False otherwise
217   @type pred: callable
218   @return: All nodes matching the search criteria
219   @rtype: list
220   '''
221   matches = []
222   _findAllDescendants(acc, pred, matches)
223   return matches
224
225 def _findAllDescendants(acc, pred, matches):
226   '''
227   Internal method for collecting all descendants. Reuses the same matches
228   list so a new one does not need to be built on each recursive step.
229   '''
230   for child in acc:
231     try:
232       if pred(child): matches.append(child)
233     except Exception:
234       pass
235     findAllDescendants(child, pred, matches)
236   
237 def findAncestor(acc, pred):
238   '''
239   Searches for an ancestor satisfying the given predicate. Note that the
240   AT-SPI hierarchy is not always doubly linked. Node A may consider node B its
241   child, but B is not guaranteed to have node A as its parent (i.e. its parent
242   may be set to None). This means some searches may never make it all the way
243   up the hierarchy to the desktop level.
244   
245   @param acc: Starting accessible object
246   @type acc: Accessibility.Accessible
247   @param pred: Search predicate returning True if accessible matches the 
248     search criteria or False otherwise
249   @type pred: callable
250   @return: Node matching the criteria or None if not found
251   @rtype: Accessibility.Accessible
252   '''
253   if acc is None:
254     # guard against bad start condition
255     return None
256   while 1:
257     if acc.parent is None:
258       # stop if there is no parent and we haven't returned yet
259       return None
260     try:
261       if pred(acc.parent): return acc.parent
262     except Exception:
263       pass
264     # move to the parent
265     acc = acc.parent
266
267 def getPath(acc):
268   '''
269   Gets the path from the application ancestor to the given accessible in
270   terms of its child index at each level.
271   
272   @param acc: Target accessible
273   @type acc: Accessibility.Accessible
274   @return: Path to the target
275   @rtype: list of integer
276   @raise LookupError: When the application accessible cannot be reached
277   '''
278   path = []
279   while 1:
280     if acc.parent is None:
281       path.reverse()
282       return path
283     try:
284       path.append(acc.getIndexInParent())
285     except Exception:
286       raise LookupError
287     acc = acc.parent
288
289 class StateSet(Accessibility__POA.StateSet):
290   '''
291   Convenience implementation of AT-SPI StateSet, for future use with Collection
292   interface.
293   
294   @param states: Set of states
295   @type states: set
296   '''
297   def __init__(self, *states):
298     '''Initializes the state set with the given states.'''
299     self.states = set(states)
300     
301   def contains(self, state):
302     '''
303     Checks if this L{StateSet} contains the given state.
304     
305     @param state: State to check
306     @type state: Accessibility.StateType
307     @return: True if the set contains the given state
308     @rtype: boolean
309     '''
310     return state in self.states
311   
312   def add(self, *state):
313     '''
314     Adds one or more states to this set.
315     
316     @param state: State(s) to add
317     @type state: Accessibility.StateType
318     '''
319     self.states.add(state)
320   
321   def remove(self, *state):
322     '''
323     Removes one or more states from this set.
324     
325     @param state: State(s) to remove
326     @type state: Accessibility.StateType
327     '''
328     self.states.remove(state)
329   
330   def equals(self, state_set):
331     '''
332     Checks if this L{StateSet} contains exactly the same members as the given
333     L{StateSet}.
334     
335     @param state_set: Another set
336     @type state_set: L{StateSet}
337     @return: Are the sets equivalent in terms of their contents?
338     @rtype: boolean
339     '''
340     return self.state_set == self.states
341   
342   def compare(self, state_set):
343     '''
344     Computes the symmetric differences of this L{StateSet} and the given
345     L{StateSet}.
346     
347     @param state_set: Another set
348     @type state_set: L{StateSet}
349     @return: Elements in only one of the two sets
350     @rtype: L{StateSet}
351     '''
352     diff = self.states.symmetric_difference(state_set.states)
353     return StateSet(*diff)
354   
355   def isEmpty(self):
356     '''
357     Checks if this L{StateSet} is empty.
358     
359     @return: Is it empty?
360     @rtype: boolean
361     '''
362     return len(self.states) == 0
363
364   def getStates(self):
365     '''
366     Gets the sequence of all states in this set.
367     
368     @return: List of states
369     @rtype: list
370     '''
371     return list(self.states)