From 4cc163c001613a8075916a0db0282424f554de69 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Sat, 15 Jul 2017 12:02:16 +1000 Subject: [PATCH 1/4] Add support for ARIA rowindex, colindex, rowcount and colcount in Gecko / Chrome for both browse mode and focus mode. Specifically: * IAccessible NVDAObject properties: rowNumber, columnNumber, rowCount and columnCount now expose the ARIA values if available, otherwise falling back to the original physical table information. * Gecko controlField attributes: table-rownumber, table-columnnumber, table-rowcount and table-columncount now expose the ARIA values if available, otherwising falling back to the original physical table information * New Gecko controlField attributes: table-physicalrownumber, table-physicalcolumnnumber, table-physicalrowcount and table-physicalcolumncount values always expose the physical table information no matter what the presentational values may be. * Added new class variables to BrowseModeDocumentTreeInterceptor: navigationalTableRowNumberAttributeName and navigationalTableColumnNumberAttributeName which are used by BrowseModeDocumentTreeInterceptor._getTableCellCoords in place of the literal strings "table-rownumber" and "table-columnnumber" respectively, to allow fetching of the correct attributes containing physical table information. * Gecko_ia2 VirtualBuffer: override navigationalTableRowNumberAttributeName and navigationalTableColumnNumberAttributeName To provide the use of table-physicalrownumber and table-physicalcolumnnumber. --- .../vbufBackends/gecko_ia2/gecko_ia2.cpp | 32 +++++++++++++++-- nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.h | 2 +- source/NVDAObjects/IAccessible/__init__.py | 36 ++++++++++++++++--- source/browseMode.py | 9 +++-- source/virtualBuffers/gecko_ia2.py | 11 ++++++ 5 files changed, 80 insertions(+), 10 deletions(-) diff --git a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp index 2a07a4e4783..5b95da3f5c0 100755 --- a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp +++ b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp @@ -80,11 +80,13 @@ template inline void fillTableCounts(VBufStorage_controlFiel long count = 0; if (paccTable->get_nRows(&count) == S_OK) { s << count; + node->addAttribute(L"table-physicalrowcount", s.str()); node->addAttribute(L"table-rowcount", s.str()); s.str(L""); } if (paccTable->get_nColumns(&count) == S_OK) { s << count; + node->addAttribute(L"table-physicalcolumncount", s.str()); node->addAttribute(L"table-columncount", s.str()); } } @@ -130,9 +132,11 @@ inline void fillTableCellInfo_IATable(VBufStorage_controlFieldNode_t* node, IAcc boolean isSelected; if (paccTable->get_rowColumnExtentsAtIndex(cellIndex, &row, &column, &rowExtents, &columnExtents, &isSelected) == S_OK) { s << row + 1; + node->addAttribute(L"table-physicalrownumber", s.str()); node->addAttribute(L"table-rownumber", s.str()); s.str(L""); s << column + 1; + node->addAttribute(L"table-physicalcolumnnumber", s.str()); node->addAttribute(L"table-columnnumber", s.str()); if (columnExtents > 1) { s.str(L""); @@ -188,9 +192,11 @@ inline void GeckoVBufBackend_t::fillTableCellInfo_IATable2(VBufStorage_controlFi boolean isSelected; if (paccTableCell->get_rowColumnExtents(&row, &column, &rowExtents, &columnExtents, &isSelected) == S_OK) { s << row + 1; + node->addAttribute(L"table-physicalrownumber", s.str()); node->addAttribute(L"table-rownumber", s.str()); s.str(L""); s << column + 1; + node->addAttribute(L"table-physicalcolumnnumber", s.str()); node->addAttribute(L"table-columnnumber", s.str()); if (columnExtents > 1) { s.str(L""); @@ -305,7 +311,7 @@ const wregex REGEX_PRESENTATION_ROLE(L"IAccessible2\\\\:\\\\:attribute_xml-roles VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, VBufStorage_buffer_t* buffer, VBufStorage_controlFieldNode_t* parentNode, VBufStorage_fieldNode_t* previousNode, - IAccessibleTable* paccTable, IAccessibleTable2* paccTable2, long tableID, + IAccessibleTable* paccTable, IAccessibleTable2* paccTable2, long tableID, const wchar_t* parentPresentationalRowNumber, bool ignoreInteractiveUnlabelledGraphics ) { nhAssert(buffer); //buffer can't be NULL @@ -342,6 +348,9 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, return NULL; } + // Save off the old parent for later propagation of some attributes + VBufStorage_fieldNode_t* origParentNode=parentNode; + //Add this node to the buffer parentNode=buffer->addControlFieldNode(parentNode,previousNode,docHandle,ID,TRUE); nhAssert(parentNode); //new node must have been created @@ -655,6 +664,23 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, } } + // Add some presentational table attributes + // Note these are only for reporting, the physical table attributes (table-physicalrownumber etc) for aiding in navigation etc are added later on. + // propagate table-rownumber down to the cell as Gecko only includes it on the row itself + if(parentPresentationalRowNumber) + parentNode->addAttribute(L"table-rownumber",parentPresentationalRowNumber); + const wchar_t* presentationalRowNumber=NULL; + if((IA2AttribsMapIt = IA2AttribsMap.find(L"rowindex")) != IA2AttribsMap.end()) { + parentNode->addAttribute(L"table-rownumber",IA2AttribsMapIt->second); + presentationalRowNumber=IA2AttribsMapIt->second.c_str(); + } + if((IA2AttribsMapIt = IA2AttribsMap.find(L"colindex")) != IA2AttribsMap.end()) + parentNode->addAttribute(L"table-columnnumber",IA2AttribsMapIt->second); + if((IA2AttribsMapIt = IA2AttribsMap.find(L"rowcount")) != IA2AttribsMap.end()) + parentNode->addAttribute(L"table-rowcount",IA2AttribsMapIt->second); + if((IA2AttribsMapIt = IA2AttribsMap.find(L"colcount")) != IA2AttribsMap.end()) + parentNode->addAttribute(L"table-columncount",IA2AttribsMapIt->second); + BSTR value=NULL; if(pacc->get_accValue(varChild,&value)==S_OK) { if(value&&SysStringLen(value)==0) { @@ -736,7 +762,7 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, continue; } paccHyperlink->Release(); - if (tempNode = this->fillVBuf(childPacc, buffer, parentNode, previousNode, paccTable, paccTable2, tableID, ignoreInteractiveUnlabelledGraphics)) { + if (tempNode = this->fillVBuf(childPacc, buffer, parentNode, previousNode, paccTable, paccTable2, tableID, presentationalRowNumber, ignoreInteractiveUnlabelledGraphics)) { previousNode=tempNode; } else { LOG_DEBUG(L"Error in fillVBuf"); @@ -768,7 +794,7 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, VariantClear(&(varChildren[i])); continue; } - if (tempNode = this->fillVBuf(childPacc, buffer, parentNode, previousNode, paccTable, paccTable2, tableID, ignoreInteractiveUnlabelledGraphics)) + if (tempNode = this->fillVBuf(childPacc, buffer, parentNode, previousNode, paccTable, paccTable2, tableID, presentationalRowNumber, ignoreInteractiveUnlabelledGraphics)) previousNode=tempNode; else LOG_DEBUG(L"Error in calling fillVBuf"); diff --git a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.h b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.h index b39de514f1e..f488737ad95 100755 --- a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.h +++ b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.h @@ -22,7 +22,7 @@ class GeckoVBufBackend_t: public VBufBackend_t { VBufStorage_fieldNode_t* fillVBuf(IAccessible2* pacc, VBufStorage_buffer_t* buffer, VBufStorage_controlFieldNode_t* parentNode, VBufStorage_fieldNode_t* previousNode, - IAccessibleTable* paccTable=NULL, IAccessibleTable2* paccTable2=NULL, long tableID=0, + IAccessibleTable* paccTable=NULL, IAccessibleTable2* paccTable2=NULL, long tableID=0, const wchar_t* parentPresentationalRowNumber=NULL, bool ignoreInteractiveUnlabelledGraphics=false ); diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 0f715155356..21e26e37e52 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -1022,6 +1022,8 @@ def getChild(self, index): return self.correctAPIForRelation(IAccessible(IAccessibleObject=child[0], IAccessibleChildID=child[1])) def _get_IA2Attributes(self): + if not isinstance(self.IAccessibleObject,IAccessibleHandler.IAccessible2): + return {} try: attribs = self.IAccessibleObject.attributes except COMError as e: @@ -1035,7 +1037,7 @@ def event_IA2AttributeChange(self): # We currently only care about changes to the accessible drag and drop attributes, which we map to states, so treat this as a stateChange. self.event_stateChange() - def _get_rowNumber(self): + def _get_IA2PhysicalRowNumber(self): table=self.table if table: if self.IAccessibleTableUsesTableCellIndexAttrib: @@ -1052,7 +1054,15 @@ def _get_rowNumber(self): log.debugWarning("IAccessibleTable::rowIndex failed", exc_info=True) raise NotImplementedError - def _get_columnNumber(self): + def _get_rowNumber(self): + index=self.IA2Attributes.get('rowindex') + if index is None and isinstance(self.parent,IAccessible): + index=self.parent.IA2Attributes.get('rowindex') + if index is None: + index=self.IA2PhysicalRowNumber + return index + + def _get_IA2PhysicalColumnNumber(self): table=self.table if table: if self.IAccessibleTableUsesTableCellIndexAttrib: @@ -1069,7 +1079,13 @@ def _get_columnNumber(self): log.debugWarning("IAccessibleTable::columnIndex failed", exc_info=True) raise NotImplementedError - def _get_rowCount(self): + def _get_columnNumber(self): + index=self.IA2Attributes.get('colindex') + if index is None: + index=self.IA2PhysicalColumnNumber + return index + + def _get_IA2PhysicalRowCount(self): if hasattr(self,'IAccessibleTableObject'): try: return self.IAccessibleTableObject.nRows @@ -1077,7 +1093,13 @@ def _get_rowCount(self): log.debugWarning("IAccessibleTable::nRows failed", exc_info=True) raise NotImplementedError - def _get_columnCount(self): + def _get_rowCount(self): + count=self.IA2Attributes.get('rowcount') + if count is None: + count=self.IA2PhysicalRowCount + return count + + def _get_IA2PhysicalColumnCount(self): if hasattr(self,'IAccessibleTableObject'): try: return self.IAccessibleTableObject.nColumns @@ -1085,6 +1107,12 @@ def _get_columnCount(self): log.debugWarning("IAccessibleTable::nColumns failed", exc_info=True) raise NotImplementedError + def _get_columnCount(self): + count=self.IA2Attributes.get('colcount') + if count is None: + count=self.IA2PhysicalColumnCount + return count + def _get__IATableCell(self): # Permanently cache the result. try: diff --git a/source/browseMode.py b/source/browseMode.py index 0a46851a8dc..85e490f5fa5 100644 --- a/source/browseMode.py +++ b/source/browseMode.py @@ -1539,6 +1539,11 @@ def script_movePastEndOfContainer(self,gesture): # Translators: Description for the Move past end of container command in browse mode. script_movePastEndOfContainer.__doc__=_("Moves past the end of the container element, such as a list or table") + #: The controlField attribute name that should be used as the row number when navigating in a table. By default this is the same as the presentational attribute name + navigationalTableRowNumberAttributeName="table-rownumber" + #: The controlField attribute name that should be used as the column number when navigating in a table. By default this is the same as the presentational attribute name + navigationalTableColumnNumberAttributeName="table-columnnumber" + def _getTableCellCoords(self, info): """ Fetches information about the deepest table cell at the given position. @@ -1556,12 +1561,12 @@ def _getTableCellCoords(self, info): # Not a control field. continue attrs = field.field - if "table-id" in attrs and "table-rownumber" in attrs: + if "table-id" in attrs and self.navigationalTableRowNumberAttributeName in attrs: break else: raise LookupError("Not in a table cell") return (attrs["table-id"], - attrs["table-rownumber"], attrs["table-columnnumber"], + attrs[self.navigationalTableRowNumberAttributeName], attrs[self.navigationalTableColumnNumberAttributeName], attrs.get("table-rowsspanned", 1), attrs.get("table-columnsspanned", 1)) def _getTableCellAt(self,tableID,startPos,row,column): diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py index 0cb5f837d0b..d84b0f8ddfe 100755 --- a/source/virtualBuffers/gecko_ia2.py +++ b/source/virtualBuffers/gecko_ia2.py @@ -23,6 +23,11 @@ class Gecko_ia2_TextInfo(VirtualBufferTextInfo): def _normalizeControlField(self,attrs): + for attr in ("table-physicalrownumber","table-physicalcolumnnumber","table-physicalrowcount","table-physicalcolumncount"): + attrVal=attrs.get(attr) + if attrVal is not None: + attrs[attr]=int(attrVal) + current = attrs.get("IAccessible2::attribute_current") if current is not None: attrs['current']= current @@ -260,6 +265,12 @@ def event_scrollingStart(self, obj, nextHandler): return nextHandler() event_scrollingStart.ignoreIsReady = True + # NVDA exposes IAccessible2 table interface row and column numbers as table-physicalrownumber and table-physicalcolumnnumber respectively. + # These should be used when navigating the physical table (I.e. these values should be provided to the table interfaces). + # The presentational table-columnnumber and table-rownumber attributes are normally duplicates of the physical ones, but are overridden by the values of aria-rowindex and aria-colindex if present. + navigationalTableRowNumberAttributeName="table-physicalrownumber" + navigationalTableColumnNumberAttributeName="table-physicalcolumnnumber" + def _getTableCellAt(self,tableID,startPos,destRow,destCol): docHandle = self.rootDocHandle table = self.getNVDAObjectFromIdentifier(docHandle, tableID) From 1d4519c69f82922bae21b5551b3ec151e1211a38 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Sun, 16 Jul 2017 10:36:29 +1000 Subject: [PATCH 2/4] No longer include layout tables in table navigation commands --- source/browseMode.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/browseMode.py b/source/browseMode.py index 85e490f5fa5..6cbb24c5aad 100644 --- a/source/browseMode.py +++ b/source/browseMode.py @@ -1556,12 +1556,23 @@ def _getTableCellCoords(self, info): if info.isCollapsed: info = info.copy() info.expand(textInfos.UNIT_CHARACTER) - for field in reversed(info.getTextWithFields()): + fields=list(info.getTextWithFields()) + # First record the ID of all layout tables so that we can skip them when searching for the deepest table + layoutIDs=set() + for field in fields: + if isinstance(field, textInfos.FieldCommand) and field.command == "controlStart" and field.field.get('table-layout'): + tableID=field.field.get('table-id') + if tableID is not None: + layoutIDs.add(tableID) + for field in reversed(fields): if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"): # Not a control field. continue attrs = field.field - if "table-id" in attrs and self.navigationalTableRowNumberAttributeName in attrs: + tableID=attrs.get('table-id') + if tableID is None or tableID in layoutIDs: + continue + if self.navigationalTableColumnNumberAttributeName in attrs and not attrs.get('table-layout'): break else: raise LookupError("Not in a table cell") @@ -1628,8 +1639,6 @@ def _tableMovementScriptHelper(self, movement="next", axis=None): return formatConfig=config.conf["documentFormatting"].copy() formatConfig["reportTables"]=True - # For now, table movement includes layout tables even if reporting of layout tables is disabled. - formatConfig["includeLayoutTables"]=True try: tableID, origRow, origCol, origRowSpan, origColSpan = self._getTableCellCoords(self.selection) except LookupError: From e43a671002826a5e0163584791c0a4bec84147c4 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Mon, 17 Jul 2017 15:24:37 +1000 Subject: [PATCH 3/4] Gecko browseMode: skip over hidden table cells in table navigation commands. MSHTML already did this. --- source/browseMode.py | 26 +++++++++++++++++++++----- source/virtualBuffers/gecko_ia2.py | 3 +++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/source/browseMode.py b/source/browseMode.py index 6cbb24c5aad..3c2ce52da2a 100644 --- a/source/browseMode.py +++ b/source/browseMode.py @@ -1592,6 +1592,7 @@ def _getTableCellAt(self,tableID,startPos,row,column): @type column: int @returns: the table cell's position in the document @rtype: L{textInfos.TextInfo} + @raises: L{HiddenCellFound} if the cell it founds is hidden, allowing the caller to try a different cell. """ raise NotImplementedError @@ -1628,11 +1629,22 @@ def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, elif axis == "column": destCol += origColSpan if movement == "next" else -1 - if destCol < 1 or destRow<1: - # Optimisation: We're definitely at the edge of the column or row. - raise LookupError - - return self._getTableCellAt(tableID,startPos,destRow,destCol) + # Try and fetch the cell at these coordinates, though if a hidden cell is hit, try up to 4 more times moving the coordinates on by one cell each time + limit=5 + while limit>0: + limit-=1 + if destCol < 1 or destRow<1: + # Optimisation: We're definitely at the edge of the column or row. + raise LookupError + try: + return self._getTableCellAt(tableID,startPos,destRow,destCol) + except HiddenCellFound: + pass + if axis=="row": + destRow+=1 if movement=="next" else -1 + else: + destCol+=1 if movement=="next" else -1 + raise LookupError def _tableMovementScriptHelper(self, movement="next", axis=None): if isScriptWaiting(): @@ -1716,3 +1728,7 @@ def _iterNotLinkBlock(self, direction="next", pos=None): "kb:control+alt+rightArrow": "nextColumn", "kb:control+alt+leftArrow": "previousColumn", } + +class HiddenCellFound(LookupError): + """ Raised when a table navigation method locates a cell but it is hidden, allowing the caller to possibly skip over it.""" + pass diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py index d84b0f8ddfe..36778163631 100755 --- a/source/virtualBuffers/gecko_ia2.py +++ b/source/virtualBuffers/gecko_ia2.py @@ -19,6 +19,7 @@ import aria import config from NVDAObjects.IAccessible import normalizeIA2TextFormatField +import browseMode class Gecko_ia2_TextInfo(VirtualBufferTextInfo): @@ -277,6 +278,8 @@ def _getTableCellAt(self,tableID,startPos,destRow,destCol): try: cell = table.IAccessibleTableObject.accessibleAt(destRow - 1, destCol - 1).QueryInterface(IAccessible2) cell = NVDAObjects.IAccessible.IAccessible(IAccessibleObject=cell, IAccessibleChildID=0) + if cell.IA2Attributes.get('hidden'): + raise browseMode.HiddenCellFound return self.makeTextInfo(cell) except (COMError, RuntimeError): raise LookupError From a2b10a083e8a9072d9627ce96977418c8e2a51c9 Mon Sep 17 00:00:00 2001 From: Michael Curran Date: Wed, 19 Jul 2017 15:48:26 +1000 Subject: [PATCH 4/4] Address review actions: * In the gecko vbufBackend: comment why two sets of table attributes are being added. * browseModeDocumentTreeInterceptor._getNearestTableCell: make the retry limit configurable via a _missingTableCellSearchLimit property. * browseModeDocumentTreeInterceptor._getNearestTableCell: try more cells for any LookupError not just hiddenTableCellFound (browsers such as chrome do not expose hidden cells). * remove browseMode.HiddenTableCellFound * Gecko_ia2 virtualBuffer's _getTableCellAt: raise LookupError if a hidden cell is found rather than HiddenTableCellFound --- nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp | 14 +++++++++++--- source/browseMode.py | 14 ++++++-------- source/virtualBuffers/gecko_ia2.py | 3 +-- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp index 5b95da3f5c0..b370609a1cd 100755 --- a/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp +++ b/nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp @@ -78,6 +78,9 @@ IAccessible2* IAccessible2FromIdentifier(int docHandle, int ID) { template inline void fillTableCounts(VBufStorage_controlFieldNode_t* node, IAccessible2* pacc, TableType* paccTable) { wostringstream s; long count = 0; + // Fetch row and column counts and add them as two sets of attributes on this vbuf node. + // The first set: table-physicalrowcount and table-physicalcolumncount represent the physical topology of the table and can be used programmatically to understand table limits. + // The second set: table-rowcount and table-columncount are duplicates of the physical ones, however may be overridden later on in fillVBuf with ARIA attributes. They are what is reported to the user. if (paccTable->get_nRows(&count) == S_OK) { s << count; node->addAttribute(L"table-physicalrowcount", s.str()); @@ -130,6 +133,10 @@ inline void fillTableCellInfo_IATable(VBufStorage_controlFieldNode_t* node, IAcc long cellIndex = _wtoi(cellIndexStr.c_str()); long row, column, rowExtents, columnExtents; boolean isSelected; + // Fetch row and column extents and add them as attributes on this node. + // for rowNumber and columnNumber, store these as two sets of attributes. + // The first set: table-physicalrownumber and table-physicalcolumnnumber represent the physical topology of the table and can be used programmatically to fetch other table cells with IAccessibleTable etc. + // The second set: table-rownumber and table-columnnumber are duplicates of the physical ones, however may be overridden later on in fillVBuf with ARIA attributes. They are what is reported to the user. if (paccTable->get_rowColumnExtentsAtIndex(cellIndex, &row, &column, &rowExtents, &columnExtents, &isSelected) == S_OK) { s << row + 1; node->addAttribute(L"table-physicalrownumber", s.str()); @@ -190,6 +197,10 @@ inline void GeckoVBufBackend_t::fillTableCellInfo_IATable2(VBufStorage_controlFi long row, column, rowExtents, columnExtents; boolean isSelected; + // Fetch row and column extents and add them as attributes on this node. + // for rowNumber and columnNumber, store these as two sets of attributes. + // The first set: table-physicalrownumber and table-physicalcolumnnumber represent the physical topology of the table and can be used programmatically to fetch other table cells with IAccessibleTable etc. + // The second set: table-rownumber and table-columnnumber are duplicates of the physical ones, however may be overridden later on in fillVBuf with ARIA attributes. They are what is reported to the user. if (paccTableCell->get_rowColumnExtents(&row, &column, &rowExtents, &columnExtents, &isSelected) == S_OK) { s << row + 1; node->addAttribute(L"table-physicalrownumber", s.str()); @@ -348,9 +359,6 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(IAccessible2* pacc, return NULL; } - // Save off the old parent for later propagation of some attributes - VBufStorage_fieldNode_t* origParentNode=parentNode; - //Add this node to the buffer parentNode=buffer->addControlFieldNode(parentNode,previousNode,docHandle,ID,TRUE); nhAssert(parentNode); //new node must have been created diff --git a/source/browseMode.py b/source/browseMode.py index 3c2ce52da2a..7df9e78cf2a 100644 --- a/source/browseMode.py +++ b/source/browseMode.py @@ -1592,14 +1592,16 @@ def _getTableCellAt(self,tableID,startPos,row,column): @type column: int @returns: the table cell's position in the document @rtype: L{textInfos.TextInfo} - @raises: L{HiddenCellFound} if the cell it founds is hidden, allowing the caller to try a different cell. + @raises: LookupError if the cell does not exist """ raise NotImplementedError + _missingTableCellSearchLimit=3 #: The number of missing cells L{_getNearestTableCell} is allowed to skip over to locate the next available cell def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, origColSpan, movement, axis): """ Locates the nearest table cell relative to another table cell in a given direction, given its coordinates. For example, this is used to move to the cell in the next column, previous row, etc. + This method will skip over missing table cells (where L{_getTableCellAt} raises LookupError), up to the number of times set by _missingTableCellSearchLimit set on this instance. @param tableID: the ID of the table @param startPos: the position in the document to start searching from. @type startPos: L{textInfos.TextInfo} @@ -1629,8 +1631,8 @@ def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, elif axis == "column": destCol += origColSpan if movement == "next" else -1 - # Try and fetch the cell at these coordinates, though if a hidden cell is hit, try up to 4 more times moving the coordinates on by one cell each time - limit=5 + # Try and fetch the cell at these coordinates, though if a cell is missing, try several more times moving the coordinates on by one cell each time + limit=self._missingTableCellSearchLimit while limit>0: limit-=1 if destCol < 1 or destRow<1: @@ -1638,7 +1640,7 @@ def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, raise LookupError try: return self._getTableCellAt(tableID,startPos,destRow,destCol) - except HiddenCellFound: + except LookupError: pass if axis=="row": destRow+=1 if movement=="next" else -1 @@ -1728,7 +1730,3 @@ def _iterNotLinkBlock(self, direction="next", pos=None): "kb:control+alt+rightArrow": "nextColumn", "kb:control+alt+leftArrow": "previousColumn", } - -class HiddenCellFound(LookupError): - """ Raised when a table navigation method locates a cell but it is hidden, allowing the caller to possibly skip over it.""" - pass diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py index 36778163631..c76e0f2e8b8 100755 --- a/source/virtualBuffers/gecko_ia2.py +++ b/source/virtualBuffers/gecko_ia2.py @@ -19,7 +19,6 @@ import aria import config from NVDAObjects.IAccessible import normalizeIA2TextFormatField -import browseMode class Gecko_ia2_TextInfo(VirtualBufferTextInfo): @@ -279,7 +278,7 @@ def _getTableCellAt(self,tableID,startPos,destRow,destCol): cell = table.IAccessibleTableObject.accessibleAt(destRow - 1, destCol - 1).QueryInterface(IAccessible2) cell = NVDAObjects.IAccessible.IAccessible(IAccessibleObject=cell, IAccessibleChildID=0) if cell.IA2Attributes.get('hidden'): - raise browseMode.HiddenCellFound + raise LookupError("Found hidden cell") return self.makeTextInfo(cell) except (COMError, RuntimeError): raise LookupError