Skip to content

Commit deaa2da

Browse files
authored
Merge 8ea644f into 4df1ded
2 parents 4df1ded + 8ea644f commit deaa2da

2 files changed

Lines changed: 166 additions & 10 deletions

File tree

source/documentBase.py

Lines changed: 162 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import config
88
import textInfos
99
import controlTypes
10-
10+
from typing import Tuple
1111

1212
class TextContainerObject(AutoPropertyObject):
1313
"""
@@ -33,6 +33,30 @@ class DocumentWithTableNavigation(TextContainerObject,ScriptableObject):
3333
The document could be an NVDAObject, or a BrowseModeDocument treeIntercepter for example.
3434
"""
3535

36+
def _maybeGetLayoutTableIds(self, info: textInfos.TextInfo):
37+
"""
38+
If "Include layout tables" option is on, this will
39+
compute the set of layout tables that this textInfo is enclosed in,
40+
otherwise it will return empty set.
41+
@param info: the position where the layout tables should be looked for.
42+
@returns: A set of table IDs or empty set.
43+
"""
44+
fields = list(info.getTextWithFields())
45+
# If layout tables should not be reported, we should First record the ID of all layout tables,
46+
# so that we can skip them when searching for the deepest table
47+
layoutIDs = set()
48+
if not config.conf["documentFormatting"]["includeLayoutTables"]:
49+
for field in fields:
50+
if (
51+
isinstance(field, textInfos.FieldCommand)
52+
and field.command == "controlStart"
53+
and field.field.get('table-layout')
54+
):
55+
tableID = field.field.get('table-id')
56+
if tableID is not None:
57+
layoutIDs.add(tableID)
58+
return layoutIDs
59+
3660
def _getTableCellCoords(self, info):
3761
"""
3862
Fetches information about the deepest table cell at the given position.
@@ -46,14 +70,7 @@ def _getTableCellCoords(self, info):
4670
info = info.copy()
4771
info.expand(textInfos.UNIT_CHARACTER)
4872
fields=list(info.getTextWithFields())
49-
# If layout tables should not be reported, we should First record the ID of all layout tables so that we can skip them when searching for the deepest table
50-
layoutIDs=set()
51-
if not config.conf["documentFormatting"]["includeLayoutTables"]:
52-
for field in fields:
53-
if isinstance(field, textInfos.FieldCommand) and field.command == "controlStart" and field.field.get('table-layout'):
54-
tableID=field.field.get('table-id')
55-
if tableID is not None:
56-
layoutIDs.add(tableID)
73+
layoutIDs = self._maybeGetLayoutTableIds(info)
5774
for field in reversed(fields):
5875
if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"):
5976
# Not a control field.
@@ -70,6 +87,40 @@ def _getTableCellCoords(self, info):
7087
attrs["table-rownumber"], attrs["table-columnnumber"],
7188
attrs.get("table-rowsspanned", 1), attrs.get("table-columnsspanned", 1))
7289

90+
def _getTableDimensions(self, info: textInfos.TextInfo) -> Tuple[int, int]:
91+
"""
92+
Fetches information about the deepest table dimension.
93+
@param info: the position where the table cell should be looked for.
94+
@returns: a tuple of table height and width.
95+
@raises: LookupError if there is no table cell at this position.
96+
"""
97+
if info.isCollapsed:
98+
info = info.copy()
99+
info.expand(textInfos.UNIT_CHARACTER)
100+
fields = list(info.getTextWithFields())
101+
layoutIDs = self._maybeGetLayoutTableIds(info)
102+
for field in reversed(fields):
103+
if not (
104+
isinstance(field, textInfos.FieldCommand)
105+
and field.command == "controlStart"
106+
and field.field.get("role") == controlTypes.Role.TABLE
107+
):
108+
# Not a table control field.
109+
continue
110+
attrs = field.field
111+
tableID = attrs.get('table-id')
112+
if tableID is None or tableID in layoutIDs:
113+
continue
114+
break
115+
else:
116+
raise LookupError("Not in a table cell")
117+
try:
118+
nRows = int(attrs.get("table-rowcount"))
119+
nCols = int(attrs.get("table-columncount"))
120+
except (TypeError, ValueError):
121+
raise LookupError("Not in a table cell")
122+
return (nRows, nCols)
123+
73124
def _getTableCellAt(self,tableID,startPos,row,column):
74125
"""
75126
Starting from the given start position, Locates the table cell with the given row and column coordinates and table ID.
@@ -138,6 +189,62 @@ def _getNearestTableCell(self, tableID, startPos, origRow, origCol, origRowSpan,
138189
destCol+=1 if movement=="next" else -1
139190
raise LookupError
140191

192+
def _getFirstOrLastTableCell(
193+
self,
194+
tableID,
195+
startPos: textInfos.TextInfo,
196+
origRow: int,
197+
origCol: int,
198+
origRowSpan: int,
199+
origColSpan: int,
200+
movement: str,
201+
axis: str
202+
) -> textInfos.TextInfo:
203+
"""
204+
Locates the first or last cell in current row or column given coordinates of current cell.
205+
When jumping to the first row/column, It will try to set current row/column index to 1.
206+
When jumping to the last row/column, it will query table dimensions and set row/column index
207+
to corresponding dimension.
208+
After figuring out exact coordinates of the cell it will try to jump directly to that cell,
209+
or if that fails (due to missing table cell), it will walk in the opposite direction skipping missing cells
210+
up to the number of times set by _missingTableCellSearchLimit set on this instance.
211+
@param tableID: the ID of the table
212+
@param startPos: the position in the document to start searching from.
213+
@param origRow: the row number of the starting cell
214+
@param origCol: the column number of the starting cell
215+
@param origRowSpan: the row span of the row of the starting cell
216+
@param origColSpan: the column span of the column of the starting cell
217+
@param movement: the direction ("next" or "previous")
218+
@param axis: the axis of movement ("row" or "column")
219+
@returns: the position of the destination table cell
220+
"""
221+
destRow, destCol = origRow, origCol
222+
if movement == "first":
223+
if axis == "column":
224+
destCol = 1
225+
else:
226+
destRow = 1
227+
else:
228+
nRows, nCols = self._getTableDimensions(startPos)
229+
if axis == "column":
230+
destCol = nCols
231+
else:
232+
destRow = nRows
233+
try:
234+
return self._getTableCellAt(tableID, startPos, destRow, destCol)
235+
except LookupError:
236+
oppositeMovement = "previous" if movement == "last" else "next"
237+
return self._getNearestTableCell(
238+
tableID,
239+
startPos,
240+
destRow,
241+
destCol,
242+
origRowSpan=1,
243+
origColSpan=1,
244+
movement=oppositeMovement,
245+
axis=axis
246+
)
247+
141248
def _tableMovementScriptHelper(self, movement="next", axis=None):
142249
# documentBase is a core module and should not depend on these UI modules and so they are imported
143250
# at run-time. (#12404)
@@ -158,7 +265,28 @@ def _tableMovementScriptHelper(self, movement="next", axis=None):
158265
return
159266

160267
try:
161-
info = self._getNearestTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis)
268+
if movement in {"previous", "next"}:
269+
info = self._getNearestTableCell(
270+
tableID,
271+
self.selection,
272+
origRow,
273+
origCol,
274+
origRowSpan,
275+
origColSpan,
276+
movement,
277+
axis
278+
)
279+
else:
280+
info = self._getFirstOrLastTableCell(
281+
tableID,
282+
self.selection,
283+
origRow,
284+
origCol,
285+
origRowSpan,
286+
origColSpan,
287+
movement,
288+
axis
289+
)
162290
except LookupError:
163291
# Translators: The message reported when a user attempts to use a table movement command
164292
# but the cursor can't be moved in that direction because it is at the edge of the table.
@@ -190,6 +318,26 @@ def script_previousColumn(self, gesture):
190318
# Translators: the description for the previous table column script on browseMode documents.
191319
script_previousColumn.__doc__ = _("moves to the previous table column")
192320

