Refactor Input Handlers (#108)

* bool is just a list

* working changes

* enum is a list too

* spacing
This commit is contained in:
pdxlocations
2025-02-02 16:52:16 -08:00
committed by GitHub
parent 40c2ef62b4
commit 07ce9dfbac
3 changed files with 56 additions and 167 deletions

View File

@@ -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)

View File

@@ -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"

View File

@@ -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"))