Source code for zpui_lib.ui.menu

from threading import Event
from time import sleep

from zpui_lib.ui.base_list_ui import BaseListUIElement, to_be_foreground
from zpui_lib.ui.loading_indicators import LoadingBar
from zpui_lib.ui.utils import clamp, clamp_list_index
from zpui_lib.ui.entry import Entry
from zpui_lib.helpers import setup_logger

logger = setup_logger(__name__, "warning")








class MenuRenderingMixin(object):
    """A mixin to add Menu-specific rendering to views.
    If you're making your own view for BaseListUIElements and want it to
    work with menu UI elements, you will probably want to use this mixin,
    like this:

    .. code-block:: python

        class MeEightPtView(MenuRenderingMixin, EightPtView):
            pass

    """

    def has_second_callback(self, entry):
        if isinstance(entry, Entry):
            if callable(entry.cb2):
                return True
        elif len(entry) > 2 and callable(entry[2]):
            return True
        return False

    def draw_graphic(self, c, index):
        # c is the canvas object
        # draw_graphic is called for each line of text - not menu entry!
        # so, index is the "line number" - assuming 4 lines/display,
        # for entry_height=1, each index will correspond to a menu entry
        # for entry_height=2, indices 0-1 will be used for the first menu entry
        # and 2-3 will be used for the second menu entry
        # hence, the /self.entry_height part
        contents_entry = self.el.contents[self.first_displayed_entry + index//self.entry_height]
        if self.has_second_callback(contents_entry):
            tw, th = self.charwidth // 2, self.charheight // 2
            right_offset = 1
            top_offset = (self.charheight - th) // 2
            coords = (
                (str(-1*(right_offset+tw)), index * self.charheight + top_offset),
                (str(-1*(right_offset+tw)), index * self.charheight + top_offset + th),
                (str(-1*(right_offset)), index * self.charheight + th),
            )
            c.polygon(coords, fill=c.default_color)

    def draw_menu_text(self, c, menu_text, left_offset):
        for i, line in enumerate(menu_text):
            y = (i * self.charheight - 1) if i != 0 else 0
            c.text(line, (left_offset, y), font=self.font)
            if "b&w" in self.o.type:
                self.draw_graphic(c, i)


Menu.view_mixin = MenuRenderingMixin


class MessagesMenu(Menu):
    """
    A modified version of the Menu class
    for displaying a list of messages and loading new ones

    HERE BE DRAGONS
    FORMATTING PROBABLY BROKEN
    PROCEED WITH CAUTION
    """

    load_more_possible = True
    load_more_marker = ["Load more"]

    def __init__(self, *args, **kwargs):
        self.load_more_callback = kwargs.pop("load_more_callback", None)
        self.load_more_trigger_point = kwargs.pop("load_more_trigger_point", 0)
        self.load_more_allow_refresh = Event()
        self.load_more_allow_refresh.set()

        Menu.__init__(self, *args, **kwargs)

    def before_activate(self):
        Menu.before_activate(self)
        self.pointer = clamp(len(self.contents) - 2, 0, len(self.contents)-1)
        if self.contents: # Not empty
            self.add_load_more_marker()

    def add_load_more_marker(self):
        if [self.load_more_marker] not in self.contents:
            self.contents = [self.load_more_marker] + self.contents

    def remove_load_more_marker(self):
        while self.load_more_marker in self.contents:
            self.contents.remove(self.load_more_marker)

    def load_more(self):
        self.load_more_allow_refresh.clear()
        before = len(self.contents)
        self.remove_load_more_marker()
        has_loaded_more_events = True
        contents_added = False
        counter = 0
        li = None
        while has_loaded_more_events and not contents_added:
            if counter == 5: # the user is let down, let's at least show them stuff is happening
                li = LoadingBar(self.i, self.o, message="Loading messages", name="{} - load_more() LoadingBar")
                li.run_in_background()
                has_loaded_more_events = self.load_more_callback()
                logger.debug("Loaded more events!")
                if has_loaded_more_events:
                    self.remove_load_more_marker()
                    self.add_load_more_marker()
                    after = len(self.contents)
            difference = after-before
            if difference > 0:
                contents_added = True
                logger.info("Loaded {} messages".format(difference))
                self.pointer += (after-before)+1
            else:
                logger.info("Loaded events but no messages, retrying")
            self.pointer = clamp_list_index(self.pointer, self.contents)
        else:
            self.remove_load_more_marker()
            counter += 1
        if li: # LoadingBar fired up, need to stop it now
            li.stop()
            sleep(0.5) # until it actually stops =D
            self.activate_input() #reset keymap back to normal
        self.load_more_allow_refresh.set()
        self.refresh()

    @to_be_foreground
    def refresh(self):
        if not self.load_more_allow_refresh.is_set():
            return
        Menu.refresh(self)

    @to_be_foreground
    def move_up(self):
        Menu.move_up(self)

        if self.pointer <= self.load_more_trigger_point:
            self.load_more()