321+
def script_firstRow(self, gesture):
322+
self._tableMovementScriptHelper(axis="row", movement="first")
323+
# Translators: the description for the first table row script on browseMode documents.
324+
script_firstRow.__doc__ = _("moves to the first table row")
325+
326+
def script_lastRow(self, gesture):
327+
self._tableMovementScriptHelper(axis="row", movement="last")
328+
# Translators: the description for the last table row script on browseMode documents.
329+
script_lastRow.__doc__ = _("moves to the last table row")
330+
331+
def script_firstColumn(self, gesture):
332+
self._tableMovementScriptHelper(axis="column", movement="first")
333+
# Translators: the description for the first table column script on browseMode documents.
334+
script_firstColumn.__doc__ = _("moves to the first table column")
335+
336+
def script_lastColumn(self, gesture):
337+
self._tableMovementScriptHelper(axis="column", movement="last")
338+
# Translators: the description for the last table column script on browseMode documents.
339+
script_lastColumn.__doc__ = _("moves to the last table column")
340+
193341
def script_toggleIncludeLayoutTables(self,gesture):
194342
# documentBase is a core module and should not depend on UI, so it is imported at run-time. (#12404)
195343
import ui
@@ -210,4 +358,8 @@ def script_toggleIncludeLayoutTables(self,gesture):
210358
"kb:control+alt+upArrow": "previousRow",
211359
"kb:control+alt+rightArrow": "nextColumn",
212360
"kb:control+alt+leftArrow": "previousColumn",
361+
"kb:control+alt+pageUp": "firstRow",
362+
"kb:control+alt+pageDown": "lastRow",
363+
"kb:control+alt+Home": "firstColumn",
364+
"kb:control+alt+End": "lastColumn",
213365
}

user_docs/en/userGuide.t2t

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,10 @@ When within a table, the following key commands are also available:
342342
| Move to next column | control+alt+rightArrow | Moves the system caret to the next column (staying in the same row) |
343343
| Move to previous row | control+alt+upArrow | Moves the system caret to the previous row (staying in the same column) |
344344
| Move to next row | control+alt+downArrow | Moves the system caret to the next row (staying in the same column) |
345+
| Move to first column | control+alt+home | Moves the system caret to the first column (staying in the same row) |
346+
| Move to last column | control+alt+end | Moves the system caret to the last column (staying in the same row) |
347+
| Move to first row | control+alt+pageUp | Moves the system caret to the first row (staying in the same column) |
348+
| Move to last row | control+alt+pageDown | Moves the system caret to the last row (staying in the same column) |
345349
%kc:endInclude
346350

347351
++ Object Navigation ++[ObjectNavigation]

0 commit comments

Comments
 (0)