Skip to content

Commit

Permalink
Merge ef25d07 into 4772a6f
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelDCurran authored Aug 19, 2024
2 parents 4772a6f + ef25d07 commit 49f8835
Showing 1 changed file with 156 additions and 40 deletions.
196 changes: 156 additions & 40 deletions source/braille.py
Original file line number Diff line number Diff line change
Expand Up @@ -1760,9 +1760,10 @@ def __init__(self, handler):
#: The translated braille representation of the entire buffer.
#: @type: [int, ...]
self.brailleCells = []
#: The position in L{brailleCells} where the display window starts (inclusive).
#: @type: int
self.windowStartPos = 0
#: A list representing the rows in the braille window,
#: each item being a tuple of start and end braille buffer offsets.
#: Splitting the window into independent rows allows for optional avoidance of splitting words across rows.
self._windowRowBufferOffsets: list[tuple[int, int]] = [(0, 0)]

def clear(self):
"""Clear the entire buffer.
Expand Down Expand Up @@ -1857,28 +1858,54 @@ def bufferPositionsToRawText(self, startPos, endPos):
)
return ""

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

def _get_windowEndPos(self):
endPos = self.windowStartPos + self.handler.displaySize
cellsLen = len(self.brailleCells)
if endPos >= cellsLen:
return cellsLen
if not config.conf["braille"]["wordWrap"]:
return endPos
try:
# Try not to split words across windows.
# To do this, break after the furthest possible space.
return min(
rindex(self.brailleCells, 0, self.windowStartPos, endPos) + 1,
endPos,
)
except ValueError:
pass
return endPos
def windowPosToBufferPos(self, windowPos: int) -> int:
"""
Converts a position relative to the braille window to a position relative to the braille buffer.
"""
windowPos = max(min(windowPos, self.handler.displaySize), 0)
row, col = divmod(windowPos, self.handler.displayNumCols)
if row < len(self._windowRowBufferOffsets):
start, end = self._windowRowBufferOffsets[row]
return min(start + col, end - 1)
raise ValueError("Position outside window")

def _get_windowStartPos(self):
return self.windowPosToBufferPos(0)

def _set_windowStartPos(self, pos):
self._windowRowBufferOffsets.clear()
if len(self.brailleCells) == 0:
# Initialising with no actual braille content.
self._windowRowBufferOffsets = [(0, 0)]
return
doWordWrap = config.conf["braille"]["wordWrap"]
bufferEnd = len(self.brailleCells)
start = pos
clippedEnd = False
for row in range(self.handler.displayNumRows):
end = start + self.handler.displayNumCols
if end > bufferEnd:
end = bufferEnd
clippedEnd = True
elif doWordWrap:
try:
end = rindex(self.brailleCells, 0, start, end) + 1
except (ValueError, IndexError):
pass # No space on line
self._windowRowBufferOffsets.append((start, end))
if clippedEnd:
break
start = end

def _get_windowEndPos(self) -> int:
start, end = self._windowRowBufferOffsets[-1]
return end

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

def _get_windowBrailleCells(self):
return self.brailleCells[self.windowStartPos : self.windowEndPos]
windowCells = []
for start, end in self._windowRowBufferOffsets:
rowCells = self.brailleCells[start:end]
remaining = self.handler.displayNumCols - len(rowCells)
if remaining > 0:
rowCells.extend([0] * remaining)
windowCells.extend(rowCells)
return windowCells

def routeTo(self, windowPos):
pos = self.windowStartPos + windowPos
pos = self.windowPosToBufferPos(windowPos)
if pos >= self.windowEndPos:
return
region, pos = self.bufferPosToRegionPos(pos)
region.routeTo(pos)

def getTextInfoForWindowPos(self, windowPos):
pos = self.windowStartPos + windowPos
pos = self.windowPosToBufferPos(windowPos)
if pos >= self.windowEndPos:
return None
region, pos = self.bufferPosToRegionPos(pos)
Expand Down Expand Up @@ -2237,6 +2271,14 @@ def formatCellsForLog(cells: List[int]) -> str:
@type value: int
"""

filter_displayNumRows = extensionPoints.Filter()
"""
Filter that allows components or add-ons to change the number of rows used for braille output.
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.
@param value: the number of rows on the current display.
@type value: int
"""

