[orca] Add support for clickables



commit d6f1eede88bd1d9ac93a5d8054ed7b02168bb01e
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Thu Aug 14 17:48:39 2014 -0400

    Add support for clickables

 src/orca/cmdnames.py                               |   20 ++-
 src/orca/formatting.py                             |    1 +
 src/orca/generator.py                              |   10 ++
 src/orca/guilabels.py                              |   12 ++
 src/orca/messages.py                               |   10 +-
 src/orca/object_properties.py                      |    4 +
 src/orca/script_utilities.py                       |    3 +
 .../scripts/toolkits/Gecko/braille_generator.py    |    2 +
 src/orca/scripts/toolkits/Gecko/formatting.py      |    2 +-
 src/orca/scripts/toolkits/Gecko/script.py          |    8 +-
 .../scripts/toolkits/Gecko/script_utilities.py     |   20 +++
 src/orca/scripts/toolkits/WebKitGtk/script.py      |    3 +-
 src/orca/speech_generator.py                       |   10 ++
 src/orca/structural_navigation.py                  |  160 ++++++++++----------
 14 files changed, 164 insertions(+), 101 deletions(-)
---
diff --git a/src/orca/cmdnames.py b/src/orca/cmdnames.py
index 01787df..2b71686 100644
--- a/src/orca/cmdnames.py
+++ b/src/orca/cmdnames.py
@@ -736,14 +736,6 @@ PRESENT_INPUT_LINE = _("Presents the contents of the input line.")
 # writing functions.
 STRUCTURAL_NAVIGATION_TOGGLE = _("Toggles structural navigation keys.")
 
-# Translators: this is for navigating among anchors in a document. An anchor is
-# a named spot that one can jump to.
-ANCHOR_PREV = _("Goes to previous anchor.")
-
-# Translators: this is for navigating among anchors in a document. An anchor is
-# a named spot that one can jump to.
-ANCHOR_NEXT = _("Goes to next anchor.")
-
 # Translators: this is for navigating among blockquotes in a document.
 BLOCKQUOTE_PREV = _("Goes to previous blockquote.")
 
@@ -771,6 +763,18 @@ CHECK_BOX_NEXT = _("Goes to next check box.")
 # Translators: this is for navigating among check boxes in a document.
 CHECK_BOX_LIST = _("Displays a list of check boxes.")
 
