diff --git a/input_handlers.py b/input_handlers.py index 87196a4..e7cb0a1 100644 --- a/input_handlers.py +++ b/input_handlers.py @@ -2,7 +2,7 @@ import curses import ipaddress from ui.colors import get_color -def get_user_input(prompt): +def get_text_input(prompt): # Calculate the dynamic height and width for the input window height = 7 # Fixed height for input prompt width = 60 @@ -54,51 +54,6 @@ def get_user_input(prompt): input_win.refresh() return user_input -def get_bool_selection(message, current_value): - message = "Select True or False:" if None else message - cvalue = current_value - options = ["True", "False"] - selected_index = 0 if current_value == "True" else 1 - - height = 7 - width = 60 - start_y = (curses.LINES - height) // 2 - start_x = (curses.COLS - width) // 2 - - bool_win = curses.newwin(height, width, start_y, start_x) - bool_win.bkgd(get_color("background")) - bool_win.attrset(get_color("window_frame")) - bool_win.keypad(True) - bool_win.erase() - - bool_win.border() - bool_win.addstr(1, 2, message, get_color("settings_default", bold=True)) - - for idx, option in enumerate(options): - if idx == selected_index: - bool_win.addstr(idx + 3, 4, option, get_color("settings_default", reverse=True)) - else: - bool_win.addstr(idx + 3, 4, option, get_color("settings_default")) - - bool_win.refresh() - - while True: - key = bool_win.getch() - - if key == curses.KEY_UP: - if(selected_index > 0): - selected_index = selected_index - 1 - bool_win.chgat(1 + 3, 4, len(options[1]), get_color("settings_default")) - bool_win.chgat(0 + 3, 4, len(options[0]), get_color("settings_default", reverse = True)) - elif key == curses.KEY_DOWN: - if(selected_index < len(options) - 1): - selected_index = selected_index + 1 - bool_win.chgat(0 + 3, 4, len(options[0]), get_color("settings_default")) - bool_win.chgat(1 + 3, 4, len(options[1]), get_color("settings_default", reverse = True)) - elif key == ord('\n'): # Enter key - return options[selected_index] - elif key == 27 or key == curses.KEY_LEFT: # ESC or Left Arrow - return cvalue def get_repeated_input(current_value): cvalue = current_value @@ -142,69 +97,6 @@ def get_repeated_input(current_value): except ValueError: pass # Ignore invalid character inputs -def move_highlight(old_idx, new_idx, options, enum_win, enum_pad): - if old_idx == new_idx: - return # no-op - - enum_pad.chgat(old_idx, 0, enum_pad.getmaxyx()[1], get_color("settings_default")) - enum_pad.chgat(new_idx, 0, enum_pad.getmaxyx()[1], get_color("settings_default", reverse = True)) - - enum_win.refresh() - - start_index = max(0, new_idx - (enum_win.getmaxyx()[0] - 4)) - - enum_win.refresh() - enum_pad.refresh(start_index, 0, - enum_win.getbegyx()[0] + 2, enum_win.getbegyx()[1] + 4, - enum_win.getbegyx()[0] + enum_win.getmaxyx()[0] - 2, enum_win.getbegyx()[1] + 4 + enum_win.getmaxyx()[1] - 4) - -def get_enum_input(options, current_value): - selected_index = options.index(current_value) if current_value in options else 0 - - height = min(len(options) + 4, curses.LINES - 2) - width = 60 - start_y = (curses.LINES - height) // 2 - start_x = (curses.COLS - width) // 2 - - enum_win = curses.newwin(height, width, start_y, start_x) - enum_win.bkgd(get_color("background")) - enum_win.attrset(get_color("window_frame")) - enum_win.keypad(True) - - enum_pad = curses.newpad(len(options) + 1, width - 8) - enum_pad.bkgd(get_color("background")) - - enum_win.erase() - enum_win.border() - enum_win.addstr(1, 2, "Select an option:", get_color("settings_default", bold=True)) - - for idx, option in enumerate(options): - if idx == selected_index: - enum_pad.addstr(idx, 0, option.ljust(width - 8), get_color("settings_default", reverse=True)) - else: - enum_pad.addstr(idx, 0, option.ljust(width - 8), get_color("settings_default")) - - enum_win.refresh() - enum_pad.refresh(0, 0, - enum_win.getbegyx()[0] + 2, enum_win.getbegyx()[1] + 4, - enum_win.getbegyx()[0] + enum_win.getmaxyx()[0] - 2, enum_win.getbegyx()[1] + enum_win.getmaxyx()[1] - 4) - - while True: - key = enum_win.getch() - - if key == curses.KEY_UP: - old_selected_index = selected_index - selected_index = max(0, selected_index - 1) - move_highlight(old_selected_index, selected_index, options, enum_win, enum_pad) - elif key == curses.KEY_DOWN: - old_selected_index = selected_index - selected_index = min(len(options) - 1, selected_index + 1) - move_highlight(old_selected_index, selected_index, options, enum_win, enum_pad) - elif key == ord('\n'): # Enter key - return options[selected_index] - elif key == 27 or key == curses.KEY_LEFT: # ESC or Left Arrow - return current_value - def get_fixed32_input(current_value): cvalue = current_value @@ -261,10 +153,9 @@ def get_fixed32_input(current_value): pass # Ignore invalid inputs - -def select_from_list(prompt, current_option, list_options): +def get_list_input(prompt, current_option, list_options): """ - Displays a scrollable list of list_options for the user to choose from using a pad. + Displays a scrollable list of list_options for the user to choose from. """ selected_index = list_options.index(current_option) if current_option in list_options else 0 @@ -282,7 +173,7 @@ def select_from_list(prompt, current_option, list_options): list_pad.bkgd(get_color("background")) # Render header - list_win.clear() + list_win.erase() list_win.border() list_win.addstr(1, 2, prompt, get_color("settings_default", bold=True)) @@ -303,28 +194,32 @@ def select_from_list(prompt, current_option, list_options): key = list_win.getch() if key == curses.KEY_UP: - - if selected_index > 0: - selected_index -= 1 - + old_selected_index = selected_index + selected_index = max(0, selected_index - 1) + move_highlight(old_selected_index, selected_index, list_options, list_win, list_pad) elif key == curses.KEY_DOWN: - if selected_index < len(list_options) - 1: - selected_index += 1 - - elif key == curses.KEY_RIGHT or key == ord('\n'): + old_selected_index = selected_index + selected_index = min(len(list_options) - 1, selected_index + 1) + move_highlight(old_selected_index, selected_index, list_options, list_win, list_pad) + elif key == ord('\n'): # Enter key return list_options[selected_index] - - elif key == curses.KEY_LEFT or key == 27: # ESC key + elif key == 27 or key == curses.KEY_LEFT: # ESC or Left Arrow return current_option - # Refresh the pad with updated selection and scroll offset - for idx, color in enumerate(list_options): - if idx == selected_index: - list_pad.addstr(idx, 0, color.ljust(width - 8), get_color("settings_default", reverse=True)) - else: - list_pad.addstr(idx, 0, color.ljust(width - 8), get_color("settings_default")) - list_win.refresh() - list_pad.refresh(0, 0, - list_win.getbegyx()[0] + 3, list_win.getbegyx()[1] + 4, - list_win.getbegyx()[0] + list_win.getmaxyx()[0] - 2, list_win.getbegyx()[1] + list_win.getmaxyx()[1] - 4) +def move_highlight(old_idx, new_idx, options, enum_win, enum_pad): + if old_idx == new_idx: + return # no-op + + enum_pad.chgat(old_idx, 0, enum_pad.getmaxyx()[1], get_color("settings_default")) + enum_pad.chgat(new_idx, 0, enum_pad.getmaxyx()[1], get_color("settings_default", reverse = True)) + + enum_win.refresh() + + start_index = max(0, new_idx - (enum_win.getmaxyx()[0] - 4)) + + enum_win.refresh() + enum_pad.refresh(start_index, 0, + enum_win.getbegyx()[0] + 3, enum_win.getbegyx()[1] + 4, + enum_win.getbegyx()[0] + enum_win.getmaxyx()[0] - 2, enum_win.getbegyx()[1] + 4 + enum_win.getmaxyx()[1] - 4) + \ No newline at end of file diff --git a/settings.py b/settings.py index 3c94441..11527dc 100644 --- a/settings.py +++ b/settings.py @@ -4,7 +4,7 @@ import os from save_to_radio import save_changes from utilities.config_io import config_export, config_import -from input_handlers import get_bool_selection, get_repeated_input, get_user_input, get_enum_input, get_fixed32_input, select_from_list +from input_handlers import get_repeated_input, get_text_input, get_fixed32_input, get_list_input from ui.menus import generate_menu_from_protobuf from ui.colors import setup_colors, get_color from utilities.arg_parser import setup_parser @@ -171,7 +171,7 @@ def settings_menu(stdscr, interface): elif selected_option == "Export Config": - filename = get_user_input("Enter a filename for the config file") + filename = get_text_input("Enter a filename for the config file") if not filename: logging.warning("Export aborted: No filename provided.") @@ -187,8 +187,8 @@ def settings_menu(stdscr, interface): yaml_file_path = os.path.join(app_directory, config_folder, filename) if os.path.exists(yaml_file_path): - overwrite = get_bool_selection(f"{filename} already exists. Overwrite?", None) - if overwrite == "False": + overwrite = get_list_input(f"{filename} already exists. Overwrite?", None, ["Yes", "No"]) + if overwrite == "Yes": logging.info("Export cancelled: User chose not to overwrite.") continue # Return to menu os.makedirs(os.path.dirname(yaml_file_path), exist_ok=True) @@ -204,50 +204,44 @@ def settings_menu(stdscr, interface): logging.error(f"Unexpected error: {e}") continue - - - elif selected_option == "Load Config": - app_directory = os.path.dirname(os.path.abspath(__file__)) config_folder = "node-configs" folder_path = os.path.join(app_directory, config_folder) file_list = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] - filename = select_from_list("Choose a config file", None, file_list) + filename = get_list_input("Choose a config file", None, file_list) if filename: file_path = os.path.join(app_directory, config_folder, filename) - overwrite = get_bool_selection(f"Are you sure you want to load {filename}?", None) - if overwrite == "True": + overwrite = get_list_input(f"Are you sure you want to load {filename}?", None, ["Yes", "No"]) + if overwrite == "Yes": config_import(globals.interface, file_path) break continue - - elif selected_option == "Reboot": - confirmation = get_bool_selection("Are you sure you want to Reboot?", 0) - if confirmation == "True": + confirmation = get_list_input("Are you sure you want to Reboot?", None, ["Yes", "No"]) + if confirmation == "Yes": interface.localNode.reboot() logging.info(f"Node Reboot Requested by menu") break continue elif selected_option == "Reset Node DB": - confirmation = get_bool_selection("Are you sure you want to Reset Node DB?", 0) - if confirmation == "True": + confirmation = get_list_input("Are you sure you want to Reset Node DB?", None, ["Yes", "No"]) + if confirmation == "Yes": interface.localNode.resetNodeDb() logging.info(f"Node DB Reset Requested by menu") break continue elif selected_option == "Shutdown": - confirmation = get_bool_selection("Are you sure you want to Shutdown?", 0) - if confirmation == "True": + confirmation = get_list_input("Are you sure you want to Shutdown?", None, ["Yes", "No"]) + if confirmation == "Yes": interface.localNode.shutdown() logging.info(f"Node Shutdown Requested by menu") break continue elif selected_option == "Factory Reset": - confirmation = get_bool_selection("Are you sure you want to Factory Reset?", 0) - if confirmation == "True": + confirmation = get_list_input("Are you sure you want to Factory Reset?", None, ["Yes", "No"]) + if confirmation == "Yes": interface.localNode.factoryReset() logging.info(f"Factory Reset Requested by menu") break @@ -265,12 +259,12 @@ def settings_menu(stdscr, interface): if selected_option in ['longName', 'shortName', 'isLicensed']: if selected_option in ['longName', 'shortName']: - new_value = get_user_input(f"Current value for {selected_option}: {current_value}") + new_value = get_text_input(f"Current value for {selected_option}: {current_value}") new_value = current_value if new_value is None else new_value current_menu[selected_option] = (field, new_value) elif selected_option == 'isLicensed': - new_value = get_bool_selection(f"Current value for {selected_option}: {current_value}", str(current_value)) + new_value = get_list_input(f"Current value for {selected_option}: {current_value}", str(current_value), ["True", "False"]) new_value = new_value == "True" current_menu[selected_option] = (field, new_value) @@ -278,7 +272,7 @@ def settings_menu(stdscr, interface): modified_settings[option] = value elif selected_option in ['latitude', 'longitude', 'altitude']: - new_value = get_user_input(f"Current value for {selected_option}: {current_value}") + new_value = get_text_input(f"Current value for {selected_option}: {current_value}") new_value = current_value if new_value is None else new_value current_menu[selected_option] = (field, new_value) @@ -287,7 +281,7 @@ def settings_menu(stdscr, interface): modified_settings[option] = current_menu[option][1] elif field.type == 8: # Handle boolean type - new_value = get_bool_selection(selected_option, str(current_value)) + new_value = get_list_input(selected_option, str(current_value), ["True", "False"]) new_value = new_value == "True" or new_value is True elif field.label == field.LABEL_REPEATED: # Handle repeated field @@ -296,22 +290,22 @@ def settings_menu(stdscr, interface): elif field.enum_type: # Enum field enum_options = {v.name: v.number for v in field.enum_type.values} - new_value_name = get_enum_input(list(enum_options.keys()), current_value) + new_value_name = get_list_input(selected_option, current_value, list(enum_options.keys())) new_value = enum_options.get(new_value_name, current_value) elif field.type == 7: # Field type 7 corresponds to FIXED32 new_value = get_fixed32_input(current_value) elif field.type == 13: # Field type 13 corresponds to UINT32 - new_value = get_user_input(f"Current value for {selected_option}: {current_value}") + new_value = get_text_input(f"Current value for {selected_option}: {current_value}") new_value = current_value if new_value is None else int(new_value) elif field.type == 2: # Field type 13 corresponds to INT64 - new_value = get_user_input(f"Current value for {selected_option}: {current_value}") + new_value = get_text_input(f"Current value for {selected_option}: {current_value}") new_value = current_value if new_value is None else float(new_value) else: # Handle other field types - new_value = get_user_input(f"Current value for {selected_option}: {current_value}") + new_value = get_text_input(f"Current value for {selected_option}: {current_value}") new_value = current_value if new_value is None else new_value for key in menu_path[3:]: # Skip "Main Menu" diff --git a/user_config.py b/user_config.py index a7abdc9..7ee723b 100644 --- a/user_config.py +++ b/user_config.py @@ -3,7 +3,7 @@ import json import curses from ui.colors import get_color, setup_colors, COLOR_MAP from default_config import format_json_single_line_arrays, loaded_config -from input_handlers import select_from_list +from input_handlers import get_list_input width = 60 save_option_text = "Save Changes" @@ -14,8 +14,8 @@ def edit_color_pair(key, current_value): Allows the user to select a foreground and background color for a key. """ color_list = [" "] + list(COLOR_MAP.keys()) - fg_color = select_from_list(f"Select Foreground Color for {key}", current_value[0], color_list) - bg_color = select_from_list(f"Select Background Color for {key}", current_value[1], color_list) + fg_color = get_list_input(f"Select Foreground Color for {key}", current_value[0], color_list) + bg_color = get_list_input(f"Select Background Color for {key}", current_value[1], color_list) return [fg_color, bg_color] @@ -48,10 +48,10 @@ def edit_value(key, current_value): if key == "theme": # Load theme names dynamically from the JSON theme_options = [k.split("_", 2)[2].lower() for k in loaded_config.keys() if k.startswith("COLOR_CONFIG")] - return select_from_list("Select Theme", current_value, theme_options) + return get_list_input("Select Theme", current_value, theme_options) elif key == "node_sort": sort_options = ['lastHeard', 'name', 'hops'] - return select_from_list("Sort By", current_value, sort_options) + return get_list_input("Sort By", current_value, sort_options) # Standard Input Mode (Scrollable) edit_win.addstr(7, 2, "New Value: ", get_color("settings_default"))