Skip to content

Commit 1a80e13

Browse files
authored
Merge 37b628e into ef31b1d
2 parents ef31b1d + 37b628e commit 1a80e13

2 files changed

Lines changed: 180 additions & 21 deletions

File tree

source/documentBase.py

Lines changed: 176 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import textInfos
1212
import controlTypes
1313

14-
1514
_TableID = Union[int, Tuple, Any]
1615
"""
1716
A variety of types can be used for a tableID.
@@ -27,6 +26,8 @@ class _Axis(str, Enum):
2726
class _Movement(str, Enum):
2827
NEXT = "next"
2928
PREVIOUS = "previous"
29+
FIRST = "first"
30+
LAST = "last"
3031

3132

3233
@dataclass
@@ -75,6 +76,30 @@ class DocumentWithTableNavigation(TextContainerObject,ScriptableObject):
7576

7677
_lastTableSelection: Optional[_TableSelection] = None
7778

79+
def _maybeGetLayoutTableIds(self, info: textInfos.TextInfo):
80+
"""
81+
If "Include layout tables" option is on, this will
82+
compute the set of layout tables that this textInfo is enclosed in,
83+
otherwise it will return empty set.
84+
@param info: the position where the layout tables should be looked for.
85+
@returns: A set of table IDs or empty set.
86+
"""
87+
fields = list(info.getTextWithFields())
88+
# If layout tables should not be reported, we should First record the ID of all layout tables,
89+
# so that we can skip them when searching for the deepest table
90+
layoutIDs = set()
91+
if not config.conf["documentFormatting"]["includeLayoutTables"]:
92+
for field in fields:
93+
if (
94+
isinstance(field, textInfos.FieldCommand)
95+
and field.command == "controlStart"
96+
and field.field.get('table-layout')
97+
):
98+
tableID = field.field.get('table-id')
99+
if tableID is not None:
100+
layoutIDs.add(tableID)
101+
return layoutIDs
102+
78103
def _getTableCellCoords(self, info):
79104
"""
80105
Fetches information about the deepest table cell at the given position.
@@ -88,14 +113,7 @@ def _getTableCellCoords(self, info):
88113
info = info.copy()
89114
info.expand(textInfos.UNIT_CHARACTER)
90115
fields=list(info.getTextWithFields())
91-
# 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
92-
layoutIDs=set()
93-
if not config.conf["documentFormatting"]["includeLayoutTables"]:
94-
for field in fields:
95-
if isinstance(field, textInfos.FieldCommand) and field.command == "controlStart" and field.field.get('table-layout'):
96-
tableID=field.field.get('table-id')
97-
if tableID is not None:
98-
layoutIDs.add(tableID)
116+
layoutIDs = self._maybeGetLayoutTableIds(info)
99117
for field in reversed(fields):
100118
if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"):
101119
# Not a control field.
@@ -112,6 +130,40 @@ def _getTableCellCoords(self, info):
112130
attrs["table-rownumber"], attrs["table-columnnumber"],
113131
attrs.get("table-rowsspanned", 1), attrs.get("table-columnsspanned", 1))
114132

