Mini Shell

Direktori : /opt/imh-python/lib/python3.9/site-packages/picotui/
Upload File :
Current File : //opt/imh-python/lib/python3.9/site-packages/picotui/widgets.py

from .basewidget import *
from .editorext import *
from .defs import *


__all__ = (
    "ACTION_OK",
    "ACTION_CANCEL",
    "ACTION_NEXT",
    "ACTION_PREV",
    "EditableWidget",
    "Dialog",
    "WLabel",
    "WFrame",
    "WButton",
    "WCheckbox",
    "WRadioButton",
    "WListBox",
    "WPopupList",
    "WDropDown",
    "WTextEntry",
    "WMultiEntry",
    "WComboBox",
    "WCompletionList",
    "WAutoComplete",
)

class Dialog(Widget):

    finish_on_esc = True

    def __init__(self, x, y, w=0, h=0, title=""):
        super().__init__()
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.title = ""
        if title:
            self.title = " %s " % title
        self.childs = []
        # On both sides
        self.border_w = 2
        self.border_h = 2
        self.focus_w = None
        self.focus_idx = -1

    def add(self, x, y, widget):
        if isinstance(widget, str):
            # Convert raw string to WLabel
            widget = WLabel(widget)
        widget.set_xy(self.x + x, self.y + y)
        self.childs.append(widget)
        widget.owner = self

    def autosize(self, extra_w=0, extra_h=0):
        w = 0
        h = 0
        for wid in self.childs:
            w = max(w, wid.x - self.x + wid.w)
            h = max(h, wid.y - self.y + wid.h)
        self.w = max(self.w, w + self.border_w - 1) + extra_w
        self.h = max(self.h, h + self.border_h - 1) + extra_h

    def redraw(self):
        # Init some state on first redraw
        if self.focus_idx == -1:
            self.autosize()
            self.focus_idx, self.focus_w = self.find_focusable_by_idx(0, 1)
            if self.focus_w:
                self.focus_w.focus = True

        # Redraw widgets with cursor off
        self.cursor(False)
        self.dialog_box(self.x, self.y, self.w, self.h, self.title)
        for w in self.childs:
            w.redraw()
        # Then give widget in focus a chance to enable cursor
        if self.focus_w:
            self.focus_w.set_cursor()

    def find_focusable_by_idx(self, from_idx, direction):
        sz = len(self.childs)
        while 0 <= from_idx < sz:
            if isinstance(self.childs[from_idx], FocusableWidget):
                return from_idx, self.childs[from_idx]
            from_idx = (from_idx + direction) % sz
        return None, None

    def find_focusable_by_xy(self, x, y):
        i = 0
        for w in self.childs:
            if isinstance(w, FocusableWidget) and w.inside(x, y):
                return i, w
            i += 1
        return None, None

    def change_focus(self, widget):
        if widget is self.focus_w:
            return
        if self.focus_w:
            self.focus_w.focus = False
            self.focus_w.redraw()
        self.focus_w = widget
        widget.focus = True
        widget.redraw()
        widget.set_cursor()

    def move_focus(self, direction):
        prev_idx = (self.focus_idx + direction) % len(self.childs)
        self.focus_idx, new_w = self.find_focusable_by_idx(prev_idx, direction)
        self.change_focus(new_w)

    def handle_key(self, key):
        if key == KEY_QUIT:
            return key
        if key == KEY_ESC and self.finish_on_esc:
            return ACTION_CANCEL
        if key == KEY_TAB:
            self.move_focus(1)
        elif key == KEY_SHIFT_TAB:
            self.move_focus(-1)
        elif self.focus_w:
            if key == KEY_ENTER:
                if self.focus_w.finish_dialog is not False:
                    return self.focus_w.finish_dialog
            res = self.focus_w.handle_key(key)
            if res == ACTION_PREV:
                self.move_focus(-1)
            elif res == ACTION_NEXT:
                self.move_focus(1)
            else:
                return res

    def handle_mouse(self, x, y):
        # Work in absolute coordinates
        if self.inside(x, y):
            self.focus_idx, w = self.find_focusable_by_xy(x, y)
#            print(w)
            if w:
                self.change_focus(w)
                return w.handle_mouse(x, y)


class WLabel(Widget):

    def __init__(self, text, w=0):
        self.t = text
        self.h = 1
        self.w = w
        if not w:
            self.w = len(text)

    def redraw(self):
        self.goto(self.x, self.y)
        self.wr_fixedw(self.t, self.w)


class WFrame(Widget):

    def __init__(self, w, h, title=""):
        self.w = w
        self.h = h
        self.t = title

    def redraw(self):
        self.draw_box(self.x, self.y, self.w, self.h)
        if self.t:
            pos = 1
            self.goto(self.x + pos, self.y)
            self.wr(" %s " % self.t)


