From 87792974245e20883e05ece13bc98d39160a10c6 Mon Sep 17 00:00:00 2001 From: Ben Lipsey Date: Sat, 12 Apr 2025 21:19:44 -0700 Subject: [PATCH] current state --- contact/ui/control_ui.py | 32 +++++++++++++++++++++++++------- contact/ui/default_config.py | 10 +++++----- contact/ui/dialog.py | 2 +- contact/ui/menus.py | 23 +++++++++++++++++------ contact/ui/splash.py | 2 +- contact/ui/user_config.py | 30 +++++++++++++++++++++--------- 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/contact/ui/control_ui.py b/contact/ui/control_ui.py index 8b96336..5a197b1 100644 --- a/contact/ui/control_ui.py +++ b/contact/ui/control_ui.py @@ -38,6 +38,9 @@ config_folder = os.path.join(locals_dir, "node-configs") # Load translations field_mapping, help_text = parse_ini_file(translation_file) +# Aliases +Segment = tuple[str, str, bool, bool] +WrappedLine = list[Segment] def display_menu(state: MenuState) -> tuple[object, object]: # curses.window or pad types @@ -183,7 +186,7 @@ def get_wrapped_help_text( selected_option: str | None, width: int, max_lines: int -): +) -> list[WrappedLine]: """Fetches and formats help text for display, ensuring it fits within the allowed lines.""" full_help_key = '.'.join(transformed_path + [selected_option]) if selected_option else None @@ -202,7 +205,7 @@ def get_wrapped_help_text( r'\\033\[4m(.*?)\\033\[0m': ('settings_default', False, True) # Underline } - def extract_ansi_segments(text): + def extract_ansi_segments(text: str) -> list[Segment]: """Extracts and replaces ANSI color codes, ensuring spaces are preserved.""" matches = [] last_pos = 0 @@ -232,7 +235,7 @@ def get_wrapped_help_text( return matches - def wrap_ansi_text(segments, wrap_width): + def wrap_ansi_text(segments: list[Segment], wrap_width: int) -> list[WrappedLine]: """Wraps text while preserving ANSI formatting and spaces.""" wrapped_lines = [] line_buffer = [] @@ -275,7 +278,17 @@ def get_wrapped_help_text( return wrapped_help -def move_highlight(old_idx, options, menu_win, menu_pad, help_win, help_text, max_help_lines, state): +def move_highlight( + old_idx: int, + options: list[str], + menu_win: object, + menu_pad: object, + help_win: object, + help_text: dict[str, str], + max_help_lines: int, + state: MenuState +) -> None: + if old_idx == state.selected_index: # No-op return @@ -323,7 +336,12 @@ def move_highlight(old_idx, options, menu_win, menu_pad, help_win, help_text, ma draw_arrows(menu_win, visible_height, max_index, state) -def draw_arrows(win, visible_height, max_index, state): +def draw_arrows( + win: object, + visible_height: int, + max_index: int, + state: MenuState +) -> None: # vh = visible_height + (1 if show_save_option else 0) mi = max_index - (2 if state.show_save_option else 0) @@ -340,7 +358,7 @@ def draw_arrows(win, visible_height, max_index, state): win.addstr(visible_height + 3, 2, " ", get_color("settings_default")) -def settings_menu(stdscr, interface): +def settings_menu(stdscr: object, interface: object) -> None: curses.update_lines_cols() menu = generate_menu_from_protobuf(interface) @@ -674,7 +692,7 @@ def settings_menu(stdscr, interface): menu_win.refresh() break -def set_region(interface): +def set_region(interface: object) -> None: node = interface.getNode('^local') device_config = node.localConfig lora_descriptor = device_config.lora.DESCRIPTOR diff --git a/contact/ui/default_config.py b/contact/ui/default_config.py index 370f5f5..20982b7 100644 --- a/contact/ui/default_config.py +++ b/contact/ui/default_config.py @@ -11,11 +11,11 @@ json_file_path = os.path.join(parent_dir, "config.json") log_file_path = os.path.join(parent_dir, "client.log") db_file_path = os.path.join(parent_dir, "client.db") -def format_json_single_line_arrays(data, indent=4): +def format_json_single_line_arrays(data: dict[str, object], indent: int = 4) -> str: """ Formats JSON with arrays on a single line while keeping other elements properly indented. """ - def format_value(value, current_indent): + def format_value(value: object, current_indent: int) -> str: if isinstance(value, dict): items = [] for key, val in value.items(): @@ -31,7 +31,7 @@ def format_json_single_line_arrays(data, indent=4): return format_value(data, indent) # Recursive function to check and update nested dictionaries -def update_dict(default, actual): +def update_dict(default: dict[str, object], actual: dict[str, object]) -> bool: updated = False for key, value in default.items(): if key not in actual: @@ -42,7 +42,7 @@ def update_dict(default, actual): updated = update_dict(value, actual[key]) or updated return updated -def initialize_config(): +def initialize_config() -> dict[str, object]: COLOR_CONFIG_DARK = { "default": ["white", "black"], "background": [" ", "black"], @@ -161,7 +161,7 @@ def initialize_config(): return loaded_config -def assign_config_variables(loaded_config): +def assign_config_variables(loaded_config: dict[str, object]) -> None: # Assign values to local variables global db_file_path, log_file_path, message_prefix, sent_message_prefix diff --git a/contact/ui/dialog.py b/contact/ui/dialog.py index ea39b43..7955e8b 100644 --- a/contact/ui/dialog.py +++ b/contact/ui/dialog.py @@ -1,7 +1,7 @@ import curses from contact.ui.colors import get_color -def dialog(stdscr, title, message): +def dialog(stdscr: curses.window, title: str, message: str) -> None: height, width = stdscr.getmaxyx() # Calculate dialog dimensions diff --git a/contact/ui/menus.py b/contact/ui/menus.py index 2ec0fa9..47fad4a 100644 --- a/contact/ui/menus.py +++ b/contact/ui/menus.py @@ -1,19 +1,27 @@ -from collections import OrderedDict -from meshtastic.protobuf import config_pb2, module_config_pb2, channel_pb2 -import logging import base64 +import logging import os +from collections import OrderedDict + +from typing import Any + +from google.protobuf.message import Message +from meshtastic.protobuf import channel_pb2, config_pb2, module_config_pb2 + locals_dir = os.path.dirname(os.path.abspath(__file__)) translation_file = os.path.join(locals_dir, "localisations", "en.ini") -def encode_if_bytes(value): +def encode_if_bytes(value: Any) -> str | Any: """Encode byte values to base64 string.""" if isinstance(value, bytes): return base64.b64encode(value).decode('utf-8') return value -def extract_fields(message_instance, current_config=None): +def extract_fields( + message_instance: Message, + current_config: Message | dict[str, Any] | None = None +) -> dict[str, Any]: if isinstance(current_config, dict): # Handle dictionaries return {key: (None, encode_if_bytes(current_config.get(key, "Not Set"))) for key in current_config} @@ -47,7 +55,10 @@ def extract_fields(message_instance, current_config=None): menu[field.name] = (field, encode_if_bytes(current_value)) return menu -def generate_menu_from_protobuf(interface): +def generate_menu_from_protobuf(interface: object) -> dict[str, Any]: + """ + Builds the full settings menu structure from the protobuf definitions. + """ menu_structure = {"Main Menu": {}} # Add User Settings diff --git a/contact/ui/splash.py b/contact/ui/splash.py index 403bc34..3473911 100644 --- a/contact/ui/splash.py +++ b/contact/ui/splash.py @@ -1,7 +1,7 @@ import curses from contact.ui.colors import get_color -def draw_splash(stdscr): +def draw_splash(stdscr: object) -> None: curses.curs_set(0) stdscr.clear() diff --git a/contact/ui/user_config.py b/contact/ui/user_config.py index eda9fd4..0b665bc 100644 --- a/contact/ui/user_config.py +++ b/contact/ui/user_config.py @@ -1,6 +1,7 @@ import os import json import curses +from typing import Any from contact.ui.colors import get_color, setup_colors, COLOR_MAP from contact.ui.default_config import format_json_single_line_arrays, loaded_config from contact.utilities.input_handlers import get_list_input @@ -9,8 +10,7 @@ width = 80 save_option = "Save Changes" sensitive_settings = [] -def edit_color_pair(key, current_value): - +def edit_color_pair(key: str, current_value: list[str]) -> list[str]: """ Allows the user to select a foreground and background color for a key. """ @@ -20,7 +20,7 @@ def edit_color_pair(key, current_value): return [fg_color, bg_color] -def edit_value(key, current_value, state): +def edit_value(key: str, current_value: str) -> str: height = 10 input_width = width - 16 # Allow space for "New Value: " @@ -96,7 +96,7 @@ def edit_value(key, current_value, state): return user_input if user_input else current_value -def display_menu(state): +def display_menu(state: Any) -> tuple[Any, Any, list[str]]: """ Render the configuration menu with a Save button directly added to the window. """ @@ -167,7 +167,14 @@ def display_menu(state): return menu_win, menu_pad, options -def move_highlight(old_idx, options, menu_win, menu_pad, state): +def move_highlight( + old_idx: int, + options: list[str], + menu_win: curses.window, + menu_pad: curses.window, + state: Any +) -> None: + if old_idx == state.selected_index: # No-op return @@ -209,7 +216,12 @@ def move_highlight(old_idx, options, menu_win, menu_pad, state): draw_arrows(menu_win, visible_height, max_index, state) -def draw_arrows(win, visible_height, max_index, state): +def draw_arrows( + win: curses.window, + visible_height: int, + max_index: int, + state: any +) -> None: mi = max_index - (2 if state.show_save_option else 0) @@ -225,7 +237,7 @@ def draw_arrows(win, visible_height, max_index, state): win.addstr(visible_height + 3, 2, " ", get_color("settings_default")) -def json_editor(stdscr, state): +def json_editor(stdscr: curses.window, state: Any) -> None: state.selected_index = 0 # Track the selected option @@ -351,13 +363,13 @@ def json_editor(stdscr, state): break -def save_json(file_path, data): +def save_json(file_path: str, data: dict[str, Any]) -> None: formatted_json = format_json_single_line_arrays(data) with open(file_path, "w", encoding="utf-8") as f: f.write(formatted_json) setup_colors(reinit=True) -def main(stdscr): +def main(stdscr: curses.window) -> None: from contact.ui.ui_state import MenuState state = MenuState()