Skip to content

Commit 49f8835

Browse files
Merge ef25d07 into 4772a6f
2 parents 4772a6f + ef25d07 commit 49f8835

1 file changed

Lines changed: 156 additions & 40 deletions

File tree

source/braille.py

Lines changed: 156 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,9 +1760,10 @@ def __init__(self, handler):
17601760
#: The translated braille representation of the entire buffer.
17611761
#: @type: [int, ...]
17621762
self.brailleCells = []
1763-
#: The position in L{brailleCells} where the display window starts (inclusive).
1764-
#: @type: int
1765-
self.windowStartPos = 0
1763+
#: A list representing the rows in the braille window,
1764+
#: each item being a tuple of start and end braille buffer offsets.
1765+
#: Splitting the window into independent rows allows for optional avoidance of splitting words across rows.
1766+
self._windowRowBufferOffsets: list[tuple[int, int]] = [(0, 0)]
17661767

17671768
def clear(self):
17681769
"""Clear the entire buffer.
@@ -1857,28 +1858,54 @@ def bufferPositionsToRawText(self, startPos, endPos):
18571858
)
18581859
return ""
18591860

1860-
def bufferPosToWindowPos(self, bufferPos):
1861-
if not (self.windowStartPos <= bufferPos < self.windowEndPos):
1862-
raise LookupError("Buffer position not in window")
1863-
return bufferPos - self.windowStartPos
1861+
def bufferPosToWindowPos(self, bufferPos: int) -> int:
1862+
for row, (start, end) in enumerate(self._windowRowBufferOffsets):
1863+
if start <= bufferPos < end:
1864+
return row * self.handler.displayNumCols + (bufferPos - start)
1865+
raise LookupError("buffer pos not in window")
18641866

1865-
def _get_windowEndPos(self):
1866-
endPos = self.windowStartPos + self.handler.displaySize
1867-
cellsLen = len(self.brailleCells)
1868-
if endPos >= cellsLen:
1869-
return cellsLen
1870-
if not config.conf["braille"]["wordWrap"]:
1871-
return endPos
1872-
try:
1873-
# Try not to split words across windows.
1874-
# To do this, break after the furthest possible space.
1875-
return min(
1876-
rindex(self.brailleCells, 0, self.windowStartPos, endPos) + 1,
1877-
endPos,
1878-
)
1879-
except ValueError:
1880-
pass
1881-
return endPos
1867+
def windowPosToBufferPos(self, windowPos: int) -> int:
1868+
"""
1869+
Converts a position relative to the braille window to a position relative to the braille buffer.
1870+
"""
1871+
windowPos = max(min(windowPos, self.handler.displaySize), 0)
1872+
row, col = divmod(windowPos, self.handler.displayNumCols)
1873+
if row < len(self._windowRowBufferOffsets):
1874+
start, end = self._windowRowBufferOffsets[row]
1875+
return min(start + col, end - 1)
1876+
raise ValueError("Position outside window")
1877+
1878+
def _get_windowStartPos(self):
1879+
return self.windowPosToBufferPos(0)
1880+
1881+
def _set_windowStartPos(self, pos):
1882+
self._windowRowBufferOffsets.clear()
1883+
if len(self.brailleCells) == 0:
1884+
# Initialising with no actual braille content.
1885+
self._windowRowBufferOffsets = [(0, 0)]
1886+
return
1887+
doWordWrap = config.conf["braille"]["wordWrap"]
1888+
bufferEnd = len(self.brailleCells)
1889+
start = pos
1890+
clippedEnd = False
1891+
for row in range(self.handler.displayNumRows):
1892+
end = start + self.handler.displayNumCols
1893+
if end > bufferEnd:
1894+
end = bufferEnd
1895+
clippedEnd = True
1896+
elif doWordWrap:
1897+
try:
1898+
end = rindex(self.brailleCells, 0, start, end) + 1
1899+
except (ValueError, IndexError):
1900+
pass # No space on line
1901+
self._windowRowBufferOffsets.append((start, end))
1902+
if clippedEnd:
1903+
break
1904+
start = end
1905+
1906+
def _get_windowEndPos(self) -> int:
1907+
start, end = self._windowRowBufferOffsets[-1]
1908+
return end
18821909

18831910
def _set_windowEndPos(self, endPos):
18841911
"""Sets the end position for the braille window and recalculates the window start position based on several variables.
@@ -2027,17 +2054,24 @@ def _get_windowRawText(self):
20272054
return self.bufferPositionsToRawText(self.windowStartPos, self.windowEndPos)
20282055

