Skip to content

Commit 3d98393

Browse files
authored
Merge 8736714 into 78fa572
2 parents 78fa572 + 8736714 commit 3d98393

File tree

11 files changed

+120
-56
lines changed

11 files changed

+120
-56
lines changed

source/IAccessibleHandler.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import UIAHandler
1818
from comInterfaces.Accessibility import *
1919
from comInterfaces.IAccessible2Lib import *
20+
from comInterfaces import IAccessible2Lib as IA2
2021
from logHandler import log
2122
import JABHandler
2223
import eventHandler
@@ -257,6 +258,7 @@ def flushEvents(self):
257258
IA2_ROLE_CONTENT_DELETION:controlTypes.ROLE_DELETED_CONTENT,
258259
IA2_ROLE_CONTENT_INSERTION:controlTypes.ROLE_INSERTED_CONTENT,
259260
IA2_ROLE_BLOCK_QUOTE:controlTypes.ROLE_BLOCKQUOTE,
261+
IA2.IA2_ROLE_LANDMARK: controlTypes.ROLE_LANDMARK,
260262
#some common string roles
261263
"frame":controlTypes.ROLE_FRAME,
262264
"iframe":controlTypes.ROLE_INTERNALFRAME,

source/NVDAObjects/IAccessible/MSHTML.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,11 @@ def event_liveRegionChange(self):
10091009
pass
10101010

10111011
def _get_roleText(self):
1012-
return self.HTMLAttributes['aria-roledescription']
1012+
roleText = self.HTMLAttributes['aria-roledescription']
1013+
if roleText:
1014+
return roleText
1015+
return super().roleText
1016+
10131017

