[orca] Initial implementation of focus versus browsing mode



commit 4836e84df710ced2e63ef0b4f722df162fa30a5d
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Fri Aug 8 16:26:03 2014 -0400

    Initial implementation of focus versus browsing mode

 src/orca/cmdnames.py                       |   16 ++
 src/orca/messages.py                       |   30 ++++
 src/orca/scripts/toolkits/Gecko/keymaps.py |    2 +
 src/orca/scripts/toolkits/Gecko/script.py  |  209 ++++++++++++----------------
 4 files changed, 134 insertions(+), 123 deletions(-)
---
diff --git a/src/orca/cmdnames.py b/src/orca/cmdnames.py
index e4722e6..dbfa15f 100644
--- a/src/orca/cmdnames.py
+++ b/src/orca/cmdnames.py
@@ -944,6 +944,22 @@ TABLE_CELL_RIGHT = _("Goes right one cell.")
 # Translators: this is for navigating among table cells in a document.
 TABLE_CELL_UP = _("Goes up one cell.")
 
+# Translators: Orca has a number of commands that override the default
+# behavior within an application. For instance, on a web page, "h" moves
+# you to the next heading. What should happen when you press an "h" in
+# an entry on a web page depends: If you want to resume reading content,
+# "h" should move to the next heading; if you want to enter text, "h"
+# should not not move you to the next heading. Similarly, if you are
+# at the bottom of an entry and press Down arrow, should you leave the
+# entry? Again, it depends on if you want to resume reading content or
+# if you are editing the text in the entry. Because Orca doesn't know
+# what you want to do, it has two modes: In browse mode, Orca treats
+# key presses as commands to read the content; in focus mode, Orca treats
+# key presses as something that should be handled by the focused widget.
+# This string is associated with the Orca command to manually switch
+# between these two modes.
+TOGGLE_PRESENTATION_MODE = _("Switches between browse mode and focus mode.")
+
 # Translators: this is for navigating among unvisited links in a document.
 UNVISITED_LINK_PREV = _("Goes to previous unvisited link.")
 
diff --git a/src/orca/messages.py b/src/orca/messages.py
index 7310d37..1d5560c 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -1075,6 +1075,36 @@ MISSPELLED_WORD = _("Misspelled word: %s")
 # containing the misspelled word in the document. This is known as the context.
 MISSPELLED_WORD_CONTEXT = _("Context is %s")
 
+# Translators: Orca has a number of commands that override the default
+# behavior within an application. For instance, on a web page, "h" moves
+# you to the next heading. What should happen when you press an "h" in
+# an entry on a web page depends: If you want to resume reading content,
+# "h" should move to the next heading; if you want to enter text, "h"
+# should not not move you to the next heading. Similarly, if you are
+# at the bottom of an entry and press Down arrow, should you leave the
+# entry? Again, it depends on if you want to resume reading content or
+# if you are editing the text in the entry. Because Orca doesn't know
+# what you want to do, it has two modes: In browse mode, Orca treats
+# key presses as commands to read the content; in focus mode, Orca treats
+# key presses as something that should be handled by the focused widget.
+# This string is the message presented when Orca switches to browse mode.
+MODE_BROWSE = _("Browse mode")
+
+# Translators: Orca has a number of commands that override the default
+# behavior within an application. For instance, on a web page, "h" moves
+# you to the next heading. What should happen when you press an "h" in
+# an entry on a web page depends: If you want to resume reading content,
+# "h" should move to the next heading; if you want to enter text, "h"
+# should not not move you to the next heading. Similarly, if you are
+# at the bottom of an entry and press Down arrow, should you leave the
+# entry? Again, it depends on if you want to resume reading content or
+# if you are editing the text in the entry. Because Orca doesn't know
+# what you want to do, it has two modes: In browse mode, Orca treats
+# key presses as commands to read the content; in focus mode, Orca treats
+# key presses as something that should be handled by the focused widget.
+# This string is the message presented when Orca switches to focus mode.
+MODE_FOCUS = _("Focus mode")
+
 # Translators: Hovering the mouse over certain objects on a web page causes a 
 # new object to appear such as a pop-up menu. Orca has a command will move the
 # user to the object which just appeared as a result of the user hovering the