displaySizeChanged = extensionPoints.Action()
"""
Action that allows components or add-ons to be notified of display size changes.
Expand Down Expand Up @@ -2297,6 +2339,13 @@ def __init__(self):
with its previous output.
If the value differs, L{displaySizeChanged} is notified.
"""
self._displayNumRows: int = 0
"""
Internal cache for the displayNumRows property.
This attribute is used to compare the displayNumRows output by l{filter_displayNumRows}
with its previous output.
If the value differs, L{displaySizeChanged} is notified with a displayNumRows keyword argument.
"""
self._enabled: bool = False
"""
Internal cache for the enabled property.
Expand Down Expand Up @@ -2430,7 +2479,7 @@ def _get_shouldAutoTether(self) -> bool:
displaySize: int
_cache_displaySize = True

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

displayNumRows: int
_cache_displayNumRows = True

def _get_displayNumRows(self) -> int:
"""
Returns the number of rows on the display.
Handlers can register themselves to L{filter_displayNumRows} to change this value on the fly.
Therefore, this is a read only property and can't be set.
"""
numRows = self.display.numRows if self.display else 0
currentDisplayNumRows = filter_displayNumRows.apply(numRows)
if self._displayNumRows != currentDisplayNumRows:
displaySizeChanged.notify(displaySize=self._displaySize, displayNumRows=currentDisplayNumRows)
self._displayNumRows = currentDisplayNumRows
return currentDisplayNumRows

def _set_displayNumRows(self, value: int):
raise AttributeError(
f"Can't set displayNumRows to {value}, consider registering a handler to filter displayNumRows",
)

displayNumCols: int
_cache_displayNumCols = True

def _get_displayNumCols(self) -> int:
"""
Returns the number of columns on the display.
This is calculated from displaySize and displayNumRows.
"""
return self.displaySize // self.displayNumRows

def _set_displayNumCols(self, value: int):
raise AttributeError(
f"Can't set displayNumCols to {value}, consider registering a handler to filter displayNumCols",
)

enabled: bool
_cache_enabled = True

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

def _normalizeCellArraySize(
self,
oldCells: list[int],
oldCellCount: int,
oldNumRows: int,
newCellCount: int,
newNumRows: int,
) -> list[int]:
"""
Given a list of braille cells of length oldCell Count layed out in sequencial rows of oldNumRows,
return a list of braille cells of length newCellCount layed out in sequencial rows of newNumRows,
padding or truncating the rows and columns as necessary.
"""
oldNumCols = oldCellCount // oldNumRows
newNumCols = newCellCount // newNumRows
if len(oldCells) < oldCellCount:
log.warning("Braille cells are shorter than the display size. Padding with blank cells.")
oldCells.extend([0] * (oldCellCount - len(oldCells)))
newCells = []
if newCellCount != oldCellCount or newNumRows != oldNumRows:
for rowIndex in range(newNumRows):
if rowIndex < oldNumRows:
start = rowIndex * oldNumCols
rowLen = min(oldNumCols, newNumCols)
end = start + rowLen
row = oldCells[start:end]
if rowLen < newNumCols:
row.extend([0] * (newNumCols - rowLen))
else:
row = [0] * newNumCols
newCells.extend(row)
else:
newCells = oldCells
return newCells

def _writeCells(self, cells: List[int]):
handlerCellCount = self.displaySize
pre_writeCells.notify(cells=cells, rawText=self._rawText, currentCellCount=handlerCellCount)
Expand All @@ -2620,18 +2740,14 @@ def _writeCells(self, cells: List[int]):
return
# Braille displays expect cells to be padded up to displayCellCount.
# However, the braille handler uses handlerCellCount to calculate the number of cells.
cellCountDif = displayCellCount - len(cells)
if cellCountDif < 0:
# There are more cells than the connected display could take.
log.warning(
f"Connected display {self.display.name!r} has {displayCellCount} cells, "
f"while braille handler is using {handlerCellCount} cells",
)
cells = cells[:displayCellCount]
elif cellCountDif > 0:
# The connected display could take more cells than the braille handler produces.
# Displays expect cells to be padded up to the number of cells.
cells += [END_OF_BRAILLE_OUTPUT_SHAPE] + [0] * (cellCountDif - 1)
# number of rows / columns may also differ.
cells = self._normalizeCellArraySize(
cells,
handlerCellCount,
self.displayNumRows,
displayCellCount,
self.display.numRows,
)
if not self.display.isThreadSafe:
try:
self.display.display(cells)
Expand Down

0 comments on commit 49f8835

Please sign in to comment.