diff --git a/source/NVDAObjects/IAccessible/MSHTML.py b/source/NVDAObjects/IAccessible/MSHTML.py
index 80956fd7a0f..16ed0988041 100644
--- a/source/NVDAObjects/IAccessible/MSHTML.py
+++ b/source/NVDAObjects/IAccessible/MSHTML.py
@@ -545,9 +545,11 @@ def _get_treeInterceptorClass(self):
def _get_isCurrent(self):
isCurrent = self.HTMLAttributes["aria-current"]
- if isCurrent == "false":
- isCurrent = None
- return isCurrent
+ try:
+ return controlTypes.IsCurrent(isCurrent)
+ except ValueError:
+ log.debugWarning(f"Unknown aria-current value: {isCurrent}")
+ return controlTypes.IsCurrent.NO
def _get_HTMLAttributes(self):
return HTMLAttribCache(self.HTMLNode)
diff --git a/source/NVDAObjects/IAccessible/ia2Web.py b/source/NVDAObjects/IAccessible/ia2Web.py
index d475130c6d0..03560d315b6 100644
--- a/source/NVDAObjects/IAccessible/ia2Web.py
+++ b/source/NVDAObjects/IAccessible/ia2Web.py
@@ -34,11 +34,13 @@ def _get_positionInfo(self):
info['level']=level
return info
- def _get_isCurrent(self):
- current = self.IA2Attributes.get("current", None)
- if current == "false":
- current = None
- return current
+ def _get_isCurrent(self) -> controlTypes.IsCurrent:
+ ia2attrCurrent: str = self.IA2Attributes.get("current", "false")
+ try:
+ return controlTypes.IsCurrent(ia2attrCurrent)
+ except ValueError:
+ log.debugWarning(f"Unknown 'current' IA2Attribute value: {ia2attrCurrent}")
+ return controlTypes.IsCurrent.NO
def _get_placeholder(self):
placeholder = self.IA2Attributes.get('placeholder', None)
diff --git a/source/NVDAObjects/UIA/edge.py b/source/NVDAObjects/UIA/edge.py
index beb661d9808..6efa1655700 100644
--- a/source/NVDAObjects/UIA/edge.py
+++ b/source/NVDAObjects/UIA/edge.py
@@ -156,7 +156,7 @@ def _getControlFieldForObject(self,obj,isEmbedded=False,startOfNode=False,endOfN
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
+ field['current'] = obj.isCurrent
if obj.placeholder and obj._isTextEmpty:
field['placeholder']=obj.placeholder
# For certain controls, if ARIA overrides the label, then force the field's content (value) to the label
@@ -470,15 +470,18 @@ def _get_ariaProperties(self):
# "false" is ignored by the regEx and will not produce a match
RE_ARIA_CURRENT_PROP_VALUE = re.compile("current=(?!false)(\w+);")
- def _get_isCurrent(self):
+ def _get_isCurrent(self) -> controlTypes.IsCurrent:
ariaProperties=self._getUIACacheablePropertyValue(UIAHandler.UIA_AriaPropertiesPropertyId)
match = self.RE_ARIA_CURRENT_PROP_VALUE.search(ariaProperties)
- log.debug("aria props = %s" % ariaProperties)
if match:
valueOfAriaCurrent = match.group(1)
- log.debug("aria current value = %s" % valueOfAriaCurrent)
- return valueOfAriaCurrent
- return None
+ try:
+ return controlTypes.IsCurrent(valueOfAriaCurrent)
+ except ValueError:
+ log.debugWarning(
+ f"Unknown aria-current value: {valueOfAriaCurrent}, ariaProperties: {ariaProperties}"
+ )
+ return controlTypes.IsCurrent.NO
def _get_roleText(self):
roleText = self.ariaProperties.get('roledescription', None)
diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py
index 97067b92011..c671755e972 100644
--- a/source/NVDAObjects/__init__.py
+++ b/source/NVDAObjects/__init__.py
@@ -975,12 +975,13 @@ def _get_statusBar(self):
"""
return None
- def _get_isCurrent(self):
+ isCurrent: controlTypes.IsCurrent #: type info for auto property _get_isCurrent
+
+ def _get_isCurrent(self) -> controlTypes.IsCurrent:
"""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 None. If this object is current
- it will return one of the following values: "true", "page", "step", "location", "date", "time"
+ elements. This maps to aria-current.
"""
- return None
+ return controlTypes.IsCurrent.NO
def _get_shouldAcceptShowHideCaretEvent(self):
"""Some objects/applications send show/hide caret events when we don't expect it, such as when the cursor is blinking.
diff --git a/source/braille.py b/source/braille.py
index 082aea7d9f4..166f5e75a9c 100644
--- a/source/braille.py
+++ b/source/braille.py
@@ -579,13 +579,9 @@ def getPropertiesBraille(**propertyValues) -> str: # noqa: C901
# %s is replaced with the column number.
columnStr = _("c{columnNumber}").format(columnNumber=columnNumber)
textList.append(columnStr)
- 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])
+ isCurrent = propertyValues.get('current', controlTypes.IsCurrent.NO)
+ if isCurrent != controlTypes.IsCurrent.NO:
+ textList.append(isCurrent.displayString)
placeholder = propertyValues.get('placeholder', None)
if placeholder:
textList.append(placeholder)
@@ -670,7 +666,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
states = field.get("states", set())
value=field.get('value',None)
- current=field.get('current', None)
+ current = field.get('current', controlTypes.IsCurrent.NO)
placeholder=field.get('placeholder', None)
roleText = field.get('roleTextBraille', field.get('roleText'))
landmark = field.get("landmark")
diff --git a/source/controlTypes.py b/source/controlTypes.py
index 639a0e3d0a3..b29b4da5b68 100644
--- a/source/controlTypes.py
+++ b/source/controlTypes.py
@@ -6,6 +6,8 @@
from typing import Dict, Union, Set, Any, Optional, List
from enum import Enum, auto
+from logHandler import log
+
ROLE_UNKNOWN=0
ROLE_WINDOW=1
ROLE_TITLEBAR=2
@@ -649,21 +651,49 @@ class OutputReason(Enum):
QUICKNAV = auto()
-#: Text to use for 'current' values. These describe if an item is the current item
-#: within a particular kind of selection.
-isCurrentLabels: Dict[Union[bool, str], str] = {
+class IsCurrent(Enum):
+ """Values to use within NVDA to denote 'current' values.
+ These describe if an item is the current item within a particular kind of selection.
+ EG aria-current
+ """
+ NO = "false"
+ YES = "true"
+ PAGE = "page"
+ STEP = "step"
+ LOCATION = "location"
+ DATE = "date"
+ TIME = "time"
+
+ @property
+ def displayString(self):
+ """
+ @return: The translated UI display string that should be used for this value of the IsCurrent enum
+ """
+ try:
+ return _isCurrentLabels[self]
+ except KeyError:
+ log.debugWarning(f"No translation mapping for: {self}")
+ # there is a value for 'current' but NVDA hasn't learned about it yet,
+ # at least describe in the general sense that this item is 'current'
+ return _isCurrentLabels[IsCurrent.YES]
+
+
+#: Text to use for 'current' values. These describe if an item is the current item
+#: within a particular kind of selection. EG aria-current
+_isCurrentLabels: Dict[Enum, str] = {
+ IsCurrent.NO: "", # There is nothing extra to say for items that are not current.
# Translators: Presented when an item is marked as current in a collection of items
- True:_("current"),
+ IsCurrent.YES: _("current"),
# Translators: Presented when a page item is marked as current in a collection of page items
- "page":_("current page"),
+ IsCurrent.PAGE: _("current page"),
# Translators: Presented when a step item is marked as current in a collection of step items
- "step":_("current step"),
+ IsCurrent.STEP: _("current step"),
# Translators: Presented when a location item is marked as current in a collection of location items
- "location":_("current location"),
+ IsCurrent.LOCATION: _("current location"),
# Translators: Presented when a date item is marked as current in a collection of date items
- "date":_("current date"),
+ IsCurrent.DATE: _("current date"),
# Translators: Presented when a time item is marked as current in a collection of time items
- "time":_("current time"),
+ IsCurrent.TIME: _("current time"),
}
diff --git a/source/speech/__init__.py b/source/speech/__init__.py
index 9bb1064eeba..41d92821c6b 100755
--- a/source/speech/__init__.py
+++ b/source/speech/__init__.py
@@ -362,7 +362,7 @@ def getObjectPropertiesSpeech( # noqa: C901
positionInfo=obj.positionInfo
elif value and name == "current":
# getPropertiesSpeech names this "current", but the NVDAObject property is
- # named "isCurrent".
+ # named "isCurrent", it's type should always be controltypes.IsCurrent
newPropertyValues['current'] = obj.isCurrent
elif value:
# Certain properties such as row and column numbers have presentational versions, which should be used for speech if they are available.
@@ -1611,15 +1611,12 @@ def getPropertiesSpeech( # noqa: C901
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:
- ariaCurrentLabel = controlTypes.isCurrentLabels[ariaCurrent]
- textList.append(ariaCurrentLabel)
- except KeyError:
- log.debugWarning("Aria-current value not handled: %s"%ariaCurrent)
- ariaCurrentLabel = controlTypes.isCurrentLabels[True]
- textList.append(ariaCurrentLabel)
+
+ # speak isCurrent property EG aria-current
+ isCurrent = propertyValues.get('current', controlTypes.IsCurrent.NO)
+ if isCurrent != controlTypes.IsCurrent.NO:
+ textList.append(isCurrent.displayString)
+
placeholder: Optional[str] = propertyValues.get('placeholder', None)
if placeholder:
textList.append(placeholder)
@@ -1682,7 +1679,7 @@ def getControlFieldSpeech( # noqa: C901
name = ""
states=attrs.get('states',set())
keyboardShortcut=attrs.get('keyboardShortcut', "")
- ariaCurrent=attrs.get('current', None)
+ isCurrent = attrs.get('current', controlTypes.IsCurrent.NO)
placeholderValue=attrs.get('placeholder', None)
value=attrs.get('value',"")
if reason == OutputReason.FOCUS or attrs.get('alwaysReportDescription', False):
@@ -1712,7 +1709,7 @@ def getControlFieldSpeech( # noqa: C901
keyboardShortcutSequence = getPropertiesSpeech(
reason=reason, keyboardShortcut=keyboardShortcut
)
- ariaCurrentSequence = getPropertiesSpeech(reason=reason, current=ariaCurrent)
+ isCurrentSequence = getPropertiesSpeech(reason=reason, current=isCurrent)
placeholderSequence = getPropertiesSpeech(reason=reason, placeholder=placeholderValue)
nameSequence = getPropertiesSpeech(reason=reason, name=name)
valueSequence = getPropertiesSpeech(reason=reason, value=value)
@@ -1829,7 +1826,7 @@ def getControlFieldSpeech( # noqa: C901
getProps['columnHeaderText'] = attrs.get("table-columnheadertext")
tableCellSequence = getPropertiesSpeech(_tableID=tableID, **getProps)
tableCellSequence.extend(stateTextSequence)
- tableCellSequence.extend(ariaCurrentSequence)
+ tableCellSequence.extend(isCurrentSequence)
types.logBadSequenceTypes(tableCellSequence)
return tableCellSequence
@@ -1878,7 +1875,7 @@ def getControlFieldSpeech( # noqa: C901
out.extend(stateTextSequence if speakStatesFirst else roleTextSequence)
out.extend(roleTextSequence if speakStatesFirst else stateTextSequence)
out.append(containerContainsText)
- out.extend(ariaCurrentSequence)
+ out.extend(isCurrentSequence)
out.extend(valueSequence)
out.extend(descriptionSequence)
out.extend(levelSequence)
@@ -1913,8 +1910,8 @@ def getControlFieldSpeech( # noqa: C901
# Special cases
elif not speakEntry and fieldType in ("start_addedToControlFieldStack","start_relative"):
out = []
- if ariaCurrent:
- out.extend(ariaCurrentSequence)
+ if isCurrent != controlTypes.IsCurrent.NO:
+ out.extend(isCurrentSequence)
# Speak expanded / collapsed / level for treeview items (in ARIA treegrids)
if role == controlTypes.ROLE_TREEVIEWITEM:
if controlTypes.STATE_EXPANDED in states:
diff --git a/source/virtualBuffers/MSHTML.py b/source/virtualBuffers/MSHTML.py
index 204a92f9705..797c7477de2 100644
--- a/source/virtualBuffers/MSHTML.py
+++ b/source/virtualBuffers/MSHTML.py
@@ -48,9 +48,17 @@ def _normalizeFormatField(self, attrs):
def _normalizeControlField(self,attrs):
level=None
- ariaCurrent = attrs.get('HTMLAttrib::aria-current', None)
- if ariaCurrent not in (None, "false"):
- attrs['current']=ariaCurrent
+
+ ariaCurrentValue = attrs.get('HTMLAttrib::aria-current', 'false')
+ try:
+ ariaCurrent = controlTypes.IsCurrent(ariaCurrentValue)
+ except ValueError:
+ log.debugWarning(f"Unknown aria-current value: {ariaCurrentValue}")
+ ariaCurrent = controlTypes.IsCurrent.NO
+
+ if ariaCurrent != controlTypes.IsCurrent.NO:
+ attrs['current'] = ariaCurrent
+
placeholder = self._getPlaceholderAttribute(attrs, 'HTMLAttrib::aria-placeholder')
if placeholder:
attrs['placeholder']=placeholder
diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py
index c2ac7be94c3..910cdb94845 100755
--- a/source/virtualBuffers/gecko_ia2.py
+++ b/source/virtualBuffers/gecko_ia2.py
@@ -52,9 +52,14 @@ def _normalizeControlField(self,attrs):
if attrVal is not None:
attrs[attr]=int(attrVal)
- current = attrs.get("IAccessible2::attribute_current")
- if current not in (None, 'false'):
- attrs['current']= current
+ valForCurrent = attrs.get("IAccessible2::attribute_current", "false")
+ try:
+ isCurrent = controlTypes.IsCurrent(valForCurrent)
+ except ValueError:
+ log.debugWarning(f"Unknown isCurrent value: {valForCurrent}")
+ isCurrent = controlTypes.IsCurrent.NO
+ if isCurrent != controlTypes.IsCurrent.NO:
+ attrs['current'] = isCurrent
placeholder = self._getPlaceholderAttribute(attrs, "IAccessible2::attribute_placeholder")
if placeholder is not None:
attrs['placeholder']= placeholder
diff --git a/user_docs/en/changes.t2t b/user_docs/en/changes.t2t
index 5eafac90abb..2f53c3aaa56 100644
--- a/user_docs/en/changes.t2t
+++ b/user_docs/en/changes.t2t
@@ -37,6 +37,15 @@ What's New in NVDA
- getConfigDirs - use globalVars.appArgs.configPath instead
- Module level REASON_* constants are removed from controlTypes - please use controlTypes.OutputReason instead. (#11969)
- REASON_QUICKNAV has been removed from browseMode - use controlTypes.OutputReason.QUICKNAV instead. (#11969)
+- 'NVDAObject' (and derivatives) property 'isCurrent' now strictly returns Enum class 'controlTypes.IsCurrent'. (#11782)
+ - 'isCurrent' is no longer Optional, and thus will not return None.
+ - When an object is not current 'controlTypes.IsCurrent.NO' is returned.
+- The 'controlTypes.isCurrentLabels' mapping has been removed. (#11782)
+ - Instead use the 'displayString' property on a 'controlTypes.IsCurrent' enum value. EG 'controlTypes.IsCurrent.YES.displayString'
+- 'NVDAObject' (and derivatives) property 'isCurrent' now strictly returns 'controlTypes.IsCurrent'. (#11782)
+ - 'isCurrent' is no longer Optional, and thus will not return None, when an object is not current 'controlTypes.IsCurrent.NO' is returned.
+- The 'controlTypes.isCurrentLabels' has been removed, instead use the 'displayString' property on a 'controlTypes.IsCurrent' enum value. (#11782)
+ - EG 'controlTypes.IsCurrent.YES.displayString'
= 2020.4 =