[orca] Begin cleanup of the focus-related code for Gecko-based apps



commit 8260578be12705f7f1fd47a2a48d9fddaff96784
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Tue Nov 12 16:04:05 2013 -0500

    Begin cleanup of the focus-related code for Gecko-based apps

 src/orca/scripts/apps/Thunderbird/script.py        |  281 ++------------------
 .../scripts/apps/Thunderbird/script_utilities.py   |    5 +-
 src/orca/scripts/toolkits/Gecko/script.py          |  186 +++----------
 3 files changed, 59 insertions(+), 413 deletions(-)
---
diff --git a/src/orca/scripts/apps/Thunderbird/script.py b/src/orca/scripts/apps/Thunderbird/script.py
index 7d1a2ee..0d6eb95 100644
--- a/src/orca/scripts/apps/Thunderbird/script.py
+++ b/src/orca/scripts/apps/Thunderbird/script.py
@@ -52,8 +52,6 @@ _settingsManager = settings_manager.getManager()
 class Script(Gecko.Script):
     """The script for Thunderbird."""
 
-    _containingPanelName = ""
-
     def __init__(self, app):
         """ Creates a new script for the given application.
 
@@ -61,27 +59,11 @@ class Script(Gecko.Script):
         - app: the application to create a script for.
         """
 
-        # Set the debug level for all the methods in this script.
-        self.debugLevel = debug.LEVEL_FINEST
-
-        # http://bugzilla.mozilla.org/show_bug.cgi?id=659018
-        if app.toolkitVersion < "7.0":
-            app.setCacheMask(pyatspi.cache.ALL ^ pyatspi.cache.NAME)
-
         # Store the last autocompleted string for the address fields
         # so that we're not too 'chatty'.  See bug #533042.
         #
         self._lastAutoComplete = ""
 
-        # When a mail message gets focus, we'll get a window:activate event
-        # followed by two focus events for the document frame.  We want to
-        # present the message if it was just opened; we don't if it was
-        # already opened and the user has just returned focus to it. Store
-        # the fact that a message was loaded which we should present once
-        # the document frame claims focus. See bug #541018.
-        #
-        self._messageLoaded = False
-
         Gecko.Script.__init__(self, app)
 
         # This will be used to cache a handle to the Thunderbird text area for
@@ -134,41 +116,6 @@ class Script(Gecko.Script):
         prefs.writelines("%s.sayAllOnLoad = %s\n" % (prefix, value))
         script_settings.sayAllOnLoad = value
 
