* event.py (Event.__init__): Catch AttributeError when ref()ing
[platform/core/uifw/at-spi2-atk.git] / pyatspi / utils.py
index 793ac95..48aa94b 100644 (file)
@@ -26,36 +26,61 @@ Portions of this code originally licensed and copyright (c) 2005, 2007
 IBM Corporation under the BSD license, available at
 U{http://www.opensource.org/licenses/bsd-license.php}
 '''
+import ORBit
 import Accessibility__POA
 
-def getInterfaceIID(cls):
+def getInterfaceIID(obj):
   '''
-  Gets the ID of an interface class in string format for use in queryInterface.
+  Gets the ID of an interface class or object in string format for use in
+  queryInterface.
   
-  @param cls: Class representing an AT-SPI interface
-  @type cls: class
+  @param obj: Class representing an AT-SPI interface or instance
+  @type obj: object
   @return: IID for the interface
   @rtype: string
   @raise AttributeError: When the parameter does not provide typecode info
   '''
-  return cls.__typecode__.repo_id
+  return obj.__typecode__.repo_id
 
-def getInterfaceName(cls):
+def getInterfaceName(obj):
   '''
-  Gets the human readable name of an interface class in string format.
+  Gets the human readable name of an interface class or object in string
+  format.
   
-  @param cls: Class representing an AT-SPI interface
-  @type cls: class
+  @param obj: Class representing an AT-SPI interface or instance
+  @type obj: class
   @return: Name of the interface
   @rtype: string
   @raise AttributeError: When the parameter does not provide typecode info
   '''
-  return cls.__typecode__.name
+  return obj.__typecode__.name
 
 # we're importing here to avoid cyclic importants; constants relies on the
 # two functions above
 import constants
 
+def listInterfaces(obj):
+  '''
+  Gets a list of the names of all interfaces supported by this object. The
+  names are the short-hand interface names like "Accessible" and "Component",
+  not the full interface identifiers.
+
+  @param obj: Arbitrary object to query for all accessibility related
+  interfaces. Must provide a queryInterface method.
+  @type obj: object
+  @return: Set of supported interface names
+  @rtype: set
+  @raise AttributeError: If the object provide does not implement
+  queryInterface
+  '''
+  names = set()
+  for ic in constants.ALL_INTERFACES:
+    io = obj.queryInterface(getInterfaceIID(ic))
+    if io is None:
+      continue
+    names.add(getInterfaceName(ic))
+  return names
+
 def stringToConst(prefix, suffix):
   '''
   Maps a string name to an AT-SPI constant. The rules for the mapping are as 
@@ -68,11 +93,12 @@ def stringToConst(prefix, suffix):
   in the L{constants} module. If such a constant does not exist, the string
   suffix is returned instead.
 
-  This method allows strings to be used to refer to roles, relations, etc. 
+  This method allows strings to be used to refer to roles, relations, etc.
   without direct access to the constants. It also supports the future expansion
   of roles, relations, etc. by allowing arbitrary strings which may or may not
-  map to the current standard set of roles, relations, etc., but may still match
-  some non-standard role, relation, etc. being reported by an application.
+  map to the current standard set of roles, relations, etc., but may still
+  match some non-standard role, relation, etc. being reported by an
+  application.
   
   @param prefix: Prefix of the constant name such as role, relation, state, 
     text, modifier, key
@@ -87,7 +113,7 @@ def stringToConst(prefix, suffix):
 
 def stateToString(value):
   '''
-  Converts a state value to a string based on the name of the state constant in 
+  Converts a state value to a string based on the name of the state constant in
   the L{constants} module that has the given value.
   
   @param value: An AT-SPI state
@@ -127,7 +153,7 @@ def findDescendant(acc, pred, breadth_first=False):
   
   my_win = findDescendant(lambda x: x.name == 'My Window')
   
-  will search all descendants of node until one is located with the name 'My
+  will search all descendants of x until one is located with the name 'My
   Window' or all nodes are exausted. Calls L{_findDescendantDepth} or
   L{_findDescendantBreadth} to start the recursive search.
   
@@ -171,7 +197,7 @@ def _findDescendantBreadth(acc, pred):
       pass
   for child in acc:
     try:
-      ret = _findDescedantBreadth(child, pred)
+      ret = _findDescendantBreadth(child, pred)
     except Exception:
       ret = None
     if ret is not None: return ret
@@ -232,7 +258,7 @@ def _findAllDescendants(acc, pred, matches):
       if pred(child): matches.append(child)
     except Exception:
       pass
-    findAllDescendants(child, pred, matches)
+    _findAllDescendants(child, pred, matches)
   
 def findAncestor(acc, pred):
   '''
@@ -286,21 +312,21 @@ def getPath(acc):
       raise LookupError
     acc = acc.parent
 
-class StateSet(Accessibility__POA.StateSet):
+class _StateSetImpl(Accessibility__POA.StateSet):
   '''
-  Convenience implementation of AT-SPI StateSet, for future use with Collection
-  interface.
+  Implementation of the StateSet interface. Clients should not use this class
+  directly, but rather the L{StateSet} proxy class.
   
   @param states: Set of states
   @type states: set
   '''
-  def __init__(self, *states):
-    '''Initializes the state set with the given states.'''
-    self.states = set(states)
+  def __init__(self):
+    '''Initializes the state set.'''
+    self.states = set()
     
   def contains(self, state):
     '''
-    Checks if this L{StateSet} contains the given state.
+    Checks if this StateSet contains the given state.
     
     @param state: State to check
     @type state: Accessibility.StateType
@@ -309,48 +335,66 @@ class StateSet(Accessibility__POA.StateSet):
     '''
     return state in self.states
   
-  def add(self, *state):
+  def add(self, state):
     '''
-    Adds one or more states to this set.
+    Adds a state to this set.
     
-    @param state: State(s) to add
+    @param state: State to add
     @type state: Accessibility.StateType
     '''
     self.states.add(state)
   
-  def remove(self, *state):
+  def remove(self, state):
     '''
-    Removes one or more states from this set.
+    Removes a state from this set.
     
-    @param state: State(s) to remove
+    @param state: State to remove
     @type state: Accessibility.StateType
     '''
     self.states.remove(state)
   
   def equals(self, state_set):
     '''
-    Checks if this L{StateSet} contains exactly the same members as the given
-    L{StateSet}.
+    Checks if this StateSet contains exactly the same members as the given
+    StateSet.
     
     @param state_set: Another set
-    @type state_set: L{StateSet}
+    @type state_set: Accessibility.StateSet
     @return: Are the sets equivalent in terms of their contents?
     @rtype: boolean
     '''
-    return self.state_set == self.states
+    # don't check private members, object might be from another process
+    # or implementation
+    return set(state_set.getStates()) == self.states
   
   def compare(self, state_set):
     '''
     Computes the symmetric differences of this L{StateSet} and the given
     L{StateSet}.
+
+    @note: This method is not currently implemented because of difficulties
+    with reference counting. This method needs to return a new
+    Accessibility.StateSet object, but the Python implementation for that
+    object needs to be kept alive. The problem is who will keep that
+    server implementation alive? As soon as it goes out of scope, it's
+    GC'ed. This object cannot keep it alive either as it may go out of
+    scope before the new object is ready to be finalized. With a global
+    cache of objects, we don't know when to invalidate.
     
     @param state_set: Another set
-    @type state_set: L{StateSet}
+    @type state_set: Accessibility.StateSet
     @return: Elements in only one of the two sets
-    @rtype: L{StateSet}
+    @rtype: Accessibility.StateSet
     '''
-    diff = self.states.symmetric_difference(state_set.states)
-    return StateSet(*diff)
+    raise ORBit.CORBA.NO_IMPLEMENT
+    
+    # don't check private members, object might be from another process
+    # or implementation
+    #states = set(state_set.getStates())
+    #diff = self.states.symmetric_difference(states)
+    #new_ss = _StateSetImpl()
+    #map(new_ss._this().add, diff)
+    #return new_ss._this()
   
   def isEmpty(self):
     '''
@@ -368,4 +412,114 @@ class StateSet(Accessibility__POA.StateSet):
     @return: List of states
     @rtype: list
     '''
-    return list(self.states)
\ No newline at end of file
+    return list(self.states)
+
+class StateSet(object):
+  '''
+  Python proxy for the L{_StateSetImpl} class. Use this to safely instantiate
+  new StateSet objects in Python.
+
+  @param impl: State set implementation
+  @type impl: L{_StateSetImpl}
+  '''
+  def __init__(self, *states):
+    '''
+    Initializes the state set with the given states.
+
+    @param states: States to add immediately
+    @type states: list
+    '''
+    self.impl = _StateSetImpl()
+    map(self.impl._this().add, states)
+    
+  def contains(self, state):
+    '''
+    Checks if this StateSet contains the given state.
+    
+    @param state: State to check
+    @type state: Accessibility.StateType
+    @return: True if the set contains the given state
+    @rtype: boolean
+    '''
+    return self.impl._this().contains(state)
+  
+  def add(self, *states):
+    '''
+    Adds states to this set.
+    
+    @param states: State(s) to add
+    @type states: Accessibility.StateType
+    '''
+    map(self.impl._this().add, states)
+    
+  def remove(self, state):
+    '''
+    Removes states from this set.
+    
+    @param states: State(s) to remove
+    @type states: Accessibility.StateType
+    '''
+    map(self.impl._this().remove, state)
+  
+  def equals(self, state_set):
+    '''
+    Checks if this StateSet contains exactly the same members as the given
+    StateSet.
+    
+    @param state_set: Another set
+    @type state_set: Accessibility.StateSet
+    @return: Are the sets equivalent in terms of their contents?
+    @rtype: boolean
+    '''
+    if isinstance(state_set, self.__class__):
+      # convenience if we're given a proxy
+      state_set = state_set.raw()
+    return self.impl._this().equals(state_set)
+  
+  def compare(self, state_set):
+    '''
+    Finds the symmetric difference between this state set andthe one provided,
+    and returns it as a new StateSet.
+
+    @note: This does not use L{_StateSetImpl.compare} which cannot be
+    implemented at this time
+    @param state_set: Set to compare against
+    @type state_set: Accessibility.StateSet
+    @return: Proxy for the new set
+    @rtype: L{StateSet}
+    '''
+    if isinstance(state_set, self.__class__):
+      # shortcut if it's another one of our proxies
+      state_set = state_set.raw()
+    a = set(self.impl._this().getStates())
+    b = set(state_set.getStates())
+    diff = a.symmetric_difference(b)
+    return StateSet(*diff)
+  
+  def isEmpty(self):
+    '''
+    Checks if this StateSet is empty.
+    
+    @return: Is it empty?
+    @rtype: boolean
+    '''
+    return self.impl._this().isEmpty()
+
+  def getStates(self):
+    '''
+    Gets the sequence of all states in this set.
+    
+    @return: List of states
+    @rtype: list
+    '''
+    return self.impl._this().getStates()
+
+  def raw(self):
+    '''
+    Gets the Accessibility.StateSet object proxied for use in a remote
+    call.
+
+    @return: State set
+    @rtype: Accessibility.StateSet
+    '''
+    return self.impl._this()