From: Michael Tremer Date: Sat, 8 May 2021 16:30:25 +0000 (+0000) Subject: tui: Add own implementation of button and list choice windows X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5381c2497d877f84b18eba7c735aba48d6e376ab;p=people%2Fms%2Fbricklayer.git tui: Add own implementation of button and list choice windows Signed-off-by: Michael Tremer --- diff --git a/src/python/errors.py b/src/python/errors.py index cfd1cee..1b94b00 100644 --- a/src/python/errors.py +++ b/src/python/errors.py @@ -23,3 +23,10 @@ class InstallError(Exception): class InstallAbortedError(InstallError): pass + + +class UserCanceledError(InstallError): + """ + Raised when the user wants to cancel the process + """ + pass diff --git a/src/python/tui.py b/src/python/tui.py index 80d5c3b..0ac99ac 100644 --- a/src/python/tui.py +++ b/src/python/tui.py @@ -26,6 +26,12 @@ from .i18n import _ # Setup logging log = logging.getLogger("bricklayer.tui") +def abort(): + raise InstallAbortedError + +def cancel(): + raise UserCanceledError + class Tui(object): def __init__(self, bricklayer): self.bricklayer = bricklayer @@ -125,40 +131,28 @@ class Tui(object): if buttons is None: buttons = (_("OK"), _("Cancel")) - return snack.ButtonChoiceWindow(self.screen, title=title, text=text, + window = ButtonsWindow(self, title=title, text=text, buttons=buttons, width=width, help=help) + return window.run() + def error(self, title, text, buttons=None, width=40): if not buttons: - buttons = [_("Abort Installation")] + buttons = [ + (_("Abort Installation"), abort), + ] return self.message(title, text, buttons=buttons, width=width) def progress(self, *args, **kwargs): return ProgressWindow(self, *args, **kwargs) - def select(self, title, text, items, buttons=None, default=None, help=None, width=40): - # Translate default - if default: - default = items.get(default, None) - - # Convert items into a list which is sorted by its values - items = sorted(items.items(), key=lambda item: item[1]) + def select(self, title, text, items, default=None, buttons=None, height=None, + width=40, help=None): + window = ListboxChoiceWindow(self, title, text, items, default=default, + buttons=buttons, height=height, width=width, help=help) - # Set some default buttons - if buttons is None: - buttons = (_("Select"), _("Cancel")) - - # Show the window - button, item = snack.ListboxChoiceWindow(self.screen, title, text, - [value for key, value in items], buttons=buttons, default=default, help=help, - width=width) - - # Find the selected item - key, value = items[item] - - # Return the key - return key + return window.run() def multi_select(self, title, text, items, selection=[], buttons=None, width=40, height=None, help=None): @@ -192,6 +186,128 @@ class Tui(object): return checkboxes.getSelection() +class Window(object): + def __init__(self, tui, title, text, height=None, width=None, help=None): + self.tui = tui + self.title = title + self.text = text + self.height = height + self.width = width + self.help = help + + @property + def max_height(self): + """ + The maximum height of any window + """ + return self.tui.screen.height - 12 + + def run(self): + raise NotImplementedError + + +class ButtonsWindow(Window): + def __init__(self, tui, title, text, buttons=None, height=None, width=None, help=None): + Window.__init__(self, tui, title, text, height=height, width=width, help=help) + + # Configure some default buttons + if buttons is None: + buttons = [ + (_("Ok"), None), + (_("Cancel"), cancel), + ] + + self.buttons = snack.ButtonBar(self.tui.screen, buttons) + + def _make_window(self): + # Create a grid + grid = snack.GridFormHelp(self.tui.screen, self.title, self.help, 1, 2) + + # Create the box that shows the text + textbox = snack.TextboxReflowed(self.width, self.text, maxHeight=self.max_height) + grid.add(textbox, 0, 0, padding=(0, 0, 0, 1)) + + # Create the button bar + grid.add(self.buttons, 0, 1, growx=True) + + return grid + + def run(self): + window = self._make_window() + + # Run the window + button = window.runOnce() + + # Which button was pressed? + callback = self.buttons.buttonPressed(button) + + # If the button had a callback, we will call the callback + if callable(callback): + return callback() + + # Otherwise call the default action + return self.default_action() + + def default_action(self): + """ + Called when a button did not have a callback + """ + pass + + +class ListboxChoiceWindow(ButtonsWindow): + def __init__(self, tui, title, text, items, default=None, buttons=None, height=None, + width=None, help=None): + # Set height to number of items by default + if height is None: + height = len(items) + + # Set some default buttons + if buttons is None: + buttons = [ + (_("Select"), None), + (_("Cancel"), cancel), + ] + + super().__init__(tui, title, text, buttons=buttons, + height=height, width=width, help=help) + + self.items = items + self.default = default + + # Should we enable scrolling? + scroll = height < len(items) + + # Create the list box + self.listbox = snack.Listbox(self.height, scroll=scroll, returnExit=True) + for key in self.items: + self.listbox.append(self.items[key], key) + + # Set the default value + if self.default: + self.listbox.setCurrent(self.default) + + def _make_window(self): + # Create a grid + grid = snack.GridFormHelp(self.tui.screen, self.title, self.help, 1, 3) + + # Create the text box + textbox = snack.TextboxReflowed(self.width, self.text) + grid.add(textbox, 0, 0) + + # Add the listbox + grid.add(self.listbox, 0, 1, padding=(0, 1, 0, 1)) + + # Add the buttons + grid.add(self.buttons, 0, 2, growx=True) + + return grid + + def default_action(self): + # Return the selected item + return self.listbox.current() + + class ProgressWindow(object): def __init__(self, tui, title, text, max_value=0, width=60, help=None): self.tui = tui