2 Utility functions for AT-SPI for querying interfaces, searching the hierarchy,
3 converting constants to strings, and so forth.
6 @organization: IBM Corporation
7 @copyright: Copyright (c) 2005, 2007 IBM Corporation
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.
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.
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.
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}
30 import Accessibility__POA
32 def getInterfaceIID(obj):
34 Gets the ID of an interface class or object in string format for use in
37 @param obj: Class representing an AT-SPI interface or instance
39 @return: IID for the interface
41 @raise AttributeError: When the parameter does not provide typecode info
43 return obj.__typecode__.repo_id
45 def getInterfaceName(obj):
47 Gets the human readable name of an interface class or object in string
50 @param obj: Class representing an AT-SPI interface or instance
52 @return: Name of the interface
54 @raise AttributeError: When the parameter does not provide typecode info
56 return obj.__typecode__.name
58 # we're importing here to avoid cyclic importants; constants relies on the
62 def listInterfaces(obj):
64 Gets a list of the names of all interfaces supported by this object. The
65 names are the short-hand interface names like "Accessible" and "Component",
66 not the full interface identifiers.
68 @param obj: Arbitrary object to query for all accessibility related
69 interfaces. Must provide a queryInterface method.
71 @return: Set of supported interface names
73 @raise AttributeError: If the object provide does not implement
77 for ic in constants.ALL_INTERFACES:
78 io = obj.queryInterface(getInterfaceIID(ic))
81 names.add(getInterfaceName(ic))
84 def stringToConst(prefix, suffix):
86 Maps a string name to an AT-SPI constant. The rules for the mapping are as
88 - The prefix is captalized and has an _ appended to it.
89 - All spaces in the suffix are mapped to the _ character.
90 - All alpha characters in the suffix are mapped to their uppercase.
92 The resulting name is used with getattr to look up a constant with that name
93 in the L{constants} module. If such a constant does not exist, the string
94 suffix is returned instead.
96 This method allows strings to be used to refer to roles, relations, etc.
97 without direct access to the constants. It also supports the future expansion
98 of roles, relations, etc. by allowing arbitrary strings which may or may not
99 map to the current standard set of roles, relations, etc., but may still
100 match some non-standard role, relation, etc. being reported by an
103 @param prefix: Prefix of the constant name such as role, relation, state,
106 @param suffix: Name of the role, relation, etc. to use to lookup the constant
108 @return: The matching constant value
111 name = prefix.upper()+'_'+suffix.upper().replace(' ', '_')
112 return getattr(constants, name, suffix)
114 def stateToString(value):
116 Converts a state value to a string based on the name of the state constant in
117 the L{constants} module that has the given value.
119 @param value: An AT-SPI state
120 @type value: Accessibility.StateType
121 @return: Human readable, untranslated name of the state
124 return constants.STATE_VALUE_TO_NAME.get(value)
126 def relationToString(value):
128 Converts a relation value to a string based on the name of the state constant
129 in the L{constants} module that has the given value.
131 @param value: An AT-SPI relation
132 @type value: Accessibility.RelationType
133 @return: Human readable, untranslated name of the relation
136 return constants.RELATION_VALUE_TO_NAME.get(value)
140 Generates all possible keyboard modifiers for use with
141 L{registry.Registry.registerKeystrokeListener}.
144 while mask <= (1 << constants.MODIFIER_NUMLOCK):
148 def findDescendant(acc, pred, breadth_first=False):
150 Searches for a descendant node satisfying the given predicate starting at
151 this node. The search is performed in depth-first order by default or
152 in breadth first order if breadth_first is True. For example,
154 my_win = findDescendant(lambda x: x.name == 'My Window')
156 will search all descendants of x until one is located with the name 'My
157 Window' or all nodes are exausted. Calls L{_findDescendantDepth} or
158 L{_findDescendantBreadth} to start the recursive search.
160 @param acc: Root accessible of the search
161 @type acc: Accessibility.Accessible
162 @param pred: Search predicate returning True if accessible matches the
163 search criteria or False otherwise
165 @param breadth_first: Search breadth first (True) or depth first (False)?
166 @type breadth_first: boolean
167 @return: Accessible matching the criteria or None if not found
168 @rtype: Accessibility.Accessible or None
171 return _findDescendantBreadth(acc, pred)
175 ret = _findDescendantDepth(acc, pred)
178 if ret is not None: return ret
180 def _findDescendantBreadth(acc, pred):
182 Internal function for locating one descendant. Called by L{findDescendant} to
185 @param acc: Root accessible of the search
186 @type acc: Accessibility.Accessible
187 @param pred: Search predicate returning True if accessible matches the
188 search criteria or False otherwise
190 @return: Matching node or None to keep searching
191 @rtype: Accessibility.Accessible or None
195 if pred(child): return child
200 ret = _findDescedantBreadth(child, pred)
203 if ret is not None: return ret
205 def _findDescendantDepth(acc, pred):
207 Internal function for locating one descendant. Called by L{findDescendant} to
210 @param acc: Root accessible of the search
211 @type acc: Accessibility.Accessible
212 @param pred: Search predicate returning True if accessible matches the
213 search criteria or False otherwise
215 @return: Matching node or None to keep searching
216 @rtype: Accessibility.Accessible or None
219 if pred(acc): return acc
224 ret = _findDescendantDepth(child, pred)
227 if ret is not None: return ret
229 def findAllDescendants(acc, pred):
231 Searches for all descendant nodes satisfying the given predicate starting at
232 this node. Does an in-order traversal. For example,
234 pred = lambda x: x.getRole() == pyatspi.ROLE_PUSH_BUTTON
235 buttons = pyatspi.findAllDescendants(node, pred)
237 will locate all push button descendants of node.
239 @param acc: Root accessible of the search
240 @type acc: Accessibility.Accessible
241 @param pred: Search predicate returning True if accessible matches the
242 search criteria or False otherwise
244 @return: All nodes matching the search criteria
248 _findAllDescendants(acc, pred, matches)
251 def _findAllDescendants(acc, pred, matches):
253 Internal method for collecting all descendants. Reuses the same matches
254 list so a new one does not need to be built on each recursive step.
258 if pred(child): matches.append(child)
261 findAllDescendants(child, pred, matches)
263 def findAncestor(acc, pred):
265 Searches for an ancestor satisfying the given predicate. Note that the
266 AT-SPI hierarchy is not always doubly linked. Node A may consider node B its
267 child, but B is not guaranteed to have node A as its parent (i.e. its parent
268 may be set to None). This means some searches may never make it all the way
269 up the hierarchy to the desktop level.
271 @param acc: Starting accessible object
272 @type acc: Accessibility.Accessible
273 @param pred: Search predicate returning True if accessible matches the
274 search criteria or False otherwise
276 @return: Node matching the criteria or None if not found
277 @rtype: Accessibility.Accessible
280 # guard against bad start condition
283 if acc.parent is None:
284 # stop if there is no parent and we haven't returned yet
287 if pred(acc.parent): return acc.parent
295 Gets the path from the application ancestor to the given accessible in
296 terms of its child index at each level.
298 @param acc: Target accessible
299 @type acc: Accessibility.Accessible
300 @return: Path to the target
301 @rtype: list of integer
302 @raise LookupError: When the application accessible cannot be reached
306 if acc.parent is None:
310 path.append(acc.getIndexInParent())
315 class _StateSetImpl(Accessibility__POA.StateSet):
317 Implementation of the StateSet interface. Clients should not use this class
318 directly, but rather the L{StateSet} proxy class.
320 @param states: Set of states
324 '''Initializes the state set.'''
327 def contains(self, state):
329 Checks if this StateSet contains the given state.
331 @param state: State to check
332 @type state: Accessibility.StateType
333 @return: True if the set contains the given state
336 return state in self.states
338 def add(self, state):
340 Adds a state to this set.
342 @param state: State to add
343 @type state: Accessibility.StateType
345 self.states.add(state)
347 def remove(self, state):
349 Removes a state from this set.
351 @param state: State to remove
352 @type state: Accessibility.StateType
354 self.states.remove(state)
356 def equals(self, state_set):
358 Checks if this StateSet contains exactly the same members as the given
361 @param state_set: Another set
362 @type state_set: Accessibility.StateSet
363 @return: Are the sets equivalent in terms of their contents?
366 # don't check private members, object might be from another process
368 return set(state_set.getStates()) == self.states
370 def compare(self, state_set):
372 Computes the symmetric differences of this L{StateSet} and the given
375 @note: This method is not currently implemented because of difficulties
376 with reference counting. This method needs to return a new
377 Accessibility.StateSet object, but the Python implementation for that
378 object needs to be kept alive. The problem is who will keep that
379 server implementation alive? As soon as it goes out of scope, it's
380 GC'ed. This object cannot keep it alive either as it may go out of
381 scope before the new object is ready to be finalized. With a global
382 cache of objects, we don't know when to invalidate.
384 @param state_set: Another set
385 @type state_set: Accessibility.StateSet
386 @return: Elements in only one of the two sets
387 @rtype: Accessibility.StateSet
389 raise ORBit.CORBA.NO_IMPLEMENT
391 # don't check private members, object might be from another process
393 #states = set(state_set.getStates())
394 #diff = self.states.symmetric_difference(states)
395 #new_ss = _StateSetImpl()
396 #map(new_ss._this().add, diff)
397 #return new_ss._this()
401 Checks if this L{StateSet} is empty.
403 @return: Is it empty?
406 return len(self.states) == 0
410 Gets the sequence of all states in this set.
412 @return: List of states
415 return list(self.states)
417 class StateSet(object):
419 Python proxy for the L{_StateSetImpl} class. Use this to safely instantiate
420 new StateSet objects in Python.
422 @param impl: State set implementation
423 @type impl: L{_StateSetImpl}
425 def __init__(self, *states):
427 Initializes the state set with the given states.
429 @param states: States to add immediately
432 self.impl = _StateSetImpl()
433 map(self.impl._this().add, states)
435 def contains(self, state):
437 Checks if this StateSet contains the given state.
439 @param state: State to check
440 @type state: Accessibility.StateType
441 @return: True if the set contains the given state
444 return self.impl._this().contains(state)
446 def add(self, *states):
448 Adds states to this set.
450 @param states: State(s) to add
451 @type states: Accessibility.StateType
453 map(self.impl._this().add, state)
455 def remove(self, state):
457 Removes states from this set.
459 @param states: State(s) to remove
460 @type states: Accessibility.StateType
462 map(self.impl._this().remove, state)
464 def equals(self, state_set):
466 Checks if this StateSet contains exactly the same members as the given
469 @param state_set: Another set
470 @type state_set: Accessibility.StateSet
471 @return: Are the sets equivalent in terms of their contents?
474 if isinstance(state_set, self.__class__):
475 # convenience if we're given a proxy
476 state_set = state_set.raw()
477 return self.impl._this().equals(state_set)
479 def compare(self, state_set):
481 Finds the symmetric difference between this state set andthe one provided,
482 and returns it as a new StateSet.
484 @note: This does not use L{_StateSetImpl.compare} which cannot be
485 implemented at this time
486 @param state_set: Set to compare against
487 @type state_set: Accessibility.StateSet
488 @return: Proxy for the new set
491 if isinstance(state_set, self.__class__):
492 # shortcut if it's another one of our proxies
493 state_set = state_set.raw()
494 a = set(self.impl._this().getStates())
495 b = set(state_set.getStates())
496 diff = a.symmetric_difference(b)
497 return StateSet(*diff)
501 Checks if this StateSet is empty.
503 @return: Is it empty?
506 return self.impl._this().isEmpty()
510 Gets the sequence of all states in this set.
512 @return: List of states
515 return self.impl._this().getStates()
519 Gets the Accessibility.StateSet object proxied for use in a remote
523 @rtype: Accessibility.StateSet
525 return self.impl._this()