"""Textual pieces shared by the dprompt and dbanner editors.""" import os import subprocess import sys from rich.style import Style from rich.text import Text from textual import events from textual.containers import Horizontal, Vertical from textual.message import Message from textual.screen import ModalScreen from textual.widget import Widget from textual.widgets import Button, Input, Label, Select from . import colors from .i18n import t NONE = "-" # Select has no None value def color_options(allow_none=True): names = { "black": ("чорний", "black"), "red": ("червоний", "red"), "green": ("зелений", "green"), "yellow": ("жовтий", "yellow"), "blue": ("синій", "blue"), "magenta": ("пурпуровий", "magenta"), "cyan": ("бірюзовий", "cyan"), "white": ("білий", "white"), } opts = [(t("— типовий —", "— default —"), NONE)] if allow_none else [] for c in colors.NAMES: base = c[7:] if c.startswith("bright_") else c label = t(*names[base]) if c.startswith("bright_"): label = t("яскравий ", "bright ") + label opts.append((Text.assemble(("██ ", c), label), c)) return opts def color_value(value): return None if value in (NONE, Select.NULL, None) else value def to_select(color): return color if color in colors.NAMES else NONE class Swatches(Widget): """A row of the 16 colors (and "none") to click; the chosen one is marked.""" DEFAULT_CSS = """ Swatches { height: 2; width: 36; } """ class Picked(Message): def __init__(self, swatches, color): super().__init__() self.swatches, self.color = swatches, color def __init__(self, color=None, allow_none=True, **kw): super().__init__(**kw) self.color, self.allow_none = color, allow_none def _items(self): return ([None] if self.allow_none else []) + list(colors.NAMES) def render(self): text = Text() for i, c in enumerate(self._items()): if i == 9: text.append("\n") mark = "▣" if c == self.color else "■" if c is None: text.append("∅ " if c != self.color else "▣ ", Style(bold=True)) else: text.append(mark * 2 + " " if c == self.color else "██ ", Style(color=c)) return text def on_click(self, event: events.Click): items = self._items() index = (event.y * 9) + event.x // 3 if 0 <= event.x // 3 < 9 and 0 <= index < len(items): self.color = items[index] self.refresh() self.post_message(self.Picked(self, self.color)) class AskDialog(ModalScreen): """A few text fields with OK/Cancel; dismisses with {key: value} or None.""" DEFAULT_CSS = """ AskDialog { align: center middle; } AskDialog > Vertical { width: 72; height: auto; border: round $accent; background: $surface; padding: 1 2; } AskDialog Label { margin-top: 1; } AskDialog Horizontal { height: auto; margin-top: 1; align-horizontal: right; } AskDialog Button { margin-left: 2; } """ BINDINGS = [("escape", "cancel", t("Скасувати", "Cancel"))] def __init__(self, heading, fields): """fields: [(key, label, value or a list of (label, value) choices, current)]""" super().__init__() self.heading, self.fields = heading, fields def compose(self): with Vertical(): yield Label(Text(self.heading, style="bold")) for key, label, value, *rest in self.fields: yield Label(Text(label)) if isinstance(value, list): yield Select(value, value=rest[0] if rest else value[0][1], allow_blank=False, id="f_" + key) else: yield Input(str(value), id="f_" + key) with Horizontal(): yield Button(t("Скасувати", "Cancel"), id="cancel") yield Button("OK", variant="primary", id="ok") def on_mount(self): inputs = self.query(Input) if inputs: inputs.first().focus() def _result(self): return {key: self.query_one("#f_" + key).value for key, *_ in self.fields} def on_button_pressed(self, event: Button.Pressed): self.dismiss(self._result() if event.button.id == "ok" else None) def on_input_submitted(self, event: Input.Submitted): self.dismiss(self._result()) def action_cancel(self): self.dismiss(None) def run_as_root(app, args): """Runs one of our commands through sudo with the TUI suspended, so sudo can ask for the password. Returns True on success.""" exe = "/usr/bin/" + args[0] if os.path.exists(exe): cmd = [exe] + args[1:] else: # from the source tree module = {"dprompt": "das_shell.cli_prompt", "dbanner": "das_shell.cli_banner"}[args[0]] env = [f"{k}={os.environ[k]}" for k in ("PYTHONPATH", "DAS_SHELL_DATA", "DAS_SHELL_ETC", "DAS_SHELL_CACHE") if k in os.environ] cmd = ["env"] + env + [sys.executable, "-m", module] + args[1:] if os.geteuid() != 0: cmd = ["sudo"] + cmd with app.suspend(): print("\n$ " + " ".join(cmd)) code = subprocess.run(cmd).returncode if code: input(t("Помилка. Enter — повернутися…", "Failed. Press Enter to go back…")) return code == 0 def bar(*buttons): row = Horizontal(*buttons, classes="buttons") return row