133+
def _getTableDimensions(self, info: textInfos.TextInfo) -> Tuple[int, int]:
134+
"""
135+
Fetches information about the deepest table dimension.
136+
@param info: the position where the table cell should be looked for.
137+
@returns: a tuple of table height and width.
138+
@raises: LookupError if there is no table cell at this position.
139+
"""
140+
if info.isCollapsed:
141+
info = info.copy()
142+
info.expand(textInfos.UNIT_CHARACTER)
143+
fields = list(info.getTextWithFields())
144+
layoutIDs = self._maybeGetLayoutTableIds(info)
145+
for field in reversed(fields):
146+
if not (
147+
isinstance(field, textInfos.FieldCommand)
148+
and field.command == "controlStart"
149+
and field.field.get("role") == controlTypes.Role.TABLE
150+
):
151+
# Not a table control field.
152+
continue
153+
attrs = field.field
154+
tableID = attrs.get('table-id')
155+
if tableID is None or tableID in layoutIDs:
156+
continue
157+
break
158+
else:
159+
raise LookupError("Not in a table cell")
160+
try:
161+
nRows = int(attrs.get("table-rowcount"))
162+
nCols = int(attrs.get("table-columncount"))
163+
except (TypeError, ValueError):
164+
raise LookupError("Not in a table cell")
165+
return (nRows, nCols)
166+
115167
def _getTableCellAt(self,tableID,startPos,row,column):
116168
"""
117169
Starting from the given start position, Locates the table cell with the given row and column coordinates and table ID.
@@ -161,10 +213,10 @@ def _getNearestTableCell(
161213
# Determine destination row and column.
162214
destRow = origRow
163215
destCol = origCol
164-
if axis == "row":
165-
destRow += origRowSpan if movement == "next" else -1
166-
elif axis == "column":
167-
destCol += origColSpan if movement == "next" else -1
216+
if axis == _Axis.ROW:
217+
destRow += origRowSpan if movement == _Movement.NEXT else -1
218+
elif axis == _Axis.COLUMN:
219+
destCol += origColSpan if movement == _Movement.NEXT else -1
168220

169221
# 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
170222
limit=self._missingTableCellSearchLimit
@@ -177,12 +229,68 @@ def _getNearestTableCell(
177229
return self._getTableCellAt(tableID,startPos,destRow,destCol)
178230
except LookupError:
179231
pass
180-
if axis=="row":
181-
destRow+=1 if movement=="next" else -1
232+
if axis == _Axis.ROW:
233+
destRow += 1 if movement == _Movement.NEXT else -1
182234
else:
183-
destCol+=1 if movement=="next" else -1
235+
destCol += 1 if movement == _Movement.NEXT else -1
184236
raise LookupError
185237

238+
def _getFirstOrLastTableCell(
239+
self,
240+
tableID: _TableID,
241+
startPos: textInfos.TextInfo,
242+
origRow: int,
243+
origCol: int,
244+
origRowSpan: int,
245+
origColSpan: int,
246+
movement: _Movement,
247+
axis: _Axis
248+
) -> textInfos.TextInfo:
249+
"""
250+
Locates the first or last cell in current row or column given coordinates of current cell.
251+
When jumping to the first row/column, It will try to set current row/column index to 1.
252+
When jumping to the last row/column, it will query table dimensions and set row/column index
253+
to corresponding dimension.
254+
After figuring out exact coordinates of the cell it will try to jump directly to that cell,
255+
or if that fails (due to missing table cell), it will walk in the opposite direction skipping missing cells
256+
up to the number of times set by _missingTableCellSearchLimit set on this instance.
257+
@param tableID: the ID of the table
258+
@param startPos: the position in the document to start searching from.
259+
@param origRow: the row number of the starting cell
260+
@param origCol: the column number of the starting cell
261+
@param origRowSpan: the row span of the row of the starting cell
262+
@param origColSpan: the column span of the column of the starting cell
263+
@param movement: the direction ("first" or "last")
264+
@param axis: the axis of movement ("row" or "column")
265+
@returns: the position of the destination table cell
266+
"""
267+
destRow, destCol = origRow, origCol
268+
if movement == _Movement.FIRST:
269+
if axis == _Axis.COLUMN:
270+
destCol = 1
271+
else:
272+
destRow = 1
273+
else:
274+
nRows, nCols = self._getTableDimensions(startPos)
275+
if axis == _Axis.COLUMN:
276+
destCol = nCols
277+
else:
278+
destRow = nRows
279+
try:
280+
return self._getTableCellAt(tableID, startPos, destRow, destCol)
281+
except LookupError:
282+
oppositeMovement = _Movement.PREVIOUS if movement == _Movement.LAST else _Movement.NEXT
283+
return self._getNearestTableCell(
284+
tableID,
285+
startPos,
286+
destRow,
287+
destCol,
288+
origRowSpan=1,
289+
origColSpan=1,
290+
movement=oppositeMovement,
291+
axis=axis
292+
)
293+
186294
def _tableMovementScriptHelper(
187295
self,
188296
movement: _Movement = _Movement.NEXT,
@@ -224,7 +332,30 @@ def _tableMovementScriptHelper(
224332
origRowSpan = self._lastTableSelection.rowSpan
225333

226334
try:
227-
info = self._getNearestTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis)
335+
if movement in {_Movement.PREVIOUS, _Movement.NEXT}:
336+
info = self._getNearestTableCell(
337+
tableID,
338+
self.selection,
339+
origRow,
340+
origCol,
341+
origRowSpan,
342+
origColSpan,
343+
movement,
344+
axis
345+
)
346+
elif movement in {_Movement.FIRST, _Movement.LAST}:
347+
info = self._getFirstOrLastTableCell(
348+
tableID,
349+
self.selection,
350+
origRow,
351+
origCol,
352+
origRowSpan,
353+
origColSpan,
354+
movement,
355+
axis
356+
)
357+
else:
358+
raise ValueError(f"Unknown movement {movement}")
228359
newTableID, newRow, newCol, newRowSpan, newColSpan = self._getTableCellCoords(info)
229360
except LookupError:
230361
# Translators: The message reported when a user attempts to use a table movement command
@@ -248,25 +379,45 @@ def _tableMovementScriptHelper(
248379
)
249380

250381
def script_nextRow(self, gesture):
251-
self._tableMovementScriptHelper(axis="row", movement="next")
382+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.NEXT)
252383
# Translators: the description for the next table row script on browseMode documents.
253384
script_nextRow.__doc__ = _("moves to the next table row")
254385

255386
def script_previousRow(self, gesture):
256-
self._tableMovementScriptHelper(axis="row", movement="previous")
387+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.PREVIOUS)
257388
# Translators: the description for the previous table row script on browseMode documents.
258389
script_previousRow.__doc__ = _("moves to the previous table row")
259390

260391
def script_nextColumn(self, gesture):
261-
self._tableMovementScriptHelper(axis="column", movement="next")
392+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.NEXT)
262393
# Translators: the description for the next table column script on browseMode documents.
263394
script_nextColumn.__doc__ = _("moves to the next table column")
264395

265396
def script_previousColumn(self, gesture):
266-
self._tableMovementScriptHelper(axis="column", movement="previous")
397+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.PREVIOUS)
267398
# Translators: the description for the previous table column script on browseMode documents.
268399
script_previousColumn.__doc__ = _("moves to the previous table column")
269400

401+
def script_firstRow(self, gesture):
402+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.FIRST)
403+
# Translators: the description for the first table row script on browseMode documents.
404+
script_firstRow.__doc__ = _("moves to the first table row")
405+
406+
def script_lastRow(self, gesture):
407+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.LAST)
408+
# Translators: the description for the last table row script on browseMode documents.
409+
script_lastRow.__doc__ = _("moves to the last table row")
410+
411+
def script_firstColumn(self, gesture):
412+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.FIRST)
413+
# Translators: the description for the first table column script on browseMode documents.
414+
script_firstColumn.__doc__ = _("moves to the first table column")
415+
416+
def script_lastColumn(self, gesture):
417+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.LAST)
418+
# Translators: the description for the last table column script on browseMode documents.
419+
script_lastColumn.__doc__ = _("moves to the last table column")
420+
270421
def script_toggleIncludeLayoutTables(self,gesture):
271422
# documentBase is a core module and should not depend on UI, so it is imported at run-time. (#12404)
272423
import ui
@@ -287,4 +438,8 @@ def script_toggleIncludeLayoutTables(self,gesture):
287438
"kb:control+alt+upArrow": "previousRow",
288439
"kb:control+alt+rightArrow": "nextColumn",
289440
"kb:control+alt+leftArrow": "previousColumn",
441+
"kb:control+alt+pageUp": "firstRow",
442+
"kb:control+alt+pageDown": "lastRow",
443+
"kb:control+alt+Home": "firstColumn",
444+
"kb:control+alt+End": "lastColumn",
290445
}

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)