diff --git a/nvdaHelper/vbufBackends/mshtml/mshtml.cpp b/nvdaHelper/vbufBackends/mshtml/mshtml.cpp
index 8644a784c2f..6a4bfc1d84d 100755
--- a/nvdaHelper/vbufBackends/mshtml/mshtml.cpp
+++ b/nvdaHelper/vbufBackends/mshtml/mshtml.cpp
@@ -489,6 +489,7 @@ inline void getAttributesFromHTMLDOMNode(IHTMLDOMNode* pHTMLDOMNode,wstring& nod
macro_addHTMLAttributeToMap(L"aria-relevant",false,pHTMLAttributeCollection2,attribsMap,tempVar,tempAttribNode);
macro_addHTMLAttributeToMap(L"aria-busy",false,pHTMLAttributeCollection2,attribsMap,tempVar,tempAttribNode);
macro_addHTMLAttributeToMap(L"aria-atomic",false,pHTMLAttributeCollection2,attribsMap,tempVar,tempAttribNode);
+ macro_addHTMLAttributeToMap(L"aria-current",false,pHTMLAttributeCollection2,attribsMap,tempVar,tempAttribNode);
pHTMLAttributeCollection2->Release();
}
diff --git a/source/NVDAObjects/IAccessible/MSHTML.py b/source/NVDAObjects/IAccessible/MSHTML.py
index 2ccc3a8898a..e68f8b065bc 100644
--- a/source/NVDAObjects/IAccessible/MSHTML.py
+++ b/source/NVDAObjects/IAccessible/MSHTML.py
@@ -517,6 +517,9 @@ def _get_treeInterceptorClass(self):
return virtualBuffers.MSHTML.MSHTML
return super(MSHTML,self).treeInterceptorClass
+ def _get_isCurrent(self):
+ return self.HTMLAttributes["aria-current"]
+
def _get_HTMLAttributes(self):
return HTMLAttribCache(self.HTMLNode)
diff --git a/source/NVDAObjects/IAccessible/ia2Web.py b/source/NVDAObjects/IAccessible/ia2Web.py
index 94a49f0654e..a0215ffd603 100644
--- a/source/NVDAObjects/IAccessible/ia2Web.py
+++ b/source/NVDAObjects/IAccessible/ia2Web.py
@@ -26,6 +26,10 @@ def _get_positionInfo(self):
info['level']=level
return info
+ def _get_isCurrent(self):
+ current = self.IA2Attributes.get("current", False)
+ return current
+
class Document(Ia2Web):
value = None
diff --git a/source/NVDAObjects/UIA/edge.py b/source/NVDAObjects/UIA/edge.py
index c344ef27e4b..0921449c60e 100644
--- a/source/NVDAObjects/UIA/edge.py
+++ b/source/NVDAObjects/UIA/edge.py
@@ -11,11 +11,11 @@
import config
import controlTypes
import cursorManager
+import re
import aria
import textInfos
import UIAHandler
from UIABrowseMode import UIABrowseModeDocument, UIABrowseModeDocumentTextInfo
-import aria
from UIAUtils import *
from . import UIA, UIATextInfo
@@ -147,6 +147,8 @@ def _getControlFieldForObject(self,obj,isEmbedded=False,startOfNode=False,endOfN
# Combo boxes with a text pattern are editable
if obj.role==controlTypes.ROLE_COMBOBOX and obj.UIATextPattern:
field['states'].add(controlTypes.STATE_EDITABLE)
+ # report if the field is 'current'
+ field['current']=obj.isCurrent
# For certain controls, if ARIA overrides the label, then force the field's content (value) to the label
# Later processing in Edge's getTextWithFields will remove descendant content from fields with a content attribute.
ariaProperties=obj.UIAElement.currentAriaProperties
@@ -391,6 +393,22 @@ def _get_description(self):
pass
return super(EdgeNode,self).description
+ # RegEx to get the value for the aria-current property. This will be looking for a the value of 'current'
+ # in a list of strings like "something=true;current=date;". We want to capture one group, after the '='
+ # character and before the ';' character.
+ # This could be one of: True, "page", "step", "location", "date", "time"
+ RE_ARIA_CURRENT_PROP_VALUE = re.compile("current=(\w+);")
+
+ def _get_isCurrent(self):
+ ariaProperties=self.UIAElement.currentAriaProperties
+ match = self.RE_ARIA_CURRENT_PROP_VALUE.match(ariaProperties)
+ log.debug("aria props = %s" % ariaProperties)
+ if match:
+ valueOfAriaCurrent = match.group(1)
+ log.debug("aria current value = %s" % valueOfAriaCurrent)
+ return valueOfAriaCurrent
+ return False
+
class EdgeList(EdgeNode):
# non-focusable lists are readonly lists (ensures correct NVDA presentation category)
diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py
index a3e3441f860..a3873911df8 100644
--- a/source/NVDAObjects/__init__.py
+++ b/source/NVDAObjects/__init__.py
@@ -799,6 +799,13 @@ def _get_statusBar(self):
"""
return None
+ def _get_isCurrent(self):
+ """Gets the value that indicates whether this object is the current element in a set of related
+ elements. This maps to aria-current. Normally returns False. If this object is current
+ it will return one of the following values: True, "page", "step", "location", "date", "time"
+ """
+ return False
+
def reportFocus(self):
"""Announces this object in a way suitable such that it gained focus.
"""
diff --git a/source/braille.py b/source/braille.py
index fb3d42a7100..c90e764ea9c 100644
--- a/source/braille.py
+++ b/source/braille.py
@@ -117,6 +117,9 @@
)
SELECTION_SHAPE = 0xC0 #: Dots 7 and 8
+# used to separate chunks of text when programmatically joined
+TEXT_SEPARATOR = " "
+
def NVDAObjectHasUsefulText(obj):
import displayModel
role = obj.role
@@ -359,9 +362,16 @@ def getBrailleTextForProperties(**propertyValues):
# Translators: Displayed in braille for a table cell column number.
# %s is replaced with the column number.
textList.append(_("c%s") % columnNumber)
+ current = propertyValues.get('current', False)
+ if current:
+ try:
+ textList.append(controlTypes.isCurrentLabels[current])
+ except KeyError:
+ log.debugWarning("Aria-current value not handled: %s"%current)
+ textList.append(controlTypes.isCurrentLabels[True])
if includeTableCellCoords and cellCoordsText:
textList.append(cellCoordsText)
- return " ".join([x for x in textList if x])
+ return TEXT_SEPARATOR.join([x for x in textList if x])
class NVDAObjectRegion(Region):
"""A region to provide a braille representation of an NVDAObject.
@@ -384,7 +394,7 @@ def update(self):
obj = self.obj
presConfig = config.conf["presentation"]
role = obj.role
- text = getBrailleTextForProperties(name=obj.name, role=role,
+ text = getBrailleTextForProperties(name=obj.name, role=role, current=obj.isCurrent,
value=obj.value if not NVDAObjectHasUsefulText(obj) else None ,
states=obj.states,
description=obj.description if presConfig["reportObjectDescriptions"] else None,
@@ -397,7 +407,7 @@ def update(self):
mathPres.ensureInit()
if mathPres.brailleProvider:
try:
- text += " " + mathPres.brailleProvider.getBrailleForMathMl(
+ text += TEXT_SEPARATOR + mathPres.brailleProvider.getBrailleForMathMl(
obj.mathMl)
except (NotImplementedError, LookupError):
pass
@@ -427,12 +437,16 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
role = field.get("role", controlTypes.ROLE_UNKNOWN)
states = field.get("states", set())
value=field.get('value',None)
+ current=field.get('current', None)
if presCat == field.PRESCAT_LAYOUT:
+ text = []
# The only item we report for these fields is clickable, if present.
if controlTypes.STATE_CLICKABLE in states:
- return getBrailleTextForProperties(states={controlTypes.STATE_CLICKABLE})
- return None
+ text.append(getBrailleTextForProperties(states={controlTypes.STATE_CLICKABLE}))
+ if current:
+ text.append(getBrailleTextForProperties(current=current))
+ return TEXT_SEPARATOR.join(text) if len(text) != 0 else None
elif role in (controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLECOLUMNHEADER, controlTypes.ROLE_TABLEROWHEADER) and field.get("table-id"):
# Table cell.
@@ -442,7 +456,8 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
"states": states,
"rowNumber": field.get("table-rownumber"),
"columnNumber": field.get("table-columnnumber"),
- "includeTableCellCoords": reportTableCellCoords
+ "includeTableCellCoords": reportTableCellCoords,
+ "current": current,
}
if reportTableHeaders:
props["columnHeaderText"] = field.get("table-columnheadertext")
@@ -453,7 +468,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
# Don't report the role for math here.
# However, we still need to pass it (hence "_role").
"_role" if role == controlTypes.ROLE_MATH else "role": role,
- "states": states,"value":value}
+ "states": states,"value":value, "current":current}
if config.conf["presentation"]["reportKeyboardShortcuts"]:
kbShortcut = field.get("keyboardShortcut")
if kbShortcut:
@@ -465,7 +480,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
content = field.get("content")
if content:
if text:
- text += " "
+ text += TEXT_SEPARATOR
text += content
elif role == controlTypes.ROLE_MATH:
import mathPres
@@ -473,7 +488,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
if mathPres.brailleProvider:
try:
if text:
- text += " "
+ text += TEXT_SEPARATOR
text += mathPres.brailleProvider.getBrailleForMathMl(
info.getMathMl(field))
except (NotImplementedError, LookupError):
@@ -501,7 +516,7 @@ def getFormatFieldBraille(field, isAtStart, formatConfig):
# Translators: Displayed in braille for a heading with a level.
# %s is replaced with the level.
textList.append(_("h%s")%headingLevel)
- return " ".join([x for x in textList if x])
+ return TEXT_SEPARATOR.join([x for x in textList if x])
class TextInfoRegion(Region):
@@ -565,7 +580,7 @@ def _getTypeformFromFormatField(self, field):
def _addFieldText(self, text, contentPos, separate=True):
if separate and self.rawText:
# Separate this field text from the rest of the text.
- text = " " + text
+ text = TEXT_SEPARATOR + text
self.rawText += text
textLen = len(text)
self.rawTextTypeforms.extend((louis.plain_text,) * textLen)
@@ -583,7 +598,7 @@ def _addTextWithFields(self, info, formatConfig, isSelection=False):
if self._endsWithField:
# The last item added was a field,
# so add a space before the content.
- self.rawText += " "
+ self.rawText += TEXT_SEPARATOR
self.rawTextTypeforms.append(louis.plain_text)
self._rawToContentPos.append(self._currentContentPos)
if isSelection and self.selectionStart is None:
@@ -722,7 +737,7 @@ def update(self):
# There is no text left after stripping line ending characters,
# or the last item added can be navigated with a cursor.
# Add a space in case the cursor is at the end of the reading unit.
- self.rawText += " "
+ self.rawText += TEXT_SEPARATOR
rawTextLen += 1
self.rawTextTypeforms.append(louis.plain_text)
self._rawToContentPos.append(self._currentContentPos)
@@ -1130,7 +1145,7 @@ def getFocusContextRegions(obj, oldFocusRegions=None):
for index, parent in enumerate(ancestors[newAncestorsStart:ancestorsEnd], newAncestorsStart):
if not parent.isPresentableFocusAncestor:
continue
- region = NVDAObjectRegion(parent, appendText=" ")
+ region = NVDAObjectRegion(parent, appendText=TEXT_SEPARATOR)
region._focusAncestorIndex = index
region.update()
yield region
@@ -1161,7 +1176,7 @@ def getFocusRegions(obj, review=False):
region2 = None
if isinstance(obj, TreeInterceptor):
obj = obj.rootNVDAObject
- region = NVDAObjectRegion(obj, appendText=" " if region2 else "")
+ region = NVDAObjectRegion(obj, appendText=TEXT_SEPARATOR if region2 else "")
region.update()
yield region
if region2:
@@ -1180,7 +1195,7 @@ def formatCellsForLog(cells):
# optimisation: This gets called a lot, so needs to be as efficient as possible.
# List comprehensions without function calls are faster than loops.
# For str.join, list comprehensions are faster than generator comprehensions.
- return " ".join([
+ return TEXT_SEPARATOR.join([
"".join([str(dot + 1) for dot in xrange(8) if cell & (1 << dot)])
if cell else "-"
for cell in cells])
diff --git a/source/controlTypes.py b/source/controlTypes.py
index 4a64cdd86b9..f6f3bf8e67a 100644
--- a/source/controlTypes.py
+++ b/source/controlTypes.py
@@ -613,6 +613,23 @@
REASON_ONLYCACHE="onlyCache"
#}
+#: Text to use for 'current' values. These describe if an item is the current item
+#: within a particular kind of selection.
+isCurrentLabels = {
+ # Translators: Presented when an item is marked as current in a collection of items
+ True:_("current"),
+ # Translators: Presented when a page item is marked as current in a collection of page items
+ "page":_("current page"),
+ # Translators: Presented when a step item is marked as current in a collection of step items
+ "step":_("current step"),
+ # Translators: Presented when a location item is marked as current in a collection of location items
+ "location":_("current location"),
+ # Translators: Presented when a date item is marked as current in a collection of date items
+ "date":_("current date"),
+ # Translators: Presented when a time item is marked as current in a collection of time items
+ "time":_("current time"),
+}
+
def processPositiveStates(role, states, reason, positiveStates):
positiveStates = positiveStates.copy()
# The user never cares about certain states.
diff --git a/source/speech.py b/source/speech.py
index e4b03e0d54d..5ed05325d2e 100755
--- a/source/speech.py
+++ b/source/speech.py
@@ -308,6 +308,7 @@ def speakObjectProperties(obj,reason=controlTypes.REASON_QUERY,index=None,**allo
newPropertyValues["_tableID"]=obj.tableID
except NotImplementedError:
pass
+ newPropertyValues['current']=obj.isCurrent
#Get the speech text for the properties we want to speak, and then speak it
text=getSpeechTextForProperties(reason,**newPropertyValues)
if text:
@@ -1031,6 +1032,13 @@ def getSpeechTextForProperties(reason=controlTypes.REASON_QUERY,**propertyValues
if rowCount or columnCount:
# The caller is entering a table, so ensure that it is treated as a new table, even if the previous table was the same.
oldTableID = None
+ ariaCurrent = propertyValues.get('current', False)
+ if ariaCurrent:
+ try:
+ textList.append(controlTypes.isCurrentLabels[ariaCurrent])
+ except KeyError:
+ log.debugWarning("Aria-current value not handled: %s"%ariaCurrent)
+ textList.append(controlTypes.isCurrentLabels[True])
indexInGroup=propertyValues.get('positionInfo_indexInGroup',0)
similarItemsInGroup=propertyValues.get('positionInfo_similarItemsInGroup',0)
if 0