[orca] Add support for INSERTION, DELETION, and MARK roles



commit ab5fe1e453eb3ab281d63d10524484bb95aa6a5b
Author: Joanmarie Diggs <joanmarie diggs gmail com>
Date:   Thu Jan 23 12:24:42 2020 -0500

    Add support for INSERTION, DELETION, and MARK roles

 src/orca/formatting.py                   | 12 +++++
 src/orca/generator.py                    |  6 +++
 src/orca/messages.py                     | 30 ++++++++++++
 src/orca/script_utilities.py             |  9 ++++
 src/orca/scripts/web/script_utilities.py | 39 ++++++++++++++++
 src/orca/speech_generator.py             | 78 ++++++++++++++++++++++++++++++++
 6 files changed, 174 insertions(+)
---
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index a90404c55..1aa151ac1 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -178,6 +178,18 @@ formatting = {
             'focused': 'labelOrName + roleName',
             'unfocused': 'labelOrName + roleName + pause + currentLineText + allTextSelection',
             },
+        # TODO - JD: When we bump dependencies to 2.34, remove this fake role and use the real one.
+        'ROLE_CONTENT_DELETION': {
+            'unfocused': 'deletionStart + pause + displayedText + pause + deletionEnd',
+            },
+        # TODO - JD: When we bump dependencies to 2.34, remove this fake role and use the real one.
+        'ROLE_CONTENT_INSERTION': {
+            'unfocused': 'insertionStart + pause + displayedText + pause + insertionEnd',
+            },
+        # TODO - JD: When we bump dependencies to 2.36, remove this fake role and use the real one.
+        'ROLE_CONTENT_MARK': {
+            'unfocused': 'markStart + pause + displayedText + pause + markEnd',
+            },
         pyatspi.ROLE_DESCRIPTION_TERM: {
             'unfocused': '(labelOrName or (displayedText + allTextSelection))',
             },
diff --git a/src/orca/generator.py b/src/orca/generator.py
index d7b6f5bef..34cf3fedd 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -1220,6 +1220,12 @@ class Generator:
             return pyatspi.ROLE_STATIC
         if self._script.utilities.isBlockquote(obj):
             return pyatspi.ROLE_BLOCK_QUOTE
+        if self._script.utilities.isContentDeletion(obj):
+            return 'ROLE_CONTENT_DELETION'
+        if self._script.utilities.isContentInsertion(obj):
+            return 'ROLE_CONTENT_INSERTION'
+        if self._script.utilities.isContentMarked(obj):
+            return 'ROLE_CONTENT_MARK'
         if self._script.utilities.isLandmark(obj):
             return pyatspi.ROLE_LANDMARK
         if self._script.utilities.isFocusableLabel(obj):
diff --git a/src/orca/messages.py b/src/orca/messages.py
index dbe8811bf..42e12a221 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -412,6 +412,36 @@ DATE_FORMAT_ABBREVIATED_DMY = "%a, %-d %b, %Y"
 DATE_FORMAT_ABBREVIATED_MDY = "%a, %b %-d, %Y"
 DATE_FORMAT_ABBREVIATED_YMD = "%Y. %b %-d, %a."
 
