[orca] Add custom support for ARIA switch role



commit d83325d4d945b01ce734330a09e70e647a82022d
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Tue Jan 24 15:38:44 2017 +0100

    Add custom support for ARIA switch role

 src/orca/formatting.py                     |   18 ++++++++
 src/orca/generator.py                      |   22 ++++++++++
 src/orca/object_properties.py              |   17 +++++++
 src/orca/script_utilities.py               |    3 +
 src/orca/scripts/web/script_utilities.py   |    6 +++
 src/orca/sound_generator.py                |   13 ++++++
 src/orca/speech_generator.py               |   11 +++++
 test/html/aria-switch.html                 |    8 ++++
 test/keystrokes/firefox/aria_switch.params |    1 +
 test/keystrokes/firefox/aria_switch.py     |   64 ++++++++++++++++++++++++++++
 10 files changed, 163 insertions(+), 0 deletions(-)
---
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index 23ff21f..c524e87 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -59,6 +59,7 @@ formatting = {
             'insensitive': object_properties.STATE_INSENSITIVE_SPEECH,
             'checkbox': object_properties.CHECK_BOX_INDICATORS_SPEECH,
             'radiobutton': object_properties.RADIO_BUTTON_INDICATORS_SPEECH,
+            'switch': object_properties.SWITCH_INDICATORS_SPEECH,
             'togglebutton': object_properties.TOGGLE_BUTTON_INDICATORS_SPEECH,
             'expansion': object_properties.EXPANSION_INDICATORS_SPEECH,
             'nodelevel': object_properties.NODE_LEVEL_SPEECH,
@@ -77,6 +78,7 @@ formatting = {
             'invalid': object_properties.INVALID_INDICATORS_BRAILLE,
             'checkbox': object_properties.CHECK_BOX_INDICATORS_BRAILLE,
             'radiobutton': object_properties.RADIO_BUTTON_INDICATORS_BRAILLE,
+            'switch': object_properties.SWITCH_INDICATORS_BRAILLE,
             'togglebutton': object_properties.TOGGLE_BUTTON_INDICATORS_BRAILLE,
             'expansion': object_properties.EXPANSION_INDICATORS_BRAILLE,
             'nodelevel': object_properties.NODE_LEVEL_BRAILLE,
@@ -89,6 +91,7 @@ formatting = {
             'insensitive': object_properties.STATE_INSENSITIVE_SOUND,
             'checkbox': object_properties.CHECK_BOX_INDICATORS_SOUND,
             'radiobutton': object_properties.RADIO_BUTTON_INDICATORS_SOUND,
+            'switch': object_properties.SWITCH_INDICATORS_SOUND,
             'togglebutton': object_properties.TOGGLE_BUTTON_INDICATORS_SOUND,
             'expansion': object_properties.EXPANSION_INDICATORS_SOUND,
             'multiselect': object_properties.STATE_MULTISELECT_SOUND,
@@ -392,6 +395,12 @@ formatting = {
         'ROLE_STATIC': {
             'unfocused': '(displayedText or name) + roleName',
         },
+        'ROLE_SWITCH': {
+            'focused': 'switchState',
+            'unfocused': 'labelOrName + roleName + switchState + availability + ' + MNEMONIC + ' + 
accelerator',
+            'basicWhereAmI': 'labelOrName + roleName + switchState'
+            },
+
         pyatspi.ROLE_TABLE: {
             'focused': 'leaving or (labelAndName + pause + table)',
             'unfocused': 'labelAndName + pause + table',
@@ -687,6 +696,11 @@ formatting = {
                           + (required and [Region(" " + asString(required))] or [])\
                           + (readOnly and [Region(" " + asString(readOnly))] or [])'
             },
+        'ROLE_SWITCH' : {
+            'unfocused': '[Component(obj,\
+                                     asString((labelOrName or description) + roleName),\
+                                     indicator=asString(switchState))]'
+            },
         #pyatspi.ROLE_SPLIT_PANE: 'default'
         #pyatspi.ROLE_TABLE: 'default'
         pyatspi.ROLE_TABLE_CELL: {
@@ -841,6 +855,10 @@ formatting = {
             'focused': 'percentage',
             'unfocused': 'roleName + percentage + availability',
         },
+        'ROLE_SWITCH': {
+            'focused': 'switchState',
+            'unfocused': 'roleName + switchState + availability',
+        },
         pyatspi.ROLE_TABLE_CELL: {
             'focused': 'expandableState',
             'unfocused': 'roleName + expandableState',
diff --git a/src/orca/generator.py b/src/orca/generator.py
index 500cde1..c1bbd22 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -587,6 +587,20 @@ class Generator:
 
         return []
 
+    def _generateSwitchState(self, obj, **args):
+        result = []
+        if not args.get('mode', None):
+            args['mode'] = self._mode
+        args['stringType'] = 'switch'
+        indicators = self._script.formatting.getString(**args)
+        state = obj.getState()
+        if state.contains(pyatspi.STATE_CHECKED) \
+           or state.contains(pyatspi.STATE_PRESSED):
+            result.append(indicators[1])
+        else:
+            result.append(indicators[0])
+        return result
+
     def _generateToggleState(self, obj, **args):
         """Returns an array of strings for use by speech and braille that
         represent the checked state of the object.  This is typically
@@ -1170,6 +1184,8 @@ class Generator:
                 return 'ROLE_MATH_TABLE_ROW'
         if self._script.utilities.isStatic(obj):
             return 'ROLE_STATIC'
+        if self._script.utilities.isSwitch(obj):
+            return 'ROLE_SWITCH'
         if self._script.utilities.isBlockquote(obj):
             return pyatspi.ROLE_BLOCK_QUOTE
         if self._script.utilities.isLandmark(obj):
@@ -1214,6 +1230,9 @@ class Generator:
         if self._script.utilities.isMenuButton(obj):
             return object_properties.ROLE_MENU_BUTTON
 
+        if self._script.utilities.isSwitch(obj):
+            return object_properties.ROLE_SWITCH
+
         if self._script.utilities.isLandmark(obj):
             if self._script.utilities.isLandmarkBanner(obj):
                 return object_properties.ROLE_LANDMARK_BANNER
@@ -1246,6 +1265,9 @@ class Generator:
         return Atk.role_get_localized_name(atkRole)
 
     def getStateIndicator(self, obj, **args):
+        if self._script.utilities.isSwitch(obj):
+            return self._generateSwitchState(obj, **args)
+
         role = args.get('role', obj.getRole())
 
         if role == pyatspi.ROLE_MENU_ITEM:
diff --git a/src/orca/object_properties.py b/src/orca/object_properties.py
index a9a11ff..45a9f72 100644
--- a/src/orca/object_properties.py
+++ b/src/orca/object_properties.py
@@ -139,6 +139,12 @@ ROLE_SPLITTER_HORIZONTAL = _("horizontal splitter")
 # dictate which keyboard keys should be used to modify the value of the widget.
 ROLE_SPLITTER_VERTICAL = _("vertical splitter")
 
+# Translators: This string should be treated as a role describing an object.
+# Examples of roles include "checkbox", "radio button", "paragraph", and "link."
+# The "switch" role is a "light switch" style toggle, such as can be seen in
+# https://developer.gnome.org/gtk3/stable/GtkSwitch.html
+ROLE_SWITCH = C_("role", "switch")
+
 # Translators: This is an alternative name for the parent object of a series
 # of icons.
 ROLE_ICON_PANEL = _("Icon panel")
@@ -240,6 +246,14 @@ STATE_CHECKED = C_("checkbox", "checked")
 # Translators: This is a state which applies to a check box.
 STATE_NOT_CHECKED = C_("checkbox", "not checked")
 
+# Translators: This is a state which applies to a switch. For an example of
+# a switch, see https://developer.gnome.org/gtk3/stable/GtkSwitch.html
+STATE_ON_SWITCH = C_("switch", "on")
+
+# Translators: This is a state which applies to a switch. For an example of
+# a switch, see https://developer.gnome.org/gtk3/stable/GtkSwitch.html
+STATE_OFF_SWITCH = C_("switch", "off")
+
 # Translators: This is a state which applies to a check box.
 STATE_PARTIALLY_CHECKED = C_("checkbox", "partially checked")
 
@@ -344,6 +358,7 @@ INVALID_INDICATORS_SPEECH = \
      STATE_INVALID_GRAMMAR_SPEECH]
 RADIO_BUTTON_INDICATORS_SPEECH = \
     [STATE_UNSELECTED_RADIO_BUTTON, STATE_SELECTED_RADIO_BUTTON]
+SWITCH_INDICATORS_SPEECH = [STATE_OFF_SWITCH, STATE_ON_SWITCH]
 TOGGLE_BUTTON_INDICATORS_SPEECH = \
     [STATE_NOT_PRESSED, STATE_PRESSED]
 
@@ -353,6 +368,7 @@ INVALID_INDICATORS_BRAILLE       = [STATE_INVALID_BRAILLE,
                                     STATE_INVALID_SPELLING_BRAILLE,
                                     STATE_INVALID_GRAMMAR_BRAILLE]
 RADIO_BUTTON_INDICATORS_BRAILLE  = ["& y", "&=y"]
+SWITCH_INDICATORS_BRAILLE = ["& y", "&=y"]
 TOGGLE_BUTTON_INDICATORS_BRAILLE = ["& y", "&=y"]
 
 TABLE_CELL_DELIMITER_BRAILLE = " "
@@ -362,6 +378,7 @@ CHECK_BOX_INDICATORS_SOUND = ["not_checked", "checked", "partially_checked"]
 EXPANSION_INDICATORS_SOUND = ["collapsed", "expanded"]
 INVALID_INDICATORS_SOUND = ["invalid", "invalid_spelling", "invalid_grammar"]
 RADIO_BUTTON_INDICATORS_SOUND = ["unselected", "selected"]
+SWITCH_INDICATORS_SOUND = ["off", "on"]
 TOGGLE_BUTTON_INDICATORS_SOUND = ["not_pressed", "pressed"]
 STATE_CLICKABLE_SOUND = "clickable"
 STATE_HAS_LONGDESC_SOUND = "haslongdesc"
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 05b7e81..623d2fd 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -1291,6 +1291,9 @@ class Utilities:
                    and not state.contains(pyatspi.STATE_EDITABLE)
         return readOnly
 
+    def isSwitch(self, obj):
+        return False
+
     def _hasSamePath(self, obj1, obj2):
         path1 = pyatspi.utils.getPath(obj1)
         path2 = pyatspi.utils.getPath(obj2)
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index f29473d..a24464e 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -2461,6 +2461,12 @@ class Utilities(script_utilities.Utilities):
         self._hasUselessCanvasDescendant[hash(obj)] = rv
         return rv
 
+    def isSwitch(self, obj):
+        if not (obj and self.inDocumentContent(obj)):
+            return super().isSwitch(obj)
+
+        return 'switch' in self._getXMLRoles(obj)
+
     def isImageMap(self, obj):
         if not (obj and self.inDocumentContent(obj)):
             return False
diff --git a/src/orca/sound_generator.py b/src/orca/sound_generator.py
index c8cbafe..e96c27d 100644
--- a/src/orca/sound_generator.py
+++ b/src/orca/sound_generator.py
@@ -236,6 +236,19 @@ class SoundGenerator(generator.Generator):
 
         return []
 
+    def _generateSwitchState(self, obj, **args):
+        """Returns an array of sounds indicating the on/off state of obj."""
+
+        if not _settingsManager.getSetting('playSoundForState'):
+            return []
+
+        filenames = super()._generateSwitchState(obj, **args)
+        result = list(map(self._convertFilenameToIcon, filenames))
+        if result:
+            return result
+
+        return []
+
     def _generateToggleState(self, obj, **args):
         """Returns an array of sounds indicating the toggled state of obj."""
 
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index f9c1cf0..ff15644 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -532,6 +532,17 @@ class SpeechGenerator(generator.Generator):
             result.extend(acss)
         return result
 
+    def _generateSwitchState(self, obj, **args):
+        """Returns an array of strings indicating the on/off state of obj."""
+        if _settingsManager.getSetting('onlySpeakDisplayedText'):
+            return []
+
+        acss = self.voice(STATE)
+        result = generator.Generator._generateSwitchState(self, obj, **args)
+        if result:
+            result.extend(acss)
+        return result
+
     def _generateToggleState(self, obj, **args):
         """Returns an array of strings for use by speech and braille that
         represent the checked state of the object.  This is typically
diff --git a/test/html/aria-switch.html b/test/html/aria-switch.html
new file mode 100644
index 0000000..545ac74
--- /dev/null
+++ b/test/html/aria-switch.html
@@ -0,0 +1,8 @@
+<html>
+<head></head>
+<body>
+  <div>Line 1</div>
+  <div>Line 2: <input type="checkbox" aria-label="Power saving" role="switch" /></div>
+  <div>Line 3</div>
+</body>
+</html>
diff --git a/test/keystrokes/firefox/aria_switch.params b/test/keystrokes/firefox/aria_switch.params
new file mode 100644
index 0000000..6e3b4a9
--- /dev/null
+++ b/test/keystrokes/firefox/aria_switch.params
@@ -0,0 +1 @@
+PARAMS=$TEST_DIR/../../html/aria-switch.html
diff --git a/test/keystrokes/firefox/aria_switch.py b/test/keystrokes/firefox/aria_switch.py
new file mode 100644
index 0000000..ad1ffdb
--- /dev/null
+++ b/test/keystrokes/firefox/aria_switch.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+
+"""Test of ARIA switch presentation."""
+
+from macaroon.playback import *
+import utils
+
+sequence = MacroSequence()
+
+#sequence.append(WaitForDocLoad())
+sequence.append(PauseAction(5000))
+sequence.append(KeyComboAction("<Control>Home"))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Tab"))
+sequence.append(utils.AssertPresentationAction(
+    "1. Tab to switch",
+    ["BRAILLE LINE:  '& y Power saving switch'",
+     "     VISIBLE:  '& y Power saving switch', cursor=1",
+     "SPEECH OUTPUT: 'Power saving switch off'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(TypeAction(" "))
+sequence.append(utils.AssertPresentationAction(
+    "2. Change state of switch",
+    ["BRAILLE LINE:  '&=y Power saving switch'",
+     "     VISIBLE:  '&=y Power saving switch', cursor=1",
+     "SPEECH OUTPUT: 'on'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(TypeAction(" "))
+sequence.append(utils.AssertPresentationAction(
+    "3. Change state of switch",
+    ["BRAILLE LINE:  '& y Power saving switch'",
+     "     VISIBLE:  '& y Power saving switch', cursor=1",
+     "SPEECH OUTPUT: 'off'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("KP_Enter"))
+sequence.append(utils.AssertPresentationAction(
+    "4. Basic whereAmI",
+    ["BRAILLE LINE:  '& y Power saving switch'",
+     "     VISIBLE:  '& y Power saving switch', cursor=1",
+     "SPEECH OUTPUT: 'Power saving switch off'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Up"))
+sequence.append(utils.AssertPresentationAction(
+    "5. Up arrow",
+    ["BRAILLE LINE:  'Line 1'",
+     "     VISIBLE:  'Line 1', cursor=1",
+     "SPEECH OUTPUT: 'Line 1'"]))
+
+sequence.append(utils.StartRecordingAction())
+sequence.append(KeyComboAction("Down"))
+sequence.append(utils.AssertPresentationAction(
+    "6. Down arrow",
+    ["BRAILLE LINE:  'Line 2: & y Power saving switch'",
+     "     VISIBLE:  'Line 2: & y Power saving switch', cursor=1",
+     "SPEECH OUTPUT: 'Line 2:'",
+     "SPEECH OUTPUT: 'Power saving switch off'"]))
+
+sequence.append(utils.AssertionSummaryAction())
+sequence.start()


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