+# Translators: this is for navigating among clickable objects in a document.
+# A "clickable" is a web element with an "onClick" handler.
+CLICKABLE_PREV = _("Goes to previous clickable.")
+
+# Translators: this is for navigating among clickable objects in a document.
+# A "clickable" is a web element with an "onClick" handler.
+CLICKABLE_NEXT = _("Goes to next clickable.")
+
+# Translators: this is for navigating among clickable objects in a document.
+# A "clickable" is a web element with an "onClick" handler.
+CLICKABLE_LIST = _("Displays a list of clickables.")
+
 # Translators: this is for navigating among combo boxes in a document.
 COMBO_BOX_PREV = _("Goes to previous combo box.")
 
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index 8ebbc35..08ce450 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -64,6 +64,7 @@ formatting = {
             'multiselect': object_properties.STATE_MULTISELECT_SPEECH,
             'iconindex': object_properties.ICON_INDEX_SPEECH,
             'groupindex': object_properties.GROUP_INDEX_SPEECH,
+            'clickable': object_properties.STATE_CLICKABLE,
         },
         'braille': {
             'eol': object_properties.EOL_INDICATOR_BRAILLE,
diff --git a/src/orca/generator.py b/src/orca/generator.py
index 7254e4b..83023eb 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -411,6 +411,16 @@ class Generator:
     #                                                                   #
     #####################################################################
 
+    def _generateClickable(self, obj, **args):
+        if not args.get('mode', None):
+            args['mode'] = self._mode
+        args['stringType'] = 'clickable'
+
+        if self._script.utilities.isClickableElement(obj):
+            return [self._script.formatting.getString(**args)]
+
+        return []
+
     def _generateAvailability(self, obj, **args):
         """Returns an array of strings for use by speech and braille that
         represent the grayed/sensitivity/availability state of the
diff --git a/src/orca/guilabels.py b/src/orca/guilabels.py
index df5d4c6..ba7674d 100644
--- a/src/orca/guilabels.py
+++ b/src/orca/guilabels.py
@@ -372,6 +372,12 @@ SN_HEADER_CHECK_BOX = C_("structural navigation", "Check Box")
 # Translators: Orca has a command that presents a list of structural navigation
 # objects in a dialog box so that users can navigate more quickly than they
 # could with native keyboard navigation. This is the title for a column which
+# contains the text displayed for a web element with an "onClick" handler.
+SN_HEADER_CLICKABLE = C_("structural navigation", "Clickable")
+
+# Translators: Orca has a command that presents a list of structural navigation
+# objects in a dialog box so that users can navigate more quickly than they
+# could with native keyboard navigation. This is the title for a column which
 # contains the selected item in a combo box.
 SN_HEADER_COMBO_BOX = C_("structural navigation", "Combo Box")
 
@@ -500,6 +506,12 @@ SN_TITLE_CHECK_BOX = C_("structural navigation", "Check Boxes")
 # Translators: Orca has a command that presents a list of structural navigation
 # objects in a dialog box so that users can navigate more quickly than they
 # could with native keyboard navigation. This is the title of such a dialog box.
+# "Clickables" are web elements which have an "onClick" handler.
+SN_TITLE_CLICKABLE = C_("structural navigation", "Clickables")
+
+# Translators: Orca has a command that presents a list of structural navigation
+# objects in a dialog box so that users can navigate more quickly than they
+# could with native keyboard navigation. This is the title of such a dialog box.
 SN_TITLE_COMBO_BOX = C_("structural navigation", "Combo Boxes")
 
 # Translators: Orca has a command that presents a list of structural navigation
diff --git a/src/orca/messages.py b/src/orca/messages.py
index f98e158..1fc1bc7 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -1147,11 +1147,6 @@ NO_FOCUS =  _("No focus")
 # has keyboard focus.
 NO_FOCUSED_APPLICATION =  _("No application has focus.")
 
-# Translators: This is for navigating document content by moving from anchor to
-# anchor. (An anchor is a named spot that one can jump to.) This is a detailed
-# message which will be presented to the user if no more anchors can be found.
-NO_MORE_ANCHORS = _("No more anchors.")
-
 # Translators: This is for navigating document content by moving from blockquote
 # to blockquote. This is a detailed message which will be presented to the user
 # if no more blockquotes can be found.
@@ -1173,6 +1168,11 @@ NO_MORE_CHECK_BOXES = _("No more check boxes.")
 # will be presented to the user if no more check boxes can be found.
 NO_MORE_CHUNKS = _("No more large objects.")
 
+# Translators: This is for navigating document content by moving amongst web
+# elements which have an "onClick" action. This is a detailed message which
+# will be presented to the user if no more clickable elements can be found.
+NO_MORE_CLICKABLES = _("No more clickables.")
+
 # Translators: This is for navigating document content by moving from combo
 # box to combo box. This is a detailed message which will be presented to the
 # user if no more combo boxes can be found.
diff --git a/src/orca/object_properties.py b/src/orca/object_properties.py
index 5d24faa..bd50294 100644
--- a/src/orca/object_properties.py
+++ b/src/orca/object_properties.py
@@ -76,6 +76,10 @@ ROLE_HEADING_LEVEL_SPEECH = _("%(role)s level %(level)d")
 # of icons.
 ROLE_ICON_PANEL = _("Icon panel")
 
+# Translators: This is a state which applies to elements in document content
+# which have an "onClick" action.
+STATE_CLICKABLE = _("clickable")
+
 # Translators: This is a state which applies to items which can be expanded
 # or collapsed such as combo boxes and nodes/groups in a treeview. Collapsed
 # means the item's children are not showing; expanded means they are.
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 15430c8..96f4aab 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -2719,3 +2719,6 @@ class Utilities:
         red, green, blue = string.split(",")
 
         return int(red), int(green), int(blue)
+
+    def isClickableElement(self, obj):
+        return False
diff --git a/src/orca/scripts/toolkits/Gecko/braille_generator.py 
b/src/orca/scripts/toolkits/Gecko/braille_generator.py
index 7772698..6ccf24e 100644
--- a/src/orca/scripts/toolkits/Gecko/braille_generator.py
+++ b/src/orca/scripts/toolkits/Gecko/braille_generator.py
@@ -175,6 +175,8 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
         oldRole = None
         if self._script.utilities.isEntry(obj):
             oldRole = self._overrideRole(pyatspi.ROLE_ENTRY, args)
+        elif self._script.utilities.isClickableElement(obj):
+            oldRole = self._overrideRole(pyatspi.ROLE_LINK, args)
 
         # Treat menu items in collapsed combo boxes as if the combo box
         # had focus. This will make things more consistent with how we
diff --git a/src/orca/scripts/toolkits/Gecko/formatting.py b/src/orca/scripts/toolkits/Gecko/formatting.py
index d82f624..b4ffa93 100644
--- a/src/orca/scripts/toolkits/Gecko/formatting.py
+++ b/src/orca/scripts/toolkits/Gecko/formatting.py
@@ -36,7 +36,7 @@ formatting = {
     'speech': {
         'suffix': {
             'focused': '[]',
-            'unfocused': 'newNodeLevel + unselectedCell + ' + orca.formatting.TUTORIAL + ' + description ',
+            'unfocused': 'newNodeLevel + unselectedCell + clickable + ' + orca.formatting.TUTORIAL + ' + 
description ',
             'basicWhereAmI': orca.formatting.TUTORIAL + ' + description + liveRegionDescription',
             'detailedWhereAmI' : '[]'
             },
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index b00407c..0a985b1 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -282,11 +282,11 @@ class Script(default.Script):
         enabled in this script.
         """
 
-        enabledTypes = [GeckoStructuralNavigation.ANCHOR,
-                        GeckoStructuralNavigation.BLOCKQUOTE,
+        enabledTypes = [GeckoStructuralNavigation.BLOCKQUOTE,
                         GeckoStructuralNavigation.BUTTON,
                         GeckoStructuralNavigation.CHECK_BOX,
                         GeckoStructuralNavigation.CHUNK,
+                        GeckoStructuralNavigation.CLICKABLE,
                         GeckoStructuralNavigation.COMBO_BOX,
                         GeckoStructuralNavigation.ENTRY,
                         GeckoStructuralNavigation.FORM_FIELD,
@@ -1508,6 +1508,7 @@ class Script(default.Script):
             if (not len(string) and role != pyatspi.ROLE_PARAGRAPH) \
                or self.utilities.isEntry(obj) \
                or self.utilities.isPasswordText(obj) \
+               or self.utilities.isClickableElement(obj) \
                or role in [pyatspi.ROLE_LINK, pyatspi.ROLE_PUSH_BUTTON]:
                 [regions, fRegion] = \
                           self.brailleGenerator.generateBraille(obj)
@@ -3147,7 +3148,8 @@ class Script(default.Script):
             if not len(string) \
                or self.utilities.isEntry(obj) \
                or self.utilities.isPasswordText(obj) \
-               or role == pyatspi.ROLE_PUSH_BUTTON and obj.name:
+               or role == pyatspi.ROLE_PUSH_BUTTON and obj.name \
+               or self.utilities.isClickableElement(obj):
                 rv = self.speechGenerator.generateSpeech(obj)
                 # Crazy crap to make clump and friends happy until we can
                 # kill them. (They don't deal well with what the speech
diff --git a/src/orca/scripts/toolkits/Gecko/script_utilities.py 
b/src/orca/scripts/toolkits/Gecko/script_utilities.py
index 81c4cac..56ebe71 100644
--- a/src/orca/scripts/toolkits/Gecko/script_utilities.py
+++ b/src/orca/scripts/toolkits/Gecko/script_utilities.py
@@ -599,3 +599,23 @@ class Utilities(script_utilities.Utilities):
                 objects.extend(toAdd)
 
         return objects
+
+    def isClickableElement(self, obj):
+        # For Gecko, we want to identify things which are ONLY clickable.
+        # Things which are focusable, while technically "clickable", are
+        # easily discoverable (e.g. via role) and activatable (e.g. via
+        # pressing Space or Enter.
+        state = obj.getState()
+        if state.contains(pyatspi.STATE_FOCUSABLE):
+            return False
+
+        try:
+            action = obj.queryAction()
+        except NotImplementedError:
+            return False
+
+        for i in range(action.nActions):
+            if action.getName(i) in ["click"]:
+                return True
+
+        return False
diff --git a/src/orca/scripts/toolkits/WebKitGtk/script.py b/src/orca/scripts/toolkits/WebKitGtk/script.py
index 8e0b4a7..01ba504 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/script.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/script.py
@@ -151,8 +151,7 @@ class Script(default.Script):
         """Returns a list of the structural navigation object types
         enabled in this script."""
 
-        enabledTypes = [StructuralNavigation.ANCHOR,
-                        StructuralNavigation.BLOCKQUOTE,
+        enabledTypes = [StructuralNavigation.BLOCKQUOTE,
                         StructuralNavigation.BUTTON,
                         StructuralNavigation.CHECK_BOX,
                         StructuralNavigation.CHUNK,
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index 179590d..53340c6 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -222,6 +222,16 @@ class SpeechGenerator(generator.Generator):
             result.extend(acss)
         return result
 
+    def _generateClickable(self, obj, **args):
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        acss = self.voice(SYSTEM)
+        result = generator.Generator._generateClickable(self, obj, **args)
+        if result:
+            result.extend(acss)
+        return result
+
     def _generateTextRole(self, obj, **args):
         """A convenience method to prevent the pyatspi.ROLE_PARAGRAPH role
         from being spoken. In the case of a pyatspi.ROLE_PARAGRAPH
diff --git a/src/orca/structural_navigation.py b/src/orca/structural_navigation.py
index e9853ee..f0a34d6 100644
--- a/src/orca/structural_navigation.py
+++ b/src/orca/structural_navigation.py
@@ -523,11 +523,11 @@ class StructuralNavigation:
     # should be all that is needed to implement navigation by blockquote
     # in OOo Writer documents.
     #
-    ANCHOR          = "anchor"
     BLOCKQUOTE      = "blockquote"
     BUTTON          = "button"
     CHECK_BOX       = "checkBox"
     CHUNK           = "chunk"
+    CLICKABLE       = "clickable"
     COMBO_BOX       = "comboBox"
     ENTRY           = "entry"
     FORM_FIELD      = "formField"
@@ -874,6 +874,7 @@ class StructuralNavigation:
                                           criteria.interfaces,
                                           criteria.matchInterfaces,
                                           criteria.invert)
+
         if criteria.applyPredicate:
             predicate = structuralNavigationObject.predicate
         else:
@@ -1780,87 +1781,6 @@ class StructuralNavigation:
 
     ########################
     #                      #
-    # Anchors              #
-    #                      #
-    ########################
-
-    def _anchorBindings(self):
-        """Returns a dictionary of [keysymstring, modifiers, description]
-        lists for navigating amongst anchors.
-        """
-
-        # NOTE: This doesn't handle the case where the anchor is not an
-        # old-school <a name/id="foo"></a> anchor. For instance on the
-        # GNOME wiki, an "anchor" is actually an id applied to some other
-        # tag (e.g. <h2 id="foo">My Heading</h2>.  We'll have to be a
-        # bit more clever for those.  With the old-school anchors, this
-        # seems to work nicely and provides the user with a way to jump
-        # among defined areas without having to find a Table of Contents
-        # group of links (assuming such a thing is even present on the
-        # page).
-
-        bindings = {}
-        prevDesc = cmdnames.ANCHOR_PREV
-        bindings["previous"] = ["a", keybindings.SHIFT_MODIFIER_MASK, prevDesc]
-
-        nextDesc = cmdnames.ANCHOR_NEXT
-        bindings["next"] = ["a", keybindings.NO_MODIFIER_MASK, nextDesc]
-        return bindings
-
-    def _anchorCriteria(self, collection, arg=None):
-        """Returns the MatchCriteria to be used for locating anchors
-        by collection.
-
-        Arguments:
-        - collection: the collection interface for the document
-        - arg: an optional argument which may need to be included in
-          the criteria (e.g. the level of a heading).
-        """
-
-        role = [pyatspi.ROLE_LINK]
-        state = [pyatspi.STATE_FOCUSABLE]
-        stateMatch = collection.MATCH_NONE
-        return MatchCriteria(collection,
-                             states=state,
-                             matchStates=stateMatch,
-                             roles=role)
-
-    def _anchorPredicate(self, obj, arg=None):
-        """The predicate to be used for verifying that the object
-        obj is an anchor.
-
-        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() == pyatspi.ROLE_LINK:
-            state = obj.getState()
-            isMatch = not state.contains(pyatspi.STATE_FOCUSABLE)
-        return isMatch
-
-    def _anchorPresentation(self, obj, arg=None):
-        """Presents the anchor or indicates that one was not found.
-
-        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).
-        """
-
-        if obj:
-            [obj, characterOffset] = self._getCaretPosition(obj)
-            self._setCaretPosition(obj, characterOffset)
-            self._presentObject(obj, characterOffset)
-        else:
-            full = messages.NO_MORE_ANCHORS
-            brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND
-            self._script.presentMessage(full, brief)
-
-    ########################
-    #                      #
     # Blockquotes          #
     #                      #
     ########################
@@ -3647,3 +3567,79 @@ class StructuralNavigation:
                     self._script.utilities.uri(obj)]
 
         return guilabels.SN_TITLE_LINK, columnHeaders, rowData
+
+    ########################
+    #                      #
+    # Clickables           #
+    #                      #
+    ########################
+
+    def _clickableBindings(self):
+        """Returns a dictionary of [keysymstring, modifiers, description]
+        lists for navigating amongst "clickable" objects."""
+
+        bindings = {}
+        prevDesc = cmdnames.CLICKABLE_PREV
+        bindings["previous"] = ["a", keybindings.SHIFT_MODIFIER_MASK, prevDesc]
+
+        nextDesc = cmdnames.CLICKABLE_NEXT
+        bindings["next"] = ["a", keybindings.NO_MODIFIER_MASK, nextDesc]
+
+        listDesc = cmdnames.CLICKABLE_LIST
+        bindings["list"] = ["a", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc]
+        return bindings
+
+    def _clickableCriteria(self, collection, arg=None):
+        """Returns the MatchCriteria to be used for locating clickables
+        by collection.
+
+        Arguments:
+        - collection: the collection interface for the document
+        - arg: an optional argument which may need to be included in
+          the criteria (e.g. the level of a heading).
+        """
+
+        # TODO - JD: At the moment, matching via interface crashes Orca.
+        # Until that's addressed, we'll just use the predicate approach.
+        # See https://bugzilla.gnome.org/show_bug.cgi?id=734805.
+        return MatchCriteria(collection,
+                             applyPredicate=True)
+
+    def _clickablePredicate(self, obj, arg=None):
+        """The predicate to be used for verifying that the object
+        obj is a clickable.
+
+        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).
+        """
+
+        return self._script.utilities.isClickableElement(obj)
+
+    def _clickablePresentation(self, obj, arg=None):
+        """Presents the clickable or indicates that one was not found.
+
+        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).
+        """
+
+        if obj:
+            [obj, characterOffset] = self._getCaretPosition(obj)
+            self._setCaretPosition(obj, characterOffset)
+            self._presentObject(obj, characterOffset)
+        elif not arg:
+            full = messages.NO_MORE_CLICKABLES
+            brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND
+            self._script.presentMessage(full, brief)
+
+    def _clickableDialogData(self):
+        columnHeaders = [guilabels.SN_HEADER_CLICKABLE]
+        columnHeaders.append(guilabels.SN_HEADER_ROLE)
+
+        def rowData(obj):
+            return [self._getText(obj), self._getRoleName(obj)]
+
+        return guilabels.SN_TITLE_CLICKABLE, columnHeaders, rowData


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