Source code for ui.menu

from threading import Event
from time import sleep

from base_list_ui import BaseListUIElement, to_be_foreground
from loading_indicators import LoadingBar
from utils import clamp, clamp_list_index
from entry import Entry

from 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"""

    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.isSet():
            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()