Files
kiauh/kiauh/core/menus/base_menu.py
2024-04-01 00:55:25 +02:00

183 lines
6.2 KiB
Python

# ======================================================================= #
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
from __future__ import annotations
import subprocess
import sys
import textwrap
from abc import abstractmethod, ABC
from typing import Dict, Union, Callable, Type, Tuple
from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException
from utils.constants import (
COLOR_GREEN,
COLOR_YELLOW,
COLOR_RED,
COLOR_CYAN,
RESET_FORMAT,
)
from utils.logger import Logger
def clear():
subprocess.call("clear", shell=True)
def print_header():
line1 = " [ KIAUH ] "
line2 = "Klipper Installation And Update Helper"
line3 = ""
color = COLOR_CYAN
count = 62 - len(color) - len(RESET_FORMAT)
header = textwrap.dedent(
f"""
/=======================================================\\
| {color}{line1:~^{count}}{RESET_FORMAT} |
| {color}{line2:^{count}}{RESET_FORMAT} |
| {color}{line3:~^{count}}{RESET_FORMAT} |
\=======================================================/
"""
)[1:]
print(header, end="")
def print_quit_footer():
text = "Q) Quit"
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
footer = textwrap.dedent(
f"""
|-------------------------------------------------------|
| {color}{text:^{count}}{RESET_FORMAT} |
\=======================================================/
"""
)[1:]
print(footer, end="")
def print_back_footer():
text = "B) « Back"
color = COLOR_GREEN
count = 62 - len(color) - len(RESET_FORMAT)
footer = textwrap.dedent(
f"""
|-------------------------------------------------------|
| {color}{text:^{count}}{RESET_FORMAT} |
\=======================================================/
"""
)[1:]
print(footer, end="")
def print_back_help_footer():
text1 = "B) « Back"
text2 = "H) Help [?]"
color1 = COLOR_GREEN
color2 = COLOR_YELLOW
count = 34 - len(color1) - len(RESET_FORMAT)
footer = textwrap.dedent(
f"""
|-------------------------------------------------------|
| {color1}{text1:^{count}}{RESET_FORMAT} | {color2}{text2:^{count}}{RESET_FORMAT} |
\=======================================================/
"""
)[1:]
print(footer, end="")
Options = Dict[str, Callable]
class BaseMenu(ABC):
options: Options = {}
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
footer_type: FooterType = FooterType.BACK
def __init__(self):
if type(self) is BaseMenu:
raise NotImplementedError("BaseMenu cannot be instantiated directly.")
@abstractmethod
def print_menu(self) -> None:
raise NotImplementedError("Subclasses must implement the print_menu method")
def print_footer(self) -> None:
if self.footer_type is FooterType.QUIT:
print_quit_footer()
elif self.footer_type is FooterType.BACK:
print_back_footer()
elif self.footer_type is FooterType.BACK_HELP:
print_back_help_footer()
else:
raise NotImplementedError("Method for printing footer not implemented.")
def display_menu(self) -> None:
# clear()
if self.header:
print_header()
self.print_menu()
self.print_footer()
def validate_user_input(self, usr_input: str) -> Tuple[Callable, str]:
"""
Validate the user input and either return an Option, a string or None
:param usr_input: The user input in form of a string
:return: Option, str or None
"""
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:
return self.default_option, usr_input
# user selected a regular option
return option, usr_input
def handle_user_input(self) -> Tuple[Callable, str]:
"""Handle the user input, return the validated input or print an error."""
while True:
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
usr_input = input().lower()
if (validated_input := self.validate_user_input(usr_input)) is not None:
return validated_input
else:
Logger.print_error("Invalid input!", False)
def run(self) -> None:
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
while True:
try:
self.display_menu()
option = self.handle_user_input()
option[0](opt_index=option[1])
except GoBackException:
return
except ExitAppException:
Logger.print_ok("###### Happy printing!", False)
sys.exit(0)