class WButton(FocusableWidget):

    def __init__(self, w, text):
        Widget.__init__(self)
        self.t = text
        self.h = 1
        self.w = w or len(text) + 2
        self.disabled = False
        self.focus = False
        self.finish_dialog = False

    def redraw(self):
        self.goto(self.x, self.y)
        if self.disabled:
            self.attr_color(C_WHITE, C_GRAY)
        else:
            if self.focus:
                self.attr_color(C_B_WHITE, C_GREEN)
            else:
                self.attr_color(C_BLACK, C_GREEN)
        self.wr(self.t.center(self.w))
        self.attr_reset()

    def handle_mouse(self, x, y):
        if not self.disabled:
            if self.finish_dialog is not False:
                return self.finish_dialog
            else:
                self.signal("click")

    def handle_key(self, key):
        if key == KEY_UP or key == KEY_LEFT:
            return ACTION_PREV
        if key == KEY_DOWN or key == KEY_RIGHT:
            return ACTION_NEXT
        # For dialog buttons (.finish_dialog=True), KEY_ENTER won't
        # reach here.
        if key == KEY_ENTER:
            self.signal("click")

    def on_click(self):
        pass


class WCheckbox(ChoiceWidget):

    def __init__(self, title, choice=False):
        super().__init__(choice)
        self.t = title
        self.h = 1
        self.w = 4 + len(title)
        self.focus = False

    def redraw(self):
        self.goto(self.x, self.y)
        if self.focus:
            self.attr_color(C_B_BLUE, None)
        self.wr("[x] " if self.choice else "[ ] ")
        self.wr(self.t)
        self.attr_reset()

    def flip(self):
        self.choice = not self.choice
        self.redraw()
        self.signal("changed")

    def handle_mouse(self, x, y):
        self.flip()

    def handle_key(self, key):
        if key == KEY_UP:
            return ACTION_PREV
        if key == KEY_DOWN:
            return ACTION_NEXT
        if key == b" ":
            self.flip()


class WRadioButton(ItemSelWidget):

    def __init__(self, items):
        super().__init__(items)
        self.h = len(items)
        self.w = 4 + self.longest(items)
        self.focus = False

    def redraw(self):
        i = 0
        if self.focus:
            self.attr_color(C_B_BLUE, None)
        for t in self.items:
            self.goto(self.x, self.y + i)
            self.wr("(*) " if self.choice == i else "( ) ")
            self.wr(t)
            i += 1
        self.attr_reset()

    def handle_mouse(self, x, y):
        self.choice = y - self.y
        self.redraw()
        self.signal("changed")

    def handle_key(self, key):
        if key == KEY_UP:
            self.move_sel(-1)
        elif key == KEY_DOWN:
            self.move_sel(1)


class WListBox(EditorExt, ChoiceWidget):

    def __init__(self, w, h, items):
        EditorExt.__init__(self)
        ChoiceWidget.__init__(self, 0)
        self.width = w
        self.w = w
        self.height = h
        self.h = h
        self.set_items(items)
        self.focus = False

    def set_items(self, items):
        self.items = items
        self.set_lines(items)

    def render_line(self, l):
        # Default identity implementation is suitable for
        # items being list of strings.
        return l

    def show_line(self, l, i):
        hlite = self.cur_line == i
        if hlite:
            if self.focus:
                self.attr_color(C_B_WHITE, C_GREEN)
            else:
                self.attr_color(C_BLACK, C_GREEN)
        if i != -1:
            l = self.render_line(l)[:self.width]
            self.wr(l)
        self.clear_num_pos(self.width - len(l))
        if hlite:
            self.attr_reset()

    def handle_mouse(self, x, y):
        res = super().handle_mouse(x, y)
        self.choice = self.cur_line
        self.redraw()
        self.signal("changed")
        return res

    def handle_key(self, key):
        res = super().handle_key(key)
        self.choice = self.cur_line
        self.redraw()
        self.signal("changed")
        return res

    def handle_edit_key(self, key):
        pass

    def set_cursor(self):
        Widget.set_cursor(self)

    def cursor(self, state):
        # Force off
        super().cursor(False)


class WPopupList(Dialog):

    class OneShotList(WListBox):

        def handle_key(self, key):
            if key == KEY_ENTER:
                return ACTION_OK
            if key == KEY_ESC:
                return ACTION_CANCEL
            return super().handle_key(key)

        def handle_mouse(self, x, y):
            if super().handle_mouse(x, y) == True:
                # (Processed) mouse click finishes selection
                return ACTION_OK

    def __init__(self, x, y, w, h, items, sel_item=0):
        super().__init__(x, y, w, h)
        self.list = self.OneShotList(w - 2, h - 2, items)
        self.list.cur_line = sel_item
        self.add(1, 1, self.list)

    def handle_mouse(self, x, y):
        if not self.inside(x, y):
            return ACTION_CANCEL
        return super().handle_mouse(x, y)

    def get_choice(self):
        return self.list.cur_line

    def get_selected_value(self):
        if not self.list.content:
            return None
        return self.list.content[self.list.cur_line]