10141018
class V6ComboBox(IAccessible):
10151019
"""The object which receives value change events for combo boxes in MSHTML/IE 6.

source/NVDAObjects/IAccessible/ia2Web.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ def _get_isPresentableFocusAncestor(self):
4949
return super(Ia2Web,self).isPresentableFocusAncestor
5050

5151
def _get_roleText(self):
52-
roleText=self.IA2Attributes.get('roledescription')
52+
roleText = self.IA2Attributes.get('roledescription')
5353
if roleText:
5454
return roleText
55-
return super(Ia2Web,self).roleText
55+
return super().roleText
5656

5757
def _get_states(self):
5858
states=super(Ia2Web,self).states
@@ -65,6 +65,11 @@ def _get_states(self):
6565
states.discard(controlTypes.STATE_EDITABLE)
6666
return states
6767

68+
def _get_landmark(self):
69+
if self.IAccessibleRole != IAccessibleHandler.IA2_ROLE_LANDMARK:
70+
return super().landmark
71+
return self.IA2Attributes.get('xml-roles', '').split(' ')[0]
72+
6873
class Document(Ia2Web):
6974
value = None
7075

source/NVDAObjects/UIA/edge.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ def _getControlFieldForObject(self,obj,isEmbedded=False,startOfNode=False,endOfN
138138
# This can affect whether the field is presented as a container (e.g. announcing entering and exiting)
139139
if role in (controlTypes.ROLE_GROUPING,controlTypes.ROLE_SECTION,controlTypes.ROLE_PARAGRAPH):
140140
field['isBlock']=True
141-
# ARIA roledescription
141+
# ARIA roledescription and landmarks
142142
field['roleText']=obj.roleText
143-
# report landmarks
143+
# provide landmarks
144144
field['landmark']=obj.landmark
145145
# Combo boxes with a text pattern are editable
146146
if obj.role==controlTypes.ROLE_COMBOBOX and obj.UIATextPattern:
@@ -479,7 +479,10 @@ def _get_isCurrent(self):
479479
return None
480480

481481
def _get_roleText(self):
482-
return self.ariaProperties.get('roledescription', None)
482+
roleText = self.ariaProperties.get('roledescription', None)
483+
if roleText:
484+
return roleText
485+
return super().roleText
483486

484487
def _get_placeholder(self):
485488
ariaPlaceholder = self.ariaProperties.get('placeholder', None)

source/NVDAObjects/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import globalPluginHandler
3030
import brailleInput
3131
import locationHelper
32+
import aria
3233

3334
class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo):
3435
"""A default TextInfo which is used to enable text review of information about widgets that don't support text content.
@@ -407,8 +408,20 @@ def _get_roleText(self):
407408
No string is provided by default, meaning that NVDA will fall back to using role.
408409
Examples of where this property might be overridden are shapes in Powerpoint, or ARIA role descriptions.
409410
"""
411+
if self.landmark and self.landmark in aria.landmarkRoles:
412+
return f"{aria.landmarkRoles[self.landmark]} {controlTypes.roleLabels[controlTypes.ROLE_LANDMARK]}"
410413
return None
411414

415+
def _get_roleTextBraille(self):
416+
"""
417+
A custom role string for this object, which is used for braille presentation,
418+
which will override the standard label for this object's role property as well as the value of roleText.
419+
By default, NVDA falls back to using roleText.
420+
"""
421+
if self.landmark and self.landmark in braille.landmarkLabels:
422+
return f"{braille.roleLabels[controlTypes.ROLE_LANDMARK]} {braille.landmarkLabels[self.landmark]}"
423+
return self.roleText
424+
412425
def _get_value(self):
413426
"""The value of this object (example: the current percentage of a scrollbar, the selected option in a combo box).
414427
@rtype: str
@@ -724,6 +737,8 @@ def _get_presentationType(self):
724737
if not name and not description:
725738
if role in (controlTypes.ROLE_WINDOW,controlTypes.ROLE_PANEL, controlTypes.ROLE_PROPERTYPAGE, controlTypes.ROLE_TEXTFRAME, controlTypes.ROLE_GROUPING,controlTypes.ROLE_OPTIONPANE,controlTypes.ROLE_INTERNALFRAME,controlTypes.ROLE_FORM,controlTypes.ROLE_TABLEBODY):
726739
return self.presType_layout
740+
if role == controlTypes.ROLE_LANDMARK and not config.conf["documentFormatting"]["reportLandmarks"]:
741+
return self.presType_layout
727742
if role == controlTypes.ROLE_TABLE and not config.conf["documentFormatting"]["reportTables"]:
728743
return self.presType_layout
729744
if role in (controlTypes.ROLE_TABLEROW,controlTypes.ROLE_TABLECOLUMN,controlTypes.ROLE_TABLECELL) and (not config.conf["documentFormatting"]["reportTables"] or not config.conf["documentFormatting"]["reportTableCellCoords"]):
@@ -1164,6 +1179,11 @@ def _get_devInfo(self):
11641179
except Exception as e:
11651180
ret = "exception: %s" % e
11661181
info.append("role: %s" % ret)
1182+
try:
1183+
ret = repr(self.roleText)
1184+
except Exception as e:
1185+
ret = f"exception: {e}"
1186+
info.append(f"roleText: {ret}")
11671187
try:
11681188
stateConsts = dict((const, name) for name, const in controlTypes.__dict__.items() if name.startswith("STATE_"))
11691189
ret = ", ".join(

source/braille.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@
169169
controlTypes.ROLE_DELETED_CONTENT: _("del"),
170170
# Translators: Displayed in braille for an object which is inserted.
171171
controlTypes.ROLE_INSERTED_CONTENT: _("ins"),
172+
# Translators: Displayed in braille for a landmark.
173+
controlTypes.ROLE_LANDMARK: _("lmk"),
172174
}
173175

