To make Fenrir easier to approach for new developer, start code migration to be pep8 compliant.
This commit is contained in:
@ -5,8 +5,10 @@
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
from fenrirscreenreader.core.i18n import _
|
||||
from collections import Counter
|
||||
|
||||
|
||||
class attributeManager():
|
||||
def __init__(self):
|
||||
self.currAttributes = None
|
||||
@ -20,44 +22,59 @@ class attributeManager():
|
||||
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setLastCursorAttribute(self, lastCursorAttribute):
|
||||
self.prevLastCursorAttribute = self.currLastCursorAttribute
|
||||
self.currLastCursorAttribute = lastCursorAttribute
|
||||
|
||||
def resetLastCursorAttribute(self):
|
||||
self.prevLastCursorAttribute = None
|
||||
self.currLastCursorAttribute = None
|
||||
|
||||
def isLastCursorAttributeChange(self):
|
||||
if self.prevLastCursorAttribute == None:
|
||||
if self.prevLastCursorAttribute is None:
|
||||
return False
|
||||
return self.prevLastCursorAttribute != self.currLastCursorAttribute
|
||||
|
||||
def getCurrAttributeCursor(self):
|
||||
return self.currAttributeCursor
|
||||
|
||||
def isAttributeCursorActive(self):
|
||||
return self.currAttributeCursor != None
|
||||
return self.currAttributeCursor is not None
|
||||
|
||||
def isAttributeChange(self):
|
||||
if not self.prevAttributes:
|
||||
if not self.prevAttributes:
|
||||
return False
|
||||
return self.currAttributes != self.prevAttributes
|
||||
|
||||
def resetAttributeAll(self):
|
||||
self.resetAttributeDelta()
|
||||
self.resetAttributeCursor()
|
||||
|
||||
def getAttributeDelta(self):
|
||||
return self.currAttributeDelta
|
||||
|
||||
def resetAttributeDelta(self):
|
||||
self.currAttributeDelta = ''
|
||||
|
||||
def setAttributeDelta(self, currAttributeDelta):
|
||||
self.currAttributeDelta = currAttributeDelta
|
||||
|
||||
def resetAttributeCursor(self):
|
||||
self.currAttributeCursor = None
|
||||
self.prefAttributeCursor = None
|
||||
|
||||
def setAttributeCursor(self, currAttributeCursor):
|
||||
self.prefAttributeCursor = self.currAttributeCursor
|
||||
self.currAttributeCursor = currAttributeCursor.copy()
|
||||
|
||||
def resetAttributes(self, currAttributes):
|
||||
self.prevAttributes = None
|
||||
self.currAttributes = currAttributes
|
||||
|
||||
def setAttributes(self, currAttributes):
|
||||
self.prevAttributes = self.currAttributes
|
||||
self.currAttributes = currAttributes.copy()
|
||||
@ -75,36 +92,43 @@ class attributeManager():
|
||||
try:
|
||||
return self.defaultAttributes[0]
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut('attributeManager getAttributeByXY: Error accessing default attributes: ' + str(e), debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
'attributeManager getAttributeByXY: Error accessing default attributes: ' +
|
||||
str(e),
|
||||
debug.debugLevel.ERROR)
|
||||
return None
|
||||
|
||||
def appendDefaultAttributes(self, attribute):
|
||||
if not attribute:
|
||||
return
|
||||
if len(attribute) != 10:
|
||||
return
|
||||
self.defaultAttributes.append(attribute)
|
||||
|
||||
def initDefaultAttributes(self):
|
||||
self.defaultAttributes = [None]
|
||||
self.defaultAttributes.append([
|
||||
'default', # fg
|
||||
'default', # bg
|
||||
False, # bold
|
||||
False, # italics
|
||||
False, # underscore
|
||||
False, # strikethrough
|
||||
False, # reverse
|
||||
False, # blink
|
||||
'default', # fontsize
|
||||
'default' # fontfamily
|
||||
]) #end attribute
|
||||
def isDefaultAttribute(self,attribute):
|
||||
'default', # fg
|
||||
'default', # bg
|
||||
False, # bold
|
||||
False, # italics
|
||||
False, # underscore
|
||||
False, # strikethrough
|
||||
False, # reverse
|
||||
False, # blink
|
||||
'default', # fontsize
|
||||
'default' # fontfamily
|
||||
]) # end attribute
|
||||
|
||||
def isDefaultAttribute(self, attribute):
|
||||
return attribute in self.defaultAttributes
|
||||
|
||||
def hasAttributes(self, cursor, update=True):
|
||||
if not cursor:
|
||||
return False
|
||||
cursorPos = cursor.copy()
|
||||
try:
|
||||
attribute = self.getAttributeByXY( cursorPos['x'], cursorPos['y'])
|
||||
attribute = self.getAttributeByXY(cursorPos['x'], cursorPos['y'])
|
||||
|
||||
if update:
|
||||
self.setLastCursorAttribute(attribute)
|
||||
@ -118,7 +142,7 @@ class attributeManager():
|
||||
return False
|
||||
return True
|
||||
|
||||
def formatAttributes(self, attribute, attributeFormatString = ''):
|
||||
def formatAttributes(self, attribute, attributeFormatString=''):
|
||||
# "black",
|
||||
# "red",
|
||||
# "green",
|
||||
@ -140,7 +164,8 @@ class attributeManager():
|
||||
# "fontsieze"
|
||||
# "fontfamily"
|
||||
if attributeFormatString == '':
|
||||
attributeFormatString = self.env['runtime']['settingsManager'].getSetting('general', 'attributeFormatString')
|
||||
attributeFormatString = self.env['runtime']['settingsManager'].getSetting(
|
||||
'general', 'attributeFormatString')
|
||||
if not attributeFormatString:
|
||||
return ''
|
||||
if attributeFormatString == '':
|
||||
@ -152,20 +177,25 @@ class attributeManager():
|
||||
|
||||
# 0 FG color (name)
|
||||
try:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFGColor', _(attribute[0]))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFGColor', _(attribute[0]))
|
||||
except Exception as e:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFGColor', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFGColor', '')
|
||||
|
||||
# 1 BG color (name)
|
||||
try:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirBGColor', _(attribute[1]))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirBGColor', _(attribute[1]))
|
||||
except Exception as e:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirBGColor', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirBGColor', '')
|
||||
|
||||
# 2 bold (True/ False)
|
||||
try:
|
||||
if attribute[2]:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirBold', _('bold'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirBold', _('bold'))
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirBold', '')
|
||||
@ -173,72 +203,94 @@ class attributeManager():
|
||||
# 3 italics (True/ False)
|
||||
try:
|
||||
if attribute[3]:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirItalics', _('italic'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirItalics', _('italic'))
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirItalics', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirItalics', '')
|
||||
|
||||
# 4 underline (True/ False)
|
||||
try:
|
||||
if attribute[4]:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirUnderline', _('underline'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirUnderline', _('underline'))
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirUnderline', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirUnderline', '')
|
||||
|
||||
# 5 strikethrough (True/ False)
|
||||
try:
|
||||
if attribute[5]:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirStrikethrough', _('strikethrough'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirStrikethrough', _('strikethrough'))
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirStrikethrough', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirStrikethrough', '')
|
||||
|
||||
# 6 reverse (True/ False)
|
||||
try:
|
||||
if attribute[6]:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirReverse', _('reverse'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirReverse', _('reverse'))
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirReverse', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirReverse', '')
|
||||
|
||||
# 7 blink (True/ False)
|
||||
try:
|
||||
if attribute[7]:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirBlink', _('blink'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirBlink', _('blink'))
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirBlink', '')
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirBlink', '')
|
||||
|
||||
# 8 font size (int/ string)
|
||||
try:
|
||||
try:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFontSize', int(attribute[8]))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFontSize', int(attribute[8]))
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut('attributeManager formatAttributeToString: Error formatting font size as int: ' + str(e), debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
'attributeManager formatAttributeToString: Error formatting font size as int: ' +
|
||||
str(e),
|
||||
debug.debugLevel.ERROR)
|
||||
try:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFontSize', str(attribute[8]))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFontSize', str(attribute[8]))
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut('attributeManager formatAttributeToString: Error formatting font size as string: ' + str(e), debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
'attributeManager formatAttributeToString: Error formatting font size as string: ' +
|
||||
str(e),
|
||||
debug.debugLevel.ERROR)
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFontSize', _('default'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFontSize', _('default'))
|
||||
|
||||
# 9 font family (string)
|
||||
try:
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFont', attribute[9])
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFont', attribute[9])
|
||||
except Exception as e:
|
||||
pass
|
||||
attributeFormatString = attributeFormatString.replace('fenrirFont', _('default'))
|
||||
attributeFormatString = attributeFormatString.replace(
|
||||
'fenrirFont', _('default'))
|
||||
|
||||
return attributeFormatString
|
||||
|
||||
def trackHighlights(self):
|
||||
"""
|
||||
Detects text with changed attributes (highlighting) between screen updates.
|
||||
|
||||
|
||||
This is crucial for screen readers to announce when text becomes highlighted,
|
||||
selected, or changes visual emphasis (bold, reverse video, color changes, etc.)
|
||||
|
||||
|
||||
Returns:
|
||||
tuple: (highlighted_text, cursor_position)
|
||||
- highlighted_text: string of characters that gained highlighting
|
||||
@ -246,60 +298,73 @@ class attributeManager():
|
||||
"""
|
||||
result = ''
|
||||
currCursor = None
|
||||
|
||||
|
||||
# Early exit conditions - no attribute comparison possible
|
||||
if self.prevAttributes == None:
|
||||
if self.prevAttributes is None:
|
||||
# First screen load - no previous attributes to compare against
|
||||
return result, currCursor
|
||||
return result, currCursor
|
||||
if self.prevAttributes == self.currAttributes:
|
||||
# No attribute changes detected
|
||||
return result, currCursor
|
||||
if self.currAttributes == None:
|
||||
return result, currCursor
|
||||
if self.currAttributes is None:
|
||||
# Error condition - current attributes missing
|
||||
return result, currCursor
|
||||
return result, currCursor
|
||||
if len(self.currAttributes) == 0:
|
||||
# Special case for PTY environments with no text content
|
||||
return result, currCursor
|
||||
|
||||
return result, currCursor
|
||||
|
||||
# Get current screen text to correlate with attribute changes
|
||||
text = self.env['runtime']['screenManager'].getScreenText()
|
||||
textLines = text.split('\n')
|
||||
|
||||
# Sanity check: text lines must match attribute array dimensions
|
||||
if len(textLines) != len(self.currAttributes):
|
||||
return result, currCursor
|
||||
|
||||
return result, currCursor
|
||||
|
||||
# Compare attributes line by line, character by character
|
||||
for line in range(len(self.prevAttributes)):
|
||||
if self.prevAttributes[line] != self.currAttributes[line]:
|
||||
# This line has attribute changes - examine each character position
|
||||
# This line has attribute changes - examine each character
|
||||
# position
|
||||
for column in range(len(self.prevAttributes[line])):
|
||||
if self.prevAttributes[line][column] == self.currAttributes[line][column]:
|
||||
# No change at this position
|
||||
continue
|
||||
# Attribute changed at this position - check if it's worth announcing
|
||||
if self.isUsefulForTracking(line, column, self.currAttributes, self.prevAttributes):
|
||||
# First highlighted character becomes cursor position for navigation
|
||||
# Attribute changed at this position - check if it's worth
|
||||
# announcing
|
||||
if self.isUsefulForTracking(
|
||||
line, column, self.currAttributes, self.prevAttributes):
|
||||
# First highlighted character becomes cursor position
|
||||
# for navigation
|
||||
if not currCursor:
|
||||
currCursor = {'x': column, 'y': line}
|
||||
# Accumulate highlighted characters
|
||||
result += textLines[line][column]
|
||||
# Add space between lines of highlighted text for speech clarity
|
||||
# Add space between lines of highlighted text for speech
|
||||
# clarity
|
||||
result += ' '
|
||||
return result, currCursor
|
||||
def isUsefulForTracking(self, line, column, currAttributes, prevAttributes, attribute=1 , mode = 'zaxe'):
|
||||
|
||||
def isUsefulForTracking(
|
||||
self,
|
||||
line,
|
||||
column,
|
||||
currAttributes,
|
||||
prevAttributes,
|
||||
attribute=1,
|
||||
mode='zaxe'):
|
||||
"""
|
||||
Determines if an attribute change at a specific position is worth announcing.
|
||||
|
||||
|
||||
This prevents announcing every minor attribute change and focuses on meaningful
|
||||
highlighting that indicates user selections, focus changes, or important emphasis.
|
||||
|
||||
|
||||
Args:
|
||||
line, column: Position of the attribute change
|
||||
currAttributes, prevAttributes: Current and previous attribute arrays
|
||||
attribute: Which attribute to examine (1=background color by default)
|
||||
mode: Detection algorithm ('zaxe', 'default', 'barrier')
|
||||
|
||||
|
||||
Returns:
|
||||
bool: True if this attribute change should be announced to user
|
||||
"""
|
||||
@ -307,37 +372,37 @@ class attributeManager():
|
||||
if len(currAttributes) <= 3:
|
||||
return False
|
||||
if line < 0:
|
||||
return False
|
||||
return False
|
||||
if line > len(currAttributes):
|
||||
return False
|
||||
|
||||
|
||||
useful = False
|
||||
|
||||
|
||||
if mode == 'default':
|
||||
# Simple mode: announce any non-default attribute
|
||||
useful = not self.isDefaultAttribute(currAttributes[line][column])
|
||||
|
||||
|
||||
elif (mode == 'zaxe') or (mode == ''):
|
||||
# Context-aware mode: only announce attributes that stand out from surroundings
|
||||
# This prevents announcing entire blocks of highlighted text character by character
|
||||
# by checking if the attribute differs from adjacent lines
|
||||
|
||||
|
||||
if line == 0:
|
||||
# Top line: compare against lines below
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and \
|
||||
(currAttributes[line][column][attribute] != currAttributes[line + 2][column][attribute])
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and (
|
||||
currAttributes[line][column][attribute] != currAttributes[line + 2][column][attribute])
|
||||
elif line >= len(prevAttributes):
|
||||
# Bottom line: compare against lines above
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute]) and \
|
||||
(currAttributes[line][column][attribute] != currAttributes[line - 2][column][attribute])
|
||||
# Bottom line: compare against lines above
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute]) and (
|
||||
currAttributes[line][column][attribute] != currAttributes[line - 2][column][attribute])
|
||||
else:
|
||||
# Middle lines: compare against both directions
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and \
|
||||
(currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute])
|
||||
|
||||
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and (
|
||||
currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute])
|
||||
|
||||
elif mode == 'barrier':
|
||||
# Barrier mode: future enhancement for detecting screen boundaries/separators
|
||||
# Barrier mode: future enhancement for detecting screen
|
||||
# boundaries/separators
|
||||
useful = True
|
||||
|
||||
return useful
|
||||
|
||||
|
Reference in New Issue
Block a user