Repository: nvaccess/nvda

Issue:
Selecting all cells "freezes" LibreOffice Calc (when using LO >= 7.3, NVDA >= 2022.1)
Commit 32bf54e1f26fec3abf60d81dc79a01e7a1817931 ("selection and merged cell announcements in LibreOffice Calc 7.3 and above (#12849)") improved announcement of selected cells for LibreOffice Calc (from version 7.3) on.

As a side-effect, NVDA currently causes a Calc freeze when all cells are selected.

### Steps to reproduce:

1) start NVDA from current git master
2) start current development version of LibreOffice Calc (version 7.3 or later)
3) press Ctrl+A to select all cells

### Actual behavior:

LibreOffice Calc appears to be frozen and doesn't react any more.

### Expected behavior:

Calc should remain responsive and some NVDA should make some announcement indicating that all cells have been selected.

### System configuration
#### NVDA installed/portable/running from source:

running from source

#### NVDA version:

source-master-20d5a25

#### Windows version:

Windows 10 21H2 (OS Build 19044.1466)

#### Name and version of other software in use when reproducing the issue:

LibreOffice Calc, current git master as of commit 093c98dcb4be1c124871468f73be4b59ec99af1f

#### Other information about your system:

n/a

### 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.

n/a

#### If NVDA add-ons are disabled, is your problem still occurring?

n/a

#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
n/a


Discussion:
The reason for the freeze is that NVDA calls the `selectedCells` method on the `IAccessibleTable2` interface of the Calc table, which means it requests to receive references to all currently selected cells. Since more than a billion cells are selected, it takes a long time for LibreOffice to generate those...

I plan to submit a PR with a potential approach to address this.

Resolving patch:
diff --git a/source/appModules/soffice.py b/source/appModules/soffice.py
--- a/source/appModules/soffice.py
+++ b/source/appModules/soffice.py
@@ -3,7 +3,13 @@
 # See the file COPYING for more details.
 # Copyright (C) 2006-2022 NV Access Limited, Bill Dengler, Leonard de Ruijter
 
+from typing import (
+	Union
+)
+
 from comtypes import COMError
+import comtypes.client
+import oleacc
 from IAccessibleHandler import IA2, splitIA2Attribs
 import appModuleHandler
 import controlTypes
@@ -209,11 +215,28 @@ def announceSelectionChange(self):
 
 	def _get_cellCoordsText(self):
 		if self.hasSelection and controlTypes.State.FOCUSED in self.states:
-			selected, count = self.table.IAccessibleTable2Object.selectedCells
-			firstAccessible = selected[0].QueryInterface(IA2.IAccessible2)
+			count = self.table.IAccessibleTable2Object.nSelectedCells
+			selection = self.table.IAccessibleObject.accSelection
+			enumObj = selection.QueryInterface(oleacc.IEnumVARIANT)
+			firstChild: Union[int, comtypes.client.dynamic._Dispatch]
+			firstChild, _retrievedCount = enumObj.Next(1)
+			# skip over all except the last element
+			enumObj.Skip(count - 2)
+			lastChild: Union[int, comtypes.client.dynamic._Dispatch]
+			lastChild, _retrieveCount = enumObj.Next(1)
+			# in LibreOffice 7.3.0, the IEnumVARIANT returns a child ID,
+			# in LibreOffice >= 7.4, it returns an IDispatch
+			if isinstance(firstChild, int):
+				tableAccessible = self.table.IAccessibleTable2Object.QueryInterface(IA2.IAccessible2)
+				firstAccessible = tableAccessible.accChild(firstChild).QueryInterface(IA2.IAccessible2)
+				lastAccessible = tableAccessible.accChild(lastChild).QueryInterface(IA2.IAccessible2)
+			elif isinstance(firstChild, comtypes.client.dynamic._Dispatch):
+				firstAccessible = firstChild.QueryInterface(IA2.IAccessible2)
+				lastAccessible = lastChild.QueryInterface(IA2.IAccessible2)
+			else:
+				raise RuntimeError(f"Unexpected LibreOffice object {firstChild}, type: {type(firstChild)}")
 			firstAddress = firstAccessible.accName(0)
 			firstValue = firstAccessible.accValue(0) or ''
-			lastAccessible = selected[count - 1].QueryInterface(IA2.IAccessible2)
 			lastAddress = lastAccessible.accName(0)
 			lastValue = lastAccessible.accValue(0) or ''
 			# Translators: LibreOffice, report selected range of cell coordinates with their values


Test patch: