Repository: nvaccess/nvda

Issue:
IAccessible NVDAObject: Use IAccessibleTable2/IAccessibleTableCell for row/col indexes/counts
The IAccessible NVDAObject currently uses IAccessibleTable to fetch row/column counts/indexes and IAccessibleTableCell for row/column headers. However, QT5 only supports IAccessibleTable2/Cell, not the older IAccessibleTable. It's reasonable to assume future implementations might do the same, though we still need to support IAccessibleTable for older implementations such as Symphony/OpenOffice/LibreOffice.

This will require additions to the table, tableID, rowCount, columnCount, rowNumber and columnNumber properties to use the new interfaces if they are available.

Note that we have an existing _IATableCell property to get that interface.



Discussion:
@MichaelDCurran, I wish I had stumbled upon this before you started working on #7410. It is probably a bit too far-fetched to have this added into that pr as well?
This is out of scope for that PR, as it primarily deals with changes related to browse mode table navigation.

P3 because there is no specific demand for this yet.

Resolving patch:
diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py
--- a/source/NVDAObjects/IAccessible/__init__.py
+++ b/source/NVDAObjects/IAccessible/__init__.py
@@ -608,9 +608,12 @@ def __init__(self,windowHandle=None,IAccessibleObject=None,IAccessibleChildID=No
 		except COMError:
 			pass
 		try:
-			self.IAccessibleTableObject=self.IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleTable)
+			self.IAccessibleTable2Object=self.IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleTable2)
 		except COMError:
-			pass
+			try:
+				self.IAccessibleTableObject=self.IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleTable)
+			except COMError:
+				pass
 		try:
 			self.IAccessibleTextObject=IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleText)
 		except COMError:
@@ -1049,6 +1052,9 @@ def event_IA2AttributeChange(self):
 		self.event_stateChange()
 
 	def _get_IA2PhysicalRowNumber(self):
+		tableCell=self._IATableCell
+		if tableCell:
+			return tableCell.rowIndex+1
 		table=self.table
 		if table:
 			if self.IAccessibleTableUsesTableCellIndexAttrib:
@@ -1074,6 +1080,9 @@ def _get_rowNumber(self):
 		return index
 
 	def _get_IA2PhysicalColumnNumber(self):
+		tableCell=self._IATableCell
+		if tableCell:
+			return tableCell.columnIndex+1
 		table=self.table
 		if table:
 			if self.IAccessibleTableUsesTableCellIndexAttrib:
@@ -1110,6 +1119,11 @@ def _get_columnNumber(self):
 		return index
 
 	def _get_IA2PhysicalRowCount(self):
+		if hasattr(self,'IAccessibleTable2Object'):
+			try:
+				return self.IAccessibleTable2Object.nRows
+			except COMError:
+				log.debugWarning("IAccessibleTable2::nRows failed", exc_info=True)
 		if hasattr(self,'IAccessibleTableObject'):
 			try:
 				return self.IAccessibleTableObject.nRows
@@ -1124,6 +1138,11 @@ def _get_rowCount(self):
 		return count
 
 	def _get_IA2PhysicalColumnCount(self):
+		if hasattr(self,'IAccessibleTable2Object'):
+			try:
+				return self.IAccessibleTable2Object.nColumns
+			except COMError:
+				log.debugWarning("IAccessibleTable2::nColumns failed", exc_info=True)
 		if hasattr(self,'IAccessibleTableObject'):
 			try:
 				return self.IAccessibleTableObject.nColumns
@@ -1187,10 +1206,10 @@ def _get_table(self):
 		if self.IAccessibleTableUsesTableCellIndexAttrib and "table-cell-index" in self.IA2Attributes:
 			checkAncestors=True
 		obj=self.parent
-		while checkAncestors and obj and not hasattr(obj,'IAccessibleTableObject'):
+		while checkAncestors and obj and not hasattr(obj,'IAccessibleTable2Object') and not hasattr(obj,'IAccessibleTableObject'):
 			parent=obj.parent=obj.parent
 			obj=parent
-		if not obj or not hasattr(obj,'IAccessibleTableObject'):
+		if not obj or (not hasattr(obj,'IAccessibleTable2Object') and not hasattr(obj,'IAccessibleTableObject')):
 			return None
 		self._table=obj
 		return obj
diff --git a/source/appModules/kindle.py b/source/appModules/kindle.py
--- a/source/appModules/kindle.py
+++ b/source/appModules/kindle.py
@@ -3,6 +3,7 @@
 #This file is covered by the GNU General Public License.
 #See the file COPYING for more details.
 
+from comtypes import COMError
 import time
 from comtypes.hresult import S_OK
 import appModuleHandler
@@ -18,6 +19,7 @@
 from browseMode import BrowseModeDocumentTreeInterceptor
 import textInfos
 from textInfos import DocumentWithPageTurns
+from IAccessibleHandler import IAccessible2
 from NVDAObjects.IAccessible import IAccessible
 from globalCommands import SCRCAT_SYSTEMCARET
 from NVDAObjects.IAccessible.ia2TextMozilla import MozillaCompoundTextInfo
@@ -63,6 +65,29 @@ def isAlive(self):
 	def __contains__(self,obj):
 		return obj==self.rootNVDAObject
 
+	def _getTableCellAt(self,tableID,startPos,destRow,destCol):
+		""" Override of documentBase.DocumentWithTableNavigation._getTableCellAt."""
+		# Locate the table in the object ancestry of the given document position. 
+		obj=startPos.NVDAObjectAtStart
+		while not obj.table and obj!=startPos.obj.rootNVDAObject:
+			obj=obj.parent
+		if not obj.table:
+			# No table could be found
+			raise LookupError
+		table = obj.table
+		try:
+			cell = table.IAccessibleTable2Object.cellAt(destRow - 1, destCol - 1).QueryInterface(IAccessible2)
+			cell = IAccessible(IAccessibleObject=cell, IAccessibleChildID=0)
+			# If the cell we fetched is marked as hidden, raise LookupError which will instruct calling code to try an adjacent cell instead.
+			if cell.IA2Attributes.get('hidden'):
+				raise LookupError("Found hidden cell") 
+			# Return the position of the found cell
+			return self.makeTextInfo(cell)
+		except (COMError, RuntimeError):
+			# Any of the above calls could throw a COMError, and sometimes a RuntimeError.
+			# Treet this as the cell not existing.
+			raise LookupError
+
 	def _changePageScriptHelper(self,gesture,previous=False):
 		if isScriptWaiting():
 			return
@@ -148,15 +173,24 @@ def _iterEmbeddedObjs(self, hypertext, startIndex, direction):
 	NODE_TYPES_TO_ROLES = {
 		"link": {controlTypes.ROLE_LINK, controlTypes.ROLE_FOOTNOTE},
 		"graphic": {controlTypes.ROLE_GRAPHIC},
+		"table": {controlTypes.ROLE_TABLE},
 	}
 
 	def _iterNodesByType(self, nodeType, direction="next", pos=None):
-		roles = self.NODE_TYPES_TO_ROLES.get(nodeType)
-		if not roles:
-			raise NotImplementedError
 		if not pos:
 			pos = self.makeTextInfo(textInfos.POSITION_FIRST if direction == "next" else textInfos.POSITION_LAST)
 		obj = pos.innerTextInfo._startObj
+		if nodeType=="container":
+			while obj!=self.rootNVDAObject:
+				if obj.role==controlTypes.ROLE_TABLE:
+					ti=self.makeTextInfo(obj)
+					yield browseMode.TextInfoQuickNavItem(nodeType, self, ti)
+					return
+				obj=obj.parent
+			return
+		roles = self.NODE_TYPES_TO_ROLES.get(nodeType)
+		if not roles:
+			raise NotImplementedError
 		# Find the first embedded object in the requested direction.
 		# Use the text, as enumerating IAccessibleHypertext means more cross-process calls.
 		offset = pos.innerTextInfo._start._startOffset
@@ -187,9 +221,13 @@ def _iterNodesByType(self, nodeType, direction="next", pos=None):
 				break # Can't go any further.
 			log.debug("Continuing in parent")
 			# Get the index of the embedded object we just came from.
+			parent = obj.parent
+			if not getattr(parent,'IAccessibleTextObject',None):
+				obj=parent
+				continue
 			hl = obj.IAccessibleObject.QueryInterface(IAccessibleHandler.IAccessibleHyperlink)
 			offset = hl.startIndex
-			obj = obj.parent
+			obj=parent
 			hli = obj.iaHypertext.hyperlinkIndex(offset)
 			# Continue the walk from the next embedded object.
 			hli += 1 if direction == "next" else -1
diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py
--- a/source/virtualBuffers/gecko_ia2.py
+++ b/source/virtualBuffers/gecko_ia2.py
@@ -305,7 +305,10 @@ def _getTableCellAt(self,tableID,startPos,destRow,destCol):
 		docHandle = self.rootDocHandle
 		table = self.getNVDAObjectFromIdentifier(docHandle, tableID)
 		try:
-			cell = table.IAccessibleTableObject.accessibleAt(destRow - 1, destCol - 1).QueryInterface(IAccessible2)
+			try:
+				cell = table.IAccessibleTable2Object.cellAt(destRow - 1, destCol - 1).QueryInterface(IAccessible2)
+			except AttributeError:
+				cell = table.IAccessibleTableObject.accessibleAt(destRow - 1, destCol - 1).QueryInterface(IAccessible2)
 			cell = NVDAObjects.IAccessible.IAccessible(IAccessibleObject=cell, IAccessibleChildID=0)
 			if cell.IA2Attributes.get('hidden'):
 				raise LookupError("Found hidden cell") 


Test patch: