from time import sleep
from helpers import setup_logger
from funcs import format_for_screen as ffs
from base_ui import BaseUIElement
from canvas import Canvas
logger = setup_logger(__name__, "info")
[docs]class DialogBox(BaseUIElement):
"""Implements a dialog box with given values (or some default ones if chosen)."""
view = None
value_selected = False
selected_option = 0
default_options = {"y":["Yes", True], 'n':["No", False], 'c':["Cancel", None]}
start_option = 0
[docs] def __init__(self, values, i, o, message="Are you sure?", name="DialogBox", **kwargs):
"""Initialises the DialogBox object.
Args:
* ``values``: values to be used. Should be a list of ``[label, returned_value]`` pairs.
* You can also pass a string "yn" to get "Yes(True), No(False)" options, or "ync" to get "Yes(True), No(False), Cancel(None)" options.
* Values put together with spaces between them shouldn't be longer than the screen's width.
* ``i``, ``o``: input&output device objects
Kwargs:
* ``message``: Message to be shown on the first line of the screen when UI element is activated
* ``name``: UI element name which can be used internally and for debugging.
"""
BaseUIElement.__init__(self, i, o, name, **kwargs)
if isinstance(values, basestring):
self.values = []
for char in values:
self.values.append(self.default_options[char])
#value_str = " ".join([value[0] for value in values])
#assert(len(value_str) <= o.cols, "Resulting string too long for the display!")
else:
if not type(values) in (list, tuple):
raise ValueError("Unsupported 'values' argument - needs a list, supplied {}".format(repr(values)))
if not values:
raise ValueError("Empty/invalid 'values' argument!")
for i, value in enumerate(values):
if isinstance(value, basestring) and value in self.default_options:
#"y", "n" or "c" supplied as a shorthand for one of three default arguments
values[i] = self.default_options[value]
self.values = values
self.message = message
self.set_view()
# Keymap will depend on view
self.set_default_keymap()
def set_view(self):
if "b&w" in self.o.type:
view_class = GraphicalView
elif "char" in self.o.type:
view_class = TextView
else:
raise ValueError("Unsupported display type: {}".format(repr(self.o.type)))
self.view = view_class(self.o, self)
[docs] def set_start_option(self, option_number):
"""
Allows you to set position of the option that'll be selected upon DialogBox activation.
"""
self.start_option = option_number
def before_activate(self):
self.value_selected = False
self.selected_option = self.start_option
@property
def is_active(self):
return self.in_foreground
def get_return_value(self):
if self.value_selected:
return self.values[self.selected_option][1]
else:
return None
def idle_loop(self):
sleep(0.1)
def generate_keymap(self):
km = {"KEY_ENTER": 'accept_value'}
scroll_is_vertical = getattr(self.view, 'scroll_is_vertical', False)
if scroll_is_vertical:
km.update({
"KEY_DOWN": 'move_right',
"KEY_UP": 'move_left',
"KEY_LEFT": 'deactivate',
})
else:
km.update({
"KEY_RIGHT": 'move_right',
"KEY_LEFT": 'move_left',
})
return km
def move_left(self):
scroll_is_vertical = getattr(self.view, 'scroll_is_vertical', False)
if self.selected_option == 0:
if not scroll_is_vertical:
self.deactivate()
return
self.selected_option -= 1
self.refresh()
def move_right(self):
if self.selected_option == len(self.values)-1:
return
self.selected_option += 1
self.refresh()
def accept_value(self):
self.value_selected = True
self.deactivate()
def refresh(self):
self.view.refresh()
class TextView(object):
def __init__(self, o, el):
self.o = o
self.el = el
self.process_values()
def process_values(self):
labels = [label for label, value in self.el.values]
label_string = " ".join(labels)
if len(label_string) > self.o.cols:
raise ValueError("DialogBox {}: all values combined are longer than screen's width".format(self.el.name))
self.right_offset = (self.o.cols - len(label_string))/2
self.displayed_label = " "*self.right_offset+label_string
#Need to go through the string to mark the first places because we need to remember where to put the cursors
labels = [label for label, value in self.el.values]
current_position = self.right_offset
self.positions = []
for label in labels:
self.positions.append(current_position)
current_position += len(label) + 1
def refresh(self):
self.o.noCursor()
self.o.setCursor(1, self.positions[self.el.selected_option])
self.o.display_data(self.el.message, self.displayed_label)
self.o.cursor()
class GraphicalView(TextView):
scroll_is_vertical = True
def process_values(self):
self.positions = []
labels = [label for label, value in self.el.values]
for label in labels:
label_width = len(label)*self.o.char_width
label_start = (self.o.width - label_width)/2
if label_start < 0: label_start = 0
self.positions.append(label_start)
def get_image(self):
c = Canvas(self.o)
#Drawing text
chunk_y = 0
formatted_message = ffs(self.el.message, self.o.cols)
if len(formatted_message)*(self.o.char_height+2) > self.o.height - self.o.char_height - 2:
raise ValueError("DialogBox {}: message is too long to fit on the screen: {}".format(self.el.name, formatted_message))
for line in formatted_message:
c.text(line, (0, chunk_y))
chunk_y += self.o.char_height + 2
first_label_y = chunk_y
for i, value in enumerate(self.el.values):
label = value[0]
label_start = self.positions[i]
c.text(label, (label_start, chunk_y))
chunk_y += self.o.char_height + 2
#Calculating the cursor dimensions
first_char_x = self.positions[self.el.selected_option]
option_length = len( self.el.values[self.el.selected_option][0] ) * self.o.char_width
c_x1 = first_char_x - 2
c_x2 = c_x1 + option_length + 2
c_y1 = first_label_y + self.el.selected_option*(2 + self.o.char_height)
c_y2 = c_y1 + self.o.char_height
#Some readability adjustments
cursor_dims = ( c_x1, c_y1, c_x2 + 2, c_y2 + 2 )
#Drawing the cursor
c.invert_rect(cursor_dims)
return c.get_image()
def refresh(self):
self.o.display_image(self.get_image())