diff --git a/src/orca/scripts/toolkits/Gecko/keymaps.py b/src/orca/scripts/toolkits/Gecko/keymaps.py
index 60b3ec2..4ee860f 100644
--- a/src/orca/scripts/toolkits/Gecko/keymaps.py
+++ b/src/orca/scripts/toolkits/Gecko/keymaps.py
@@ -85,6 +85,8 @@ commonKeymap = (
     ("F12", defaultModifierMask, ORCA_MODIFIER_MASK,
     "toggleCaretNavigationHandler"),
 
+    ("a", defaultModifierMask, ORCA_MODIFIER_MASK, "togglePresentationModeHandler"),
+
     ("Right", defaultModifierMask, ORCA_MODIFIER_MASK,
     "goNextObjectInOrderHandler"),
 
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index fa3da4b..1af428d 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -234,6 +234,8 @@ class Script(default.Script):
         self.preMouseOverContext = [None, -1]
         self.inMouseOverObject = False
 
+        self._inFocusMode = False
+
         # See bug 665522 - comment 5
         app.setCacheMask(pyatspi.cache.DEFAULT ^ pyatspi.cache.CHILDREN)
 
@@ -428,6 +430,11 @@ class Script(default.Script):
                 Script.moveToMouseOver,
                 cmdnames.MOUSE_OVER_MOVE)
 
+        self.inputEventHandlers["togglePresentationModeHandler"] = \
+            input_event.InputEventHandler(
+                Script.togglePresentationMode,
+                cmdnames.TOGGLE_PRESENTATION_MODE)
+
     def __getArrowBindings(self):
         """Returns an instance of keybindings.KeyBindings that use the
         arrow keys for navigating HTML content.
@@ -856,11 +863,15 @@ class Script(default.Script):
 
         self.setCaretContext(obj, event.detail1)
         if not _settingsManager.getSetting('caretNavigationEnabled') \
+           or self._inFocusMode \
            or obj.getState().contains(pyatspi.STATE_EDITABLE):
             orca.setLocusOfFocus(event, obj, False)
 
         default.Script.onCaretMoved(self, event)
 
+        if self._useFocusMode(obj) != self._inFocusMode:
+            self.togglePresentationMode(None)
+
     def onTextDeleted(self, event):
         """Called whenever text is from an an object.
 
@@ -1095,6 +1106,9 @@ class Script(default.Script):
     def onFocus(self, event):
         """Callback for focus: accessibility events."""
 
+        if not self.inDocumentContent(event.source):
+            return
+
         # NOTE: This event type is deprecated and Orca should no longer use it.
         # This callback remains just to handle bugs in applications and toolkits
         # during the remainder of the unstable (3.11) development cycle.
@@ -1214,6 +1228,49 @@ class Script(default.Script):
         if not self.utilities.hasMatchingHierarchy(event.source, rolesList):
             default.Script.handleProgressBarUpdate(self, event, obj)
 
+    def _useFocusMode(self, obj):
+        try:
+            role = obj.getRole()
+            state = obj.getState()
+        except:
+            return False
+
+        if not state.contains(pyatspi.STATE_FOCUSED) \
+           and not state.contains(pyatspi.STATE_SELECTED):
+            return False
+
+        if state.contains(pyatspi.STATE_EDITABLE) \
+           or state.contains(pyatspi.STATE_EXPANDABLE):
+            return True
+
+        focusModeRoles = [pyatspi.ROLE_COMBO_BOX,
+                          pyatspi.ROLE_ENTRY,
+                          pyatspi.ROLE_LIST_BOX,
+                          pyatspi.ROLE_LIST_ITEM,
+                          pyatspi.ROLE_MENU,
+                          pyatspi.ROLE_MENU_ITEM,
+                          pyatspi.ROLE_CHECK_MENU_ITEM,
+                          pyatspi.ROLE_RADIO_MENU_ITEM,
+                          pyatspi.ROLE_PAGE_TAB,
+                          pyatspi.ROLE_PASSWORD_TEXT,
+                          pyatspi.ROLE_PROGRESS_BAR,
+                          pyatspi.ROLE_SLIDER,
+                          pyatspi.ROLE_SPIN_BUTTON,
+                          pyatspi.ROLE_TOOL_BAR,
+                          pyatspi.ROLE_TABLE_CELL,
+                          pyatspi.ROLE_TABLE_ROW,
+                          pyatspi.ROLE_TABLE,
+                          pyatspi.ROLE_TREE_TABLE,
+                          pyatspi.ROLE_TREE]
+
+        if role in focusModeRoles:
+            return True
+
+        if obj.parent.getRole() in focusModeRoles:
+            return True
+
+        return False
+
     def locusOfFocusChanged(self, event, oldFocus, newFocus):
         """Called when the object with focus changes.
 
@@ -1232,6 +1289,7 @@ class Script(default.Script):
 
         if not self.inDocumentContent(newFocus):
             default.Script.locusOfFocusChanged(self, event, oldFocus, newFocus)
+            self._inFocusMode = False
             return
 
         caretOffset = -1
@@ -1245,6 +1303,9 @@ class Script(default.Script):
         self.setCaretContext(newFocus, caretOffset)
         default.Script.locusOfFocusChanged(self, event, oldFocus, newFocus)
 
+        if self._useFocusMode(newFocus) != self._inFocusMode:
+            self.togglePresentationMode(None)
+
     def findObjectOnLine(self, obj, offset, contents):
         """Determines if the item described by the object and offset is
         in the line contents.
