[orca] Begin cleanup of the focus-related code for Gecko-based apps
- From: Joanmarie Diggs <joanied src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Begin cleanup of the focus-related code for Gecko-based apps
- Date: Thu, 14 Nov 2013 05:16:01 +0000 (UTC)
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]