-    def _debug(self, msg):
-        """ Convenience method for printing debug messages"""
-
-        debug.println(self.debugLevel, "Thunderbird.py: "+msg)
-
-    def _isSpellCheckListItemFocus(self, event):
-        """Check if this event is for a list item in the spell checking
-        dialog and whether it has a FOCUSED state.
-
-        Arguments:
-        - event: the Event
-
-        Return True is this event is for a list item in the spell checking 
-        dialog and it doesn't have a FOCUSED state, Otherwise return False.
-        """
-
-        rolesList = [pyatspi.ROLE_LIST_ITEM, \
-                     pyatspi.ROLE_LIST, \
-                     pyatspi.ROLE_DIALOG, \
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            dialog = event.source.parent.parent
-
-            # Translators: this is what the name of the spell checking
-            # dialog in Thunderbird begins with. The translated form
-            # has to match what Thunderbird is using.  We hate keying
-            # off stuff like this, but we're forced to do so in this case.
-            #
-            if dialog.name.startswith(_("Check Spelling")):
-                state = event.source.getState()
-                if not state.contains(pyatspi.STATE_FOCUSED):
-                    return True
-
-        return False
-
     def onCaretMoved(self, event):
         """Called whenever the caret moves.
 
@@ -230,177 +177,41 @@ class Script(Gecko.Script):
     def onFocusedChanged(self, event):
         """Callback for object:state-changed:focused accessibility events."""
 
-        # If we get an "object:state-changed:focused" event for a list
-        # item in the spell checking dialog, and it doesn't have a
-        # FOCUSED state (i.e. we didn't navigate to it), then ignore it.
-        # See bug #535192 for more details.
-        #
-        if self._isSpellCheckListItemFocus(event):
+        if not event.detail1:
             return
 
-        # TODO - JD: Determine how much of the stuff below is still needed.
-
-        obj = event.source
-        parent = obj.parent
-        top = self.utilities.topLevelObject(obj)
-        consume = False
-
-        # Clear the stored autocomplete string.
-        #
         self._lastAutoComplete = ""
 
-        # Don't speak chrome URLs.
-        #
-        if obj.name.startswith("chrome://"):
+        obj = event.source
+        if not self.inDocumentContent(obj):
+            default.Script.onFocusedChanged(self, event)
             return
 
-        # This is a better fix for bug #405541. Thunderbird gives
-        # focus to the cell in the column that is being sorted
-        # (e.g., Date). Braille should show the row beginning with
-        # the first populated cell. Set the locusOfFocus to that
-        # cell and consume the event so that the Gecko script
-        # doesn't reset it.
-        #
-        if obj.getRole() == pyatspi.ROLE_TABLE_CELL \
-           and parent.getRole() != pyatspi.ROLE_LIST_ITEM:
-            table = parent.queryTable()
-            row = table.getRowAtIndex(self.utilities.cellIndex(obj))
-            for i in range(0, table.nColumns):
-                acc = table.getAccessibleAt(row, i)
-                if acc.name:
-                    # For some reason orca.py's check to see if the
-                    # object we're setting the locusOfFocus to is the
-                    # same as the current locusOfFocus is returning
-                    # True when it's not actually True. Therefore,
-                    # we'll force the propagation as a precaution.
-                    #
-                    orca.setLocusOfFocus(event, acc, force=True)
-                    consume = True
-                    break
-
-        # Text area (for caching handle for spell checking purposes).
-        #
-        # This works in conjunction with code in the onNameChanged()
-        # method. Check to see if focus is currently in the Thunderbird
-        # message area. If it is, then, if this is the first time, save
-        # a pointer to the document frame which contains the text being
-        # edited.
-        #
-        # Note that this drops through to then use the default event
-        # processing in the parent class for this "focus:" event.
-
-        rolesList = [pyatspi.ROLE_DOCUMENT_FRAME,
-                     pyatspi.ROLE_INTERNAL_FRAME,
-                     pyatspi.ROLE_FRAME,
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            self._debug("onFocus - message text area.")
-
-            self.textArea = event.source
-            # Fall-thru to process the event with the default handler.
-
-        # Handle dialogs.
-        #
-        if top and top.getRole() == pyatspi.ROLE_DIALOG:
-            self._speakEnclosingPanel(obj)
-
-        # Handle a newly-opened message.
-        #
-        if event.source.getRole() == pyatspi.ROLE_DOCUMENT_FRAME \
-           and orca_state.locusOfFocus \
-           and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_FRAME:
-            if self._messageLoaded:
-                consume = True
-                self._presentMessage(event.source)
-
-            # If the user just gave focus to the message window (e.g. by
-            # Alt+Tabbing back into it), we might have an existing caret
-            # context. But we'll need the document frame in order to verify
-            # this. Therefore try to find the document frame.
-            #
-            elif self.getCaretContext() == [None, -1]:
-                documentFrame = None
-                for child in orca_state.locusOfFocus:
-                    if child.getRole() == pyatspi.ROLE_INTERNAL_FRAME \
-                       and child.childCount \
-                       and child[0].getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
-                        documentFrame = child[0]
-                        break
-                try:
-                    contextObj, contextOffset = \
-                        self._documentFrameCaretContext[hash(documentFrame)]
-                    if contextObj:
-                        orca.setLocusOfFocus(event, contextObj)
-                except:
-                    pass
-
-        if not consume:
-            # Much of the Gecko code is designed to handle Gecko's broken
-            # caret navigation. This is not needed in -- and can sometimes
-            # interfere with our presentation of -- a simple message being
-            # composed by the user. Surely we can count on Thunderbird to
-            # handle navigation in that case.
-            #
-            if self.isEditableMessage(event.source):
-                default.Script.onFocusedChanged(self, event)
-            else:
-                Gecko.Script.onFocusedChanged(self, event)
-
-    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
-        """Called when the visual object with focus changes.
-
-        Arguments:
-        - event: if not None, the Event that caused the change
-        - oldLocusOfFocus: Accessible that is the old locus of focus
-        - newLocusOfFocus: Accessible that is the new locus of focus
-        """
-
-        # If the user has just deleted a message from the middle of the 
-        # message header list, then we want to speak the newly focused 
-        # message in the header list (even though it has the same row 
-        # number as the previously deleted message).
-        # See bug #536451 for more details.
-        #
-        rolesList = [pyatspi.ROLE_TABLE_CELL,
-                     pyatspi.ROLE_TREE_TABLE,
-                     pyatspi.ROLE_SCROLL_PANE,
-                     pyatspi.ROLE_SCROLL_PANE]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            lastKey, mods = self.utilities.lastKeyAndModifiers()
-            if lastKey == "Delete":
-                oldLocusOfFocus = None
+        if self.isEditableMessage(obj):
+            self.textArea = obj
+            default.Script.onFocusedChanged(self, event)
+            return
 
-        # If the user has just deleted an open mail message, then we want to
-        # try to speak the new name of the open mail message frame.
-        # See bug #540039 for more details.
-        #
-        rolesList = [pyatspi.ROLE_DOCUMENT_FRAME, \
-                     pyatspi.ROLE_INTERNAL_FRAME, \
-                     pyatspi.ROLE_FRAME, \
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            lastKey, mods = self.utilities.lastKeyAndModifiers()
-            if lastKey == "Delete":
-                oldLocusOfFocus = None
-                state = newLocusOfFocus.getState()
-                if state.contains(pyatspi.STATE_DEFUNCT):
-                    newLocusOfFocus = event.source
+        role = obj.getRole()
+        if role != pyatspi.ROLE_DOCUMENT_FRAME:
+            Gecko.Script.onFocusedChanged(self, event)
+            return
 
-        # Pass the event onto the parent class to be handled in the default way.
+        contextObj, contextOffset = self.getCaretContext()
+        if contextObj:
+            return
 
-        Gecko.Script.locusOfFocusChanged(self, event,
-                                         oldLocusOfFocus, newLocusOfFocus)
+        orca.setLocusOfFocus(event, obj, notifyScript=False)
 
     def onBusyChanged(self, event):
         """Callback for object:state-changed:busy accessibility events."""
 
         obj = event.source
         if obj.getRole() == pyatspi.ROLE_DOCUMENT_FRAME and not event.detail1:
-            self._messageLoaded = True
             if self.inDocumentContent():
+                self.speakMessage(obj.name)
                 self._presentMessage(obj)
 
-
     def onShowingChanged(self, event):
         """Callback for object:state-changed:showing accessibility events."""
 
@@ -489,19 +300,6 @@ class Script(Gecko.Script):
 
         Gecko.Script.onTextInserted(self, event)
 
-    def onVisibleDataChanged(self, event):
-        """Called when the visible data of an object changes."""
-
-        # [[[TODO: JD - In Gecko.py, we need onVisibleDataChanged() to
-        # to detect when the user switches between the tabs holding
-        # different URLs in Firefox.  Thunderbird issues very similar-
-        # looking events as the user types a subject in the message
-        # composition window. For now, rather than trying to distinguish
-        # them  in Gecko.py, we'll simply prevent Gecko.py from seeing when
-        # Thunderbird issues such an event.]]]
-        #
-        return
-
     def onNameChanged(self, event):
         """Called whenever a property on an object changes.
 
@@ -562,50 +360,6 @@ class Script(Gecko.Script):
                         allTokens += tokens
                         self.speakMisspeltWord(allTokens, badWord)
 
-    def _speakEnclosingPanel(self, obj):
-        """Speak the enclosing panel for the object, if it is
-        named. Going two containers up the hierarchy appears to be far
-        enough to find a named panel, if there is one.  Don't speak
-        panels whose name begins with 'chrome://'"""
-
-        self._debug("_speakEnclosingPanel")
-
-        parent = obj.parent
-        if not parent:
-            return
-
-        if parent.name != "" \
-            and (not parent.name.startswith("chrome://")) \
-            and (parent.getRole() == pyatspi.ROLE_PANEL):
-
-            # Speak the parent panel name, but only once.
-            #
-            if parent.name != self._containingPanelName:
-                self._containingPanelName = parent.name
-                utterances = []
-                # Translators: this is the name of a panel in Thunderbird.
-                #
-                text = _("%s panel") % parent.name
-                utterances.append(text)
-                speech.speak(utterances)
-        else:
-            grandparent = parent.parent
-            if grandparent \
-                and (grandparent.name != "") \
-                and (not grandparent.name.startswith("chrome://")) \
-                and (grandparent.getRole() == pyatspi.ROLE_PANEL):
-
-                # Speak the grandparent panel name, but only once.
-                #
-                if grandparent.name != self._containingPanelName:
-                    self._containingPanelName = grandparent.name
-                    utterances = []
-                    # Translators: this is the name of a panel in Thunderbird.
-                    #
-                    text = _("%s panel") % grandparent.name
-                    utterances.append(text)
-                    speech.speak(utterances)
-
     def _presentMessage(self, documentFrame):
         """Presents the first line of the message, or the entire message,
         depending on the user's sayAllOnLoad setting."""
@@ -616,7 +370,6 @@ class Script(Gecko.Script):
             self.presentLine(obj, offset)
         elif _settingsManager.getSetting('enableSpeech'):
             self.sayAll(None)
-        self._messageLoaded = False
 
     def sayCharacter(self, obj):
         """Speaks the character at the current caret position."""
diff --git a/src/orca/scripts/apps/Thunderbird/script_utilities.py 
b/src/orca/scripts/apps/Thunderbird/script_utilities.py
index 94b0073..7623470 100644
--- a/src/orca/scripts/apps/Thunderbird/script_utilities.py
+++ b/src/orca/scripts/apps/Thunderbird/script_utilities.py
@@ -62,10 +62,13 @@ class Utilities(Gecko.Utilities):
         Overridden here because multiple open messages are not arranged
         in tabs like they are in Firefox."""
 
+        obj = orca_state.locusOfFocus
+        if not obj:
+            return None
+
         if self.inFindToolbar():
             return Gecko.Utilities.documentFrame(self)
 
-        obj = orca_state.locusOfFocus
         while obj:
             role = obj.getRole()
             if role in [pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_EMBEDDED]:
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index 19baa77..4e07c43 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -1126,10 +1126,14 @@ class Script(default.Script):
         if role != pyatspi.ROLE_DOCUMENT_FRAME:
              return
 
+        try:
+            focusRole = orca_state.locusOfFocus.getRole()
+        except:
+            focusRole = None
+
         # The event is for the changing contents of the help frame as the user
         # navigates from topic to topic in the list on the left. Ignore this.
-        if orca_state.locusOfFocus \
-           and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_LIST_ITEM \
+        if focusRole == pyatspi.ROLE_LIST_ITEM \
            and not self.inDocumentContent(orca_state.locusOfFocus):
             return
  
@@ -1317,70 +1321,16 @@ class Script(default.Script):
             default.Script.onFocusedChanged(self, event)
             return
 
-        try:
-            eventSourceRole = event.source.getRole()
-        except:
-            return
-
-        if eventSourceRole in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]:
+        role = obj.getRole()
+        if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]:
             orca.setLocusOfFocus(event, event.source)
             return
 
-        # TODO - JD: Go through all of the crap below. :-/
-
-
-        # We also ignore focus events on the panel that holds the document
-        # frame.  We end up getting these typically because we've called
-        # grabFocus on this panel when we're doing caret navigation.  In
-        # those cases, we want the locus of focus to be the subcomponent
-        # that really holds the caret.
-        #
-        if eventSourceRole == pyatspi.ROLE_PANEL:
-            documentFrame = self.utilities.documentFrame()
-            if documentFrame and (documentFrame.parent == event.source):
-                return
-            else:
-                # Web pages can contain their own panels.  If the locus
-                # of focus is within that panel, we probably moved off
-                # of a focusable item (like a link within the panel)
-                # to something non-focusable (like text within the panel).
-                # If we don't ignore this event, we'll loop to the top
-                # of the panel.
-                #
-                containingPanel = self.utilities.ancestorWithRole(
-                    orca_state.locusOfFocus,
-                    [pyatspi.ROLE_PANEL],
-                    [pyatspi.ROLE_DOCUMENT_FRAME])
-                if self.utilities.isSameObject(containingPanel, event.source):
-                    return
-
-        # When we get a focus event on the document frame, it's usually
-        # because we did a grabFocus on its parent in setCaretPosition.
-        # We try to handle this here by seeing if there is already a
-        # 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:
-            try:
-                [obj, characterOffset] = self.getCaretContext()
-                state = obj.getState()
-                if not state.contains(pyatspi.STATE_FOCUSED):
-                    if not state.contains(pyatspi.STATE_FOCUSABLE) \
-                       or not self.inDocumentContent():
-                        orca.setLocusOfFocus(event, obj)
-                    return
-            except:
-                pass
-
-        elif eventSourceRole != pyatspi.ROLE_LINK:
-            [obj, characterOffset] = \
-                self.findFirstCaretContext(event.source, 0)
-            self.setCaretContext(obj, characterOffset)
-            if not self.utilities.isSameObject(event.source, obj):
-                if not self.utilities.isSameObject(
-                        obj, orca_state.locusOfFocus):
-                    orca.setLocusOfFocus(event, obj, notifyScript=False)
-                return
+        # As the caret moves into a non-focusable element, Gecko emits the
+        # signal on the first focusable element in the ancestry.
+        rolesToIgnore = pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_PANEL
+        if role in rolesToIgnore:
+            return
 
         default.Script.onFocusedChanged(self, event)
 
@@ -1437,68 +1387,33 @@ class Script(default.Script):
         if not self.utilities.hasMatchingHierarchy(event.source, rolesList):
             default.Script.handleProgressBarUpdate(self, event, obj)
 
-    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
-        """Called when the visual object with focus changes.
+    def locusOfFocusChanged(self, event, oldFocus, newFocus):
+        """Called when the object with focus changes.
 
         Arguments:
         - event: if not None, the Event that caused the change
-        - oldLocusOfFocus: Accessible that is the old locus of focus
-        - newLocusOfFocus: Accessible that is the new locus of focus
+        - oldFocus: Accessible that is the old focus
+        - newFocus: Accessible that is the new focus
         """
-        # Sometimes we get different accessibles for the same object.
-        #
-        if self.utilities.isSameObject(oldLocusOfFocus, newLocusOfFocus):
-            return
 
-        # We always automatically go back to focus tracking mode when
-        # the focus changes.
-        #
-        if self.flatReviewContext:
-            self.toggleFlatReviewMode()
-
-        # Try to handle the case where a spurious focus event was tossed
-        # at us.
-        #
-        if newLocusOfFocus and self.inDocumentContent(newLocusOfFocus):
-            text = self.utilities.queryNonEmptyText(newLocusOfFocus)
-            if text:
-                caretOffset = text.caretOffset
+        if not newFocus:
+            orca_state.noFocusTimeStamp = time.time()
+            return
 
-                # If the old locusOfFocus was not in the document frame, and
-                # if the old locusOfFocus's frame is the same as the frame
-                # containing the new locusOfFocus, we likely just returned
-                # from a toolbar (find, location, menu bar, etc.).  We do
-                # not want to speak the hierarchy between that toolbar and
-                # the document frame.
-                #
-                if oldLocusOfFocus and \
-                   not self.inDocumentContent(oldLocusOfFocus):
-                    oldFrame = self.utilities.ancestorWithRole(
-                        oldLocusOfFocus, [pyatspi.ROLE_FRAME], [])
-                    newFrame = self.utilities.ancestorWithRole(
-                        newLocusOfFocus, [pyatspi.ROLE_FRAME], [])
-                    if self.utilities.isSameObject(oldFrame, newFrame) or \
-                           newLocusOfFocus.getRole() == pyatspi.ROLE_DIALOG:
-                        self.setCaretPosition(newLocusOfFocus, caretOffset)
-                        self.presentLine(newLocusOfFocus, caretOffset)
-                        return
+        if self.utilities.inFindToolbar(newFocus):
+            self.madeFindAnnouncement = False
 
-            else:
-                caretOffset = 0
-            [obj, characterOffset] = \
-                  self.findFirstCaretContext(newLocusOfFocus, caretOffset)
-            self.setCaretContext(obj, characterOffset)
+        if not self.inDocumentContent(newFocus):
+            default.Script.locusOfFocusChanged(self, event, oldFocus, newFocus)
+            return
 
-        # If we've just landed in the Find toolbar, reset
-        # self.madeFindAnnouncement.
-        #
-        if newLocusOfFocus and self.utilities.inFindToolbar(newLocusOfFocus):
-            self.madeFindAnnouncement = False
+        caretOffset = 0
+        text = self.utilities.queryNonEmptyText(newFocus)
+        if text and (0 <= text.caretOffset < text.characterCount):
+            caretOffset = text.caretOffset
 
-        default.Script.locusOfFocusChanged(self,
-                                           event,
-                                           oldLocusOfFocus,
-                                           newLocusOfFocus)
+        self.setCaretContext(newFocus, caretOffset)
+        default.Script.locusOfFocusChanged(self, event, oldFocus, newFocus)
 
     def findObjectOnLine(self, obj, offset, contents):
         """Determines if the item described by the object and offset is
@@ -3976,44 +3891,19 @@ class Script(default.Script):
         given object.
         """
 
-        # Clear the flat review context if the user is currently in a
-        # flat review.
-        #
         if self.flatReviewContext:
             self.toggleFlatReviewMode()
 
-        caretContext = self.getCaretContext()
-
-        # Save where we are in this particular document frame.
-        # We do this because the user might have several URLs
-        # open in several different tabs, and we keep track of
-        # where the caret is for each documentFrame.
-        #
-        documentFrame = self.utilities.documentFrame()
-        if documentFrame:
-            self._documentFrameCaretContext[hash(documentFrame)] = caretContext
-
-        if caretContext == [obj, characterOffset]:
-            return
-
         self.setCaretContext(obj, characterOffset)
 
-        # If the item is a focusable list in an HTML form, we're here
-        # because we've arrowed to it.  We don't want to grab focus on
-        # it and trap the user in the list. The same is true for combo
-        # boxes.
-        #
-        if obj \
-           and obj.getRole() in [pyatspi.ROLE_LIST, pyatspi.ROLE_COMBO_BOX] \
-           and obj.getState().contains(pyatspi.STATE_FOCUSABLE):
-            characterOffset = self.utilities.characterOffsetInParent(obj)
-            obj = obj.parent
-            self.setCaretContext(obj, characterOffset)
+        try:
+            state = obj.getState()
+        except:
+            return
 
-        # Reset focus if need be.
-        #
-        if obj != orca_state.locusOfFocus:
-            orca.setLocusOfFocus(None, obj, notifyScript=False)
+        orca.setLocusOfFocus(None, obj, notifyScript=False)
+        if state.contains(pyatspi.STATE_FOCUSABLE):
+            obj.queryComponent().grabFocus()
 
         text = self.utilities.queryNonEmptyText(obj)
         if text:


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