Skip to content

Commit 7ef2fa4

Browse files
authored
Merge c5d772e into c446688
2 parents c446688 + c5d772e commit 7ef2fa4

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
@@ -67,6 +68,30 @@ class DocumentWithTableNavigation(TextContainerObject,ScriptableObject):
6768

6869
_lastTableSelection: Optional[_TableSelection] = None
6970

71+
def _maybeGetLayoutTableIds(self, info: textInfos.TextInfo):
72+
"""
73+
If "Include layout tables" option is on, this will
74+
compute the set of layout tables that this textInfo is enclosed in,
75+
otherwise it will return empty set.
76+
@param info: the position where the layout tables should be looked for.
77+
@returns: A set of table IDs or empty set.
78+
"""
79+
fields = list(info.getTextWithFields())
80+
# If layout tables should not be reported, we should First record the ID of all layout tables,
81+
# so that we can skip them when searching for the deepest table
82+
layoutIDs = set()
83+
if not config.conf["documentFormatting"]["includeLayoutTables"]:
84+
for field in fields:
85+
if (
86+
isinstance(field, textInfos.FieldCommand)
87+
and field.command == "controlStart"
88+
and field.field.get('table-layout')
89+
):
90+
tableID = field.field.get('table-id')
91+
if tableID is not None:
92+
layoutIDs.add(tableID)
93+
return layoutIDs
94+
7095
def _getTableCellCoords(self, info):
7196
"""
7297
Fetches information about the deepest table cell at the given position.
@@ -80,14 +105,7 @@ def _getTableCellCoords(self, info):
80105
info = info.copy()
81106
info.expand(textInfos.UNIT_CHARACTER)
82107
fields=list(info.getTextWithFields())
83-
# 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
84-
layoutIDs=set()
85-
if not config.conf["documentFormatting"]["includeLayoutTables"]:
86-
for field in fields:
87-
if isinstance(field, textInfos.FieldCommand) and field.command == "controlStart" and field.field.get('table-layout'):
88-
tableID=field.field.get('table-id')
89-
if tableID is not None:
90-
layoutIDs.add(tableID)
108+
layoutIDs = self._maybeGetLayoutTableIds(info)
91109
for field in reversed(fields):
92110
if not (isinstance(field, textInfos.FieldCommand) and field.command == "controlStart"):
93111
# Not a control field.
@@ -104,6 +122,40 @@ def _getTableCellCoords(self, info):
104122
attrs["table-rownumber"], attrs["table-columnnumber"],
105123
attrs.get("table-rowsspanned", 1), attrs.get("table-columnsspanned", 1))
106124

125+
def _getTableDimensions(self, info: textInfos.TextInfo) -> Tuple[int, int]:
126+
"""
127+
Fetches information about the deepest table dimension.
128+
@param info: the position where the table cell should be looked for.
129+
@returns: a tuple of table height and width.
130+
@raises: LookupError if there is no table cell at this position.
131+
"""
132+
if info.isCollapsed:
133+
info = info.copy()
134+
info.expand(textInfos.UNIT_CHARACTER)
135+
fields = list(info.getTextWithFields())
136+
layoutIDs = self._maybeGetLayoutTableIds(info)
137+
for field in reversed(fields):
138+
if not (
139+
isinstance(field, textInfos.FieldCommand)
140+
and field.command == "controlStart"
141+
and field.field.get("role") == controlTypes.Role.TABLE
142+
):
143+
# Not a table control field.
144+
continue
145+
attrs = field.field
146+
tableID = attrs.get('table-id')
147+
if tableID is None or tableID in layoutIDs:
148+
continue
149+
break
150+
else:
151+
raise LookupError("Not in a table cell")
152+
try:
153+
nRows = int(attrs.get("table-rowcount"))
154+
nCols = int(attrs.get("table-columncount"))
155+
except (TypeError, ValueError):
156+
raise LookupError("Not in a table cell")
157+
return (nRows, nCols)
158+
107159
def _getTableCellAt(self,tableID,startPos,row,column):
108160
"""
109161
Starting from the given start position, Locates the table cell with the given row and column coordinates and table ID.
@@ -153,10 +205,10 @@ def _getNearestTableCell(
153205
# Determine destination row and column.
154206
destRow = origRow
155207
destCol = origCol
156-
if axis == "row":
157-
destRow += origRowSpan if movement == "next" else -1
158-
elif axis == "column":
159-
destCol += origColSpan if movement == "next" else -1
208+
if axis == _Axis.ROW:
209+
destRow += origRowSpan if movement == _Movement.NEXT else -1
210+
elif axis == _Axis.COLUMN:
211+
destCol += origColSpan if movement == _Movement.NEXT else -1
160212

161213
# 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
162214
limit=self._missingTableCellSearchLimit
@@ -169,12 +221,68 @@ def _getNearestTableCell(
169221
return self._getTableCellAt(tableID,startPos,destRow,destCol)
170222
except LookupError:
171223
pass
172-
if axis=="row":
173-
destRow+=1 if movement=="next" else -1
224+
if axis == _Axis.ROW:
225+
destRow += 1 if movement == _Movement.NEXT else -1
174226
else:
175-
destCol+=1 if movement=="next" else -1
227+
destCol += 1 if movement == _Movement.NEXT else -1
176228
raise LookupError
177229

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

217325
try:
218-
info = self._getNearestTableCell(tableID, self.selection, origRow, origCol, origRowSpan, origColSpan, movement, axis)
326+
if movement in {_Movement.PREVIOUS, _Movement.NEXT}:
327+
info = self._getNearestTableCell(
328+
tableID,
329+
self.selection,
330+
origRow,
331+
origCol,
332+
origRowSpan,
333+
origColSpan,
334+
movement,
335+
axis
336+
)
337+
elif movement in {_Movement.FIRST, _Movement.LAST}:
338+
info = self._getFirstOrLastTableCell(
339+
tableID,
340+
self.selection,
341+
origRow,
342+
origCol,
343+
origRowSpan,
344+
origColSpan,
345+
movement,
346+
axis
347+
)
348+
else:
349+
raise ValueError(f"Unknown movement {movement}")
219350
except LookupError:
220351
# Translators: The message reported when a user attempts to use a table movement command
221352
# but the cursor can't be moved in that direction because it is at the edge of the table.
@@ -236,25 +367,45 @@ def _tableMovementScriptHelper(
236367
)
237368

238369
def script_nextRow(self, gesture):
239-
self._tableMovementScriptHelper(axis="row", movement="next")
370+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.NEXT)
240371
# Translators: the description for the next table row script on browseMode documents.
241372
script_nextRow.__doc__ = _("moves to the next table row")
242373

243374
def script_previousRow(self, gesture):
244-
self._tableMovementScriptHelper(axis="row", movement="previous")
375+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.PREVIOUS)
245376
# Translators: the description for the previous table row script on browseMode documents.
246377
script_previousRow.__doc__ = _("moves to the previous table row")
247378