20292056
def _get_windowBrailleCells(self):
2030-
return self.brailleCells[self.windowStartPos : self.windowEndPos]
2057+
windowCells = []
2058+
for start, end in self._windowRowBufferOffsets:
2059+
rowCells = self.brailleCells[start:end]
2060+
remaining = self.handler.displayNumCols - len(rowCells)
2061+
if remaining > 0:
2062+
rowCells.extend([0] * remaining)
2063+
windowCells.extend(rowCells)
2064+
return windowCells
20312065

20322066
def routeTo(self, windowPos):
2033-
pos = self.windowStartPos + windowPos
2067+
pos = self.windowPosToBufferPos(windowPos)
20342068
if pos >= self.windowEndPos:
20352069
return
20362070
region, pos = self.bufferPosToRegionPos(pos)
20372071
region.routeTo(pos)
20382072

20392073
def getTextInfoForWindowPos(self, windowPos):
2040-
pos = self.windowStartPos + windowPos
2074+
pos = self.windowPosToBufferPos(windowPos)
20412075
if pos >= self.windowEndPos:
20422076
return None
20432077
region, pos = self.bufferPosToRegionPos(pos)
@@ -2237,6 +2271,14 @@ def formatCellsForLog(cells: List[int]) -> str:
22372271
@type value: int
22382272
"""
22392273

2274+
filter_displayNumRows = extensionPoints.Filter()
2275+
"""
2276+
Filter that allows components or add-ons to change the number of rows used for braille output.
2277+
For example, when a system has a display with 10 rows, but is being controlled by a remote system with a display of 5 rows, the display number of rows should be lowered to 5.
2278+
@param value: the number of rows on the current display.
2279+
@type value: int
2280+
"""
2281+
22402282
displaySizeChanged = extensionPoints.Action()
22412283
"""
22422284
Action that allows components or add-ons to be notified of display size changes.
@@ -2297,6 +2339,13 @@ def __init__(self):
22972339
with its previous output.
22982340
If the value differs, L{displaySizeChanged} is notified.
22992341
"""
2342+
self._displayNumRows: int = 0
2343+
"""
2344+
Internal cache for the displayNumRows property.
2345+
This attribute is used to compare the displayNumRows output by l{filter_displayNumRows}
2346+
with its previous output.
2347+
If the value differs, L{displaySizeChanged} is notified with a displayNumRows keyword argument.
2348+
"""
23002349
self._enabled: bool = False
23012350
"""
23022351
Internal cache for the enabled property.
@@ -2430,7 +2479,7 @@ def _get_shouldAutoTether(self) -> bool:
24302479
displaySize: int
24312480
_cache_displaySize = True
24322481