@@ -1367,8 +1428,7 @@ class Script(default.Script):
         if not obj:
             return
 
-        if obj.getState().contains(pyatspi.STATE_FOCUSED) \
-           and obj.getRole() not in [pyatspi.ROLE_LINK, pyatspi.ROLE_ALERT]:
+        if self._inFocusMode:
             default.Script.updateBraille(self, obj, extraRegion)
             return
 
@@ -1705,7 +1765,8 @@ class Script(default.Script):
         """Returns True if we should do our own caret navigation.
         """
 
-        if not _settingsManager.getSetting('caretNavigationEnabled'):
+        if not _settingsManager.getSetting('caretNavigationEnabled') \
+           or self._inFocusMode:
             return False
 
         if not self.inDocumentContent():
@@ -1723,95 +1784,7 @@ class Script(default.Script):
         if not orca_state.locusOfFocus:
             return False
 
-        weHandleIt = True
-        obj = orca_state.locusOfFocus
-        role = obj.getRole()
-        if self.utilities.isEntry(obj):
-            text        = obj.queryText()
-            length      = text.characterCount
-            caretOffset = text.caretOffset
-            singleLine  = obj.getState().contains(
-                pyatspi.STATE_SINGLE_LINE)
-
-            # Single line entries have an additional newline character
-            # at the end.
-            #
-            newLineAdjustment = int(not singleLine)
-
-            # Home and End should not be overridden if we're in an
-            # entry.
-            #
-            if keyboardEvent.event_string in ["Home", "End"]:
-                return False
-
-            if obj.parent.getRole() == pyatspi.ROLE_COMBO_BOX \
-              and keyboardEvent.event_string in ["Up", "Down"]:
-               return False
-
-            # We want to use our caret navigation model in an entry if
-            # there's nothing in the entry, we're at the beginning of
-            # the entry and press Left or Up, or we're at the end of the
-            # entry and press Right or Down.
-            #
-            if length == 0 \
-               or ((length == 1) and (text.getText(0, -1) == "\n")):
-                weHandleIt = True
-            elif caretOffset <= 0:
-                weHandleIt = keyboardEvent.event_string \
-                             in ["Up", "Left"]
-            elif caretOffset >= length - newLineAdjustment \
-                 and not self._autocompleteVisible:
-                weHandleIt = keyboardEvent.event_string \
-                             in ["Down", "Right"]
-            else:
-                weHandleIt = False
-
-            if singleLine and not weHandleIt \
-               and not self._autocompleteVisible:
-                weHandleIt = keyboardEvent.event_string in ["Up", "Down"]
-
-        elif keyboardEvent.modifiers & keybindings.ALT_MODIFIER_MASK:
-            # Alt+Down Arrow is the Firefox command to expand/collapse the
-            # *currently focused* combo box.  When Orca is controlling the
-            # caret, it is possible to navigate into a combo box *without
-            # giving focus to that combo box*.  Under these circumstances,
-            # the menu item has focus.  Because the user knows that he/she
-            # is on a combo box, he/she expects to be able to use Alt+Down
-            # Arrow to expand the combo box.  Therefore, if a menu item has
-            # focus and Alt+Down Arrow is pressed, we will handle it by
-            # giving the combo box focus and expanding it as the user
-            # expects.  We also want to avoid grabbing focus on a combo box.
-            # Therefore, if the caret is immediately before a combo box,
-            # we'll hand it the same way.
-            #
-            if keyboardEvent.event_string == "Down":
-                [obj, offset] = self.getCaretContext()
-                index = self.getChildIndex(obj, offset)
-                if index >= 0:
-                    weHandleIt = \
-                        obj[index].getRole() == pyatspi.ROLE_COMBO_BOX
-                if not weHandleIt:
-                    weHandleIt = role == pyatspi.ROLE_MENU_ITEM
-
-        elif role in [pyatspi.ROLE_COMBO_BOX, pyatspi.ROLE_LIST_BOX]:
-            weHandleIt = keyboardEvent.event_string in ["Left", "Right"]
-
-        elif role == pyatspi.ROLE_LIST_ITEM:
-            weHandleIt = not obj.getState().contains(pyatspi.STATE_FOCUSED)
-
-        elif role in [pyatspi.ROLE_LIST, pyatspi.ROLE_TABLE_CELL]:
-            weHandleIt = not obj.getState().contains(pyatspi.STATE_FOCUSABLE)
-
-        elif role in [pyatspi.ROLE_MENU, pyatspi.ROLE_MENU_ITEM]:
-            weHandleIt = False
-
-        elif role in [pyatspi.ROLE_SLIDER, pyatspi.ROLE_SPIN_BUTTON]:
-            weHandleIt = False
-
-        elif role == pyatspi.ROLE_PAGE_TAB:
-            weHandleIt = False
-
-        return weHandleIt
+        return True
 
     def useStructuralNavigationModel(self):
         """Returns True if we should do our own structural navigation.
@@ -1819,43 +1792,16 @@ class Script(default.Script):
         or a list.
         """
 
