@@ -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+
22402282displaySizeChanged = extensionPoints .Action ()
22412283"""
22422284Action 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