174176
positiveStateLabels = {
@@ -473,7 +475,7 @@ def getBrailleTextForProperties(**propertyValues):
473475
if name:
474476
textList.append(name)
475477
role = propertyValues.get("role")
476-
roleText = propertyValues.get("roleText")
478+
roleText = propertyValues.get('roleText')
477479
states = propertyValues.get("states")
478480
positionInfo = propertyValues.get("positionInfo")
479481
level = positionInfo.get("level") if positionInfo else None
@@ -596,7 +598,7 @@ def update(self):
596598
text = getBrailleTextForProperties(
597599
name=obj.name,
598600
role=role,
599-
roleText=obj.roleText,
601+
roleText=obj.roleTextBraille,
600602
current=obj.isCurrent,
601603
placeholder=placeholderValue,
602604
value=obj.value if not NVDAObjectHasUsefulText(obj) else None ,
@@ -643,8 +645,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
643645
value=field.get('value',None)
644646
current=field.get('current', None)
645647
placeholder=field.get('placeholder', None)
646-
roleText=field.get('roleText')
647-
648+
roleText = field.get('roleTextBraille', field.get('roleText'))
648649
if presCat == field.PRESCAT_LAYOUT:
649650
text = []
650651
if current:
@@ -674,6 +675,11 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
674675
# However, we still need to pass it (hence "_role").
675676
"_role" if role == controlTypes.ROLE_MATH else "role": role,
676677
"states": states,"value":value, "current":current, "placeholder":placeholder,"roleText":roleText}
678+
if formatConfig["reportLandmarks"] and field.get("landmark") and field.get("_startOfNode"):
679+
# Ensure that the name of the field gets presented even if normally it wouldn't.
680+
name = field.get("name")
681+
if name:
682+
props["name"] = name
677683
if config.conf["presentation"]["reportKeyboardShortcuts"]:
678684
kbShortcut = field.get("keyboardShortcut")
679685
if kbShortcut:

source/browseMode.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,41 +1106,6 @@ def move():
11061106

11071107
class BrowseModeDocumentTextInfo(textInfos.TextInfo):
11081108

1109-
def getControlFieldSpeech(self, attrs, ancestorAttrs, fieldType, formatConfig=None, extraDetail=False, reason=None):
1110-
textList = []
1111-
landmark = attrs.get("landmark")
1112-
if formatConfig["reportLandmarks"] and fieldType == "start_addedToControlFieldStack" and landmark:
1113-
# Ensure that the name of the field gets presented even if normally it wouldn't.
1114-
name=attrs.get('name')
1115-
if name and attrs.getPresentationCategory(ancestorAttrs,formatConfig,reason) is None:
1116-
textList.append(name)
1117-
if landmark == "region":
1118-
# The word landmark is superfluous for regions.
1119-
textList.append(aria.landmarkRoles[landmark])
1120-
if landmark != "region":
1121-
textList.append(_("%s landmark") % aria.landmarkRoles[landmark])
1122-
textList.append(super(BrowseModeDocumentTextInfo, self).getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason))
1123-
return " ".join(textList)
1124-
1125-
def getControlFieldBraille(self, field, ancestors, reportStart, formatConfig):
1126-
textList = []
1127-
landmark = field.get("landmark")
1128-
if formatConfig["reportLandmarks"] and reportStart and landmark and field.get("_startOfNode"):
1129-
# Ensure that the name of the field gets presented even if normally it wouldn't.
1130-
name=field.get('name')
1131-
if name and field.getPresentationCategory(ancestors,formatConfig) is None:
1132-
textList.append(name)
1133-
if landmark == "region":
1134-
# The word landmark is superfluous for regions.
1135-
textList.append(braille.landmarkLabels[landmark])
1136-
if landmark != "region":
1137-
# Translators: This is brailled to indicate a landmark (example output: lmk main).
1138-
textList.append(_("lmk %s") % braille.landmarkLabels[landmark])
1139-
text = super(BrowseModeDocumentTextInfo, self).getControlFieldBraille(field, ancestors, reportStart, formatConfig)
1140-
if text:
1141-
textList.append(text)
1142-
return " ".join(textList)
1143-
11441109
def _get_focusableNVDAObjectAtStart(self):
11451110
try:
11461111
item = next(self.obj._iterNodesByType("focusable", "up", self))

source/controlTypes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
ROLE_CHARTELEMENT=146
153153
ROLE_DELETED_CONTENT=147
154154
ROLE_INSERTED_CONTENT=148
155+
ROLE_LANDMARK = 149
155156

156157
STATE_UNAVAILABLE=0X1
157158
STATE_FOCUSED=0X2
@@ -492,6 +493,8 @@
492493
ROLE_DELETED_CONTENT:_("deleted"),
493494
# Translators: Identifies inserted content.
494495
ROLE_INSERTED_CONTENT:_("inserted"),
496+
# Translators: Identifies a landmark.
497+
ROLE_LANDMARK: _("landmark"),
495498
}
496499