-        letThemDoItEditableRoles = [pyatspi.ROLE_ENTRY,
-                                    pyatspi.ROLE_TEXT,
-                                    pyatspi.ROLE_PASSWORD_TEXT]
-        letThemDoItSelectionRoles = [pyatspi.ROLE_LIST,
-                                     pyatspi.ROLE_LIST_ITEM,
-                                     pyatspi.ROLE_MENU_ITEM]
+        if not self.structuralNavigation.enabled or self._inFocusMode:
+            return False
 
-        if not self.structuralNavigation.enabled:
+        if not self.inDocumentContent():
             return False
 
         if self._loadingDocumentContent:
             return False
 
-        # If the Orca_Modifier key was pressed, we're handling it.
-        #
-        if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
-            mods = orca_state.lastInputEvent.modifiers
-            isOrcaKey = mods & keybindings.ORCA_MODIFIER_MASK
-            if isOrcaKey:
-                return True
-
-        obj = orca_state.locusOfFocus
-        while obj:
-            if obj.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
-                # Don't use the structural navivation model if the
-                # user is editing the document.
-                return not obj.getState().contains(pyatspi.STATE_EDITABLE)
-            elif obj.getRole() in letThemDoItEditableRoles:
-                return not obj.getState().contains(pyatspi.STATE_EDITABLE)
-            elif obj.getRole() in letThemDoItSelectionRoles:
-                return not obj.getState().contains(pyatspi.STATE_FOCUSED)
-            elif obj.getRole() == pyatspi.ROLE_COMBO_BOX:
-                return False
-            else:
-                obj = obj.parent
-
-        return False
+        return True
 
     def _getAttrDictionary(self, obj):
         if not obj:
@@ -4161,6 +4107,23 @@ class Script(default.Script):
         else:
             self.presentMessage(messages.LIVE_REGIONS_OFF)
 
+    def togglePresentationMode(self, inputEvent):
+        if self._inFocusMode:
+            [obj, characterOffset] = self.getCaretContext()
+            try:
+                parentRole = obj.parent.getRole()
+            except:
+                parentRole = None
+            if parentRole == pyatspi.ROLE_LIST_BOX:
+                self.setCaretContext(obj.parent, -1)
+            elif parentRole == pyatspi.ROLE_MENU:
+                self.setCaretContext(obj.parent.parent, -1)
+
+            self.presentMessage(messages.MODE_BROWSE)
+        else:
+            self.presentMessage(messages.MODE_FOCUS)
+        self._inFocusMode = not self._inFocusMode
+
     def toggleCaretNavigation(self, inputEvent):
         """Toggles between Firefox native and Orca caret navigation."""
 


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