+# Translators: This phrase is spoken to inform the user that what is about to
+# be said is content marked for deletion in a document, such as content which
+# is inside an HTML 'del' element, or the removed code in a diff.
+CONTENT_DELETION_START = C_("content", "deletion start")
+
+# Translators: This phrase is spoken to inform the user that they have reached
+# the end of content marked for deletion in a document, such as content which
+# is inside an HTML 'del' element, or the removed code in a diff.
+CONTENT_DELETION_END = C_("content", "deletion end")
+
+# Translators: This phrase is spoken to inform the user that what is about to
+# be said is content marked for insertion in a document, such as content which
+# is inside an HTML 'ins' element, or the added code in a diff.
+CONTENT_INSERTION_START = C_("content", "insertion start")
+
+# Translators: This phrase is spoken to inform the user that they have reached
+# the end of content marked for deletion in a document, such as content which
+# is inside an HTML 'ins' element, or the added code in a diff.
+CONTENT_INSERTION_END = C_("content", "insertion end")
+
+# Translators: This phrase is spoken to inform the user that what is about to
+# be said is content marked/highlighted in a document, such as content which
+# is inside an HTML 'mark' element.
+CONTENT_MARK_START = C_("content", "highlight start")
+
+# Translators: This phrase is spoken to inform the user that they have reached
+# the end of content marked/highlighted in a document, such as content which
+# is inside an HTML 'mark' element.
+CONTENT_MARK_END = C_("content", "highlight 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
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 01ceb1856..cd513c677 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -770,6 +770,15 @@ class Utilities:
 
         return False
 
+    def isContentDeletion(self, obj):
+        return False
+
+    def isContentInsertion(self, obj):
+        return False
+
+    def isContentMarked(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 921f3369a..2b4957188 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -2113,6 +2113,45 @@ class Utilities(script_utilities.Utilities):
 
         return self._getTag(obj) == 'blockquote'
 
+    def isContentDeletion(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isContentDeletion(obj)
+
+        # Remove this check when we bump dependencies to 2.34
+        try:
+            if obj.getRole() == pyatspi.ROLE_CONTENT_DELETION:
+                return True
+        except:
+            pass
+
+        return 'deletion' in self._getXMLRoles(obj) or 'del' in self._getTag(obj)
+
+    def isContentInsertion(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isContentInsertion(obj)
+
+        # Remove this check when we bump dependencies to 2.34
+        try:
+            if obj.getRole() == pyatspi.ROLE_CONTENT_INSERTION:
+                return True
+        except:
+            pass
+
+        return 'insertion' in self._getXMLRoles(obj) or 'ins' in self._getTag(obj)
+
+    def isContentMarked(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isContentMarked(obj)
+
+        # Remove this check when we bump dependencies to 2.36
+        try:
+            if obj.getRole() == pyatspi.ROLE_MARK:
+                return True
+        except:
+            pass
+
+        return 'mark' in self._getXMLRoles(obj) or 'mark' in self._getTag(obj)
+
     def speakMathSymbolNames(self, obj=None):
         obj = obj or orca_state.locusOfFocus
         return self.isMath(obj)
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index 1f9d05208..6e41be127 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -306,6 +306,84 @@ class SpeechGenerator(generator.Generator):
             result.extend(acss)
         return result
 
+    def _generateDeletionStart(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        startOffset = args.get('startOffset', 0)
+        if startOffset != 0:
+            return []
+
+        result = [messages.CONTENT_DELETION_START]
+        result.extend(self.voice(SYSTEM))
+        return result
+
+    def _generateDeletionEnd(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        endOffset = args.get('endOffset')
+        if endOffset is not None:
+            text = self._script.utilities.queryNonEmptyText(obj)
+            if text  and text.characterCount != endOffset:
+                return []
+
+        result = [messages.CONTENT_DELETION_END]
+        result.extend(self.voice(SYSTEM))
+        return result
+
+    def _generateInsertionStart(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        startOffset = args.get('startOffset', 0)
+        if startOffset != 0:
+            return []
+
+        result = [messages.CONTENT_INSERTION_START]
+        result.extend(self.voice(SYSTEM))
+        return result
+
+    def _generateInsertionEnd(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        endOffset = args.get('endOffset')
+        if endOffset is not None:
+            text = self._script.utilities.queryNonEmptyText(obj)
+            if text and text.characterCount != endOffset:
+                return []
+
+        result = [messages.CONTENT_INSERTION_END]
+        result.extend(self.voice(SYSTEM))
+        return result
+
+    def _generateMarkStart(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        startOffset = args.get('startOffset', 0)
+        if startOffset != 0:
+            return []
+
+        result = [messages.CONTENT_MARK_START]
+        result.extend(self.voice(SYSTEM))
+        return result
+
+    def _generateMarkEnd(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        endOffset = args.get('endOffset')
+        if endOffset is not None:
+            text = self._script.utilities.queryNonEmptyText(obj)
+            if text and text.characterCount != endOffset:
+                return []
+
+        result = [messages.CONTENT_MARK_END]
+        result.extend(self.voice(SYSTEM))
+        return result
+
     def _generateAvailability(self, obj, **args):
         if _settingsManager.getSetting('onlySpeakDisplayedText'):
             return []


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