mirror of
https://github.com/pdxlocations/contact.git
synced 2026-07-04 00:41:18 +02:00
fix super-nested menus
This commit is contained in:
+139
-38
@@ -1,7 +1,7 @@
|
||||
import curses
|
||||
from meshtastic import config_pb2, module_config_pb2
|
||||
import meshtastic.serial_interface, meshtastic.tcp_interface
|
||||
|
||||
import ipaddress
|
||||
|
||||
def display_enum_menu(stdscr, enum_values, setting_string):
|
||||
menu_height = len(enum_values) + 2
|
||||
@@ -146,7 +146,7 @@ def get_uint_input(stdscr, setting_string):
|
||||
elif key == curses.KEY_BACKSPACE or key == 127: # Backspace key
|
||||
# Delete the last character from input_text
|
||||
input_text = input_text[:-1]
|
||||
elif 48 <= key <= 57: # Numbers (ASCII range)
|
||||
elif 48 <= key <= 57: # Numbers(ASCII range)
|
||||
# Append the character to input_text
|
||||
input_text += chr(key)
|
||||
elif key == 27 or key == curses.KEY_LEFT: # Check if escape key is pressed
|
||||
@@ -158,6 +158,68 @@ def get_uint_input(stdscr, setting_string):
|
||||
input_win.refresh()
|
||||
|
||||
|
||||
def ip_to_fixed32(ip):
|
||||
# Parse the IP address
|
||||
ip_obj = ipaddress.ip_address(ip)
|
||||
# Convert IP address to 32-bit integer
|
||||
return int(ip_obj)
|
||||
|
||||
def fixed32_to_ip(fixed32):
|
||||
# Convert 32-bit integer to IPv4Address object
|
||||
ip_obj = ipaddress.IPv4Address(fixed32)
|
||||
# Convert IPv4Address object to string representation
|
||||
return str(ip_obj)
|
||||
|
||||
def get_fixed32_input(stdscr, setting_string):
|
||||
popup_height = 5
|
||||
popup_width = 40
|
||||
y_start = (curses.LINES - popup_height) // 2
|
||||
x_start = (curses.COLS - popup_width) // 2
|
||||
|
||||
try:
|
||||
input_win = curses.newwin(popup_height, popup_width, y_start, x_start)
|
||||
except curses.error as e:
|
||||
print("Error occurred while initializing curses window:", e)
|
||||
|
||||
input_win.border()
|
||||
input_win.keypad(True)
|
||||
input_win.refresh()
|
||||
|
||||
input_win.addstr(1, 1, fixed32_to_ip(setting_string)) # Prepopulate input field with the setting value
|
||||
input_win.refresh()
|
||||
# Get user input
|
||||
curses.curs_set(1) # Show cursor
|
||||
input_text = ""
|
||||
|
||||
while True:
|
||||
# Display the current input text
|
||||
input_win.addstr(1, 1, input_text)
|
||||
input_win.border()
|
||||
input_win.refresh()
|
||||
|
||||
# Get a character from the user
|
||||
key = stdscr.getch()
|
||||
|
||||
if key == curses.KEY_ENTER or key == 10 or key == 13: # Enter key
|
||||
curses.curs_set(0) # Hide cursor
|
||||
input_win.clear()
|
||||
input_win.refresh()
|
||||
return ip_to_fixed32(input_text), True
|
||||
elif key == curses.KEY_BACKSPACE or key == 127: # Backspace key
|
||||
# Delete the last character from input_text
|
||||
input_text = input_text[:-1]
|
||||
elif 48 <= key <= 57 or key == 46: # Numbers + period (ASCII range)
|
||||
# Append the character to input_text
|
||||
input_text += chr(key)
|
||||
elif key == 27 or key == curses.KEY_LEFT: # Check if escape key is pressed
|
||||
curses.curs_set(0) # Hide cursor
|
||||
input_win.refresh()
|
||||
return None, False
|
||||
|
||||
input_win.clear()
|
||||
input_win.refresh()
|
||||
|
||||
|
||||
def display_bool_menu(stdscr, setting_value):
|
||||
bool_options = ["False", "True"]
|
||||
return display_enum_menu(stdscr, bool_options, setting_value)
|
||||
@@ -180,7 +242,7 @@ def generate_menu_from_protobuf(message_instance, interface):
|
||||
def change_setting(stdscr, interface, menu_path):
|
||||
node = interface.getNode('^local')
|
||||
field_descriptor = None
|
||||
setting_value = None
|
||||
setting_value = 0
|
||||
|
||||
stdscr.clear()
|
||||
stdscr.border()
|
||||
@@ -188,7 +250,7 @@ def change_setting(stdscr, interface, menu_path):
|
||||
menu_header(stdscr, f"{menu_path[-1]}")
|
||||
|
||||
# Determine the level of nesting based on the length of menu_path
|
||||
if len(menu_path) ==4:
|
||||
if len(menu_path) == 4:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setting_string = getattr(getattr(node.localConfig, str(menu_path[2])), menu_path[3])
|
||||
field_descriptor = getattr(node.localConfig, menu_path[2]).DESCRIPTOR.fields_by_name[menu_path[3]]
|
||||
@@ -197,6 +259,15 @@ def change_setting(stdscr, interface, menu_path):
|
||||
setting_string = getattr(getattr(node.moduleConfig, str(menu_path[2])), menu_path[3])
|
||||
field_descriptor = getattr(node.moduleConfig, menu_path[2]).DESCRIPTOR.fields_by_name[menu_path[3]]
|
||||
|
||||
elif len(menu_path) == 5:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setting_string = getattr(getattr(getattr(node.localConfig, str(menu_path[2])), menu_path[3]), menu_path[4])
|
||||
field_descriptor = getattr(getattr(node.localConfig, menu_path[2]), menu_path[3]).DESCRIPTOR.fields_by_name[menu_path[4]]
|
||||
|
||||
elif menu_path[1] == "Module Settings":
|
||||
setting_string = getattr(getattr(getattr(node.moduleConfig, str(menu_path[2])), menu_path[3]), menu_path[4])
|
||||
field_descriptor = getattr(getattr(node.moduleConfig, menu_path[2]), menu_path[3]).DESCRIPTOR.fields_by_name[menu_path[3]]
|
||||
|
||||
|
||||
if field_descriptor.enum_type is not None:
|
||||
enum_values = [enum_value.name for enum_value in field_descriptor.enum_type.values]
|
||||
@@ -232,7 +303,15 @@ def change_setting(stdscr, interface, menu_path):
|
||||
menu_path.pop()
|
||||
return # Exit function if escape was pressed during input
|
||||
|
||||
formatted_text = f"{menu_path[2]}.{menu_path[3]} = {setting_value}"
|
||||
else: # Catch fixed32
|
||||
setting_value, do_change_setting = get_fixed32_input(stdscr, setting_string)
|
||||
if not do_change_setting:
|
||||
stdscr.clear()
|
||||
stdscr.border()
|
||||
menu_path.pop()
|
||||
return # Exit function if escape was pressed during input
|
||||
|
||||
# formatted_text = f"{menu_path[2]}.{menu_path[3]} = {setting_value}"
|
||||
# menu_header(stdscr,formatted_text,2)
|
||||
|
||||
ourNode = interface.getNode('^local')
|
||||
@@ -247,29 +326,43 @@ def change_setting(stdscr, interface, menu_path):
|
||||
setting_value_int = setting_value
|
||||
|
||||
try:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setattr(getattr(ourNode.localConfig, menu_path[2]), menu_path[3], setting_value_int)
|
||||
elif menu_path[1] == "Module Settings":
|
||||
setattr(getattr(ourNode.moduleConfig, menu_path[2]), menu_path[3], setting_value_int)
|
||||
if len(menu_path) == 4:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setattr(getattr(ourNode.localConfig, menu_path[2]), menu_path[3], setting_value_int)
|
||||
elif menu_path[1] == "Module Settings":
|
||||
setattr(getattr(ourNode.moduleConfig, menu_path[2]), menu_path[3], setting_value_int)
|
||||
|
||||
elif len(menu_path) == 5:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setattr(getattr(getattr(ourNode.localConfig, menu_path[2]), menu_path[3]), menu_path[4], setting_value_int)
|
||||
elif menu_path[1] == "Module Settings":
|
||||
setattr(getattr(getattr(ourNode.moduleConfig, menu_path[2]), menu_path[3]), menu_path[4], setting_value_int)
|
||||
|
||||
except AttributeError as e:
|
||||
print("Error setting attribute:", e)
|
||||
|
||||
|
||||
|
||||
ourNode.writeConfig(menu_path[2])
|
||||
menu_path.pop()
|
||||
|
||||
|
||||
|
||||
|
||||
def display_values(stdscr, interface, key_list, menu_path, setting_name = None):
|
||||
def display_values(stdscr, interface, key_list, menu_path):
|
||||
node = interface.getNode('^local')
|
||||
for i, key in enumerate(key_list):
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setting = getattr(getattr(node.localConfig, str(setting_name)), key_list[i])
|
||||
if menu_path[1] == "Module Settings":
|
||||
setting = getattr(getattr(node.moduleConfig, str(setting_name)), key_list[i])
|
||||
stdscr.addstr(i+3, 40, str(setting))
|
||||
if len(menu_path) == 3:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setting = getattr(getattr(node.localConfig, menu_path[2]), key_list[i])
|
||||
if menu_path[1] == "Module Settings":
|
||||
setting = getattr(getattr(node.moduleConfig, menu_path[2]), key_list[i])
|
||||
stdscr.addstr(i+3, 40, str(setting)[:14])
|
||||
|
||||
if len(menu_path) == 4:
|
||||
if menu_path[1] == "Radio Settings":
|
||||
setting = getattr(getattr(getattr(node.localConfig, menu_path[2]), menu_path[3]), key_list[i])
|
||||
if menu_path[1] == "Module Settings":
|
||||
setting = getattr(getattr(getattr(node.moduleConfig, menu_path[2]), menu_path[3]), key_list[i])
|
||||
stdscr.addstr(i+3, 40, str(setting)[:14])
|
||||
|
||||
stdscr.refresh()
|
||||
|
||||
def menu_header(window, text, start_y=1):
|
||||
@@ -287,14 +380,18 @@ def nested_menu(stdscr, menu, interface):
|
||||
current_menu = menu
|
||||
prev_menu = []
|
||||
menu_index = 0
|
||||
next_key = None
|
||||
|
||||
setting_name = None
|
||||
# setting_name = None
|
||||
key_list = []
|
||||
menu_path = ["Main Menu"]
|
||||
|
||||
last_menu_level = False
|
||||
|
||||
while True:
|
||||
# Display current menu
|
||||
if current_menu is not None:
|
||||
|
||||
menu_header(stdscr, f"{menu_path[menu_index]}")
|
||||
for i, key in enumerate(current_menu.keys(), start=0):
|
||||
if i == menu_item:
|
||||
@@ -305,9 +402,7 @@ def nested_menu(stdscr, menu, interface):
|
||||
else:
|
||||
stdscr.addstr(i+3, 1, key)
|
||||
|
||||
|
||||
if len(menu_path) == 3:
|
||||
display_values(stdscr, interface, key_list, menu_path, setting_name)
|
||||
display_values(stdscr, interface, key_list, menu_path)
|
||||
|
||||
char = stdscr.getch()
|
||||
|
||||
@@ -315,9 +410,13 @@ def nested_menu(stdscr, menu, interface):
|
||||
selected_value = current_menu[selected_key]
|
||||
|
||||
if char == curses.KEY_DOWN:
|
||||
if last_menu_level == True:
|
||||
last_menu_level = False
|
||||
menu_item = min(len(current_menu) - 1, menu_item + 1)
|
||||
|
||||
elif char == curses.KEY_UP:
|
||||
if last_menu_level == True:
|
||||
last_menu_level = False
|
||||
menu_item = max(0, menu_item - 1)
|
||||
|
||||
elif char == curses.KEY_RIGHT:
|
||||
@@ -327,13 +426,16 @@ def nested_menu(stdscr, menu, interface):
|
||||
menu_index += 1
|
||||
current_menu = selected_value
|
||||
menu_item = 0
|
||||
|
||||
if len(menu_path) < 4 and selected_key not in ["Reboot", "Reset NodeDB", "Shutdown", "Factory Reset"]:
|
||||
menu_path.append(selected_key)
|
||||
last_menu_level = False
|
||||
else:
|
||||
last_menu_level = True
|
||||
|
||||
if selected_key not in ["Reboot", "Reset NodeDB", "Shutdown", "Factory Reset"]:
|
||||
menu_path.append(selected_key)
|
||||
|
||||
elif char == curses.KEY_LEFT:
|
||||
if len(menu_path) == 4:
|
||||
menu_path.pop()
|
||||
if last_menu_level == True:
|
||||
last_menu_level = False
|
||||
if len(menu_path) > 1:
|
||||
menu_path.pop()
|
||||
current_menu = prev_menu[menu_index-1]
|
||||
@@ -341,9 +443,8 @@ def nested_menu(stdscr, menu, interface):
|
||||
menu_index -= 1
|
||||
menu_item = 0
|
||||
|
||||
|
||||
elif char == ord('\n'):
|
||||
# If user presses enter, display the selected value if it's not a submenu
|
||||
|
||||
if selected_key == "Reboot":
|
||||
settings_reboot(interface)
|
||||
elif selected_key == "Reset NodeDB":
|
||||
@@ -367,16 +468,16 @@ def nested_menu(stdscr, menu, interface):
|
||||
next_key = list(current_menu.keys())[menu_item]
|
||||
key_list = list(current_menu.keys())
|
||||
|
||||
if menu_index==1:
|
||||
setting_name = next_key
|
||||
else:
|
||||
break # Exit loop if current_menu is None
|
||||
|
||||
if len(menu_path) == 4:
|
||||
change_setting(stdscr, interface, menu_path)
|
||||
if last_menu_level == True:
|
||||
if not isinstance(current_menu.get(next_key), dict):
|
||||
change_setting(stdscr, interface, menu_path)
|
||||
|
||||
|
||||
def settings(stdscr, interface):
|
||||
popup_height = 25
|
||||
popup_height = 20
|
||||
popup_width = 60
|
||||
popup_win = None
|
||||
y_start = (curses.LINES - popup_height) // 2
|
||||
@@ -434,10 +535,10 @@ def settings_factory_reset(interface):
|
||||
if __name__ == "__main__":
|
||||
|
||||
interface = meshtastic.serial_interface.SerialInterface()
|
||||
# radio = config_pb2.Config()
|
||||
# module = module_config_pb2.ModuleConfig()
|
||||
# print(generate_menu_from_protobuf(radio))
|
||||
# print(generate_menu_from_protobuf(module))
|
||||
radio = config_pb2.Config()
|
||||
module = module_config_pb2.ModuleConfig()
|
||||
# print(generate_menu_from_protobuf(radio, interface))
|
||||
# print(generate_menu_from_protobuf(module, interface))
|
||||
|
||||
def main(stdscr):
|
||||
stdscr.keypad(True)
|
||||
|
||||
Reference in New Issue
Block a user