3f0e8505ba977918bac22f354cd9982677914199
[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 def getInterfaceIID(cls):
30   '''
31   Gets the ID of an interface class in string format for use in queryInterface.
32   
33   @param cls: Class representing an AT-SPI interface
34   @type cls: class
35   @return: IID for the interface
36   @rtype: string
37   @raise AttributeError: When the parameter does not provide typecode info
38   '''
39   return cls.__typecode__.repo_id
40
41 def getInterfaceName(cls):
42   '''
43   Gets the human readable name of an interface class in string format.
44   
45   @param cls: Class representing an AT-SPI interface
46   @type cls: class
47   @return: Name of the interface
48   @rtype: string
49   @raise AttributeError: When the parameter does not provide typecode info
50   '''
51   return cls.__typecode__.name
52
53 # we're importing here to avoid cyclic importants; constants relies on the
54 # two functions above
55 import constants
56
57 def stringToConst(prefix, suffix):
58   '''
59   Maps a string name to an AT-SPI constant. The rules for the mapping are as 
60   follows:
61     - The prefix is captalized and has an _ appended to it.
62     - All spaces in the suffix are mapped to the _ character. 
63     - All alpha characters in the suffix are mapped to their uppercase.
64     
65   The resulting name is used with getattr to look up a constant with that name
66   in the L{pyLinAcc.Constants} module. If such a constant does not exist, the
67   string suffix is returned instead. 
68
69   This method allows strings to be used to refer to roles, relations, etc. 
70   without direct access to the constants. It also supports the future expansion
71   of roles, relations, etc. by allowing arbitrary strings which may or may not
72   map to the current standard set of roles, relations, etc., but may still match
73   some non-standard role, relation, etc. being reported by an application.
74   
75   @param prefix: Prefix of the constant name such as role, relation, state, 
76     text, modifier, key
77   @type prefix: string
78   @param suffix: Name of the role, relation, etc. to use to lookup the constant
79   @type suffix: string
80   @return: The matching constant value
81   @rtype: object
82   '''
83   name = prefix.upper()+'_'+suffix.upper().replace(' ', '_')
84   return getattr(constants, name, suffix)
85
86 def stateToString(value):
87   '''
88   Converts a state value to a string based on the name of the state constant in 
89   the L{Constants} module that has the given value.
90   
91   @param value: An AT-SPI state
92   @type value: Accessibility.StateType
93   @return: Human readable, untranslated name of the state
94   @rtype: string
95   '''
96   return constants.STATE_VALUE_TO_NAME.get(value)
97
98 def relationToString(value):
99   '''
100   Converts a relation value to a string based on the name of the state constant
101   in the L{Constants} module that has the given value.
102   
103   @param value: An AT-SPI relation
104   @type value: Accessibility.RelationType
105   @return: Human readable, untranslated name of the relation
106   @rtype: string
107   '''
108   return constants.RELATION_VALUE_TO_NAME.get(value)
109
110 def allModifiers():
111   '''
112   Generates all possible keyboard modifiers for use with 
113   L{Registry.Registry.registerKeystrokeListener}.
114   '''
115   mask = 0
116   while mask <= (1 << constants.MODIFIER_NUMLOCK):
117     yield mask
118     mask += 1
119
120 def findDescendant(acc, pred, breadth_first=False):
121   '''
122   Searches for a descendant node satisfying the given predicate starting at 
123   this node. The search is performed in depth-first order by default or
124   in breadth first order if breadth_first is True. For example,
125   
126   my_win = findDescendant(lambda x: x.name == 'My Window')
127   
128   will search all descendants of node until one is located with the name 'My
129   Window' or all nodes are exausted. Calls L{_findDescendantDepth} or
130   L{_findDescendantBreadth} to start the recursive search.
131   
132   @param acc: Root accessible of the search
133   @type acc: Accessibility.Accessible
134   @param pred: Search predicate returning True if accessible matches the 
135       search criteria or False otherwise
136   @type pred: callable
137   @param breadth_first: Search breadth first (True) or depth first (False)?
138   @type breadth_first: boolean
139   @return: Accessible matching the criteria or None if not found
140   @rtype: Accessibility.Accessible or None
141   '''
142   if breadth_first:
143     return _findDescendantBreadth(acc, pred)
144
145   for child in acc:
146     try:
147       ret = _findDescendantDepth(acc, pred)
148     except Exception:
149       ret = None
150     if ret is not None: return ret
151
152 def _findDescendantBreadth(acc, pred):
153   '''
154   Internal function for locating one descendant. Called by 
155   L{AccessibleMixin.findDescendant} to start the search.
156   
157   @param acc: Root accessible of the search
158   @type acc: Accessibility.Accessible
159   @param pred: Search predicate returning True if accessible matches the 
160       search criteria or False otherwise
161   @type pred: callable
162   @return: Matching node or None to keep searching
163   @rtype: Accessibility.Accessible or None
164   '''
165   for child in acc:
166     try:
167       if pred(child): return child
168     except Exception:
169       pass
170   for child in acc:
171     try:
172       ret = _findDescedantBreadth(child, pred)
173     except Exception:
174       ret = None
175     if ret is not None: return ret
176
177 def _findDescendantDepth(acc, pred):
178   '''
179   Internal function for locating one descendant. Called by 
180   L{AccessibleMixin.findDescendant} to start the search.
181
182   @param acc: Root accessible of the search
183   @type acc: Accessibility.Accessible
184   @param pred: Search predicate returning True if accessible matches the 
185     search criteria or False otherwise
186   @type pred: callable
187   @return: Matching node or None to keep searching
188   @rtype: Accessibility.Accessible or None
189   '''
190   try:
191     if pred(acc): return acc
192   except Exception:
193     pass
194   for child in acc:
195     try:
196       ret = _findDescendantDepth(child, pred)
197     except Exception:
198       ret = None
199     if ret is not None: return ret
200     
201 def findAllDescendants(acc, pred):
202   '''
203   Searches for all descendant nodes satisfying the given predicate starting at 
204   this node. Does an in-order traversal. For example,
205   
206   pred = lambda x: x.getRole() == pyatspi.ROLE_PUSH_BUTTON
207   buttons = pyatspi.findAllDescendants(node, pred)
208   
209   will locate all push button descendants of node.
210   
211   @param acc: Root accessible of the search
212   @type acc: Accessibility.Accessible
213   @param pred: Search predicate returning True if accessible matches the 
214       search criteria or False otherwise
215   @type pred: callable
216   @return: All nodes matching the search criteria
217   @rtype: list
218   '''
219   matches = []
220   _findAllDescendants(acc, pred, matches)
221   return matches
222
223 def _findAllDescendants(acc, pred, matches):
224   '''
225   Internal method for collecting all descendants. Reuses the same matches
226   list so a new one does not need to be built on each recursive step.
227   '''
228   for child in acc:
229     try:
230       if pred(child): matches.append(child)
231     except Exception:
232       pass
233     findAllDescendants(child, pred, matches)
234   
235 def findAncestor(acc, pred):
236   '''
237   Searches for an ancestor satisfying the given predicate. Note that the
238   AT-SPI hierarchy is not always doubly linked. Node A may consider node B its
239   child, but B is not guaranteed to have node A as its parent (i.e. its parent
240   may be set to None). This means some searches may never make it all the way
241   up the hierarchy to the desktop level.
242   
243   @param acc: Starting accessible object
244   @type acc: Accessibility.Accessible
245   @param pred: Search predicate returning True if accessible matches the 
246     search criteria or False otherwise
247   @type pred: callable
248   @return: Node matching the criteria or None if not found
249   @rtype: Accessibility.Accessible
250   '''
251   if acc is None:
252     # guard against bad start condition
253     return None
254   while 1:
255     if acc.parent is None:
256       # stop if there is no parent and we haven't returned yet
257       return None
258     try:
259       if pred(acc.parent): return acc.parent
260     except Exception:
261       pass
262     # move to the parent
263     acc = acc.parent
264
265 def getPath(acc):
266   '''
267   Gets the path from the application ancestor to the given accessible in
268   terms of its child index at each level.
269   
270   @param acc: Target accessible
271   @type acc: Accessibility.Accessible
272   @return: Path to the target
273   @rtype: list of integer
274   @raise LookupError: When the application accessible cannot be reached
275   '''
276   path = []
277   while 1:
278     if acc.parent is None:
279       path.reverse()
280       return path
281     try:
282       path.append(acc.getIndexInParent())
283     except Exception:
284       raise LookupError
285     acc = acc.parent