Repository: nvaccess/nvda

Issue:
Windows 10/11 Calculator: "list index out of range" when entering expressions in portable NVDA with Calculator compact overlay on
Hi,

Discovered while testing parts of Windows App Essentials add-on in hopes of catching out of date documentation:

### Steps to reproduce:
Prerequisites: a portable copy of NVDA with no add-ons, Windows 10/11 Calculator:

1. Start Calculator.
2. Press Alt+H, then select "standard calculator".
3. Press Alt+Down arrow to make sure Calculator is displaying with all controls shown.
4. Enter expressions, and NVDA will annonce expressions and results when Enter is pressed.
5. Press Alt+Up arrow to switch to compact overlay.
6. Enter calculations.

### Actual behavior:
Calculations are announced in installed copy but does not on portable copy, resulting in the following traceback:

ERROR - eventHandler.executeEvent (08:53:02.104) - MainThread (22024):
error executing event: UIA_notification on <NVDAObjects.Dynamic_EditableTextWithAutoSelectDetectionUIA object at 0x0418D910> with extra args of {'notificationKind': 4, 'notificationProcessing': 1, 'displayString': 'Display is 15', 'activityId': 'DisplayUpdated'}
Traceback (most recent call last):
  File "eventHandler.pyc", line 300, in executeEvent
  File "eventHandler.pyc", line 101, in __init__
  File "eventHandler.pyc", line 110, in next
  File "appModules\calculator.pyc", line 94, in event_UIA_notification
IndexError: list index out of range

### Expected behavior:
Both installed and portable copy announces calculations and results with compact overlay on or off.

### NVDA logs, crash dumps and other attachments:

### System configuration
#### NVDA installed/portable/running from source:
Installed and portable

#### NVDA version:
alpha-27763,d957a684

#### Windows version:
Windows 11 22H2 build 22621.1265

#### Name and version of other software in use when reproducing the issue:
Windows Calculator 11.2210.0.0

#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your computer?
Yes

#### Have you tried any other versions of NVDA? If so, please report their behaviors.
Reproducible in 2022.4 and 2023.1 beta 2

#### If NVDA add-ons are disabled, is your problem still occurring?
Yes

#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
Not applicable

### Technical details:
UIA notification event handler inside Calculator app module performs object hierarchy traversal to locate results. While useful, object traversal has become an issue with changes to UIA tree in places such as Notepad and Calculator. The traceback is caused by the fact that, in Calculator compact overlay interface, NVDA cannot locate child elements of the foreground window properly. A solution exists in the form of using UIA's own find descendants method, the method employed in places such as File Explorer to locate elements such as status bar (see PR #14622). The solution also removes the need to check Calculator versions.

Thanks.


Discussion:

Resolving patch:
diff --git a/source/appModules/calculator.py b/source/appModules/calculator.py
--- a/source/appModules/calculator.py
+++ b/source/appModules/calculator.py
@@ -1,5 +1,5 @@
 # A part of NonVisual Desktop Access (NVDA)
-# Copyright (C) 2020-2022 NV Access Limited, Joseph Lee
+# Copyright (C) 2020-2023 NV Access Limited, Joseph Lee
 # This file is covered by the GNU General Public License.
 # See the file COPYING for more details.
 
@@ -13,6 +13,8 @@
 import ui
 import scriptHandler
 import braille
+import UIAHandler
+from comtypes import COMError
 
 # #9428: do not announce current values until calculations are done in order to avoid repetitions.
 noCalculatorEntryAnnouncements = [
@@ -84,20 +86,24 @@ def event_UIA_notification(self, obj, nextHandler, displayString=None, activityI
 		# The event handler copy is used to handle the overall notification announcement later.
 		doNotAnnounceCalculatorResults = self._noCalculatorResultsGesturePressed
 		self._noCalculatorResultsGesturePressed = False
-		calculatorVersion = int(self.productVersion.split(".")[0])
 		# #12268: for "DisplayUpdated", announce display strings in braille  no matter what they are.
 		# There are other activity Id's such as "MemorySlotAdded" and "MemoryCleared"
 		# but they do not involve number entry.
 		# Therefore, only handle the below activity Id.
 		if activityId == "DisplayUpdated":
 			braille.handler.message(displayString)
-			resultElement = api.getForegroundObject().children[1].lastChild
-			# Descend one more time in Windows 11 Calculator.
-			if calculatorVersion >= 11:
-				resultElement = resultElement.firstChild
+			# Locate results via UIA tree traversal.
 			# Redesigned in 2019 due to introduction of "always on top" i.e. compact overlay mode.
-			if resultElement.UIAElement.cachedClassName != "LandmarkTarget":
-				resultElement = resultElement.parent.children[1]
+			clientObject = UIAHandler.handler.clientObject
+			condition = clientObject.createPropertyCondition(UIAHandler.UIA_ClassNamePropertyId, "LandmarkTarget")
+			walker = clientObject.createTreeWalker(condition)
+			uiItemWindow = clientObject.elementFromHandle(obj.windowHandle)
+			try:
+				element = walker.getFirstChildElement(uiItemWindow)
+				element = element.buildUpdatedCache(UIAHandler.handler.baseCacheRequest)
+			except (ValueError, COMError):
+				return
+			resultElement = UIA(UIAElement=element)
 			# Display string announcement is redundant if speak typed characters is on.
 			if (
 				resultElement


Test patch: