Skip to content

Commit 31d2e99

Browse files
authored
Merge 90e393c into 2325be8
2 parents 2325be8 + 90e393c commit 31d2e99

2 files changed

Lines changed: 133 additions & 2 deletions

File tree

source/documentBase.py

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ def _getTableCellAt(self,tableID,startPos,row,column):
8686
"""
8787
raise NotImplementedError
8888

89+
def _getTableDimensions(self,tableID):
90+
"""
91+
Returns height and width of the table.
92+
@param tableID: the ID of the table.
93+
@returns: Height and width of the table.
94+
@rtype: L{Tuple[int, int]}
95+
"""
96+
raise NotImplementedError
97+
8998
_missingTableCellSearchLimit=3 #: The number of missing cells L{_getNearestTableCell} is allowed to skip over to locate the next available cell
9099
def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, origColSpan, movement, axis):
91100
"""
@@ -138,6 +147,94 @@ def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan,
138147
destCol+=1 if movement=="next" else -1
139148
raise LookupError
140149

150+
def _probeTableDimension(self, tableID, startPos, origRow, origCol, axis):
151+
"""
152+
Measures the dimension (width or height) of current table along L{axis} by repeatedly trying
153+
to call L{_getTableCellAt} with ever increasing index along L{axis} to determine the largest possible value
154+
for which L{_getTableCellAt} doesn't raise L{LookupError}.
155+
@param tableID: the ID of the table
156+
@param startPos: the position in the document to start searching from.
157+
@type startPos: L{textInfos.TextInfo}
158+
@param origRow: the row number of the starting cell
159+
@type origRow: int
160+
@param origCol: the column number of the starting cell
161+
@type origCol: int
162+
@param axis: the axis of movement ("row" or "column")
163+
@type axis: string
164+
@returns: the measured dimension of the table along requested axis.
165+
@rtype: L{int}
166+
"""
167+
result = origRow if axis == "row" else origCol
168+
row, col = origRow, origCol
169+
maxTableSize = 1000
170+
numLookupErrors = 0
171+
for i in range(maxTableSize ):
172+
if axis == "row":
173+
row += 1
174+
newResult = row
175+
else:
176+
col += 1
177+
newResult = col
178+
try:
179+
self._getTableCellAt(tableID, startPos, row, col)
180+
numLookupErrors= 0
181+
result = newResult
182+
except LookupError:
183+
numLookupErrors += 1
184+
if numLookupErrors >= self._missingTableCellSearchLimit:
185+
break
186+
return result
187+
188+
189+
def _getFirstOrLastTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, origColSpan, movement, axis):
190+
"""
191+
Locates the first or last cell in current row or column given coordinates of current cell.
192+
When jumping to the first row/column, It will try to set current row/column index to 1.
193+
When jumping to the last row/column, it will try to get table size if implementation of _getTableDimensions is provided,
194+
or otherwise, it will call _probeTableDimension to compute corresponding table dimension via brute force.
195+
After figuring out exact coordinates of the cell it will try to jump directly to that cell,
196+
or if that fails (due to missing table cell), it will walk in the opposite direction skipping missing cells
197+
up to the number of times set by _missingTableCellSearchLimit set on this instance.
198+
@param tableID: the ID of the table
199+
@param startPos: the position in the document to start searching from.
200+
@type startPos: L{textInfos.TextInfo}
201+
@param origRow: the row number of the starting cell
202+
@type origRow: int
203+
@param origCol: the column number of the starting cell
204+
@type origCol: int
205+
@param origRowSpan: the row span of the row of the starting cell
206+
@type origRowSpan: int
207+
@param origColSpan: the column span of the column of the starting cell
208+
@type origColSpan: int
209+
@param movement: the direction ("next" or "previous")
210+
@type movement: string
211+
@param axis: the axis of movement ("row" or "column")
212+
@type axis: string
213+
@returns: the position of the destination table cell
214+
@rtype: L{textInfos.TextInfo}
215+
"""
216+
destRow, destCol= origRow, origCol
217+
if movement == "first":
218+
if axis == "column":
219+
destCol= 1
220+
else:
221+
destRow = 1
222+
else:
223+
try:
224+
nRows, nCols = self._getTableDimensions(tableID)
225+
except (NotImplementedError, LookupError):
226+
nRows, nCols = None, None
227+
probedDimension = self._probeTableDimension(tableID, startPos, origRow, origCol, axis)
228+
if axis == "column":
229+
destCol = nCols or probedDimension
230+
else:
231+
destRow = nRows or probedDimension
232+
try:
233+
return self._getTableCellAt(tableID,startPos,destRow,destCol)
234+
except LookupError:
235+
oppositeMovement = "previous" if movement == "last" else "next"
236+
return self._getNearestTableCell(tableID, startPos, destRow, destCol, origRowSpan=1, origColSpan=1, movement=oppositeMovement , axis=axis)
237+
141238
def _tableMovementScriptHelper(self, movement="next", axis=None):
142239
# documentBase is a core module and should not depend on these UI modules and so they are imported
143240
# at run-time. (#12404)
@@ -158,7 +255,10 @@ def _tableMovementScriptHelper(self, movement="next", axis=None):
158255
return
159256

160257
try:
161-
info = self._getNearestTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis)
258+
if movement in {"previous", "next"}:
259+
info = self._getNearestTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis)
260+
else:
261+
info = self._getFirstOrLastTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis)
162262
except LookupError:
163263
# Translators: The message reported when a user attempts to use a table movement command
164264
# but the cursor can't be moved in that direction because it is at the edge of the table.
@@ -189,7 +289,22 @@ def script_previousColumn(self, gesture):
189289
self._tableMovementScriptHelper(axis="column", movement="previous")
190290
# Translators: the description for the previous table column script on browseMode documents.
191291
script_previousColumn.__doc__ = _("moves to the previous table column")
192-
292+
def script_firstRow(self, gesture):
293+
self._tableMovementScriptHelper(axis="row", movement="first")
294+
# Translators: the description for the first table row script on browseMode documents.
295+
script_firstRow.__doc__ = _("moves to the first table row")
296+
def script_lastRow(self, gesture):
297+
self._tableMovementScriptHelper(axis="row", movement="last")
298+
# Translators: the description for the last table row script on browseMode documents.
299+
script_lastRow.__doc__ = _("moves to the last table row")
300+
def script_firstColumn(self, gesture):
301+
self._tableMovementScriptHelper(axis="column", movement="first")
302+
# Translators: the description for the first table column script on browseMode documents.
303+
script_firstColumn.__doc__ = _("moves to the first table column")
304+
def script_lastColumn(self, gesture):
305+
self._tableMovementScriptHelper(axis="column", movement="last")
306+
# Translators: the description for the last table column script on browseMode documents.
307+
script_lastColumn.__doc__ = _("moves to the last table column")
193308
def script_toggleIncludeLayoutTables(self,gesture):
194309
# documentBase is a core module and should not depend on UI, so it is imported at run-time. (#12404)
195310
import ui
@@ -210,4 +325,8 @@ def script_toggleIncludeLayoutTables(self,gesture):
210325
"kb:control+alt+upArrow": "previousRow",
211326
"kb:control+alt+rightArrow": "nextColumn",
212327
"kb:control+alt+leftArrow": "previousColumn",
328+
"kb:control+alt+pageUp": "firstRow",
329+
"kb:control+alt+pageDown": "lastRow",
330+
"kb:control+alt+Home": "firstColumn",
331+
"kb:control+alt+End": "lastColumn",
213332
}

source/virtualBuffers/gecko_ia2.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,18 @@ def _getTableCellAt(self,tableID,startPos,destRow,destCol):
509509
except (COMError, RuntimeError):
510510
raise LookupError
511511

512+
def _getTableDimensions(self,tableID):
513+
docHandle = self.rootDocHandle
514+
table = self.getNVDAObjectFromIdentifier(docHandle, tableID)
515+
try:
516+
return (
517+
table.IAccessibleTable2Object.nRows,
518+
table.IAccessibleTable2Object.nColumns,
519+
)
520+
except OSError:#(COMError, RuntimeError, AttributeError):
521+
raise LookupError
522+
523+
512524
def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan, origColSpan, movement, axis):
513525
# Skip the VirtualBuffer implementation as the base BrowseMode implementation is good enough for us here.
514526
return super(VirtualBuffer,self)._getNearestTableCell(tableID, startPos, origRow, origCol, origRowSpan, origColSpan, movement, axis)

0 commit comments

Comments
 (0)