Source code for ui.char_input

from time import sleep
import logging

import string

def to_be_foreground(func): #A safety check wrapper so that certain checks don't get called if menu is not the one active
    def wrapper(self, *args, **kwargs):
        if self.in_foreground:
            return func(self, *args, **kwargs)
        else:
            return False
    return wrapper

[docs]class CharArrowKeysInput(): """Implements a character input dialog which allows to input a character string using arrow keys to scroll through characters Attributes: * ``value``: A list of characters of the currently displayed value * ``position``: Position of the currently edited character. * ``cancel_flag``: A flag that is set when editing is cancelled. * ``in_foreground``: A flag which indicates if UI element is currently displayed. If it's not active, inhibits any of element's actions which can interfere with other UI element being displayed. * ``charmap``: Internal string that contains all of the possible character values * ``char_indices``: A list containing char's index in ``charmap`` for every char in ``value`` list * ``first_displayed_char``: An integer pointer to the first character currently displayed (for the occasions where part of value is off-screen) * ``last_displayed_char``: An integer pointer to the last character currently displayed """ chars = string.ascii_lowercase Chars = string.ascii_uppercase numbers = '0123456789' hexadecimals = '0123456789ABCDEF' specials = "!\"#$%&'()[]<>*+,-./:;=?^_|" space = ' ' backspace = chr(0x08) mapping = { '][c':chars, '][C':Chars, '][n':numbers, '][S':space, '][b':backspace, '][h':hexadecimals, '][s':specials} in_foreground = False value = [] position = 0 cancel_flag = False charmap = "" last_displayed_char = 0 first_displayed_char = 0
[docs] def __init__(self, i, o, initial_value = "", message="Value:", allowed_chars=["][S", "][c", "][C", "][s", "][n"], name="CharArrowKeysInput"): """Initialises the CharArrowKeysInput object. Args: * ``i``, ``o``: input&output device objects Kwargs: * ``initial_value``: Value to be edited. If not set, will start with an empty string. * ``allowed_chars``: Characters to be used during input. Is a list of strings designating ranges which can be the following: * '][c' for lowercase ASCII characters * '][C' for uppercase ASCII characters * '][s' for special characters from those supported by HD44780 character maps * '][S' for space * '][n' for numbers * '][h' for hexadecimal characters (0-F) If a string does not designate a range of characters, it'll be added to character map as-is. * ``message``: Message to be shown in the first row of the display * ``name``: UI element name which can be used internally and for debugging. """ self.i = i self.o = o self.screen_cols = self.o.cols self.last_displayed_char = self.screen_cols self.message = message self.name = name self.generate_keymap() self.allowed_chars = allowed_chars self.allowed_chars.append("][b") self.generate_charmap() if type(initial_value) != str: raise ValueError("CharArrowKeysInput needs a string!") self.value = list(initial_value) self.char_indices = [] #Fixes a bug with char_indixes remaining from previous input ( 0_0 ) for char in self.value: self.char_indices.append(self.charmap.index(char))
def to_foreground(self): """ Is called when ``activate()`` method is used, sets flags and performs all the actions so that UI element can display its contents and receive keypresses. Also, refreshes the screen.""" logging.info("{0} enabled".format(self.name)) self.in_foreground = True self.refresh() self.set_keymap()
[docs] def activate(self): """ A method which is called when input element needs to start operating. Is blocking, sets up input&output devices, renders the element and waits until self.in_background is False, while menu callbacks are executed from the input device thread. This method returns the selected value if KEY_ENTER was pressed, thus accepting the selection. This method returns None when the UI element was exited by KEY_LEFT and thus the value was not accepted. """ logging.info("{0} activated".format(self.name)) self.to_foreground() self.o.cursor() while self.in_foreground: #All the work is done in input callbacks sleep(0.1) self.o.noCursor() logging.debug(self.name+" exited") if self.cancel_flag: return None else: return ''.join(self.value) #Making string from the list we have
[docs] def deactivate(self): """ Deactivates the UI element, exiting it and thus making activate() return.""" self.in_foreground = False logging.info("{0} deactivated".format(self.name))
[docs] def print_value(self): """ A debug method. Useful for hooking up to an input event so that you can see current value. """ logging.info(self.value)
[docs] def print_name(self): """ A debug method. Useful for hooking up to an input event so that you can see which UI element is currently processing input events. """ logging.info("{0} active".format(self.name))
@to_be_foreground def move_up(self): """Changes the current character to the next character in the charmap""" while len(self.char_indices) <= self.position: self.char_indices.append(0) self.value.append(self.charmap[0]) char_index = self.char_indices[self.position] if char_index >= (len(self.charmap)-1): char_index = 0 else: char_index += 1 self.char_indices[self.position] = char_index self.value[self.position] = self.charmap[char_index] self.refresh() @to_be_foreground def move_down(self): """Changes the current character to the previous character in the charmap""" while len(self.char_indices) <= self.position: self.char_indices.append(0) self.value.append(self.charmap[0]) char_index = self.char_indices[self.position] if char_index == 0: char_index = len(self.charmap) - 1 else: char_index -= 1 self.char_indices[self.position] = char_index self.value[self.position] = self.charmap[char_index] self.refresh() @to_be_foreground def move_right(self): """Moves cursor to the next element. """ self.check_for_backspace() self.position += 1 if self.last_displayed_char < self.position: #Went too far to the part of the value that isn't currently displayed self.last_displayed_char = self.position self.first_displayed_char = self.position - self.screen_cols self.refresh() @to_be_foreground def move_left(self): """Moves cursor to the previous element. If first element is chosen, exits and makes the element return None.""" self.check_for_backspace() if self.position == 0: self.exit() return self.position -= 1 if self.first_displayed_char > self.position: #Went too far back to the part that's not currently displayed self.first_displayed_char = self.position self.last_displayed_char = self.position + self.screen_cols self.refresh() @to_be_foreground def accept_value(self): """Selects the currently active number value, making activate() return it.""" self.check_for_backspace() logging.debug("Value accepted") self.deactivate() @to_be_foreground def exit(self): """Exits discarding all the changes to the value.""" logging.debug("{} exited without changes".format(self.name)) self.cancel_flag = True self.deactivate() def generate_keymap(self): self.keymap = { "KEY_RIGHT":lambda: self.move_right(), "KEY_UP":lambda: self.move_up(), "KEY_DOWN":lambda: self.move_down(), "KEY_LEFT":lambda: self.move_left(), "KEY_KPENTER":lambda: self.accept_value(), "KEY_ENTER":lambda: self.accept_value() } def generate_charmap(self): for value in self.allowed_chars: if value in self.mapping.keys(): self.charmap += self.mapping[value] else: self.charmap += value @to_be_foreground def set_keymap(self): self.i.stop_listen() self.i.clear_keymap() self.i.keymap = self.keymap self.i.listen() def check_for_backspace(self): for i, char_value in enumerate(self.value): if char_value == self.backspace: self.value.pop(i) self.char_indices.pop(i) def get_displayed_data(self): """Formats the value and the message to show it on the screen, then returns a list that can be directly used by o.display_data""" if self.first_displayed_char >= len(self.value): #Value is off-screen value = "" else: value = ''.join(self.value)[self.first_displayed_char:][:self.screen_cols] value = value.replace(self.backspace, chr(0x7f)) value = value.replace(' ', chr(255)) #Displaying all spaces as black boxes return [self.message, value] @to_be_foreground def refresh(self): self.o.display_data(*self.get_displayed_data()) self.o.setCursor(1, self.position-self.first_displayed_char) logging.debug("{}: refreshed data on display".format(self.name))