[orca] Fix for bgo#620299 - Orca does not treat editable document frames as entries



commit 4c64737e2f24e72cc8f0ec515864a887094e2793
Author: Joanmarie Diggs <joanmarie diggs gmail com>
Date:   Fri Jun 11 15:14:31 2010 -0400

    Fix for bgo#620299 - Orca does not treat editable document frames as entries

 .../scripts/apps/Thunderbird/script_utilities.py   |   10 ++++
 .../scripts/toolkits/Gecko/braille_generator.py    |    7 +++
 src/orca/scripts/toolkits/Gecko/script.py          |   45 +++++++++--------
 .../scripts/toolkits/Gecko/script_utilities.py     |   22 ++++++++
 .../scripts/toolkits/Gecko/speech_generator.py     |    5 ++
 .../toolkits/Gecko/structural_navigation.py        |   53 ++++++++++++++++++++
 src/orca/structural_navigation.py                  |   13 +++--
 7 files changed, 129 insertions(+), 26 deletions(-)
---
diff --git a/src/orca/scripts/apps/Thunderbird/script_utilities.py b/src/orca/scripts/apps/Thunderbird/script_utilities.py
index 07d73fd..8ceac80 100644
--- a/src/orca/scripts/apps/Thunderbird/script_utilities.py
+++ b/src/orca/scripts/apps/Thunderbird/script_utilities.py
@@ -75,6 +75,16 @@ class Utilities(Gecko.Utilities):
 
         return None
 
+    def isEntry(self, obj):
+        """Returns True if we should treat this object as an entry."""
+
+        return obj and obj.getRole() == pyatspi.ROLE_ENTRY
+
+    def isPasswordText(self, obj):
+        """Returns True if we should treat this object as password text."""
+
+        return obj and obj.getRole() == pyatspi.ROLE_PASSWORD_TEXT
+
     #########################################################################
     #                                                                       #
     # Utilities for working with the accessible text interface              #
diff --git a/src/orca/scripts/toolkits/Gecko/braille_generator.py b/src/orca/scripts/toolkits/Gecko/braille_generator.py
index 9a3760b..6a729d6 100644
--- a/src/orca/scripts/toolkits/Gecko/braille_generator.py
+++ b/src/orca/scripts/toolkits/Gecko/braille_generator.py
@@ -245,6 +245,11 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
             self._script.isAriaWidget(obj) \
             or ((obj.getRole() == pyatspi.ROLE_LIST) \
                 and (not obj.getState().contains(pyatspi.STATE_FOCUSABLE)))
+
+        oldRole = None
+        if self._script.utilities.isEntry(obj):
+            oldRole = self._overrideRole(pyatspi.ROLE_ENTRY, args)
+
         # Treat menu items in collapsed combo boxes as if the combo box
         # had focus. This will make things more consistent with how we
         # present combo boxes outside of Gecko.
@@ -259,4 +264,6 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
                           generateBraille(self, obj, **args))
         del args['includeContext']
         del args['useDefaultFormatting']
+        if oldRole:
+            self._restoreRole(oldRole, args)
         return result
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index be16a28..c5eeeda 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -1284,7 +1284,7 @@ class Script(default.Script):
                 #    caret moved event for some object within the document
                 #    frame.
                 #
-                if eventSourceRole != pyatspi.ROLE_ENTRY \
+                if not self.utilities.isEntry(event.source) \
                    and self.utilities.isSameObject(
                         event.source, orca_state.locusOfFocus):
                     return
@@ -1351,7 +1351,7 @@ class Script(default.Script):
         # onCaretMoved will handle.
         #
         if eventSourceInDocument and not self.isAriaWidget(event.source):
-            if event.source.getRole() != pyatspi.ROLE_ENTRY:
+            if not self.utilities.isEntry(event.source):
                 [obj, characterOffset] = \
                     self.findFirstCaretContext(event.source, event.detail1)
             else:
@@ -1595,7 +1595,8 @@ class Script(default.Script):
         # caret context for the document frame.  If we succeed, then
         # we set the focus on the object that's holding the caret.
         #
-        if eventSourceRole == pyatspi.ROLE_DOCUMENT_FRAME:
+        if eventSourceRole == pyatspi.ROLE_DOCUMENT_FRAME \
+           and not event.source.getState().contains(pyatspi.STATE_EDITABLE):
             try:
                 [obj, characterOffset] = self.getCaretContext()
                 state = obj.getState()
