[orca] Continued work on support for aria-details
- From: Joanmarie Diggs <joanied src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Continued work on support for aria-details
- Date: Tue, 7 Jan 2020 11:44:15 +0000 (UTC)
commit 8dcd93c3f6ae8066ab47654cc6c235fb66054bc4
Author: Joanmarie Diggs <jdiggs igalia com>
Date: Tue Jan 7 12:43:21 2020 +0100
Continued work on support for aria-details
* Announce when entering and exiting details container via caret
navigation. When entering, announce the name and type of object
the details apply to.
* Announce the name and type of object when an object has details.
src/orca/formatting.py | 7 ++--
src/orca/generator.py | 3 ++
src/orca/messages.py | 11 ++++++
src/orca/object_properties.py | 36 ++++++++++++++----
src/orca/script_utilities.py | 6 +++
src/orca/scripts/web/script_utilities.py | 65 ++++++++++++++++++++++++++++++++
src/orca/scripts/web/speech_generator.py | 36 +++++++++++++++---
src/orca/speech_generator.py | 16 +++++++-
8 files changed, 163 insertions(+), 17 deletions(-)
---
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index f213b8eac..61955d26f 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -69,7 +69,8 @@ formatting = {
'groupindex': object_properties.GROUP_INDEX_SPEECH,
'clickable': object_properties.STATE_CLICKABLE,
'haslongdesc': object_properties.STATE_HAS_LONGDESC,
- 'hasdetails': object_properties.STATE_HAS_DETAILS,
+ 'hasdetails': object_properties.RELATION_HAS_DETAILS,
+ 'detailsfor': object_properties.RELATION_DETAILS_FOR
},
'braille': {
'eol': object_properties.EOL_INDICATOR_BRAILLE,
@@ -110,14 +111,14 @@ formatting = {
'speech': {
'prefix': {
- 'focused': '[]',
+ 'focused': 'detailsFor',
'unfocused': 'oldAncestors + newAncestors',
'basicWhereAmI': 'toolbar',
'detailedWhereAmI' : '[]'
},
'suffix': {
'focused': '[]',
- 'unfocused': 'newNodeLevel + unselectedCell + clickable + pause + hasLongDesc + hasDetails +' +
TUTORIAL + ' + description + pause + hasPopup',
+ 'unfocused': 'newNodeLevel + unselectedCell + clickable + pause + hasLongDesc + hasDetails +
detailsFor +' + TUTORIAL + ' + description + pause + hasPopup',
'basicWhereAmI': TUTORIAL + ' + clickable + hasLongDesc + description + pause + hasPopup',
'detailedWhereAmI': TUTORIAL + ' + clickable + hasLongDesc + description + pause + hasPopup'
},
diff --git a/src/orca/generator.py b/src/orca/generator.py
index d072320ec..328a8f604 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -489,6 +489,9 @@ class Generator:
def _generateHasDetails(self, obj, **args):
return []
+ def _generateDetailsFor(self, obj, **args):
+ return []
+
def _generateHasPopup(self, obj, **args):
return []
diff --git a/src/orca/messages.py b/src/orca/messages.py
index 9e5725f75..dbe8811bf 100644
--- a/src/orca/messages.py
+++ b/src/orca/messages.py
@@ -1112,6 +1112,17 @@ LEARN_MODE_START_SPEECH = \
# blockquote and then navigates out of it.
LEAVING_BLOCKQUOTE = _("leaving blockquote.")
+# Translators: In web content, authors can identify an element which contains
+# detailed information about another element. For instance, for a password
+# field, there may be a list of requirements (number of characters, number of
+# special symbols, etc.). For an image, there may be an extended description
+# before or after the image. Often there are visual clues connecting the
+# detailed information to its related object. We need to convey this non-visually.
+# This message is presented when a user just navigated out of a container holding
+# detailed information about another object.
+# See https://w3c.github.io/aria/#aria-details
+LEAVING_DETAILS = _("leaving details.")
+
# Translators: This message is presented when a user is navigating within
# an object and then navigates out of it. The word or phrase that follows
# "leaving" should be consistent with the translation provided for the
diff --git a/src/orca/object_properties.py b/src/orca/object_properties.py
index 8d6bf65b6..a64891427 100644
--- a/src/orca/object_properties.py
+++ b/src/orca/object_properties.py
@@ -65,6 +65,35 @@ NODE_LEVEL_SPEECH = _("tree level %d")
# ancestors the node has). This is the braille version.
NODE_LEVEL_BRAILLE = _("TREE LEVEL %d")
+# Translators: In web content, authors can identify an element which contains
+# detailed information about another element. For instance, for a password
+# field, there may be a list of requirements (number of characters, number of
+# special symbols, etc.). For an image, there may be an extended description
+# before or after the image. Often there are visual clues connecting the
+# detailed information to its related object. We need to convey this non-visually.
+# This relationship will be presented for the object containing the details, e.g.
+# when arrowing into or out of it. The string substitution is for the object to
+# which the detailed information applies. For instance, when navigating into
+# the details for an image named Pythagorean Theorem, Orca would present:
+# "details for Pythagorean Theorem image".
+# See https://w3c.github.io/aria/#aria-details
+RELATION_DETAILS_FOR = _("details for %s")
+
+# Translators: In web content, authors can identify an element which contains
+# detailed information about another element. For instance, for a password
+# field, there may be a list of requirements (number of characters, number of
+# special symbols, etc.). For an image, there may be an extended description
+# before or after the image. Often there are visual clues connecting the
+# detailed information to its related object. We need to convey this non-visually.
+# This relationship will be presented for the object which has details to tell
+# the user the type of object where the details can be found so that they can
+# more quickly navigate to it. The string substitution is for the object to
+# which the detailed information applies. For instance, when navigating to
+# a password field which has details in a list named "Requirements", Orca would
+# present: "has details in Requirements list".
+# See https://w3c.github.io/aria/#aria-details
+RELATION_HAS_DETAILS = _("has details in %s")
+
# Translators: This string should be treated as a role describing an object.
# Examples of roles include "checkbox", "radio button", "paragraph", and "link."
# The reason for including the editable state as part of the role is to make it
@@ -453,13 +482,6 @@ STATE_EXPANDED = _("expanded")
# which have a longdesc attribute. http://www.w3.org/TR/WCAG20-TECHS/H45.html
STATE_HAS_LONGDESC = _("has long description")
-# Translators: This is a state which applies to elements in document content
-# which have a detailed description or explanation. That description might
-# be hidden or might be in a different location on the page. Therefore Orca
-# announces the presence of the additional information so that the user can
-# use native application and/or Orca commands to read those details.
-STATE_HAS_DETAILS = _("has details")
-
# Translators: This is a state which applies to the orientation of widgets
# such as sliders and scroll bars.
STATE_HORIZONTAL = _("horizontal")
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 0134513fc..fd95e3550 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -3943,6 +3943,12 @@ class Utilities:
def hasDetails(self, obj):
return False
+ def isDetails(self, obj):
+ return False
+
+ def detailsFor(self, obj):
+ return []
+
def popupType(self, obj):
return ''
diff --git a/src/orca/scripts/web/script_utilities.py b/src/orca/scripts/web/script_utilities.py
index 272ebb79a..f7f6afcc8 100644
--- a/src/orca/scripts/web/script_utilities.py
+++ b/src/orca/scripts/web/script_utilities.py
@@ -77,6 +77,7 @@ class Utilities(script_utilities.Utilities):
self._hasNoSize = {}
self._hasLongDesc = {}
self._hasDetails = {}
+ self._isDetails = {}
self._popupType = {}
self._hasUselessCanvasDescendant = {}
self._id = {}
@@ -158,6 +159,7 @@ class Utilities(script_utilities.Utilities):
self._hasNoSize = {}
self._hasLongDesc = {}
self._hasDetails = {}
+ self._isDetails = {}
self._popupType = {}
self._hasUselessCanvasDescendant = {}
self._id = {}
@@ -3604,6 +3606,69 @@ class Utilities(script_utilities.Utilities):
self._hasDetails[hash(obj)] = rv
return rv
+ def detailsIn(self, obj):
+ if not self.hasDetails(obj):
+ return []
+
+ try:
+ relations = obj.getRelationSet()
+ except:
+ msg = 'ERROR: Exception getting relationset for %s' % obj
+ debug.println(debug.LEVEL_INFO, msg, True)
+ return []
+
+ rv = []
+ relation = filter(lambda x: x.getRelationType() == pyatspi.RELATION_DETAILS, relations)
+ for r in relation:
+ for i in range(r.getNTargets()):
+ rv.append(r.getTarget(i))
+
+ return rv
+
+ def isDetails(self, obj):
+ if not (obj and self.inDocumentContent(obj)):
+ return super().isDetails(obj)
+
+ rv = self._isDetails.get(hash(obj))
+ if rv is not None:
+ return rv
+
+ try:
+ relations = obj.getRelationSet()
+ except:
+ msg = 'ERROR: Exception getting relationset for %s' % obj
+ debug.println(debug.LEVEL_INFO, msg, True)
+ return False
+
+ rv = False
+ relation = filter(lambda x: x.getRelationType() == pyatspi.RELATION_DETAILS_FOR, relations)
+ for r in relation:
+ if r.getNTargets() > 0:
+ rv = True
+ break
+
+ self._isDetails[hash(obj)] = rv
+ return rv
+
+ def detailsFor(self, obj):
+ if not self.isDetails(obj):
+ return []
+
+ try:
+ relations = obj.getRelationSet()
+ except:
+ msg = 'ERROR: Exception getting relationset for %s' % obj
+ debug.println(debug.LEVEL_INFO, msg, True)
+ return []
+
+ rv = []
+ relation = filter(lambda x: x.getRelationType() == pyatspi.RELATION_DETAILS_FOR, relations)
+ for r in relation:
+ for i in range(r.getNTargets()):
+ rv.append(r.getTarget(i))
+
+ return rv
+
def popupType(self, obj):
if not (obj and self.inDocumentContent(obj)):
return 'false'
diff --git a/src/orca/scripts/web/speech_generator.py b/src/orca/scripts/web/speech_generator.py
index 85f43b6d6..fd05dd6c7 100644
--- a/src/orca/scripts/web/speech_generator.py
+++ b/src/orca/scripts/web/speech_generator.py
@@ -205,13 +205,39 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if not self._script.utilities.inDocumentContent(obj):
return super()._generateHasDetails(obj, **args)
+ objs = self._script.utilities.detailsIn(obj)
+ if not objs:
+ return []
+
+ objString = lambda x: "%s %s" % (x.name, self.getLocalizedRoleName(x))
+ toPresent = ", ".join(list(map(objString, objs)))
+
args['stringType'] = 'hasdetails'
- if self._script.utilities.hasDetails(obj):
- result = [self._script.formatting.getString(**args)]
- result.extend(self.voice(speech_generator.SYSTEM))
- return result
+ result = [self._script.formatting.getString(**args) % toPresent]
+ result.extend(self.voice(speech_generator.SYSTEM))
+ return result
- return []
+ def _generateDetailsFor(self, obj, **args):
+ if _settingsManager.getSetting('onlySpeakDisplayedText'):
+ return []
+
+ if not self._script.utilities.inDocumentContent(obj):
+ return super()._generateDetailsFor(obj, **args)
+
+ objs = self._script.utilities.detailsFor(obj)
+ if not objs:
+ return []
+
+ if args.get('leaving'):
+ return []
+
+ objString = lambda x: "%s %s" % (x.name, self.getLocalizedRoleName(x))
+ toPresent = ", ".join(list(map(objString, objs)))
+
+ args['stringType'] = 'detailsfor'
+ result = [self._script.formatting.getString(**args) % toPresent]
+ result.extend(self.voice(speech_generator.SYSTEM))
+ return result
def _generateLabelOrName(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index 96234bad9..27bae30c6 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -296,6 +296,16 @@ class SpeechGenerator(generator.Generator):
result.extend(acss)
return result
+ def _generateDetailsFor(self, obj, **args):
+ if _settingsManager.getSetting('onlySpeakDisplayedText'):
+ return []
+
+ acss = self.voice(SYSTEM)
+ result = generator.Generator._generateDetailsFor(self, obj, **args)
+ if result:
+ result.extend(acss)
+ return result
+
def _generateAvailability(self, obj, **args):
if _settingsManager.getSetting('onlySpeakDisplayedText'):
return []
@@ -1632,13 +1642,15 @@ class SpeechGenerator(generator.Generator):
role = args.get('role', obj.getRole())
enabled, disabled = self._getEnabledAndDisabledContextRoles()
- if not role in enabled:
+ if not (role in enabled or self._script.utilities.isDetails(obj)):
return []
count = args.get('count', 1)
result = []
- if role == pyatspi.ROLE_BLOCK_QUOTE:
+ if self._script.utilities.isDetails(obj):
+ result.append(messages.LEAVING_DETAILS)
+ elif role == pyatspi.ROLE_BLOCK_QUOTE:
if count > 1:
result.append(messages.leavingNBlockquotes(count))
else:
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]