Initial table mode added. Probably bugs.
This commit is contained in:
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
from fenrirscreenreader.utils import word_utils
|
from fenrirscreenreader.utils import word_utils
|
||||||
|
from fenrirscreenreader.core import debug
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
@ -38,14 +39,42 @@ class command:
|
|||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
|
||||||
if curr_word.isspace():
|
# Check if we're in table mode and provide table-aware output
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
_("blank"), interrupt=True, flush=False
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
)
|
f"review_curr_word: is_table_mode={is_table_mode}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
if is_table_mode:
|
||||||
|
# Get current cell info using internal column tracking
|
||||||
|
table_info = self.env["runtime"]["TableManager"].get_current_table_cell_info()
|
||||||
|
if table_info:
|
||||||
|
# Announce with table context - cell content first, then header
|
||||||
|
output_text = f"{table_info['cell_content']} {table_info['column_header']}"
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return # Exit early for table mode
|
||||||
|
else:
|
||||||
|
# Fallback to regular word announcement
|
||||||
|
if curr_word.isspace():
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("blank"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
curr_word, interrupt=True, flush=False
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
# Regular word announcement
|
||||||
curr_word, interrupt=True, flush=False
|
if curr_word.isspace():
|
||||||
)
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("blank"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
curr_word, interrupt=True, flush=False
|
||||||
|
)
|
||||||
if end_of_screen:
|
if end_of_screen:
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"review", "end_of_screen"
|
"review", "end_of_screen"
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
from fenrirscreenreader.utils import word_utils
|
from fenrirscreenreader.utils import word_utils
|
||||||
|
from fenrirscreenreader.core import debug
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
@ -22,6 +23,40 @@ class command:
|
|||||||
return _("moves review to the next word ")
|
return _("moves review to the next word ")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Check if we're in table mode FIRST - before any word navigation
|
||||||
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"review_next_word: is_table_mode={is_table_mode}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
if is_table_mode:
|
||||||
|
table_info = self.env["runtime"]["TableManager"].move_to_next_column()
|
||||||
|
if table_info and table_info.get("at_end"):
|
||||||
|
# Stay on current cell and play end of line sound
|
||||||
|
current_info = table_info["current_info"]
|
||||||
|
if current_info:
|
||||||
|
output_text = f"{current_info['cell_content']}"
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
# Play end of line sound
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("end of line"), interrupt=False, sound_icon="EndOfLine"
|
||||||
|
)
|
||||||
|
elif table_info:
|
||||||
|
# Normal column navigation - announce cell content with column info
|
||||||
|
output_text = f"{table_info['cell_content']} {table_info['column_header']}"
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# No table info available, but still in table mode
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("no table data"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return # ALWAYS return in table mode to prevent regular word navigation
|
||||||
|
|
||||||
|
# Regular word navigation (only when NOT in table mode)
|
||||||
self.env["screen"]["oldCursorReview"] = self.env["screen"][
|
self.env["screen"]["oldCursorReview"] = self.env["screen"][
|
||||||
"newCursorReview"
|
"newCursorReview"
|
||||||
]
|
]
|
||||||
@ -42,6 +77,7 @@ class command:
|
|||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Regular word announcement
|
||||||
if next_word.isspace():
|
if next_word.isspace():
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("blank"), interrupt=True, flush=False
|
_("blank"), interrupt=True, flush=False
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
from fenrirscreenreader.utils import word_utils
|
from fenrirscreenreader.utils import word_utils
|
||||||
|
from fenrirscreenreader.core import debug
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
@ -22,6 +23,36 @@ class command:
|
|||||||
return _("moves review focus to the previous word ")
|
return _("moves review focus to the previous word ")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
# Check if we're in table mode FIRST - before any word navigation
|
||||||
|
is_table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"review_prev_word: is_table_mode={is_table_mode}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
if is_table_mode:
|
||||||
|
table_info = self.env["runtime"]["TableManager"].move_to_prev_column()
|
||||||
|
if table_info and table_info.get("at_start"):
|
||||||
|
# Stay on current cell at beginning of line
|
||||||
|
current_info = table_info["current_info"]
|
||||||
|
if current_info:
|
||||||
|
output_text = f"{current_info['cell_content']} {current_info['column_header']}"
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
elif table_info:
|
||||||
|
# Normal column navigation - announce cell content with column info
|
||||||
|
output_text = f"{table_info['cell_content']} {table_info['column_header']}"
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
output_text, interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# No table info available, but still in table mode
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("no table data"), interrupt=True, flush=False
|
||||||
|
)
|
||||||
|
return # ALWAYS return in table mode to prevent regular word navigation
|
||||||
|
|
||||||
|
# Regular word navigation (only when NOT in table mode)
|
||||||
self.env["runtime"][
|
self.env["runtime"][
|
||||||
"CursorManager"
|
"CursorManager"
|
||||||
].enter_review_mode_curr_text_cursor()
|
].enter_review_mode_curr_text_cursor()
|
||||||
@ -38,6 +69,7 @@ class command:
|
|||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Regular word announcement
|
||||||
if prev_word.isspace():
|
if prev_word.isspace():
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("blank"), interrupt=True, flush=False
|
_("blank"), interrupt=True, flush=False
|
||||||
|
@ -28,6 +28,20 @@ class command:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# If in table mode, set header row instead of regular mark
|
||||||
|
if self.env["runtime"]["TableManager"].is_table_mode():
|
||||||
|
success = self.env["runtime"]["TableManager"].set_header_row_from_cursor()
|
||||||
|
if success:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("header row set"), sound_icon="PlaceStartMark", interrupt=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("could not set header row"), interrupt=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Regular mark functionality
|
||||||
curr_mark = self.env["runtime"]["CursorManager"].set_mark()
|
curr_mark = self.env["runtime"]["CursorManager"].set_mark()
|
||||||
if curr_mark == 1:
|
if curr_mark == 1:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
from fenrirscreenreader.core import debug
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
@ -21,26 +22,56 @@ class command:
|
|||||||
return _("enables or disables tracking of highlighted text")
|
return _("enables or disables tracking of highlighted text")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
curr_mode = self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
highlight_mode = self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"focus", "highlight"
|
"focus", "highlight"
|
||||||
)
|
)
|
||||||
|
cursor_mode = self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"focus", "cursor"
|
||||||
|
)
|
||||||
|
table_mode = self.env["runtime"]["TableManager"].is_table_mode()
|
||||||
|
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"toggle_highlight_tracking: highlight={highlight_mode}, cursor={cursor_mode}, table={table_mode}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
# Cycle through modes: highlight → cursor → table → highlight
|
||||||
"focus", "highlight", str(not curr_mode)
|
if highlight_mode and not table_mode:
|
||||||
)
|
# Switch to cursor mode
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
"focus", "cursor", str(curr_mode)
|
"focus", "highlight", "False"
|
||||||
)
|
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
|
||||||
"focus", "highlight"
|
|
||||||
):
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
|
||||||
_("highlight tracking"), sound_icon="", interrupt=True
|
|
||||||
)
|
)
|
||||||
else:
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
|
"focus", "cursor", "True"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["TableManager"].set_table_mode(False)
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("cursor tracking"), sound_icon="", interrupt=True
|
_("cursor tracking"), sound_icon="", interrupt=True
|
||||||
)
|
)
|
||||||
|
elif cursor_mode and not table_mode:
|
||||||
|
# Switch to table mode
|
||||||
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
|
"focus", "highlight", "False"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
|
"focus", "cursor", "False"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["TableManager"].set_table_mode(True)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("table mode"), sound_icon="", interrupt=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Switch to highlight mode (default) - also handles stuck table mode
|
||||||
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
|
"focus", "highlight", "True"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
|
"focus", "cursor", "False"
|
||||||
|
)
|
||||||
|
self.env["runtime"]["TableManager"].set_table_mode(False)
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
_("highlight tracking"), sound_icon="", interrupt=True
|
||||||
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
@ -56,6 +56,17 @@ class ScreenManager:
|
|||||||
def get_screen_text(self):
|
def get_screen_text(self):
|
||||||
return self.currScreenText
|
return self.currScreenText
|
||||||
|
|
||||||
|
def get_line_text(self, line_number):
|
||||||
|
"""Get text from a specific line (0-based index)"""
|
||||||
|
if not self.env["screen"]["new_content_text"]:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
lines = self.env["screen"]["new_content_text"].split("\n")
|
||||||
|
if line_number < 0 or line_number >= len(lines):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return lines[line_number]
|
||||||
|
|
||||||
def get_curr_screen(self):
|
def get_curr_screen(self):
|
||||||
try:
|
try:
|
||||||
self.env["runtime"]["ScreenDriver"].get_curr_screen()
|
self.env["runtime"]["ScreenDriver"].get_curr_screen()
|
||||||
|
@ -16,6 +16,10 @@ class TableManager:
|
|||||||
self.noOfHeadLineColumns = 0
|
self.noOfHeadLineColumns = 0
|
||||||
self.headColumnSep = ""
|
self.headColumnSep = ""
|
||||||
self.rowColumnSep = ""
|
self.rowColumnSep = ""
|
||||||
|
self.headerRow = []
|
||||||
|
self.tableMode = False
|
||||||
|
self.currentColumn = 0
|
||||||
|
self.currentRow = 0
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
@ -23,6 +27,340 @@ class TableManager:
|
|||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def is_table_mode(self):
|
||||||
|
return self.tableMode
|
||||||
|
|
||||||
|
def set_table_mode(self, active):
|
||||||
|
self.tableMode = active
|
||||||
|
if not active:
|
||||||
|
self.clear_header_row()
|
||||||
|
else:
|
||||||
|
# Initialize current position when entering table mode
|
||||||
|
self.sync_table_position_from_cursor()
|
||||||
|
|
||||||
|
def set_header_row_from_cursor(self):
|
||||||
|
"""Set header row from current cursor position"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return False
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get the line text at cursor position
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Parse the line into columns
|
||||||
|
self.headerRow = self.parse_line_into_columns(line_text)
|
||||||
|
return len(self.headerRow) > 0
|
||||||
|
|
||||||
|
def clear_header_row(self):
|
||||||
|
"""Clear the stored header row"""
|
||||||
|
self.headerRow = []
|
||||||
|
|
||||||
|
def has_header_row(self):
|
||||||
|
"""Check if header row is set"""
|
||||||
|
return len(self.headerRow) > 0
|
||||||
|
|
||||||
|
def get_column_header(self, column_index):
|
||||||
|
"""Get header for a specific column (0-based)"""
|
||||||
|
if column_index < 0:
|
||||||
|
return f"Column {column_index + 1}"
|
||||||
|
|
||||||
|
if len(self.headerRow) == 0:
|
||||||
|
return f"Column {column_index + 1}"
|
||||||
|
|
||||||
|
if column_index >= len(self.headerRow):
|
||||||
|
return f"Column {column_index + 1}"
|
||||||
|
|
||||||
|
header = self.headerRow[column_index].strip()
|
||||||
|
if not header: # If header is empty
|
||||||
|
return f"Column {column_index + 1}"
|
||||||
|
|
||||||
|
return header
|
||||||
|
|
||||||
|
def get_column_count(self):
|
||||||
|
"""Get number of columns based on header row"""
|
||||||
|
return len(self.headerRow)
|
||||||
|
|
||||||
|
def parse_line_into_columns(self, line_text):
|
||||||
|
"""Parse a line into columns using various separators"""
|
||||||
|
if not line_text:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Try different separators in order of preference
|
||||||
|
separators = [',', '|', ';', '\t']
|
||||||
|
|
||||||
|
for sep in separators:
|
||||||
|
if sep in line_text:
|
||||||
|
columns = line_text.split(sep)
|
||||||
|
if len(columns) > 1:
|
||||||
|
return columns
|
||||||
|
|
||||||
|
# If no clear separator, try to detect aligned columns
|
||||||
|
return self.detect_aligned_columns(line_text)
|
||||||
|
|
||||||
|
def detect_aligned_columns(self, line_text):
|
||||||
|
"""Detect columns in space-aligned text"""
|
||||||
|
if not line_text:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Split on multiple spaces (2 or more)
|
||||||
|
parts = re.split(r' +', line_text)
|
||||||
|
if len(parts) > 1:
|
||||||
|
return parts
|
||||||
|
|
||||||
|
# Fallback: treat as single column
|
||||||
|
return [line_text]
|
||||||
|
|
||||||
|
def get_cell_content(self, line_text, column_index):
|
||||||
|
"""Get content of a specific cell in a line"""
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if column_index < 0 or column_index >= len(columns):
|
||||||
|
return ""
|
||||||
|
return columns[column_index].strip()
|
||||||
|
|
||||||
|
def get_table_cell_info(self, cursor_pos):
|
||||||
|
"""Get table cell information for speech output"""
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Find which column we're in based on cursor x position
|
||||||
|
column_index = self.get_column_index_from_x_position(line_text, cursor_pos["x"])
|
||||||
|
|
||||||
|
if column_index < 0 or column_index >= len(columns):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Use header if available, otherwise generic column name
|
||||||
|
if self.has_header_row():
|
||||||
|
column_header = self.get_column_header(column_index)
|
||||||
|
else:
|
||||||
|
column_header = f"Column {column_index + 1}"
|
||||||
|
|
||||||
|
return {
|
||||||
|
'column_index': column_index,
|
||||||
|
'column_header': column_header,
|
||||||
|
'cell_content': columns[column_index].strip(),
|
||||||
|
'total_columns': len(columns),
|
||||||
|
'row_number': cursor_pos["y"] + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_column_index_from_x_position(self, line_text, x_pos):
|
||||||
|
"""Determine which column an x position falls into"""
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Handle CSV/delimited text
|
||||||
|
if ',' in line_text or '|' in line_text or ';' in line_text or '\t' in line_text:
|
||||||
|
current_pos = 0
|
||||||
|
for i, column in enumerate(columns):
|
||||||
|
column_end = current_pos + len(column)
|
||||||
|
if current_pos <= x_pos <= column_end:
|
||||||
|
return i
|
||||||
|
current_pos = column_end + 1 # +1 for separator
|
||||||
|
else:
|
||||||
|
# Handle space-aligned text - find the column by position ranges
|
||||||
|
current_pos = 0
|
||||||
|
for i, column in enumerate(columns):
|
||||||
|
# Find where this column starts in the original text
|
||||||
|
if i == 0:
|
||||||
|
column_start = 0
|
||||||
|
else:
|
||||||
|
# Find the column by searching from current position
|
||||||
|
column_start = line_text.find(column.strip(), current_pos)
|
||||||
|
if column_start == -1:
|
||||||
|
column_start = current_pos
|
||||||
|
|
||||||
|
column_end = column_start + len(column.strip())
|
||||||
|
if column_start <= x_pos <= column_end:
|
||||||
|
return i
|
||||||
|
current_pos = column_end
|
||||||
|
|
||||||
|
# If past the end, return last column
|
||||||
|
return len(columns) - 1
|
||||||
|
|
||||||
|
def sync_table_position_from_cursor(self):
|
||||||
|
"""Sync internal table position from current cursor position"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if cursor_pos:
|
||||||
|
old_column = self.currentColumn
|
||||||
|
self.currentRow = cursor_pos["y"]
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if line_text:
|
||||||
|
self.currentColumn = self.get_column_index_from_x_position(line_text, cursor_pos["x"])
|
||||||
|
else:
|
||||||
|
self.currentColumn = 0
|
||||||
|
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"TableManager sync: old_column={old_column}, new_column={self.currentColumn}, cursor_x={cursor_pos['x']}, cursor_y={cursor_pos['y']}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
def move_to_next_column(self):
|
||||||
|
"""Move to next column in table"""
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"TableManager move_to_next_column: currentColumn={self.currentColumn}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"TableManager: line_text='{line_text}', columns={columns}, currentColumn={self.currentColumn}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
# Don't sync from cursor position - maintain our own tracking
|
||||||
|
# Check if we're already at the last column
|
||||||
|
if self.currentColumn >= len(columns) - 1:
|
||||||
|
# At end of line - return special indicator but keep position
|
||||||
|
return {"at_end": True, "current_info": self.get_table_cell_info_by_indices(cursor_pos["y"], self.currentColumn)}
|
||||||
|
|
||||||
|
# Move to next column
|
||||||
|
self.currentColumn += 1
|
||||||
|
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"TableManager: moved to column {self.currentColumn}",
|
||||||
|
debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return info for the new column without moving cursor position
|
||||||
|
return self.get_table_cell_info_by_indices(cursor_pos["y"], self.currentColumn)
|
||||||
|
|
||||||
|
def move_to_prev_column(self):
|
||||||
|
"""Move to previous column in table"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(cursor_pos["y"])
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Don't sync from cursor position - maintain our own tracking
|
||||||
|
# Check if we're already at the first column
|
||||||
|
if self.currentColumn <= 0:
|
||||||
|
# At beginning of line - return special indicator but keep position
|
||||||
|
return {"at_start": True, "current_info": self.get_table_cell_info_by_indices(cursor_pos["y"], self.currentColumn)}
|
||||||
|
|
||||||
|
# Move to previous column
|
||||||
|
self.currentColumn -= 1
|
||||||
|
|
||||||
|
# Return info for the new column without moving cursor position
|
||||||
|
return self.get_table_cell_info_by_indices(cursor_pos["y"], self.currentColumn)
|
||||||
|
|
||||||
|
def get_current_table_cell_info(self):
|
||||||
|
"""Get current table cell info using internal position tracking"""
|
||||||
|
if not self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
return None
|
||||||
|
|
||||||
|
cursor_pos = self.env["runtime"]["CursorManager"].get_review_or_text_cursor()
|
||||||
|
if not cursor_pos:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.get_table_cell_info_by_indices(cursor_pos["y"], self.currentColumn)
|
||||||
|
|
||||||
|
def get_table_cell_info_by_indices(self, row, column_index):
|
||||||
|
"""Get table cell info for specific row and column indices"""
|
||||||
|
line_text = self.env["runtime"]["ScreenManager"].get_line_text(row)
|
||||||
|
if not line_text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns or column_index < 0 or column_index >= len(columns):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Always get column header (fallback to generic if needed)
|
||||||
|
column_header = self.get_column_header(column_index)
|
||||||
|
|
||||||
|
cell_content = columns[column_index].strip()
|
||||||
|
if not cell_content: # Handle empty cells
|
||||||
|
cell_content = "blank"
|
||||||
|
|
||||||
|
return {
|
||||||
|
'column_index': column_index,
|
||||||
|
'column_header': column_header,
|
||||||
|
'cell_content': cell_content,
|
||||||
|
'total_columns': len(columns),
|
||||||
|
'row_number': row + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_column_start_position(self, line_text, column_index):
|
||||||
|
"""Get the starting x position of a specific column"""
|
||||||
|
columns = self.parse_line_into_columns(line_text)
|
||||||
|
if not columns or column_index < 0 or column_index >= len(columns):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# For CSV/delimited text - find the actual position in the original line
|
||||||
|
if ',' in line_text or '|' in line_text or ';' in line_text or '\t' in line_text:
|
||||||
|
# Determine the separator being used
|
||||||
|
separator = ','
|
||||||
|
if '|' in line_text and line_text.count('|') > line_text.count(','):
|
||||||
|
separator = '|'
|
||||||
|
elif ';' in line_text and line_text.count(';') > line_text.count(','):
|
||||||
|
separator = ';'
|
||||||
|
elif '\t' in line_text:
|
||||||
|
separator = '\t'
|
||||||
|
|
||||||
|
# Find the position by splitting and calculating
|
||||||
|
if column_index == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Count characters up to the target column
|
||||||
|
parts = line_text.split(separator)
|
||||||
|
position = 0
|
||||||
|
for i in range(column_index):
|
||||||
|
if i < len(parts):
|
||||||
|
position += len(parts[i]) + 1 # +1 for separator
|
||||||
|
return position
|
||||||
|
else:
|
||||||
|
# For space-aligned text, find the actual position of the column
|
||||||
|
if column_index == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Find the column text in the line
|
||||||
|
target_text = columns[column_index].strip()
|
||||||
|
search_start = 0
|
||||||
|
for i in range(column_index):
|
||||||
|
prev_text = columns[i].strip()
|
||||||
|
found_pos = line_text.find(prev_text, search_start)
|
||||||
|
if found_pos != -1:
|
||||||
|
search_start = found_pos + len(prev_text)
|
||||||
|
|
||||||
|
column_pos = line_text.find(target_text, search_start)
|
||||||
|
return column_pos if column_pos != -1 else search_start
|
||||||
|
|
||||||
def reset_table_mode(self):
|
def reset_table_mode(self):
|
||||||
self.set_head_line()
|
self.set_head_line()
|
||||||
|
|
||||||
@ -34,11 +372,21 @@ class TableManager:
|
|||||||
self.coun_no_of_head_columns()
|
self.coun_no_of_head_columns()
|
||||||
|
|
||||||
def coun_no_of_head_columns(self):
|
def coun_no_of_head_columns(self):
|
||||||
pass
|
if self.headLine and self.headColumnSep:
|
||||||
|
self.noOfHeadLineColumns = len(self.headLine.split(self.headColumnSep))
|
||||||
|
|
||||||
def search_for_head_column_sep(self, headLine):
|
def search_for_head_column_sep(self, headLine):
|
||||||
if " " in headLine:
|
"""Find the most likely column separator in a header line"""
|
||||||
return " "
|
separators = [',', '|', ';', '\t']
|
||||||
|
|
||||||
|
for sep in separators:
|
||||||
|
if sep in headLine:
|
||||||
|
return sep
|
||||||
|
|
||||||
|
# Check for multiple spaces (aligned columns)
|
||||||
|
if ' ' in headLine:
|
||||||
|
return ' '
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def set_row_column_sep(self, columnSep=""):
|
def set_row_column_sep(self, columnSep=""):
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2025.07.05"
|
version = "2025.07.07"
|
||||||
codeName = "testing"
|
codeName = "testing"
|
||||||
code_name = "testing"
|
code_name = "testing"
|
||||||
|
Reference in New Issue
Block a user