@@ -2202,9 +2203,9 @@ class Script(default.Script):
 
             role = obj.getRole()
             if (not len(string) and role != pyatspi.ROLE_PARAGRAPH) \
-               or role in [pyatspi.ROLE_ENTRY,
-                           pyatspi.ROLE_PASSWORD_TEXT,
-                           pyatspi.ROLE_LINK]:
+               or self.utilities.isEntry(obj) \
+               or self.utilities.isPasswordText(obj) \
+               or role == pyatspi.ROLE_LINK:
                 [regions, fRegion] = \
                           self.brailleGenerator.generateBraille(obj)
 
@@ -2330,8 +2331,7 @@ class Script(default.Script):
         # things, however, we can defer to the default scripts.
         #
 
-        if not self.inDocumentContent() \
-           or obj.getRole() == pyatspi.ROLE_ENTRY:
+        if not self.inDocumentContent() or self.utilities.isEntry(obj):
             default.Script.sayCharacter(self, obj)
             return
 
@@ -2386,7 +2386,7 @@ class Script(default.Script):
         wordContents = self.getWordContentsAtOffset(obj, characterOffset)
         [textObj, startOffset, endOffset, word] = wordContents[0]
         self.speakMisspelledIndicator(textObj, startOffset)
-        if obj.getRole() != pyatspi.ROLE_ENTRY:
+        if not self.utilities.isEntry(textObj):
             self.speakContents(wordContents)
         else:
             word = self.utilities.substring(textObj, startOffset, endOffset)
@@ -2399,8 +2399,7 @@ class Script(default.Script):
         # EMBEDDED_OBJECT_CHARACTER model of Gecko.  For all other
         # things, however, we can defer to the default scripts.
         #
-        if not self.inDocumentContent() or \
-           obj.getRole() == pyatspi.ROLE_ENTRY:
+        if not self.inDocumentContent() or self.utilities.isEntry(obj):
             default.Script.sayLine(self, obj)
             return
 
@@ -2674,7 +2673,7 @@ class Script(default.Script):
 
         weHandleIt = True
         obj = orca_state.locusOfFocus
-        if obj and (obj.getRole() == pyatspi.ROLE_ENTRY):
+        if self.utilities.isEntry(obj):
             text        = obj.queryText()
             length      = text.characterCount
             caretOffset = text.caretOffset