248379
def script_nextColumn(self, gesture):
249-
self._tableMovementScriptHelper(axis="column", movement="next")
380+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.NEXT)
250381
# Translators: the description for the next table column script on browseMode documents.
251382
script_nextColumn.__doc__ = _("moves to the next table column")
252383

253384
def script_previousColumn(self, gesture):
254-
self._tableMovementScriptHelper(axis="column", movement="previous")
385+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.PREVIOUS)
255386
# Translators: the description for the previous table column script on browseMode documents.
256387
script_previousColumn.__doc__ = _("moves to the previous table column")
257388

389+
def script_firstRow(self, gesture):
390+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.FIRST)
391+
# Translators: the description for the first table row script on browseMode documents.
392+
script_firstRow.__doc__ = _("moves to the first table row")
393+
394+
def script_lastRow(self, gesture):
395+
self._tableMovementScriptHelper(axis=_Axis.ROW, movement=_Movement.LAST)
396+
# Translators: the description for the last table row script on browseMode documents.
397+
script_lastRow.__doc__ = _("moves to the last table row")
398+
399+
def script_firstColumn(self, gesture):
400+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.FIRST)
401+
# Translators: the description for the first table column script on browseMode documents.
402+
script_firstColumn.__doc__ = _("moves to the first table column")
403+
404+
def script_lastColumn(self, gesture):
405+
self._tableMovementScriptHelper(axis=_Axis.COLUMN, movement=_Movement.LAST)
406+
# Translators: the description for the last table column script on browseMode documents.
407+
script_lastColumn.__doc__ = _("moves to the last table column")
408+
258409
def script_toggleIncludeLayoutTables(self,gesture):
259410
# documentBase is a core module and should not depend on UI, so it is imported at run-time. (#12404)
260411
import ui
@@ -275,4 +426,8 @@ def script_toggleIncludeLayoutTables(self,gesture):
275426
"kb:control+alt+upArrow": "previousRow",
276427
"kb:control+alt+rightArrow": "nextColumn",
277428
"kb:control+alt+leftArrow": "previousColumn",
429+
"kb:control+alt+pageUp": "firstRow",
430+
"kb:control+alt+pageDown": "lastRow",
431+
"kb:control+alt+Home": "firstColumn",
432+
"kb:control+alt+End": "lastColumn",
278433
}

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)