* Fixed bug #436949, Need util to list all supported interfaces
[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(obj):
32   '''
33   Gets the ID of an interface class or object in string format for use in
34   queryInterface.
35   
36   @param obj: Class representing an AT-SPI interface or instance
37   @type obj: object
38   @return: IID for the interface
39   @rtype: string
40   @raise AttributeError: When the parameter does not provide typecode info
41   '''
42   return obj.__typecode__.repo_id
43
44 def getInterfaceName(obj):
45   '''
46   Gets the human readable name of an interface class or object in string
47   format.
48   
49   @param obj: Class representing an AT-SPI interface or instance
50   @type obj: class
51   @return: Name of the interface
52   @rtype: string
53   @raise AttributeError: When the parameter does not provide typecode info
54   '''
55   return obj.__typecode__.name
56
57 # we're importing here to avoid cyclic importants; constants relies on the
58 # two functions above
59 import constants
60
61 def listInterfaces(obj):
62   '''
63   Gets a list of the names of all interfaces supported by this object. The
64   names are the short-hand interface names like "Accessible" and "Component",
65   not the full interface identifiers.
66
67   @param obj: Arbitrary object to query for all accessibility related
68   interfaces. Must provide a queryInterface method.
69   @type obj: object
70   @return: Set of supported interface names
71   @rtype: set
72   @raise AttributeError: If the object provide does not implement
73   queryInterface
74   '''
75   names = set()
76   for ic in constants.ALL_INTERFACES:
77     io = obj.queryInterface(getInterfaceIID(ic))
78     if io is None:
79       continue
80     names.add(getInterfaceName(ic))
81   return names
82
83 def stringToConst(prefix, suffix):
84   '''
85   Maps a string name to an AT-SPI constant. The rules for the mapping are as 
86   follows:
87     - The prefix is captalized and has an _ appended to it.
88     - All spaces in the suffix are mapped to the _ character. 
89     - All alpha characters in the suffix are mapped to their uppercase.
90     
91   The resulting name is used with getattr to look up a constant with that name
92   in the L{constants} module. If such a constant does not exist, the string
93   suffix is returned instead.
94
95   This method allows strings to be used to refer to roles, relations, etc.
96   without direct access to the constants. It also supports the future expansion
97   of roles, relations, etc. by allowing arbitrary strings which may or may not
98   map to the current standard set of roles, relations, etc., but may still
99   match some non-standard role, relation, etc. being reported by an
100   application.
101   
102   @param prefix: Prefix of the constant name such as role, relation, state, 
103     text, modifier, key
104   @type prefix: string
105   @param suffix: Name of the role, relation, etc. to use to lookup the constant
106   @type suffix: string
107   @return: The matching constant value
108   @rtype: object
109   '''
110   name = prefix.upper()+'_'+suffix.upper().replace(' ', '_')
111   return getattr(constants, name, suffix)
112
113 def stateToString(value):
114   '''
115   Converts a state value to a string based on the name of the state constant in
116   the L{constants} module that has the given value.
117   
118   @param value: An AT-SPI state
119   @type value: Accessibility.StateType
120   @return: Human readable, untranslated name of the state
121   @rtype: string
122   '''
123   return constants.STATE_VALUE_TO_NAME.get(value)
124
125 def relationToString(value):
126   '''
127   Converts a relation value to a string based on the name of the state constant
128   in the L{constants} module that has the given value.
129   
130   @param value: An AT-SPI relation
131   @type value: Accessibility.RelationType
132   @return: Human readable, untranslated name of the relation
133   @rtype: string
134   '''
135   return constants.RELATION_VALUE_TO_NAME.get(value)
136
137 def allModifiers():
138   '''
139   Generates all possible keyboard modifiers for use with 
140   L{registry.Registry.registerKeystrokeListener}.
141   '''
142   mask = 0
143   while mask <= (1 << constants.MODIFIER_NUMLOCK):
144     yield mask
145     mask += 1
146
147 def findDescendant(acc, pred, breadth_first=False):
148   '''
149   Searches for a descendant node satisfying the given predicate starting at 
150   this node. The search is performed in depth-first order by default or
151   in breadth first order if breadth_first is True. For example,
152   
153   my_win = findDescendant(lambda x: x.name == 'My Window')
154   
155   will search all descendants of x until one is located with the name 'My
156   Window' or all nodes are exausted. Calls L{_findDescendantDepth} or
157   L{_findDescendantBreadth} to start the recursive 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   @param breadth_first: Search breadth first (True) or depth first (False)?
165   @type breadth_first: boolean
166   @return: Accessible matching the criteria or None if not found
167   @rtype: Accessibility.Accessible or None
168   '''
169   if breadth_first:
170     return _findDescendantBreadth(acc, pred)
171
172   for child in acc:
173     try:
174       ret = _findDescendantDepth(acc, pred)
175     except Exception:
176       ret = None
177     if ret is not None: return ret
178
179 def _findDescendantBreadth(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   for child in acc:
193     try:
194       if pred(child): return child
195     except Exception:
196       pass
197   for child in acc:
198     try:
199       ret = _findDescedantBreadth(child, pred)
200     except Exception:
201       ret = None
202     if ret is not None: return ret
203
204 def _findDescendantDepth(acc, pred):
205   '''
206   Internal function for locating one descendant. Called by L{findDescendant} to
207   start the search.
208
209   @param acc: Root accessible of the search
210   @type acc: Accessibility.Accessible
211   @param pred: Search predicate returning True if accessible matches the 
212     search criteria or False otherwise
213   @type pred: callable
214   @return: Matching node or None to keep searching
215   @rtype: Accessibility.Accessible or None
216   '''
217   try:
218     if pred(acc): return acc
219   except Exception:
220     pass
221   for child in acc:
222     try:
223       ret = _findDescendantDepth(child, pred)
224     except Exception:
225       ret = None
226     if ret is not None: return ret
227     
228 def findAllDescendants(acc, pred):
229   '''
230   Searches for all descendant nodes satisfying the given predicate starting at 
231   this node. Does an in-order traversal. For example,
232   
233   pred = lambda x: x.getRole() == pyatspi.ROLE_PUSH_BUTTON
234   buttons = pyatspi.findAllDescendants(node, pred)
235   
236   will locate all push button descendants of node.
237   
238   @param acc: Root accessible of the search
239   @type acc: Accessibility.Accessible
240   @param pred: Search predicate returning True if accessible matches the 
241       search criteria or False otherwise
242   @type pred: callable
243   @return: All nodes matching the search criteria
244   @rtype: list
245   '''
246   matches = []
247   _findAllDescendants(acc, pred, matches)
248   return matches
249
250 def _findAllDescendants(acc, pred, matches):
251   '''
252   Internal method for collecting all descendants. Reuses the same matches
253   list so a new one does not need to be built on each recursive step.
254   '''
255   for child in acc:
256     try:
257       if pred(child): matches.append(child)
258     except Exception:
259       pass
260     findAllDescendants(child, pred, matches)
261   
262 def findAncestor(acc, pred):
263   '''
264   Searches for an ancestor satisfying the given predicate. Note that the
265   AT-SPI hierarchy is not always doubly linked. Node A may consider node B its
266   child, but B is not guaranteed to have node A as its parent (i.e. its parent
267   may be set to None). This means some searches may never make it all the way
268   up the hierarchy to the desktop level.
269   
270   @param acc: Starting accessible object
271   @type acc: Accessibility.Accessible
272   @param pred: Search predicate returning True if accessible matches the 
273     search criteria or False otherwise
274   @type pred: callable
275   @return: Node matching the criteria or None if not found
276   @rtype: Accessibility.Accessible
277   '''
278   if acc is None:
279     # guard against bad start condition
280     return None
281   while 1:
282     if acc.parent is None:
283       # stop if there is no parent and we haven't returned yet
284       return None
285     try:
286       if pred(acc.parent): return acc.parent
287     except Exception:
288       pass
289     # move to the parent
290     acc = acc.parent
291
292 def getPath(acc):
293   '''
294   Gets the path from the application ancestor to the given accessible in
295   terms of its child index at each level.
296   
297   @param acc: Target accessible
298   @type acc: Accessibility.Accessible
299   @return: Path to the target
300   @rtype: list of integer
301   @raise LookupError: When the application accessible cannot be reached
302   '''
303   path = []
304   while 1:
305     if acc.parent is None:
306       path.reverse()
307       return path
308     try:
309       path.append(acc.getIndexInParent())
310     except Exception:
311       raise LookupError
312     acc = acc.parent
313
314 class StateSet(Accessibility__POA.StateSet):
315   '''
316   Convenience implementation of AT-SPI StateSet, for future use with Collection
317   interface.
318   
319   @param states: Set of states
320   @type states: set
321   '''
322   def __init__(self, *states):
323     '''Initializes the state set with the given states.'''
324     self.states = set(states)
325     
326   def contains(self, state):
327     '''
328     Checks if this L{StateSet} contains the given state.
329     
330     @param state: State to check
331     @type state: Accessibility.StateType
332     @return: True if the set contains the given state
333     @rtype: boolean
334     '''
335     return state in self.states
336   
337   def add(self, *state):
338     '''
339     Adds one or more states to this set.
340     
341     @param state: State(s) to add
342     @type state: Accessibility.StateType
343     '''
344     self.states.add(state)
345   
346   def remove(self, *state):
347     '''
348     Removes one or more states from this set.
349     
350     @param state: State(s) to remove
351     @type state: Accessibility.StateType
352     '''
353     self.states.remove(state)
354   
355   def equals(self, state_set):
356     '''
357     Checks if this L{StateSet} contains exactly the same members as the given
358     L{StateSet}.
359     
360     @param state_set: Another set
361     @type state_set: L{StateSet}
362     @return: Are the sets equivalent in terms of their contents?
363     @rtype: boolean
364     '''
365     return self.state_set == self.states
366   
367   def compare(self, state_set):
368     '''
369     Computes the symmetric differences of this L{StateSet} and the given
370     L{StateSet}.
371     
372     @param state_set: Another set
373     @type state_set: L{StateSet}
374     @return: Elements in only one of the two sets
375     @rtype: L{StateSet}
376     '''
377     diff = self.states.symmetric_difference(state_set.states)
378     return StateSet(*diff)
379   
380   def isEmpty(self):
381     '''
382     Checks if this L{StateSet} is empty.
383     
384     @return: Is it empty?
385     @rtype: boolean
386     '''
387     return len(self.states) == 0
388
389   def getStates(self):
390     '''
391     Gets the sequence of all states in this set.
392     
393     @return: List of states
394     @rtype: list
395     '''
396     return list(self.states)