from time import sleep
from copy import copy
import logging
from threading import Event
#logging.basicConfig(level=logging.DEBUG)
def to_be_foreground(func): #A safety check wrapper so that certain functions don't get called if refresher is not the one active
def wrapper(self, *args, **kwargs):
if self.in_foreground:
return func(self, *args, **kwargs)
else:
print(func.__name__+" misbehaves")
return wrapper
[docs]class Refresher():
"""Implements a state where display is refreshed from time to time, updating the screen with information from a function.
"""
refresh_function = None
refresh_interval = 0
display_callback = None
in_foreground = False
name = ""
keymap = None
[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()``
* ``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
* ``name``: Refresher name which can be used internally and for debugging.
"""
self.i = i
self.o = o
self.name = name
self.refresh_interval = refresh_interval
self.refresh_function = refresh_function
self.set_keymap(keymap if keymap else {})
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."""
logging.info("refresher {0} in foreground".format(self.name))
self.in_background.set()
self.in_foreground = True
self.refresh()
self.activate_keymap()
def to_background(self):
""" Signals ``activate`` to finish executing """
self.in_foreground = False
logging.info("refresher {0} 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."""
logging.info("refresher {0} activated".format(self.name))
self.to_foreground()
counter = 0
divisor = 2.0
sleep_time = self.refresh_interval/divisor
while self.in_background.isSet():
if self.in_foreground:
if counter == divisor:
counter = 0
if counter == 0:
self.refresh()
counter += 1
sleep(sleep_time)
logging.debug(self.name+" exited")
return True
[docs] def deactivate(self):
""" Deactivates the refresher completely, exiting it."""
self.in_foreground = False
self.in_background.clear()
logging.info("refresher {0} 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. """
logging.info("Active refresher is {0}".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)
logging.debug("Executed wrapped function: {}".format(func.__name__))
if self.in_background.isSet():
self.to_foreground()
wrapper.__name__ == func.__name__
return wrapper
def process_keymap(self, keymap):
"""Sets the keymap. In future, will allow per-system keycode-to-callback tweaking using a config file. """
logging.debug("{}: processing keymap - {}".format(self.name, keymap))
for key in keymap:
callback = self.process_callback(keymap[key])
keymap[key] = callback
if not "KEY_LEFT" in keymap:
keymap["KEY_LEFT"] = self.deactivate
#if not "KEY_RIGHT" in keymap and:
# keymap["KEY_RIGHT"] = self.print_name
return keymap
def set_keymap(self, keymap):
"""Generate and sets the input device's keycode-to-callback mapping. Re-starts the input device because of passing-variables-between-threads issues."""
self.keymap = self.process_keymap(keymap)
@to_be_foreground
def activate_keymap(self):
self.i.stop_listen()
self.i.clear_keymap()
self.i.keymap = self.keymap
self.i.listen()
@to_be_foreground
def refresh(self):
logging.debug("{0}: refreshed data on display".format(self.name))
self.o.display_data(*self.refresh_function())