[orca] Add support for new 'suggestion' role



commit 5f91ae95b279ac45a236d7bb806966f51137fd04
Author: Joanmarie Diggs <joanmarie diggs gmail com>
Date:   Thu Jan 23 14:56:33 2020 -0500

    Add support for new 'suggestion' role

 src/orca/formatting.py                   |  4 ++++
 src/orca/generator.py                    |  5 +++++
 src/orca/messages.py                     | 14 ++++++++++++++
 src/orca/object_properties.py            |  7 +++++++
 src/orca/script_utilities.py             |  6 ++++++
 src/orca/scripts/web/script_utilities.py | 26 ++++++++++++++++++++++++++
 src/orca/scripts/web/speech_generator.py |  3 ++-
 src/orca/speech_generator.py             | 24 ++++++++++++++++++++++--
 8 files changed, 86 insertions(+), 3 deletions(-)
---
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index 1aa151ac1..a4aeb0530 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -190,6 +190,10 @@ formatting = {
         'ROLE_CONTENT_MARK': {
             'unfocused': 'markStart + pause + displayedText + pause + markEnd',
             },
+        # TODO - JD: When we bump dependencies to 2.36, remove this fake role and use the real one.
+        'ROLE_CONTENT_SUGGESTION': {
+            'focused': 'leaving or (labelOrName + roleName)',
+            },
         pyatspi.ROLE_DESCRIPTION_TERM: {
             'unfocused': '(labelOrName or (displayedText + allTextSelection))',
             },
diff --git a/src/orca/generator.py b/src/orca/generator.py
index 34cf3fedd..ca2cfa257 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -1226,6 +1226,8 @@ class Generator:
             return 'ROLE_CONTENT_INSERTION'
         if self._script.utilities.isContentMarked(obj):
             return 'ROLE_CONTENT_MARK'
+        if self._script.utilities.isContentSuggestion(obj):
+            return 'ROLE_CONTENT_SUGGESTION'
         if self._script.utilities.isLandmark(obj):
             return pyatspi.ROLE_LANDMARK
         if self._script.utilities.isFocusableLabel(obj):
@@ -1265,6 +1267,9 @@ class Generator:
                 if isVertical:
                     return object_properties.ROLE_SPLITTER_HORIZONTAL
 
+        if self._script.utilities.isContentSuggestion(obj):
+            return object_properties.ROLE_CONTENT_SUGGESTION
+
         if self._script.utilities.isFeed(obj):
             return object_properties.ROLE_FEED
 
diff --git a/src/orca/messages.py b/src/orca/messages.py
index 42e12a221..58199759a 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -442,6 +442,13 @@ CONTENT_MARK_START = C_("content", "highlight start")
 # is inside an HTML 'mark' element.
 CONTENT_MARK_END = C_("content", "highlight end")
 
+# Translators: This phrase is spoken to inform the user that the content being
+# presented is the end of an inline suggestion a document. A "suggestion" is a
+# proposed change. This change can include the insertion and/or deletion
+# of content, and would typically be seen in a collaborative editor, such as
+# in Google Docs.
+CONTENT_SUGGESTION_END = C_("content", "suggestion end")
+
 # Translators: This is for navigating document content by moving to the start
 # or end of a container. Examples of containers include tables, lists, and
 # blockquotes. When moving to the end of a container, Orca attempts to place
@@ -1380,6 +1387,13 @@ LEAVING_PULLQUOTE = C_("role", "leaving pullquote.")
 # for the corresponding term with context "role" found in object_properties.py
 LEAVING_QNA = C_("role", "leaving QNA.")
 
+# Translators: This message is presented when a user is navigating within a
+# suggestion and then navigates out of it. A "suggestion" is a container with
+# a proposed change. This change can include the insertion and/or deletion
+# of content, and would typically be seen in a collaborative editor, such as
+# in Google Docs.
+LEAVING_SUGGESTION = C_("role", "leaving suggestion.")
+
 # Translators: This message is presented when a user is navigating within
 # a document container and then navigates out of it. The word or phrase
 # that follows "leaving" should be consistent with the translation provided
diff --git a/src/orca/object_properties.py b/src/orca/object_properties.py
index bef7cf6a4..5202f4eba 100644
--- a/src/orca/object_properties.py
+++ b/src/orca/object_properties.py
@@ -94,6 +94,13 @@ RELATION_DETAILS_FOR = _("details for %s")
 # See https://w3c.github.io/aria/#aria-details
 RELATION_HAS_DETAILS = _("has details in %s")
 
+# Translators: This string should be treated as a role describing an object.
+# Examples of roles include "checkbox", "radio button", "paragraph", and "link."
+# This role refers to a container with a proposed change. This change can
+# include the insertion and/or deletion of content, and would typically be seen
+# in a collaborative editor, such as in Google Docs.
+ROLE_CONTENT_SUGGESTION = C_("role", "suggestion")
+
 # 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
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index cd513c677..9df886b3c 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -779,6 +779,12 @@ class Utilities:
     def isContentMarked(self, obj):
         return False
 
+    def isContentSuggestion(self, obj):
+        return False
+
+    def isLastItemInInlineContentSuggestion(self, obj):
+        return False
+
     def isEmpty(self, obj):
         return False
 
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index 2b4957188..57db2ed2b 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -2152,6 +2152,30 @@ class Utilities(script_utilities.Utilities):
 
         return 'mark' in self._getXMLRoles(obj) or 'mark' in self._getTag(obj)
 
+    def isContentSuggestion(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isContentSuggestion(obj)
+
+        # Remove this check when we bump dependencies to 2.36
+        try:
+            if obj.getRole() == pyatspi.ROLE_SUGGESTION:
+                return True
+        except:
+            pass
+
+        return 'suggestion' in self._getXMLRoles(obj)
+
+    def isLastItemInInlineContentSuggestion(self, obj):
+        suggestion = pyatspi.findAncestor(obj, self.isContentSuggestion)
+        if not (suggestion and suggestion.childCount):
+            return False
+
+        displayStyle = self._getDisplayStyle(suggestion)
+        if "inline" not in displayStyle:
+            return False
+
+        return suggestion[-1] == obj
+
     def speakMathSymbolNames(self, obj=None):
         obj = obj or orca_state.locusOfFocus
         return self.isMath(obj)
@@ -2719,6 +2743,8 @@ class Utilities(script_utilities.Utilities):
             rv = False
         elif self.isLandmark(obj):
             rv = False
+        elif self.isContentSuggestion(obj):
+            rv = False
         elif self.isDPub(obj):
             rv = False
         elif self.isFeed(obj):
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index 8b6e8eb84..d6ec95c3e 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -245,7 +245,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
 
         if self._script.utilities.isTextBlockElement(obj) \
            and not self._script.utilities.isLandmark(obj) \
-           and not self._script.utilities.isDPub(obj):
+           and not self._script.utilities.isDPub(obj) \
+           and not self._script.utilities.isContentSuggestion(obj):
             return []
 
         if obj.name:
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index 6e41be127..467fd8514 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -330,6 +330,12 @@ class SpeechGenerator(generator.Generator):
 
         result = [messages.CONTENT_DELETION_END]
         result.extend(self.voice(SYSTEM))
+
+        if self._script.utilities.isLastItemInInlineContentSuggestion(obj):
+            result.extend(self._generatePause(obj, **args))
+            result.extend([messages.CONTENT_SUGGESTION_END])
+            result.extend(self.voice(SYSTEM))
+
         return result
 
     def _generateInsertionStart(self, obj, **args):
@@ -356,6 +362,12 @@ class SpeechGenerator(generator.Generator):
 
         result = [messages.CONTENT_INSERTION_END]
         result.extend(self.voice(SYSTEM))
+
+        if self._script.utilities.isLastItemInInlineContentSuggestion(obj):
+            result.extend(self._generatePause(obj, **args))
+            result.extend([messages.CONTENT_SUGGESTION_END])
+            result.extend(self.voice(SYSTEM))
+
         return result
 
     def _generateMarkStart(self, obj, **args):
@@ -1667,6 +1679,7 @@ class SpeechGenerator(generator.Generator):
 
     def _getEnabledAndDisabledContextRoles(self):
         allRoles = [pyatspi.ROLE_BLOCK_QUOTE,
+                    'ROLE_CONTENT_SUGGESTION',
                     pyatspi.ROLE_FORM,
                     pyatspi.ROLE_LANDMARK,
                     'ROLE_DPUB_LANDMARK',
@@ -1684,7 +1697,9 @@ class SpeechGenerator(generator.Generator):
             if _settingsManager.getSetting('sayAllContextList'):
                 enabled.append(pyatspi.ROLE_LIST)
             if _settingsManager.getSetting('sayAllContextPanel'):
-                enabled.extend([pyatspi.ROLE_PANEL, 'ROLE_DPUB_SECTION'])
+                enabled.extend([pyatspi.ROLE_PANEL,
+                                'ROLE_CONTENT_SUGGESTION',
+                                'ROLE_DPUB_SECTION'])
             if _settingsManager.getSetting('sayAllContextNonLandmarkForm'):
                 enabled.append(pyatspi.ROLE_FORM)
             if _settingsManager.getSetting('sayAllContextTable'):
@@ -1697,7 +1712,9 @@ class SpeechGenerator(generator.Generator):
             if _settingsManager.getSetting('speakContextList'):
                 enabled.append(pyatspi.ROLE_LIST)
             if _settingsManager.getSetting('speakContextPanel'):
-                enabled.extend([pyatspi.ROLE_PANEL, 'ROLE_DPUB_SECTION'])
+                enabled.extend([pyatspi.ROLE_PANEL,
+                                'ROLE_CONTENT_SUGGESTION',
+                                'ROLE_DPUB_SECTION'])
             if _settingsManager.getSetting('speakContextNonLandmarkForm'):
                 enabled.append(pyatspi.ROLE_FORM)
             if _settingsManager.getSetting('speakContextTable'):
@@ -1818,6 +1835,8 @@ class SpeechGenerator(generator.Generator):
                 result = ['']
         elif role == pyatspi.ROLE_FORM:
             result.append(messages.LEAVING_FORM)
+        elif role == 'ROLE_CONTENT_SUGGESTION':
+            result.append(messages.LEAVING_SUGGESTION)
         else:
             result = ['']
         if result:
@@ -1953,6 +1972,7 @@ class SpeechGenerator(generator.Generator):
         args['includeOnly'] = [pyatspi.ROLE_BLOCK_QUOTE,
                                pyatspi.ROLE_FORM,
                                pyatspi.ROLE_LANDMARK,
+                               'ROLE_CONTENT_SUGGESTION',
                                'ROLE_DPUB_LANDMARK',
                                'ROLE_DPUB_SECTION',
                                pyatspi.ROLE_LIST,


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