497500
stateLabels={

source/speech/__init__.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import characterProcessing
3131
import languageHandler
3232
from .commands import *
33+
import aria
3334

3435
speechMode_off=0
3536
speechMode_beeps=1
@@ -344,7 +345,28 @@ def speakObject(obj, reason=controlTypes.REASON_QUERY, _prefixSpeechCommand=None
344345
# objects that do not report as having navigableText should not report their text content either
345346
not obj._hasNavigableText
346347
)
347-
allowProperties={'name':True,'role':True,'roleText':True,'states':True,'value':True,'description':True,'keyboardShortcut':True,'positionInfo_level':True,'positionInfo_indexInGroup':True,'positionInfo_similarItemsInGroup':True,"cellCoordsText":True,"rowNumber":True,"columnNumber":True,"includeTableCellCoords":True,"columnCount":True,"rowCount":True,"rowHeaderText":True,"columnHeaderText":True,"rowSpan":True,"columnSpan":True}
348+
allowProperties = {
349+
'name': True,
350+
'role': True,
351+
'roleText': True,
352+
'states': True,
353+
'value': True,
354+
'description': True,
355+
'keyboardShortcut': True,
356+
'positionInfo_level': True,
357+
'positionInfo_indexInGroup': True,
358+
'positionInfo_similarItemsInGroup': True,
359+
"cellCoordsText": True,
360+
"rowNumber": True,
361+
"columnNumber": True,
362+
"includeTableCellCoords": True,
363+
"columnCount": True,
364+
"rowCount": True,
365+
"rowHeaderText": True,
366+
"columnHeaderText": True,
367+
"rowSpan": True,
368+
"columnSpan": True
369+
}
348370

349371
if reason==controlTypes.REASON_FOCUSENTERED:
350372
allowProperties["value"]=False
@@ -372,8 +394,10 @@ def speakObject(obj, reason=controlTypes.REASON_QUERY, _prefixSpeechCommand=None
372394
if not formatConf["reportTableHeaders"]:
373395
allowProperties["rowHeaderText"]=False
374396
allowProperties["columnHeaderText"]=False
375-
if (not formatConf["reportTables"]
376-
or (not formatConf["reportTableCellCoords"] and not formatConf["reportTableHeaders"])):
397+
if (
398+
not formatConf["reportTables"]
399+
or (not formatConf["reportTableCellCoords"] and not formatConf["reportTableHeaders"])
400+
):
377401
# We definitely aren't reporting any table info at all.
378402
allowProperties["rowNumber"]=False
379403
allowProperties["columnNumber"]=False
@@ -1179,10 +1203,19 @@ def getControlFieldSpeech(attrs,ancestorAttrs,fieldType,formatConfig=None,extraD
11791203

11801204
presCat=attrs.getPresentationCategory(ancestorAttrs,formatConfig, reason=reason)
11811205
childControlCount=int(attrs.get('_childcontrolcount',"0"))
1182-
if reason==controlTypes.REASON_FOCUS or attrs.get('alwaysReportName',False):
1183-
name=attrs.get('name',"")
1206+
landmark = attrs.get("landmark")
1207+
if (
1208+
reason==controlTypes.REASON_FOCUS
1209+
or attrs.get('alwaysReportName',False)
1210+
or (
1211+
formatConfig["reportLandmarks"]
1212+
and landmark
1213+
and attrs.get("_startOfNode")
1214+
)
1215+
):
1216+
name = attrs.get('name',"")
11841217
else:
1185-
name=""
1218+
name = ""
11861219
role=attrs.get('role',controlTypes.ROLE_UNKNOWN)
11871220
states=attrs.get('states',set())
11881221
keyboardShortcut=attrs.get('keyboardShortcut', "")
@@ -1200,9 +1233,12 @@ def getControlFieldSpeech(attrs,ancestorAttrs,fieldType,formatConfig=None,extraD
12001233
else:
12011234
tableID = None
12021235

1203-
roleText=attrs.get('roleText')
1236+
roleText = attrs.get('roleText')
12041237
if not roleText:
1205-
roleText=getSpeechTextForProperties(reason=reason,role=role)
1238+
if landmark and formatConfig["reportLandmarks"]:
1239+
roleText = f"{aria.landmarkRoles[landmark]} {controlTypes.roleLabels[controlTypes.ROLE_LANDMARK]}"
1240+
else:
1241+
roleText=getSpeechTextForProperties(reason=reason,role=role)
12061242
stateText=getSpeechTextForProperties(reason=reason,states=states,_role=role)
12071243
keyboardShortcutText=getSpeechTextForProperties(reason=reason,keyboardShortcut=keyboardShortcut) if config.conf["presentation"]["reportKeyboardShortcuts"] else ""
12081244
ariaCurrentText=getSpeechTextForProperties(reason=reason,current=ariaCurrent)

source/textInfos/__init__.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def getPresentationCategory(self, ancestors, formatConfig, reason=controlTypes.R
6363
table = None
6464
if not table or (not formatConfig["includeLayoutTables"] and table.get("table-layout", None)) or table.get('isHidden',False):
6565
return self.PRESCAT_LAYOUT
66+
landmark = self.get("landmark")
6667
if reason in (controlTypes.REASON_CARET, controlTypes.REASON_SAYALL, controlTypes.REASON_FOCUS) and (
6768
(role == controlTypes.ROLE_LINK and not formatConfig["reportLinks"])
6869
or (role == controlTypes.ROLE_HEADING and not formatConfig["reportHeadings"])
@@ -71,6 +72,10 @@ def getPresentationCategory(self, ancestors, formatConfig, reason=controlTypes.R
7172
or (role in (controlTypes.ROLE_LIST, controlTypes.ROLE_LISTITEM) and controlTypes.STATE_READONLY in states and not formatConfig["reportLists"])
7273
or (role in (controlTypes.ROLE_FRAME, controlTypes.ROLE_INTERNALFRAME) and not formatConfig["reportFrames"])
7374
or (role in (controlTypes.ROLE_DELETED_CONTENT,controlTypes.ROLE_INSERTED_CONTENT) and not formatConfig["reportRevisions"])
75+
or (
76+
(role == controlTypes.ROLE_LANDMARK or landmark)
77+
and not formatConfig["reportLandmarks"]
78+
)
7479
):
7580
# This is just layout as far as the user is concerned.
7681
return self.PRESCAT_LAYOUT
@@ -102,16 +107,30 @@ def getPresentationCategory(self, ancestors, formatConfig, reason=controlTypes.R
102107
or (role == controlTypes.ROLE_LIST and controlTypes.STATE_READONLY not in states)
103108
):
104109
return self.PRESCAT_SINGLELINE
105-
elif role in (controlTypes.ROLE_SEPARATOR, controlTypes.ROLE_FOOTNOTE, controlTypes.ROLE_ENDNOTE, controlTypes.ROLE_EMBEDDEDOBJECT, controlTypes.ROLE_MATH):
110+
elif (
111+
role in (controlTypes.ROLE_SEPARATOR, controlTypes.ROLE_FOOTNOTE, controlTypes.ROLE_ENDNOTE, controlTypes.ROLE_EMBEDDEDOBJECT, controlTypes.ROLE_MATH)
112+
or (role == controlTypes.ROLE_LANDMARK or landmark)
113+
):
106114
return self.PRESCAT_MARKER
107115
elif role in (controlTypes.ROLE_APPLICATION, controlTypes.ROLE_DIALOG):
108116
# Applications and dialogs should be reported as markers when embedded within content, but not when they themselves are the root
109117
return self.PRESCAT_MARKER if ancestors else self.PRESCAT_LAYOUT
110118
elif role in (controlTypes.ROLE_TABLECELL, controlTypes.ROLE_TABLECOLUMNHEADER, controlTypes.ROLE_TABLEROWHEADER):
111119
return self.PRESCAT_CELL
112120
elif (
113-
role in (controlTypes.ROLE_BLOCKQUOTE, controlTypes.ROLE_FRAME, controlTypes.ROLE_INTERNALFRAME, controlTypes.ROLE_TOOLBAR, controlTypes.ROLE_MENUBAR, controlTypes.ROLE_POPUPMENU, controlTypes.ROLE_TABLE)
114-
or (role == controlTypes.ROLE_EDITABLETEXT and (controlTypes.STATE_READONLY not in states or controlTypes.STATE_FOCUSABLE in states) and controlTypes.STATE_MULTILINE in states)
121+
role in (
122+
controlTypes.ROLE_BLOCKQUOTE,
123+
controlTypes.ROLE_FRAME,
124+
controlTypes.ROLE_INTERNALFRAME,
125+
controlTypes.ROLE_TOOLBAR,
126+
controlTypes.ROLE_MENUBAR,
127+
controlTypes.ROLE_POPUPMENU,
128+
controlTypes.ROLE_TABLE,
129+
)
130+
or (role == controlTypes.ROLE_EDITABLETEXT and (
131+
controlTypes.STATE_READONLY not in states
132+
or controlTypes.STATE_FOCUSABLE in states
133+
) and controlTypes.STATE_MULTILINE in states)
115134
or (role == controlTypes.ROLE_LIST and controlTypes.STATE_READONLY in states)
116135
or (controlTypes.STATE_FOCUSABLE in states and controlTypes.STATE_EDITABLE in states)
117136
):

0 commit comments

Comments
 (0)