2433-
def _get_displaySize(self):
2482+
def _get_displaySize(self) -> int:
24342483
"""Returns the display size to use for braille output.
24352484
Handlers can register themselves to L{filter_displaySize} to change this value on the fly.
24362485
Therefore, this is a read only property and can't be set.
@@ -2452,6 +2501,42 @@ def _set_displaySize(self, value):
24522501
f"Can't set displaySize to {value}, consider registering a handler to filter_displaySize",
24532502
)
24542503

2504+
displayNumRows: int
2505+
_cache_displayNumRows = True
2506+
2507+
def _get_displayNumRows(self) -> int:
2508+
"""
2509+
Returns the number of rows on the display.
2510+
Handlers can register themselves to L{filter_displayNumRows} to change this value on the fly.
2511+
Therefore, this is a read only property and can't be set.
2512+
"""
2513+
numRows = self.display.numRows if self.display else 0
2514+
currentDisplayNumRows = filter_displayNumRows.apply(numRows)
2515+
if self._displayNumRows != currentDisplayNumRows:
2516+
displaySizeChanged.notify(displaySize=self._displaySize, displayNumRows=currentDisplayNumRows)
2517+
self._displayNumRows = currentDisplayNumRows
2518+
return currentDisplayNumRows
2519+
2520+
def _set_displayNumRows(self, value: int):
2521+
raise AttributeError(
2522+
f"Can't set displayNumRows to {value}, consider registering a handler to filter displayNumRows",
2523+
)
2524+
2525+
displayNumCols: int
2526+
_cache_displayNumCols = True
2527+
2528+
def _get_displayNumCols(self) -> int:
2529+
"""
2530+
Returns the number of columns on the display.
2531+
This is calculated from displaySize and displayNumRows.
2532+
"""
2533+
return self.displaySize // self.displayNumRows
2534+
2535+
def _set_displayNumCols(self, value: int):
2536+
raise AttributeError(
2537+
f"Can't set displayNumCols to {value}, consider registering a handler to filter displayNumCols",
2538+
)
2539+
24552540
enabled: bool
24562541
_cache_enabled = True
24572542

@@ -2611,6 +2696,41 @@ def _updateDisplay(self):
26112696
# Make sure we start the blink timer from the main thread to avoid wx assertions
26122697
wx.CallAfter(self._cursorBlinkTimer.Start, blinkRate)
26132698

2699+
def _normalizeCellArraySize(
2700+
self,
2701+
oldCells: list[int],
2702+
oldCellCount: int,
2703+
oldNumRows: int,
2704+
newCellCount: int,
2705+
newNumRows: int,
2706+
) -> list[int]:
2707+
"""
2708+
Given a list of braille cells of length oldCell Count layed out in sequencial rows of oldNumRows,
2709+
return a list of braille cells of length newCellCount layed out in sequencial rows of newNumRows,
2710+
padding or truncating the rows and columns as necessary.
2711+
"""
2712+
oldNumCols = oldCellCount // oldNumRows
2713+
newNumCols = newCellCount // newNumRows
2714+
if len(oldCells) < oldCellCount:
2715+
log.warning("Braille cells are shorter than the display size. Padding with blank cells.")
2716+
oldCells.extend([0] * (oldCellCount - len(oldCells)))
2717+
newCells = []
2718+
if newCellCount != oldCellCount or newNumRows != oldNumRows:
2719+
for rowIndex in range(newNumRows):
2720+
if rowIndex < oldNumRows:
2721+
start = rowIndex * oldNumCols
2722+
rowLen = min(oldNumCols, newNumCols)
2723+
end = start + rowLen
2724+
row = oldCells[start:end]
2725+
if rowLen < newNumCols:
2726+
row.extend([0] * (newNumCols - rowLen))
2727+
else:
2728+
row = [0] * newNumCols
2729+
newCells.extend(row)
2730+
else:
2731+
newCells = oldCells
2732+
return newCells
2733+
26142734
def _writeCells(self, cells: List[int]):
26152735
handlerCellCount = self.displaySize
26162736
pre_writeCells.notify(cells=cells, rawText=self._rawText, currentCellCount=handlerCellCount)
@@ -2620,18 +2740,14 @@ def _writeCells(self, cells: List[int]):
26202740
return
26212741
# Braille displays expect cells to be padded up to displayCellCount.
26222742
# However, the braille handler uses handlerCellCount to calculate the number of cells.
2623-
cellCountDif = displayCellCount - len(cells)
2624-
if cellCountDif < 0:
2625-
# There are more cells than the connected display could take.
2626-
log.warning(
2627-
f"Connected display {self.display.name!r} has {displayCellCount} cells, "
2628-
f"while braille handler is using {handlerCellCount} cells",
2629-
)
2630-
cells = cells[:displayCellCount]
2631-
elif cellCountDif > 0:
2632-
# The connected display could take more cells than the braille handler produces.
2633-
# Displays expect cells to be padded up to the number of cells.
2634-
cells += [END_OF_BRAILLE_OUTPUT_SHAPE] + [0] * (cellCountDif - 1)
2743+
# number of rows / columns may also differ.
2744+
cells = self._normalizeCellArraySize(
2745+
cells,
2746+
handlerCellCount,
2747+
self.displayNumRows,
2748+
displayCellCount,
2749+
self.display.numRows,
2750+
)
26352751
if not self.display.isThreadSafe:
26362752
try:
26372753
self.display.display(cells)

0 commit comments

Comments
 (0)