[elbe-devel] [PATCH v2 01/13] Debianize - TUI
dion at linutronix.de
dion at linutronix.de
Thu Aug 1 17:49:22 CEST 2019
From: Olivier Dion <dion at linutronix.de>
The TUI uses the Urwid package, see <http://urwid.org/>.
The code is copy-paste from one of my previous collaborate project
with a friend at <https://github.com/abelfodil/inf1900-grader> and is
under GPL-3+.
I've also made some modifications to fit the debianizer needs.
* TUI
The TUI is a singleton object that controls the terminal. It
divides it in 3 sections. The header, the body and the footer. Is
also installs signal handlers to ensure proper restoration of the
terminal settings.
** Header
The header is a Text widget. It's manipulated through the
TUI.print and TUI.clear method.
*** TUI.print
Print a formatted text to the header. The previous text is
overwritten.
*** TUI.clear
Clear the header.
** Footer
The footer is a Text widget. It's not intend to be manipulated.
It only shows useful keybinding to the user.
** Body
The body can be anything. It's the main view of the application.
Signed-off-by: Olivier Dion <dion at linutronix.de>
---
elbepack/debianize/base/tui.py | 180 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 180 insertions(+)
create mode 100644 elbepack/debianize/base/tui.py
diff --git a/elbepack/debianize/base/tui.py b/elbepack/debianize/base/tui.py
new file mode 100644
index 00000000..e0ca5e26
--- /dev/null
+++ b/elbepack/debianize/base/tui.py
@@ -0,0 +1,180 @@
+# ELBE - Debian Based Embedded Rootfilesystem Builder
+# Copyright (c) 2019 Olivier Dion <dion at linutronix.de>
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+
+import os
+import signal
+
+from urwid import (
+ ExitMainLoop,
+ Frame,
+ MainLoop,
+ Text
+)
+
+
+# +=====================================================+
+# |+-: root (box) -------------------------------------+|
+# ||+-: header (flow) --------------------------------+||
+# ||| |||
+# ||+-------------------------------------------------+||
+# ||+-: body (box) -----------------------------------+||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||| |||
+# ||+-------------------------------------------------+||
+# ||+-: helper (flow) --------------------------------+||
+# ||| | |
+# ||+-------------------------------------------------+||
+# |+---------------------------------------------------+|
+# +=====================================================+
+
+
+def generate_helper_text(hints):
+ markup = []
+ for key, text, text_palette in hints:
+ markup.extend((("helper_key", key), " ", (text_palette, text), " "))
+ return markup
+
+
+class TUISignal(object):
+ QUIT = "on_quit"
+ CLICK = "on_click"
+ FLUSH = "on_flush"
+
+class TUIException(Exception):
+ pass
+
+
+class TUI(object):
+
+ palette = [
+ ("blue_head", "dark blue", ""),
+ ("red_head", "dark red", ""),
+ ("header", "bold, underline, default", ""),
+ ("error", "bold, light red", ""),
+ ("normal_box", "default", "default"),
+ ("selected_box", "black", "light gray"),
+ ("confirm_button", "default", "dark green"),
+ ("abort_button", "light red", "dark red"),
+ ("progress_low", "default", "yellow"),
+ ("progress_hight", "default", "dark green"),
+ ("helper_key", "bold", "default"),
+ ("helper_text_brown", "black", "brown"),
+ ("helper_text_red", "black", "dark red"),
+ ("helper_text_green", "black", "dark green"),
+ ("helper_text_light", "white", "dark blue"),
+ ]
+
+ main_helper_text = generate_helper_text([
+ ("C-f", "Forward", "helper_text_brown"),
+ ("C-b", "Backward", "helper_text_brown"),
+ ("C-p", "Previous", "helper_text_brown"),
+ ("C-n", "Next", "helper_text_brown"),
+ ("TAB", "Next", "helper_text_brown"),
+ ("backtab", "Previous", "helper_text_brown"),
+ ("C-\\", "Quit", "helper_text_red"),
+ ])
+
+ keybind = {}
+
+ def __init__(self, body):
+
+ TUI.root = Frame(body.root,
+ Text(("header", ""), "center"),
+ Text(TUI.main_helper_text, "center"))
+
+ TUI.loop = MainLoop(TUI.root, TUI.palette,
+ unhandled_input=TUI.unhandled_input)
+
+ TUI.install_signals_handler()
+
+ def __call__(self):
+ TUI.loop.run()
+
+ @classmethod
+ def focus_header(cls):
+ cls.root.focus_position = "header"
+
+ @classmethod
+ def focus_body(cls):
+ cls.root.focus_position = "body"
+
+ @classmethod
+ def focus_footer(cls):
+ cls.root.focus_position = "footer"
+
+ @classmethod
+ def header(cls, flow_widget=None):
+ if flow_widget is not None:
+ if "flow" not in flow_widget.sizing():
+ raise TUIException("Header must be of sizing flow")
+ cls.root.contents["header"] = flow_widget
+ return cls.root.contents["header"]
+
+ @classmethod
+ def body(cls, box_widget=None):
+ if box_widget is not None:
+ if "box" not in box_widget.sizing():
+ raise TUIException("Body must be of sizing box")
+ cls.root.contents["body"] = (box_widget, TUI.root.options())
+ return cls.root.contents["body"]
+
+ @classmethod
+ def footer(cls, flow_widget=None):
+ if flow_widget is not None:
+ if "flow" not in flow_widget.sizing():
+ raise TUIException("Header must be of sizing flow")
+ cls.root.contents["footer"] = flow_widget
+ return cls.root.contents["footer"]
+
+ @classmethod
+ def unhandled_input(cls, key):
+ if key in cls.keybind:
+ cls.keybind[key]()
+ return None
+
+ @classmethod
+ def bind_global(cls, key, callback):
+ cls.keybind[key] = callback
+
+ @classmethod
+ def printf(cls, fmt, *args):
+ cls.header()[0].set_text(("header", fmt.format(*args)))
+
+ @classmethod
+ def clear(cls):
+ cls.printf("")
+
+ @staticmethod
+ def quit(*args):
+ raise ExitMainLoop()
+
+ @staticmethod
+ def pause(*args):
+ TUI.loop.stop()
+ os.kill(os.getpid(), signal.SIGSTOP)
+ TUI.loop.start()
+ TUI.loop.draw_screen()
+
+ @staticmethod
+ def interrupt(*kargs):
+ pass
+
+ @staticmethod
+ def install_signals_handler():
+
+ if os.sys.platform != "win32":
+ signal.signal(signal.SIGQUIT, TUI.quit)
+ signal.signal(signal.SIGTSTP, TUI.pause)
+
+ signal.signal(signal.SIGINT, TUI.interrupt)
--
2.11.0
More information about the elbe-devel
mailing list