Skip to content

Commit 5e6e0b3

Browse files
Merge 25662c6 into 42b667b
2 parents 42b667b + 25662c6 commit 5e6e0b3

7 files changed

Lines changed: 136 additions & 46 deletions

File tree

source/IAccessibleHandler/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ class RelationType(str, enum.Enum):
2525
CONTAINING_DOCUMENT = "containingDocument"
2626
DETAILS = "details"
2727
DETAILS_FOR = "detailsFor"
28+
CONTROLLER_FOR = "controllerFor"

source/NVDAObjects/IAccessible/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,12 @@ def _getIA2RelationFirstTarget(
15961596
#: Type definition for auto prop '_get_detailsRelations'
15971597
detailsRelations: typing.Iterable["IAccessible"]
15981598

1599+
def _get_controllerFor(self) -> List[NVDAObject]:
1600+
control = self._getIA2RelationFirstTarget(IAccessibleHandler.RelationType.CONTROLLER_FOR)
1601+
if control:
1602+
return [control]
1603+
return []
1604+
15991605
def _get_detailsRelations(self) -> typing.Iterable["IAccessible"]:
16001606
relationTarget = self._getIA2RelationFirstTarget(IAccessibleHandler.RelationType.DETAILS)
16011607
if not relationTarget:
@@ -1655,9 +1661,6 @@ def _get_groupName(self):
16551661
else:
16561662
return super(IAccessible,self)._get_groupName()
16571663

1658-
def event_selection(self):
1659-
return self.event_stateChange()
1660-
16611664
def event_selectionAdd(self):
16621665
return self.event_stateChange()
16631666

source/NVDAObjects/IAccessible/ia2Web.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,27 @@
2121
import api
2222
import speech
2323
import config
24+
import NVDAObjects
25+
2426

2527
class Ia2Web(IAccessible):
2628
IAccessibleTableUsesTableCellIndexAttrib=True
2729
caretMovementDetectionUsesEvents = False
2830

31+
def isDescendantOf(self, obj: "NVDAObjects.NVDAObject") -> bool:
32+
if obj.windowHandle != self.windowHandle:
33+
# Only supported on the same window.
34+
raise NotImplementedError
35+
if not isinstance(obj, Ia2Web):
36+
# #4080: Input composition NVDAObjects are the same window but not IAccessible2!
37+
raise NotImplementedError
38+
accId = obj.IA2UniqueID
39+
try:
40+
res = obj.IAccessibleObject.accChild(accId)
41+
except COMError:
42+
return False
43+
return bool(res)
44+
2945
def _get_positionInfo(self):
3046
info=super(Ia2Web,self).positionInfo
3147
level=info.get('level',None)

source/NVDAObjects/UIA/__init__.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Dict,
1414
Tuple,
1515
)
16+
import array
1617
from ctypes.wintypes import POINT
1718
from comtypes import COMError
1819
import time
@@ -60,6 +61,7 @@
6061
import locationHelper
6162
import ui
6263
import winVersion
64+
import NVDAObjects
6365

6466