@@ -3236,6 +3235,7 @@ class Script(default.Script):
         formRoles = [pyatspi.ROLE_CHECK_BOX,
                      pyatspi.ROLE_RADIO_BUTTON,
                      pyatspi.ROLE_COMBO_BOX,
+                     pyatspi.ROLE_DOCUMENT_FRAME,
                      pyatspi.ROLE_LIST,
                      pyatspi.ROLE_ENTRY,
                      pyatspi.ROLE_PASSWORD_TEXT,
@@ -3246,6 +3246,9 @@ class Script(default.Script):
                   and state.contains(pyatspi.STATE_FOCUSABLE) \
                   and state.contains(pyatspi.STATE_SENSITIVE)
 
+        if obj.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
+            isField = isField and state.contains(pyatspi.STATE_EDITABLE)
+
         return isField
 
     def isUselessObject(self, obj):
@@ -3631,7 +3634,7 @@ class Script(default.Script):
             # of several objects, so we'll examine the strings of what
             # we've got and pop off the ones that match.
             #
-            elif role == pyatspi.ROLE_ENTRY:
+            elif self.utilities.isEntry(item[0]):
                 labelGuess = self.guessLabelFromLine(item[0])
                 index = len(lineContents) - 1
                 while labelGuess and index >= 0:
@@ -4237,7 +4240,7 @@ class Script(default.Script):
         if text:
             unicodeText = self.utilities.unicodeText(obj)
             if characterOffset >= len(unicodeText):
-                if obj.getRole() != pyatspi.ROLE_ENTRY:
+                if not self.utilities.isEntry(obj):
                     return [obj, -1]
                 else:
                     # We're at the end of an entry.  If we return -1,
@@ -4760,12 +4763,9 @@ class Script(default.Script):
         # We'll let the default script handle entries and other entry-like
         # things (e.g. the text portion of a dojo spin button).
         #
-        isEntry = obj.getState().contains(pyatspi.STATE_EDITABLE) \
-                  or obj.getRole() in [pyatspi.ROLE_ENTRY,
-                                       pyatspi.ROLE_TEXT,
-                                       pyatspi.ROLE_PASSWORD_TEXT]
-
-        if not self.inDocumentContent(obj) or isEntry:
+        if not self.inDocumentContent(obj) \
+           or self.utilities.isEntry(obj) \
+           or self.utilities.isPasswordText(obj):
             return default.Script.getTextLineAtCaret(self, obj, offset)
 
         # Find the current line.
@@ -5281,7 +5281,8 @@ class Script(default.Script):
             # role.
             #
             if not len(string) \
-               or role in [pyatspi.ROLE_ENTRY, pyatspi.ROLE_PASSWORD_TEXT]:
+               or self.utilities.isEntry(obj) \
+               or self.utilities.isPasswordText(obj):
                 utterance = self.speechGenerator.generateSpeech(obj)
             else:
                 utterance = [string]
@@ -5365,7 +5366,7 @@ class Script(default.Script):
             if character and character != self.EMBEDDED_OBJECT_CHARACTER:
                 speech.speakCharacter(character,
                                       self.getACSS(obj, character))
-            elif obj.getRole() != pyatspi.ROLE_ENTRY:
+            elif not self.utilities.isEntry(obj):
                 # We won't have a character if we move to the end of an
                 # entry (in which case we're not on a character and therefore
                 # have nothing to say), or when we hit a component with no
diff --git a/src/orca/scripts/toolkits/Gecko/script_utilities.py b/src/orca/scripts/toolkits/Gecko/script_utilities.py
index b189591..7e872b8 100644
--- a/src/orca/scripts/toolkits/Gecko/script_utilities.py
+++ b/src/orca/scripts/toolkits/Gecko/script_utilities.py
@@ -213,6 +213,23 @@ class Utilities(script_utilities.Utilities):
 
         return None
 
+    def isEntry(self, obj):
+        """Returns True if we should treat this object as an entry."""
+
+        if not obj:
+            return False
+
+        if obj.getRole() == pyatspi.ROLE_ENTRY:
+            return True
+
+        if obj.getState().contains(pyatspi.STATE_EDITABLE) \
+           and obj.getRole() in [pyatspi.ROLE_DOCUMENT_FRAME,
+                                 pyatspi.ROLE_PARAGRAPH,
+                                 pyatspi.ROLE_TEXT]:
+            return True
+
+        return False
+
     def isLayoutOnly(self, obj):
         """Returns True if the given object is for layout purposes only."""
 
@@ -224,6 +241,11 @@ class Utilities(script_utilities.Utilities):
         else:
             return script_utilities.Utilities.isLayoutOnly(self, obj)
 
+    def isPasswordText(self, obj):
+        """Returns True if we should treat this object as password text."""
+
+        return obj and obj.getRole() == pyatspi.ROLE_PASSWORD_TEXT
+
     def isReadOnlyTextArea(self, obj):
         """Returns True if obj is a text entry area that is read only."""
 
diff --git a/src/orca/scripts/toolkits/Gecko/speech_generator.py b/src/orca/scripts/toolkits/Gecko/speech_generator.py
index 18355e3..f4669c4 100644
--- a/src/orca/scripts/toolkits/Gecko/speech_generator.py
+++ b/src/orca/scripts/toolkits/Gecko/speech_generator.py
@@ -499,6 +499,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
             result.extend(speech_generator.SpeechGenerator.\
                                            generateSpeech(self, obj, **args))
             self._restoreRole(oldRole, args)
+        elif self._script.utilities.isEntry(obj):
+            oldRole = self._overrideRole(pyatspi.ROLE_ENTRY, args)
+            result.extend(speech_generator.SpeechGenerator.\
+                                           generateSpeech(self, obj, **args))
+            self._restoreRole(oldRole, args)
         # ARIA widgets get treated like regular default widgets.
         #
         else:
diff --git a/src/orca/scripts/toolkits/Gecko/structural_navigation.py b/src/orca/scripts/toolkits/Gecko/structural_navigation.py
index 8bd1298..8560650 100644
--- a/src/orca/scripts/toolkits/Gecko/structural_navigation.py
+++ b/src/orca/scripts/toolkits/Gecko/structural_navigation.py
@@ -232,3 +232,56 @@ class GeckoStructuralNavigation(structural_navigation.StructuralNavigation):
                     eocs = float(string.count(embeddedObjectChar))
                     # print "Guess #2", string, eocs/len(string)
                     return eocs/len(string) < 0.005
+
+    ########################
+    #                      #
+    # Entries              #
+    #                      #
+    ########################
+
+    def _entryPredicate(self, obj, arg=None):
+        """The predicate to be used for verifying that the object
+        obj is an entry.
+
+        Arguments:
+        - obj: the accessible object under consideration.
+        - arg: an optional argument which may need to be included in
+          the criteria (e.g. the level of a heading).
+        """
+
+        isMatch = False
+        if self._script.utilities.isEntry(obj) \
+           or self._script.utilities.isPasswordText(obj):
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE) \
+                  and state.contains(pyatspi.STATE_EDITABLE)
+
+        return isMatch
+
+    ########################
+    #                      #
+    # Form Fields          #
+    #                      #
+    ########################
+
+    def _formFieldPredicate(self, obj, arg=None):
+        """The predicate to be used for verifying that the object
+        obj is a form field.
+
+        Arguments:
+        - obj: the accessible object under consideration.
+        - arg: an optional argument which may need to be included in
+          the criteria (e.g. the level of a heading).
+        """
+
+        isMatch = False
+        if obj and obj.getRole() in self.FORM_ROLES:
+            state = obj.getState()
+            isMatch = state.contains(pyatspi.STATE_FOCUSABLE) \
+                  and state.contains(pyatspi.STATE_SENSITIVE)
+
+            if obj.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
+                isMatch = isMatch and state.contains(pyatspi.STATE_EDITABLE)
+
+        return isMatch
diff --git a/src/orca/structural_navigation.py b/src/orca/structural_navigation.py
index 4385fa8..7dbf299 100644
--- a/src/orca/structural_navigation.py
+++ b/src/orca/structural_navigation.py
@@ -465,6 +465,7 @@ class StructuralNavigation:
     FORM_ROLES = [pyatspi.ROLE_CHECK_BOX,
                   pyatspi.ROLE_RADIO_BUTTON,
                   pyatspi.ROLE_COMBO_BOX,
+                  pyatspi.ROLE_DOCUMENT_FRAME, # rich text editing
                   pyatspi.ROLE_LIST,
                   pyatspi.ROLE_ENTRY,
                   pyatspi.ROLE_PASSWORD_TEXT,
@@ -2438,7 +2439,8 @@ class StructuralNavigation:
           the criteria (e.g. the level of a heading).
         """
 
-        role = [pyatspi.ROLE_ENTRY,
+        role = [pyatspi.ROLE_DOCUMENT_FRAME,
+                pyatspi.ROLE_ENTRY,
                 pyatspi.ROLE_PASSWORD_TEXT,
                 pyatspi.ROLE_TEXT]
         roleMatch = collection.MATCH_ANY
@@ -2450,7 +2452,8 @@ class StructuralNavigation:
                              states=state,
                              matchStates=stateMatch,
                              roles=role,
-                             matchRoles=roleMatch)
+                             matchRoles=roleMatch,
+                             applyPredicate=True)
 
     def _entryPredicate(self, obj, arg=None):
         """The predicate to be used for verifying that the object
@@ -2463,7 +2466,8 @@ class StructuralNavigation:
         """
 
         isMatch = False
-        if obj and obj.getRole() in [pyatspi.ROLE_ENTRY,
+        if obj and obj.getRole() in [pyatspi.ROLE_DOCUMENT_FRAME,
+                                     pyatspi.ROLE_ENTRY,
                                      pyatspi.ROLE_PASSWORD_TEXT,
                                      pyatspi.ROLE_TEXT]:
             state = obj.getState()
@@ -2547,7 +2551,8 @@ class StructuralNavigation:
                              states=state,
                              matchStates=stateMatch,
                              roles=role,
-                             matchRoles=roleMatch)
+                             matchRoles=roleMatch,
+                             applyPredicate=True)
 
     def _formFieldPredicate(self, obj, arg=None):
         """The predicate to be used for verifying that the object



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