|
import sys |
|
|
|
if sys.platform == 'win32': |
|
|
|
|
|
|
|
curses = None |
|
else: |
|
import curses |
|
|
|
|
|
class Menu: |
|
"""A curses menu that allows the user to select one item from each list. |
|
|
|
Args: |
|
lists (list[list[str]]): A list of lists of strings, where each list |
|
represents a list of items to be selected from. |
|
prompts (list[str], optional): A list of prompts to be displayed above |
|
each list. Defaults to None, in which case each list will be |
|
displayed without a prompt. |
|
""" |
|
|
|
def __init__(self, lists, prompts=None): |
|
self.choices_lists = lists |
|
self.prompts = prompts or ['Please make a selection:'] * len(lists) |
|
self.choices = [] |
|
self.current_window = [] |
|
|
|
def draw_menu(self, stdscr, selected_row_idx, offset, max_rows): |
|
stdscr.clear() |
|
h, w = stdscr.getmaxyx() |
|
for idx, row in enumerate(self.current_window[offset:offset + |
|
max_rows]): |
|
x = w // 2 - len(row) // 2 |
|
y = min(h - 1, |
|
idx + 1) |
|
if idx == selected_row_idx - offset: |
|
stdscr.attron(curses.color_pair(1)) |
|
stdscr.addstr(y, x, row) |
|
stdscr.attroff(curses.color_pair(1)) |
|
else: |
|
stdscr.addstr(y, x, row) |
|
stdscr.refresh() |
|
|
|
def run(self): |
|
curses.wrapper(self.main_loop) |
|
return self.choices |
|
|
|
def main_loop(self, stdscr): |
|
curses.curs_set(0) |
|
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE) |
|
h, w = stdscr.getmaxyx() |
|
max_rows = h - 2 |
|
|
|
for choices, prompt in zip(self.choices_lists, self.prompts): |
|
self.current_window = [prompt] + choices |
|
current_row_idx = 1 |
|
offset = 0 |
|
|
|
while 1: |
|
self.draw_menu(stdscr, current_row_idx, offset, max_rows) |
|
key = stdscr.getch() |
|
|
|
if key == curses.KEY_UP and current_row_idx > 1: |
|
current_row_idx -= 1 |
|
if current_row_idx - offset < 1: |
|
offset -= 1 |
|
|
|
elif key == curses.KEY_DOWN and current_row_idx < len(choices): |
|
current_row_idx += 1 |
|
if current_row_idx - offset > max_rows - 1: |
|
offset += 1 |
|
|
|
elif key == curses.KEY_ENTER or key in [10, 13]: |
|
self.choices.append(choices[current_row_idx - 1]) |
|
break |
|
|