6567
paragraphIndentIDs = {
@@ -2014,6 +2016,27 @@ def _get_positionInfo(self):
20142016
def scrollIntoView(self):
20152017
pass
20162018

2019+
def isDescendantOf(self, obj: "NVDAObjects.NVDAObject") -> bool:
2020+
if isinstance(obj, UIA):
2021+
# As both objects are UIA,
2022+
# We can search this object's ancestors for obj with a UIA treeWalker
2023+
# which is much more efficient than fetching each parent.
2024+
objID = obj.UIAElement.GetRuntimeId()
2025+
objIDArray = array.array("l", objID)
2026+
UIACondition = UIAHandler.handler.clientObject.createPropertyCondition(
2027+
UIAHandler.UIA_RuntimeIdPropertyId,
2028+
objIDArray
2029+
)
2030+
UIAWalker = UIAHandler.handler.clientObject.createTreeWalker(UIACondition)
2031+
try:
2032+
objUIAElement = UIAWalker.normalizeElement(self.UIAElement)
2033+
except COMError:
2034+
log.debugWarning("Error walking ancestors", exc_info=True)
2035+
objUIAElement = None
2036+
return bool(objUIAElement)
2037+
else: # not UIA
2038+
raise NotImplementedError
2039+
20172040
def _get_controllerFor(self):
20182041
e=self._getUIACacheablePropertyValue(UIAHandler.UIA_ControllerForPropertyId)
20192042
if UIAHandler.handler.clientObject.checkNotSupported(e):
@@ -2028,8 +2051,11 @@ def _get_controllerFor(self):
20282051
objList.append(obj)
20292052
return objList
20302053

2054+
def event_UIA_controllerFor(self) -> None:
2055+
return self.event_controllerForChange()
2056+
20312057
def event_UIA_elementSelected(self):
2032-
self.event_stateChange()
2058+
self.event_selection()
20332059

20342060
def event_valueChange(self):
20352061
if issubclass(self.TextInfo, UIATextInfo):
@@ -2273,15 +2299,9 @@ def event_stateChange(self):
22732299

22742300
class SearchField(EditableTextWithSuggestions, UIA):
22752301
"""An edit field that presents suggestions based on a search term.
2302+
This is now an empty class as functionality has been moved to the base EditableText behaviour.
22762303
"""
22772304

2278-
def event_UIA_controllerFor(self):
2279-
# Only useful if suggestions appear and disappear.
2280-
if self == api.getFocusObject() and len(self.controllerFor)>0:
2281-
self.event_suggestionsOpened()
2282-
else:
2283-
self.event_suggestionsClosed()
2284-
22852305

22862306
class SuggestionsList(UIA):
22872307
"""A list of suggestions in response to search terms being entered.
@@ -2310,20 +2330,11 @@ def event_UIA_layoutInvalidated(self):
23102330
class SuggestionListItem(UIA):
23112331
"""Recent Windows releases use suggestions lists for various things, including Start menu suggestions, Store, Settings app and so on.
23122332
Unlike suggestions list class, top suggestion is automatically selected.
2333+
Note that support for reporting the selection is now handled generically on the base NVDAObject.
23132334
"""
23142335

23152336
role = controlTypes.Role.LISTITEM
23162337

2317-
def event_UIA_elementSelected(self):
2318-
focusControllerFor = api.getFocusObject().controllerFor
2319-
if len(focusControllerFor) > 0 and focusControllerFor[0].appModule is self.appModule and self.name:
2320-
speech.cancelSpeech()
2321-
if api.setNavigatorObject(self, isFocus=True):
2322-
self.reportFocus()
2323-
# Display results as flash messages.
2324-
braille.handler.message(braille.getPropertiesBraille(
2325-
name=self.name, role=self.role, positionInfo=self.positionInfo
2326-
))
23272338

23282339
# NetUIDropdownAnchor comboBoxes (such as in the MS Office Options dialog)
23292340
class NetUIDropdownAnchor(UIA):

source/NVDAObjects/__init__.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,10 @@ def reportFocus(self):
10931093
"""
10941094
speech.speakObject(self, reason=controlTypes.OutputReason.FOCUS)
10951095

1096+
def isDescendantOf(self, obj: "NVDAObject") -> bool:
1097+
""" is this object a descendant of obj? """
1098+
raise NotImplementedError
1099+
10961100
def _get_placeholder(self):
10971101
"""If it exists for this object get the value of the placeholder text.
10981102
For example this might be the aria-placeholder text for a field in a web page.
@@ -1177,6 +1181,29 @@ def event_mouseMove(self,x,y):
11771181
speech.cancelSpeech()
11781182
speech.speakText(text)
11791183

1184+
def event_selection(self):
1185+
# This object has been selected.
1186+
# If this object's container / parent is being controlled by the focus,
1187+
# then report this selection.
1188+
focus = api.getFocusObject()
1189+
controls = focus.controllerFor
1190+
for control in controls:
1191+
# The focus is controling one or more objects.
1192+
# If possible, check if this object is a descendant of the object being controlled.
1193+
try:
1194+
isDescendant = self.isDescendantOf(control)
1195+
except NotImplementedError:
1196+
isDescendant = False
1197+
if isDescendant:
1198+
speech.cancelSpeech()
1199+
if api.setNavigatorObject(self, isFocus=True):
1200+
self.reportFocus()
1201+
# Display results as flash messages.
1202+
braille.handler.message(braille.getPropertiesBraille(
1203+
name=self.name, role=self.role, positionInfo=self.positionInfo
1204+
))
1205+
self.event_stateChange()
1206+
11801207
def event_stateChange(self):
11811208
if self is api.getFocusObject():
11821209
speech.speakObjectProperties(self, states=True, reason=controlTypes.OutputReason.CHANGE)
@@ -1246,6 +1273,9 @@ def event_descriptionChange(self):
12461273
braille.handler.handleUpdate(self)
12471274
vision.handler.handleUpdate(self, property="description")
12481275

1276+
def event_controllerForChange(self):
1277+
pass
1278+
12491279
def event_caret(self):
12501280
if self is api.getFocusObject() and not eventHandler.isPendingEvents("gainFocus"):
12511281
braille.handler.handleCaretMove(self)

source/NVDAObjects/behaviors.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,42 @@ def _get_isPresentableFocusAncestor(self):
163163
self.isPresentableFocusAncestor = res = super(Dialog, self).isPresentableFocusAncestor
164164
return res
165165

166-
class EditableText(editableText.EditableText, NVDAObject):
166+
167+
class InputFieldWithSuggestions(NVDAObject):
168+
"""Allows NVDA to announce appearance/disappearance of suggestions as content is entered.
169+
This is used in various places, including Windows 10 search edit fields and others.
170+
Subclasses should provide L{event_suggestionsOpened} and can optionally override L{event_suggestionsClosed}.
171+
These events are fired when suggestions appear and disappear, respectively.
172+
"""
173+
174+
def event_suggestionsOpened(self):
175+
"""Called when suggestions appear when text is entered e.g. search suggestions.
176+
Subclasses should provide custom implementations if possible.
177+
By default NVDA will announce appearance of suggestions using speech, braille or a sound will be played.
178+
"""
179+
# Translators: Announced in braille when suggestions appear when search term is entered
180+
# in various search fields such as Start search box in Windows 10.
181+
braille.handler.message(_("Suggestions"))
182+
if config.conf["presentation"]["reportAutoSuggestionsWithSound"]:
183+
nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "suggestionsOpened.wav"))
184+
185+
def event_suggestionsClosed(self):
186+
"""Called when suggestions list or container is closed.
187+
Subclasses should provide custom implementations if possible.
188+
By default NVDA will announce this via speech, braille or via a sound.
189+
"""
190+
if config.conf["presentation"]["reportAutoSuggestionsWithSound"]:
191+
nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "suggestionsClosed.wav"))
192+
193+
def event_controllerForChange(self):
194+
# Report when suggestions appear and disappear.
195+
if self is api.getFocusObject() and len(self.controllerFor) > 0:
196+
self.event_suggestionsOpened()
197+
else:
198+
self.event_suggestionsClosed()
199+
200+
201+
class EditableTextBase(editableText.EditableText, NVDAObject):
167202
"""Provides scripts to report appropriately when moving the caret in editable text fields.
168203
This does not handle selection changes.
169204
To handle selection changes, use either L{EditableTextWithAutoSelectDetection} or L{EditableTextWithoutAutoSelectDetection}.
@@ -236,6 +271,20 @@ def event_typedCharacter(self, ch: str):
236271
super().event_typedCharacter(ch)
237272

238273

274+
class EditableTextWithSuggestions(InputFieldWithSuggestions, EditableTextBase):
275+
""" Represents an editable text field that shows suggestions as you type.
276+
This is an empty class as functionality has been moved to the base InputFieldWithSuggestions class.
277+
"""
278+
279+
280+
class EditableText(EditableTextWithSuggestions, EditableTextBase):
281+
""" Represents an editable text field.
282+
This is an empty class as functionality has been moved to the base EditableTextBase class.
283+
This class also supports reporting of the appearance and disappearance of suggestions
284+
by inheriting from the EditableTextWithSuggestions class.
285+
"""
286+
287+
239288
class EditableTextWithAutoSelectDetection(EditableText):
240289
"""In addition to L{EditableText}, handles reporting of selection changes for objects which notify of them.
241290
To have selection changes reported, the object must notify of selection changes via the caret event.
@@ -873,30 +922,6 @@ def event_alert(self):
873922

874923
event_show = event_alert
875924

876-
class EditableTextWithSuggestions(NVDAObject):
877-
"""Allows NvDA to announce appearance/disappearance of suggestions as text is entered.
878-
This is used in various places, including Windows 10 search edit fields and others.
879-
Subclasses should provide L{event_suggestionsOpened} and can optionally override L{event_suggestionsClosed}.
880-
These events are fired when suggestions appear and disappear, respectively.
881-
"""
882-
883-
def event_suggestionsOpened(self):
884-
"""Called when suggestions appear when text is entered e.g. search suggestions.
885-
Subclasses should provide custom implementations if possible.
886-
By default NVDA will announce appearance of suggestions using speech, braille or a sound will be played.
887-
"""
888-
# Translators: Announced in braille when suggestions appear when search term is entered in various search fields such as Start search box in Windows 10.
889-
braille.handler.message(_("Suggestions"))
890-
if config.conf["presentation"]["reportAutoSuggestionsWithSound"]:
891-
nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "suggestionsOpened.wav"))
892-
893-
def event_suggestionsClosed(self):
894-
"""Called when suggestions list or container is closed.
895-
Subclasses should provide custom implementations if possible.
896-
By default NVDA will announce this via speech, braille or via a sound.
897-
"""
898-
if config.conf["presentation"]["reportAutoSuggestionsWithSound"]:
899-
nvwave.playWaveFile(os.path.join(globalVars.appDir, "waves", "suggestionsClosed.wav"))
900925

901926
class WebDialog(NVDAObject):
902927
"""

user_docs/en/changes.t2t

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ What's New in NVDA
1919

2020
== Bug Fixes ==
2121
- In Windows 11, NVDA will announce search highlights when opening Start menu. (#13841)
22+
- Suggestions are reported when typing an @mention in in Microsoft Excel comments. (#13764)
23+
- In the Google Chrome location bar, suggestion controls (switch to tab, remove suggestion etc) are now reported when selected. (#13522)
2224
- When requesting formatting information, colors are now explicitly reported in Wordpad or log viewer, rather than only "Default color". (#13959)
2325
-
2426

@@ -29,6 +31,8 @@ Add-ons will need to be re-tested and have their manifest updated.
2931
Please refer to [the developer guide https://www.nvaccess.org/files/nvda/documentation/developerGuide.html#API] for information on NVDA's API deprecation and removal process.
3032

3133
- System test should now pass when run locally on non-English systems. (#13362)
34+
- It is no longer necessary to use ``SearchField`` and ``SuggestionListItem`` ``UIA`` ``NVDAObjects`` in new UI Automation scenarios, where automatic reporting of search suggestions, and where typing has been exposed via UI Automation with the ``controllerFor`` pattern.
35+
This functionality is now available generically via ``behaviours.EditableText`` and the base ``NVDAObject`` respectively.
3236
-
3337

3438
=== API Breaking Changes ===

0 commit comments

Comments
 (0)