class WDropDown(ChoiceWidget):

    def __init__(self, w, items, *, dropdown_h=5):
        super().__init__(0)
        self.items = items
        self.h = 1
        self.w = w
        self.dropdown_h = dropdown_h
        self.focus = False

    def redraw(self):
        self.goto(self.x, self.y)
        if self.focus:
            self.attr_color(C_B_WHITE, C_CYAN)
        else:
            self.attr_color(C_BLACK, C_CYAN)
        self.wr_fixedw(self.items[self.choice], self.w - 1)
        self.attr_reset()
        self.wr(DOWN_ARROW)

    def handle_mouse(self, x, y):
        popup = WPopupList(self.x, self.y + 1, self.w, self.dropdown_h, self.items, self.choice)
        res = popup.loop()
        if res == ACTION_OK:
            self.choice = popup.get_choice()
            self.signal("changed")
        self.owner.redraw()

    def handle_key(self, key):
        self.handle_mouse(0, 0)


class WTextEntry(EditorExt, EditableWidget):

    def __init__(self, w, text):
        EditorExt.__init__(self, width=w, height=1)
        self.t = text
        self.h = 1
        self.w = w
        self.focus = False
        self.set(text)
        self.col = len(text)
        self.adjust_cursor_eol()
        self.just_started = True

    def get(self):
        return self.get_cur_line()

    def set(self, text):
        self.set_lines([text])

    def handle_cursor_keys(self, key):
        if super().handle_cursor_keys(key):
            if self.just_started:
                self.just_started = False
                self.redraw()
            return True
        return False

    def handle_edit_key(self, key):
        if key == KEY_ENTER:
            # Don't treat as editing key
            return True
        if self.just_started:
            if key != KEY_BACKSPACE:
                # Overwrite initial string with new content
                self.set_lines([""])
                self.col = 0
            self.just_started = False

        return super().handle_edit_key(key)

    def handle_mouse(self, x, y):
        if self.just_started:
            self.just_started = False
            self.redraw()
        super().handle_mouse(x, y)

    def show_line(self, l, i):
        if self.just_started:
            fg = C_WHITE
        else:
            fg = C_BLACK
        self.attr_color(fg, C_CYAN)
        super().show_line(l, i)
        self.attr_reset()


class WMultiEntry(EditorExt, EditableWidget):

    def __init__(self, w, h, lines):
        EditorExt.__init__(self, width=w, height=h)
        self.h = h
        self.w = w
        self.focus = False
        self.set_lines(lines)

    def get(self):
        return self.content

    def set(self, lines):
        self.set_lines(lines)

    def show_line(self, l, i):
        self.attr_color(C_BLACK, C_CYAN)
        super().show_line(l, i)
        self.attr_reset()


class WComboBox(WTextEntry):

    popup_class = WPopupList
    popup_h = 5

    def __init__(self, w, text, items):
        # w - 1 width goes to Editor widget
        super().__init__(w - 1, text)
        # We have full requested width, will show arrow symbol as last char
        self.w = w
        self.items = items

    def redraw(self):
        self.goto(self.x + self.w - 1, self.y)
        self.wr(DOWN_ARROW)
        super().redraw()

    def get_choices(self, substr):
        return self.items

    def show_popup(self):
        choices = self.get_choices(self.get())
        popup = self.popup_class(self.x, self.y + 1, self.longest(choices) + 2, self.popup_h, choices)
        popup.main_widget = self
        res = popup.loop()
        if res == ACTION_OK:
            val = popup.get_selected_value()
            if val is not None:
                self.set_lines([val])
                self.margin = 0
                self.col = sys.maxsize
                self.adjust_cursor_eol()
                self.just_started = False
        self.owner.redraw()

    def handle_key(self, key):
        if key == KEY_DOWN:
            self.show_popup()
        else:
            return super().handle_key(key)

    def handle_mouse(self, x, y):
        if x == self.x + self.w - 1:
            self.show_popup()
        else:
            super().handle_mouse(x, y)


class WCompletionList(WPopupList):

    def __init__(self, x, y, w, h, items):
        Dialog.__init__(self, x, y, w, h)
        self.list = self.OneShotList(w - 2, h - 2, items)
        self.add(1, 1, self.list)
        chk = WCheckbox("Prefix")
        def is_prefix_changed(wid):
            main = self.main_widget
            choices = main.get_choices(main.get(), wid.choice)
            self.list.set_lines(choices)
            self.list.top_line = 0
            self.list.cur_line = 0
            self.list.row = 0
            self.list.redraw()
        chk.on("changed", is_prefix_changed)
        self.add(1, h - 1, chk)


class WAutoComplete(WComboBox):

    popup_class = WCompletionList

    def get_choices(self, substr, only_prefix=False):
        substr = substr.lower()
        if only_prefix:
            choices = list(filter(lambda x: x.lower().startswith(substr), self.items))
        else:
            choices = list(filter(lambda x: substr in x.lower(), self.items))
        return choices

Zerion Mini Shell 1.0