[orca] Improve presentation and handling of editable ARIA combo boxes



commit d913138c146614b2fd93d222447e3f78ee4f47e0
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Mon Jul 11 19:11:37 2016 -0400

    Improve presentation and handling of editable ARIA combo boxes
    
    If we can determine a combo box is editable (which we cannot always do):
    * Announce that it is editable
    * Include it in structural navigation for entry

 src/orca/object_properties.py            |    7 ++++
 src/orca/script_utilities.py             |    6 +++
 src/orca/scripts/web/script_utilities.py |   51 ++++++++++++++++++++++++++++++
 src/orca/scripts/web/speech_generator.py |    3 ++
 src/orca/speech_generator.py             |    6 +++-
 src/orca/structural_navigation.py        |    2 +
 6 files changed, 74 insertions(+), 1 deletions(-)
---
diff --git a/src/orca/object_properties.py b/src/orca/object_properties.py
index 7c010e4..a50e692 100644
--- a/src/orca/object_properties.py
+++ b/src/orca/object_properties.py
@@ -65,6 +65,13 @@ NODE_LEVEL_SPEECH = _("tree level %d")
 # ancestors the node has). This is the braille version.
 NODE_LEVEL_BRAILLE = _("TREE LEVEL %d")
 
+# Translators: This string should be treated as a role describing an object.
+# Examples of roles include "checkbox", "radio button", "paragraph", and "link."
+# The reason for including the editable state as part of the role is to make it
+# possible for users to quickly identify combo boxes in which a value can be
+# typed or arrowed to.
+ROLE_EDITABLE_COMBO_BOX = _("editable combo box")
+
 # Translators: The 'h' in this string represents a heading level attribute for
 # content that you might find in something such as HTML content (e.g., <h1>).
 # The translated form is meant to be a single character followed by a numeric
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 04d9afc..ac5e25e 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -3256,6 +3256,12 @@ class Utilities:
     def isEntryCompletionPopupItem(self, obj):
         return False
 
+    def isEditableComboBox(self, obj):
+        return False
+
+    def isEditableDescendantOfComboBox(self, obj):
+        return False
+
     def isPopOver(self, obj):
         return False
 
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index 3764c8b..ea7dce8 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -65,6 +65,8 @@ class Utilities(script_utilities.Utilities):
         self._id = {}
         self._isClickableElement = {}
         self._isAnchor = {}
+        self._isEditableComboBox = {}
+        self._isEditableDescendantOfComboBox = {}
         self._isLandmark = {}
         self._isLiveRegion = {}
         self._isLink = {}
@@ -117,6 +119,8 @@ class Utilities(script_utilities.Utilities):
         self._id = {}
         self._isClickableElement = {}
         self._isAnchor = {}
+        self._isEditableComboBox = {}
+        self._isEditableDescendantOfComboBox = {}
         self._isLandmark = {}
         self._isLiveRegion = {}
         self._isLink = {}
@@ -2136,6 +2140,53 @@ class Utilities(script_utilities.Utilities):
         self._isClickableElement[hash(obj)] = rv
         return rv
 
+    def isEditableDescendantOfComboBox(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isEditableDescendantOfComboBox(obj)
+
+        rv = self._isEditableDescendantOfComboBox.get(hash(obj))
+        if rv is not None:
+            return rv
+
+        try:
+            state = obj.getState()
+        except:
+            msg = "ERROR: Exception getting state for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return False
+
+        if not state.contains(pyatspi.STATE_EDITABLE):
+            return False
+
+        isComboBox = lambda x: x and x.getRole() == pyatspi.ROLE_COMBO_BOX
+        rv = pyatspi.findAncestor(obj, isComboBox) is not None
+
+        self._isEditableDescendantOfComboBox[hash(obj)] = rv
+        return rv
+
+    def isEditableComboBox(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isEditableComboBox(obj)
+
+        rv = self._isEditableComboBox.get(hash(obj))
+        if rv is not None:
+            return rv
+
+        try:
+            role = obj.getRole()
+            state = obj.getState()
+        except:
+            msg = "ERROR: Exception getting role and state for %s" % obj
+            debug.println(debug.LEVEL_INFO, msg, True)
+            return False
+
+        rv = False
+        if role == pyatspi.ROLE_COMBO_BOX:
+            rv = state.contains(pyatspi.STATE_EDITABLE)
+
+        self._isEditableComboBox[hash(obj)] = rv
+        return rv
+
     def isLandmark(self, obj):
         if not (obj and self.inDocumentContent(obj)):
             return False
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index ae72a85..49a2055 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -408,6 +408,9 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
         if self._script.utilities.isTextBlockElement(obj):
             return []
 
+        if self._script.utilities.isEditableComboBox(obj):
+            return []
+
         position = self._script.utilities.getPositionInSet(obj)
         total = self._script.utilities.getSetSize(obj)
         if position is None or total is None:
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index d9350e5..d9f12f5 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -377,7 +377,7 @@ class SpeechGenerator(generator.Generator):
         - role: an optional pyatspi role to use instead
         """
 
-        if not isinstance(role, pyatspi.Role):
+        if not isinstance(role, (pyatspi.Role, Atspi.Role)):
             try:
                 return obj.getLocalizedRoleName()
             except:
@@ -386,6 +386,10 @@ class SpeechGenerator(generator.Generator):
         if not role:
             return ''
 
+        if self._script.utilities.isEditableComboBox(obj) \
+           or self._script.utilities.isEditableDescendantOfComboBox(obj):
+            return object_properties.ROLE_EDITABLE_COMBO_BOX
+
         if role == pyatspi.ROLE_LINK and obj.getState().contains(pyatspi.STATE_VISITED):
             return object_properties.ROLE_VISITED_LINK
 
diff --git a/src/orca/structural_navigation.py b/src/orca/structural_navigation.py
index 418ebd2..e225a2b 100644
--- a/src/orca/structural_navigation.py
+++ b/src/orca/structural_navigation.py
@@ -1735,6 +1735,7 @@ class StructuralNavigation:
         """
 
         role = [pyatspi.ROLE_DOCUMENT_FRAME,
+                pyatspi.ROLE_COMBO_BOX,
                 pyatspi.ROLE_ENTRY,
                 pyatspi.ROLE_PASSWORD_TEXT,
                 pyatspi.ROLE_TEXT]
@@ -1762,6 +1763,7 @@ class StructuralNavigation:
 
         isMatch = False
         if obj and obj.getRole() in [pyatspi.ROLE_DOCUMENT_FRAME,
+                                     pyatspi.ROLE_COMBO_BOX,
                                      pyatspi.ROLE_ENTRY,
                                      pyatspi.ROLE_PASSWORD_TEXT,
                                      pyatspi.ROLE_TEXT]:


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]