diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py index beff44a..a54040d 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_error_menu.py @@ -21,8 +21,7 @@ class KlipperNoFirmwareErrorMenu(BaseMenu): super().__init__() self.flash_options = FlashOptions() - self.options = {"": self.go_back} - self.default_options = self.go_back + self.default_option = self.go_back self.footer_type = FooterType.BLANK self.input_label_txt = "Press ENTER to go back to [Advanced Menu]" @@ -64,8 +63,7 @@ class KlipperNoBoardTypesErrorMenu(BaseMenu): def __init__(self): super().__init__() - self.options = {"": self.go_back} - self.default_options = self.go_back + self.default_option = self.go_back self.footer_type = FooterType.BLANK self.input_label_txt = "Press ENTER to go back to [Main Menu]" diff --git a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py index 9b1b7cd..27f1269 100644 --- a/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py +++ b/kiauh/components/klipper_firmware/menus/klipper_flash_menu.py @@ -48,10 +48,10 @@ class KlipperFlashMethodMenu(BaseMenu): super().__init__() self.previous_menu: BaseMenu = previous_menu + self.help_menu = KlipperFlashMethodHelpMenu self.options = { "1": self.select_regular, "2": self.select_sdcard, - "h": self.help_menu, } self.input_label_txt = "Select flash method" self.footer_type = FooterType.BACK_HELP @@ -99,9 +99,6 @@ class KlipperFlashMethodMenu(BaseMenu): else: KlipperNoFirmwareErrorMenu().run() - def help_menu(self, **kwargs): - KlipperFlashMethodHelpMenu(previous_menu=self).run() - # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic @@ -110,10 +107,10 @@ class KlipperFlashCommandMenu(BaseMenu): super().__init__() self.previous_menu: BaseMenu = previous_menu + self.help_menu = KlipperFlashCommandHelpMenu self.options = { "1": self.select_flash, "2": self.select_serialflash, - "h": self.help_menu, } self.default_option = self.select_flash self.input_label_txt = "Select flash command" @@ -145,9 +142,6 @@ class KlipperFlashCommandMenu(BaseMenu): def goto_next_menu(self, **kwargs): KlipperSelectMcuConnectionMenu(previous_menu=self).run() - def help_menu(self, **kwargs): - KlipperFlashCommandHelpMenu(previous_menu=self).run() - # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic @@ -157,11 +151,11 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): self.__standalone = standalone self.previous_menu: BaseMenu = previous_menu + self.help_menu = KlipperMcuConnectionHelpMenu self.options = { "1": self.select_usb, "2": self.select_dfu, "3": self.select_usb_dfu, - "h": self.help_menu, } self.input_label_txt = "Select connection type" self.footer_type = FooterType.BACK_HELP @@ -229,9 +223,6 @@ class KlipperSelectMcuConnectionMenu(BaseMenu): def goto_next_menu(self, **kwargs): KlipperSelectMcuIdMenu(previous_menu=self).run() - def help_menu(self, **kwargs): - KlipperMcuConnectionHelpMenu(previous_menu=self).run() - # noinspection PyUnusedLocal # noinspection PyMethodMayBeStatic diff --git a/kiauh/core/menus/__init__.py b/kiauh/core/menus/__init__.py index ed37c68..06c68b2 100644 --- a/kiauh/core/menus/__init__.py +++ b/kiauh/core/menus/__init__.py @@ -17,14 +17,6 @@ class FooterType(Enum): BLANK = "BLANK" -NAVI_OPTIONS = { - FooterType.QUIT: ["q"], - FooterType.BACK: ["b"], - FooterType.BACK_HELP: ["b", "h"], - FooterType.BLANK: [], -} - - class ExitAppException(Exception): pass diff --git a/kiauh/core/menus/base_menu.py b/kiauh/core/menus/base_menu.py index 0637f72..84f4a13 100644 --- a/kiauh/core/menus/base_menu.py +++ b/kiauh/core/menus/base_menu.py @@ -12,10 +12,10 @@ from __future__ import annotations import subprocess import sys import textwrap -from abc import abstractmethod, ABC +from abc import abstractmethod from typing import Dict, Union, Callable, Type, Tuple -from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException +from core.menus import FooterType, ExitAppException, GoBackException from utils.constants import ( COLOR_GREEN, COLOR_YELLOW, @@ -96,22 +96,51 @@ def print_blank_footer(): print("\=======================================================/") -Options = Dict[str, Callable] +class PostInitCaller(type): + def __call__(cls, *args, **kwargs): + obj = type.__call__(cls, *args, **kwargs) + obj.__post_init__() + return obj -class BaseMenu(ABC): - options: Options = {} +# noinspection PyUnusedLocal +# noinspection PyMethodMayBeStatic +class BaseMenu(metaclass=PostInitCaller): + options: Dict[str, Callable] = {} options_offset: int = 0 default_option: Union[Callable, None] = None input_label_txt: str = "Perform action" header: bool = False previous_menu: Union[Type[BaseMenu], BaseMenu] = None + help_menu: Type[BaseMenu] = None footer_type: FooterType = FooterType.BACK - def __init__(self): + def __init__(self, **kwargs): if type(self) is BaseMenu: raise NotImplementedError("BaseMenu cannot be instantiated directly.") + def __post_init__(self): + # conditionally add options based on footer type + if self.footer_type is FooterType.QUIT: + self.options["q"] = self.__exit + if self.footer_type is FooterType.BACK: + self.options["b"] = self.__go_back + if self.footer_type is FooterType.BACK_HELP: + self.options["b"] = self.__go_back + self.options["h"] = self.__go_to_help + # if defined, add the default option to the options dict + if self.default_option is not None: + self.options[""] = self.default_option + + def __go_back(self, **kwargs): + raise GoBackException() + + def __go_to_help(self, **kwargs): + self.help_menu(previous_menu=self).run() + + def __exit(self, **kwargs): + raise ExitAppException() + @abstractmethod def print_menu(self) -> None: raise NotImplementedError("Subclasses must implement the print_menu method") @@ -144,20 +173,8 @@ class BaseMenu(ABC): usr_input = usr_input.lower() option = self.options.get(usr_input, None) - # check if usr_input contains a character used for basic navigation, e.g. b, h or q - # and if the current menu has the appropriate footer to allow for that action - is_valid_navigation = self.footer_type in NAVI_OPTIONS - user_navigated = usr_input in NAVI_OPTIONS[self.footer_type] - if is_valid_navigation and user_navigated: - if usr_input == "q": - raise ExitAppException() - elif usr_input == "b": - raise GoBackException() - elif usr_input == "h": - return option, usr_input - - # if usr_input is None or an empty string, we execute the menues default option if specified - if usr_input == "" and self.default_option is not None: + # if option/usr_input is None/empty string, we execute the menus default option if specified + if (option is None or usr_input == "") and self.default_option is not None: return self.default_option, usr_input # user selected a regular option @@ -168,8 +185,10 @@ class BaseMenu(ABC): while True: print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="") usr_input = input().lower() + validated_input = self.validate_user_input(usr_input) + method_to_call = validated_input[0] - if (validated_input := self.validate_user_input(usr_input)) is not None: + if method_to_call is not None: return validated_input else: Logger.print_error("Invalid input!", False)