Source code for ui.refresher

from threading import Event
from time import sleep

import PIL

from helpers import setup_logger
from ui.utils import to_be_foreground

logger = setup_logger(__name__, "info")


class RefresherExitException(Exception):
    pass


[docs]class Refresher(object): """ A Refresher allows you to update the screen on a regular interval. All you need is to provide a function that'll return the text/image you want to display; that function will then be called with the desired frequency and the display will be updated with whatever it returns. """
[docs] def __init__(self, refresh_function, i, o, refresh_interval=1, keymap=None, name="Refresher"): """Initialises the Refresher object. Args: * ``refresh_function``: a function which returns data to be displayed on the screen upon being called, in the format accepted by ``screen.display_data()`` or ``screen.display_image()``. To be exact, supported return values are: * Tuples and lists - are converted to lists and passed to ``display_data()`` * Strings - are converted to a single-element list and passed to ``display_data()`` * `PIL.Image` objects - are passed to ``display_image()`` * ``i``, ``o``: input&output device objects Kwargs: * ``refresh_interval``: Time between display refreshes (and, accordingly, ``refresh_function`` calls). * ``keymap``: Keymap entries you want to set while Refresher is active. By default, KEY_LEFT deactivates the Refresher, if you wan tto override it, do it carefully. * ``name``: Refresher name which can be used internally and for debugging. """ self.i = i self.o = o self.name = name self.set_refresh_interval(refresh_interval) self.refresh_function = refresh_function self.calculate_intervals() self.set_keymap(keymap if keymap else {}) self.in_foreground = False self.in_background = Event()
def to_foreground(self): """ Is called when refresher's ``activate()`` method is used, sets flags and performs all the actions so that refresher can display its contents and receive keypresses.""" logger.debug("refresher {} in foreground".format(self.name)) self.in_background.set() self.in_foreground = True self.activate_keymap() self.refresh() def to_background(self): """ Signals ``activate`` to finish executing """ self.in_foreground = False logger.debug("refresher {} in background".format(self.name))
[docs] def activate(self): """ A method which is called when refresher needs to start operating. Is blocking, sets up input&output devices, renders the refresher, periodically calls the refresh function&refreshes the screen while self.in_foreground is True, while refresher callbacks are executed from the input device thread.""" logger.debug("refresher {} activated".format(self.name)) self.to_foreground() while self.in_background.isSet(): self.idle_loop() logger.debug(self.name+" exited") return True
@property def activated(self): return self.in_background def pause(self): """ Pauses the refresher, not allowing it to print anything on the screen while it's paused. """ self.in_foreground = False def resume(self): """ Resumes the refresher after it's been paused, allowing it to continue printing things on the screen. Refreshes the screen when it's called. """ if not self.in_foreground: self.in_foreground = True self.activate_keymap() self.refresh() def set_refresh_interval(self, new_interval): """Allows setting Refresher's refresh intervals after it's been initialized""" #interval for checking the in_background property in the activate() #when refresh_interval is small enough, is the same as refresh_interval self.refresh_interval = new_interval self.sleep_time = 0.1 if new_interval > 0.1 else new_interval self.calculate_intervals() def calculate_intervals(self): """Calculates the sleep intervals of the refresher, so that no matter the ``refresh_interval``, the refresher is responsive. Also, sets the counter to zero.""" #in_background of the refresher needs to be checked approx. each 0.1 second, #since users expect the refresher to exit almost instantly iterations_before_refresh = self.refresh_interval/self.sleep_time if iterations_before_refresh < 1: logger.warning("{}: self.refresh_interval is smaller than self.sleep_time!".format(self.name)) #Failsafe self.iterations_before_refresh = 1 else: self.iterations_before_refresh = int(iterations_before_refresh) self._counter = 0 def idle_loop(self): if self.in_foreground: if self._counter == self.iterations_before_refresh: self._counter = 0 if self._counter == 0: self.refresh() self._counter += 1 sleep(self.sleep_time)
[docs] def deactivate(self): """ Deactivates the refresher completely, exiting it.""" self.in_foreground = False self.in_background.clear() logger.debug("refresher {} deactivated".format(self.name))
[docs] def print_name(self): """ A debug method. Useful for hooking up to an input event so that you can see which refresher is currently active. """ logger.debug("Active refresher is {}".format(self.name))
def process_callback(self, func): """ Decorates a function to be used by Refresher element. |Is typically used as a wrapper for a callback from input event processing thread. |After callback's execution is finished, sets the keymap again and refreshes the refresher.""" def wrapper(*args, **kwargs): self.to_background() func(*args, **kwargs) logger.debug("{}: executed wrapped function: {}".format(self.name, func.__name__)) if self.in_background.isSet(): self.to_foreground() wrapper.__name__ == func.__name__ return wrapper def process_keymap(self, keymap, override_left=True): """ Processes the keymap, wrapping all callbacks using the ``process_callback`` method. Also, sets KEY_LEFT unless ``override_left`` keyword argument is False (use with caution, there aren't many good reasons to do this). """ logger.debug("{}: processing keymap - {}".format(self.name, keymap)) for key in keymap: callback = self.process_callback(keymap[key]) keymap[key] = callback if override_left: if not "KEY_LEFT" in keymap: keymap["KEY_LEFT"] = self.deactivate return keymap def set_keymap(self, keymap): """Sets the refresher's keymap (filtered using ``process_keymap`` before setting).""" self.keymap = self.process_keymap(keymap) def update_keymap(self, new_keymap): """Sets the refresher's keymap (filtered using ``process_keymap`` before setting).""" processed_keymap = self.process_keymap(new_keymap, override_left=False) self.keymap.update(processed_keymap) @to_be_foreground def activate_keymap(self): if self.i: self.i.stop_listen() self.i.clear_keymap() self.i.set_keymap(self.keymap) self.i.listen() else: logger.warning("{}: no input device object supplied, not setting the keymap".format(self.name)) @to_be_foreground def refresh(self): logger.debug("{}: refreshed data on display".format(self.name)) try: data_to_display = self.refresh_function() except RefresherExitException: logger.info("{}: received exit exception, deactivating".format(self.name)) self.deactivate() return if isinstance(data_to_display, basestring): #Passed a string, not a list. #Let's be user-friendly and wrap it in a list! data_to_display = [data_to_display] elif isinstance(data_to_display, tuple): #Passed a tuple. Let's convert it into a list! data_to_display = list(data_to_display) elif isinstance(data_to_display, PIL.Image.Image): if "b&w-pixel" not in self.o.type: raise ValueError("The screen doesn't support showing images!") self.o.display_image(data_to_display) return elif not isinstance(data_to_display, list): raise ValueError("refresh_function returned an unsupported type: {}!".format(type(data_to_display))) self.o.display_data(*data_to_display)