def _show(self): '''Create a scrollable region and fill it with the install log''' self.center_win.border_size = (0, 0) self.scroll_area = WindowArea(self.win_size_y, self.win_size_x, 0, 0, len(self.get_log_data())) log = ScrollWindow(self.scroll_area, window=self.center_win) log.add_paragraph(self.get_log_data(), 0, 2) self.center_win.activate_object(log)
def _show(self): '''Prepare a text summary from the install_profile and display it to the user in a ScrollWindow ''' y_loc = 1 y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc) y_loc += 1 summary_text = self.build_summary() # Wrap the summary text, accounting for the INDENT (used below in # the call to add_paragraph) max_chars = self.win_size_x - SummaryScreen.INDENT - 1 summary_text = convert_paragraph(summary_text, max_chars) area = WindowArea(x_loc=0, y_loc=y_loc, scrollable_lines=(len(summary_text)+1)) area.lines = self.win_size_y - y_loc area.columns = self.win_size_x scroll_region = ScrollWindow(area, window=self.center_win) scroll_region.add_paragraph(summary_text, start_x=SummaryScreen.INDENT) self.center_win.activate_object(scroll_region)
class MainWindow(object): '''Represent initscr (the whole screen), and break it into a border, header, and central region. Map F# keystrokes to Actions ''' def __init__(self, initscr, screen_list, theme=None, force_bw=False): '''Set the theme, and call reset to initialize the terminal to prepare for the first screen. ''' if theme is not None: self.theme = theme else: self.theme = ColorTheme(force_bw=force_bw) self.screen_list = screen_list self.initscr = initscr self.default_cursor_pos = (initscr.getmaxyx()[0] - 1, 0) self.cursor_pos = self.default_cursor_pos self.footer = None self.header = None self._cur_header_text = None self.central_area = None self.popup_win = None self.error_line = None self._active_win = None self.continue_action = None self.back_action = None self.help_action = None self.quit_action = None self.actions = None self.reset() def redrawwin(self): '''Completely repaint the screen''' self.header.redrawwin() self.footer.redrawwin() self.error_line.redrawwin() self.central_area.redrawwin() if self._active_win is self.popup_win: self.popup_win.redrawwin() def do_update(self): '''Wrapper to curses.doupdate()''' curses.setsyx(*self.get_cursor_loc()) curses.doupdate() def get_cursor_loc(self): '''Retrieve the current cursor position from the active UI element. ''' cursor = self.central_area.get_cursor_loc() if cursor is None: cursor = self.cursor_pos return cursor def reset(self): '''Create the InnerWindows representing the header, footer/border, error line, and main central_area ''' window_size = self.initscr.getmaxyx() win_size_y = window_size[0] win_size_x = window_size[1] footer_area = WindowArea(1, win_size_x, win_size_y - 1, 0) self.footer = InnerWindow(footer_area, color_theme=self.theme, color=self.theme.border) top = self.initscr.derwin(1, win_size_x, 0, 0) left = self.initscr.derwin(win_size_y - 2, 1, 1, 0) right = self.initscr.derwin(win_size_y - 2, 1, 1, win_size_x - 1) self.footer.more_windows = [top, left, right] self.footer.set_color(self.theme.border) header_area = WindowArea(1, win_size_x - 2, 1, 1) self.header = InnerWindow(header_area, color_theme=self.theme, color=self.theme.header) central_win_area = WindowArea(win_size_y - 4, win_size_x - 2, 2, 1) self.central_area = InnerWindow(central_win_area, border_size=(0, 2), color_theme=self.theme) self._active_win = self.central_area popup_win_area = WindowArea(central_win_area.lines - 10, central_win_area.columns - 20, 5, 10, central_win_area.lines - 10) self.popup_win = ScrollWindow(popup_win_area, window=self.central_area, color=self.theme.error_msg, highlight_color=self.theme.error_msg) error_area = WindowArea(1, win_size_x - 2, win_size_y - 2, 1) self.error_line = ErrorWindow(error_area, color_theme=self.theme) self.reset_actions() def reset_actions(self): '''Reset the actions to the defaults, clearing any custom actions registered by individual screens ''' self.continue_action = Action(curses.KEY_F2, _("Continue"), self.screen_list.get_next) self.back_action = Action(curses.KEY_F3, _("Back"), self.screen_list.previous_screen) self.help_action = Action(curses.KEY_F6, _("Help"), self.screen_list.show_help) self.quit_action = Action(curses.KEY_F9, _("Quit"), self.screen_list.quit) self.set_default_actions() def clear(self): '''Clear all InnerWindows and reset_actions()''' self.header.clear() self.footer.clear() self.central_area.clear() self.error_line.clear_err() self.reset_actions() def set_header_text(self, header_text): '''Set the header_text''' text = center_columns(header_text, self.header.area.columns - 1) self.header.add_text(text) self._cur_header_text = text def set_default_actions(self): '''Clear the actions dictionary and add the default actions back into it ''' self.actions = {} self.actions[self.continue_action.key] = self.continue_action self.actions[self.back_action.key] = self.back_action self.actions[self.help_action.key] = self.help_action self.actions[self.quit_action.key] = self.quit_action def show_actions(self): '''Read through the actions dictionary, displaying all the actions descriptive text along the footer (along with a prefix linked to its associated keystroke) ''' self.footer.window.clear() if InnerWindow.USE_ESC: prefix = " Esc-" else: prefix = " F" strings = [] length = 0 action_format = "%s%i_%s" for key in sorted(self.actions.keys()): key_num = key - curses.KEY_F0 action_text = self.actions[key].text action_str = action_format % (prefix, key_num, action_text) strings.append(action_str) display_str = "".join(strings) max_len = self.footer.window.getmaxyx()[1] length = textwidth(display_str) if not InnerWindow.USE_ESC: length += (len(" Esc-") - len(" F")) * len(self.actions) if length > max_len: raise ValueError("Can't display footer actions - string too long") self.footer.window.addstr(display_str.encode(get_encoding())) self.footer.window.noutrefresh() def getch(self): '''Call down into central_area to get a keystroke, and, if necessary, update the footer to switch to using the Esc- prefixes ''' input_key = self._active_win.getch() if input_key == InnerWindow.REPAINT_KEY: self.redrawwin() input_key = None if InnerWindow.UPDATE_FOOTER: InnerWindow.UPDATE_FOOTER = False self.show_actions() return input_key def process_input(self, current_screen): '''Read input until a keystroke that fires a screen change is caught ''' input_key = None while input_key not in self.actions: input_key = self.getch() input_key = self.central_area.process(input_key) self.do_update() return self.actions[input_key].do_action(current_screen) def pop_up(self, header, question, left_btn_txt, right_btn_txt, color=None): '''Suspend the current screen, setting the header to 'header', presenting the 'question,' and providing two 'buttons'. Returns True if the RIGHT button is selected, False if the LEFT is selected. The LEFT button is initially selected. ''' # Hide the cursor, storing its previous state (visibility) so # it can be restored when finished. Then, move the cursor # to the default position (in case this terminal type does not support # hiding the cursor entirely) try: old_cursor_state = curses.curs_set(0) except curses.error: old_cursor_state = 2 cursor_loc = curses.getsyx() curses.setsyx(self.cursor_pos[0], self.cursor_pos[1]) # Add the header, a border, and the question to the window self.popup_win.window.border() header_x = (self.popup_win.area.columns - textwidth(header)) / 2 self.popup_win.add_text(header, 0, header_x) y_loc = 2 y_loc += self.popup_win.add_paragraph(question, y_loc, 2) y_loc += 2 # Set the background color based on the parameter given, or choose # a default based on the theme. Set the highlight_color by flipping # the A_REVERSE bit of the color if color is None: color = self.popup_win.color self.popup_win.set_color(color) highlight_color = color ^ curses.A_REVERSE # Create two "buttons" of equal size by finding the larger of the # two, and centering them max_len = max(textwidth(left_btn_txt), textwidth(right_btn_txt)) left_btn_txt = " [ %s ]" % left_btn_txt.center(max_len) right_btn_txt = " [ %s ]" % right_btn_txt.center(max_len) button_len = textwidth(left_btn_txt) + 1 win_size = self.popup_win.window.getmaxyx() left_area = WindowArea(1, button_len, y_loc, (win_size[1] / 2) - (button_len + 2)) left_button = ListItem(left_area, window=self.popup_win, text=left_btn_txt, color=color, highlight_color=highlight_color) right_area = WindowArea(1, button_len, y_loc, win_size[1] / 2 + 2) right_button = ListItem(right_area, window=self.popup_win, text=right_btn_txt, color=color, highlight_color=highlight_color) # Highlight the left button, clear any errors on the screen, # and display the pop up self.popup_win.activate_object(left_button) self.popup_win.no_ut_refresh() self.error_line.clear_err() self.do_update() self._active_win = self.popup_win # Loop until the user selects an option. input_key = None while input_key != curses.KEY_ENTER: input_key = self.getch() input_key = self.popup_win.process(input_key) if input_key == curses.KEY_LEFT: self.popup_win.activate_object(left_button) elif input_key == curses.KEY_RIGHT: self.popup_win.activate_object(right_button) self.do_update() self._active_win = self.central_area user_selected = (self.popup_win.get_active_object() is right_button) # Clear the pop up and restore the previous screen, including the # cursor position and visibility self.popup_win.clear() self.central_area.redrawwin() curses.setsyx(cursor_loc[0], cursor_loc[1]) try: curses.curs_set(old_cursor_state) except curses.error: pass self.do_update() return user_selected
class HelpScreen(BaseScreen): '''Show localized help file pertaining to last traversed screen or help topics list from which to choose a desired help topic. ''' HELP_HEADER = _("Help Topics") HELP_INDEX = _("Help Index") INTRO = _("Select a topic and press Continue.") def __init__(self, main_win): super(HelpScreen, self).__init__(main_win) self.locale = locale.setlocale(locale.LC_MESSAGES, "") logging.debug("locale=%s", self.locale) self.screen = None self.screen_last = None self.help_info = [] self.help_dict = None self.topics = False self.scroll_region = None self.cur_help_idx = 0 self.is_x86 = (platform.processor() == "i386") self.setup_help_data() def setup_help_data(self): '''Setup the help_dict and help_info structures help_dict contains: key: screen name tuple: (<helpfile_name>, <header for help screen>) tuple: (<helpfile_name>, <header for help screen and help topics menu entry>) ''' self.help_dict = \ { "WelcomeScreen": ("welcome.txt", _("Welcome and Navigation Instructions")), "DiskScreen": ("disks.txt", _("Disks")), "NetworkTypeScreen": ("network.txt", _("Network")), "NICSelect": ("network_manual.txt", _("Manual Network Configuration")), "NICConfigure": ("network_manual.txt", _("Manually Configure: NIC")), "TimeZone": ("timezone.txt", _("Time Zone")), "DateTimeScreen": ("date_time.txt", _("Date and Time")), "UserScreen": ("users.txt", _("Users")), "SummaryScreen": ("summary.txt", _("Installation Summary")) } # add x86 and SPARC specific help_dict entries if self.is_x86: self.help_dict["FDiskPart"] = \ ("x86_fdisk_partitions.txt", _("Fdisk Partitions")) self.help_dict["PartEditScreen"] = \ ("x86_fdisk_partitions_select.txt", _("Select Partition")) self.help_dict["FDiskPart.slice"] = \ ("x86_fdisk_slices.txt", _("Solaris Partition Slices")) self.help_dict["PartEditScreen.slice"] = \ ("x86_fdisk_slices_select.txt", _("Select Slice")) else: self.help_dict["FDiskPart"] = \ ("sparc_solaris_slices.txt", _("Solaris Slices")) self.help_dict["PartEditScreen"] = \ ("sparc_solaris_slices_select.txt", _("Select Slice")) logging.debug("self.help_dict=%s", self.help_dict) # help_info contains tuples: # (tuple of screen names, format of text) self.help_info = [] self.help_info.append((("WelcomeScreen", ), "%s")) self.help_info.append((("DiskScreen", ), "%s")) # add x86 and SPARC specific entries to self.help_info if self.is_x86: self.help_info.append((("FDiskPart", ), " %s")) self.help_info.append((("PartEditScreen", ), " %s")) self.help_info.append((("FDiskPart.slice", ), " %s")) self.help_info.append((("PartEditScreen.slice", ), " %s")) else: self.help_info.append((("FDiskPart", ), " %s")) self.help_info.append((("PartEditScreen", ), " %s")) self.help_info.append((("NetworkTypeScreen", ), "%s")) self.help_info.append((("NICSelect", ), " %s")) self.help_info.append((("NICConfigure", ), " %s")) self.help_info.append((("TimeZone", ), "%s")) self.help_info.append((("DateTimeScreen", ), "%s")) self.help_info.append((("UserScreen", ), "%s")) self.help_info.append((("SummaryScreen", ), "%s")) logging.debug("self.help_info=%s", self.help_info) def set_actions(self): '''Remove the continue key for help screen and Help key for help topics screen. Redirect F2_Continue to display the selected topic, when at the topics list ''' logging.debug("in set_actions self.class_name=%s", self.__class__.__name__) # change F6 description self.main_win.help_action.text = HelpScreen.HELP_INDEX # change continue to call continue_action, rather than # normal continue. Though we stay on the same screen, # we simulate the continue here by changing the screen text. # help_continue = Action(curses.KEY_F2, _("Continue"), self.continue_action) self.main_win.actions[help_continue.key] = help_continue if (self.screen == self.__class__.__name__): # help topics screen self.main_win.actions.pop(self.main_win.help_action.key, None) else: # help screen self.main_win.actions.pop(self.main_win.continue_action.key, None) def display_help_topics(self): '''Display the help topics screen.''' self.main_win.set_header_text(HelpScreen.HELP_HEADER) y_loc = 1 y_loc += self.center_win.add_paragraph(HelpScreen.INTRO, y_loc, 1, max_x=(self.win_size_x - 1)) y_loc += 1 area = WindowArea(scrollable_lines=(len(self.help_info) + 1), y_loc=y_loc, x_loc=0) logging.debug("lines=%s", len(self.help_dict)) area.lines = self.win_size_y - (y_loc + 1) area.columns = self.win_size_x self.scroll_region = ScrollWindow(area, window=self.center_win) # add the entries to the screen logging.debug("range=%s", len(self.help_info)) for idx, info in enumerate(self.help_info): # create ListItem for each help topic topic_format = info[1] help_topic = self.get_help_topic(info[0]) help_topic = topic_format % help_topic hilite = min(self.win_size_x, textwidth(help_topic) + 1) list_item = ListItem(WindowArea(1, hilite, idx, 0), window=self.scroll_region, text=help_topic) help_screens = info[0] logging.debug("help_screens=%s", list(help_screens)) logging.debug("self.screen_last=%s", self.screen_last) if self.screen_last in help_screens: logging.debug("Set cur_help_idx = %s", idx) self.cur_help_idx = idx logging.debug("beg_y=%d, beg_x=%d", *list_item.window.getbegyx()) self.center_win.activate_object(self.scroll_region) self.scroll_region.activate_object(self.cur_help_idx) def continue_action(self, dummy=None): '''Called when user preses F2 on help topics screen. Results in show being called again to display single file help of chosen topic. ''' logging.debug("continue_action:%s", self.scroll_region.active_object) cur_topic = self.scroll_region.active_object self.screen = self.help_info[cur_topic][0][0] logging.debug("continue_action self.screen=%s", self.screen) self.topics = False return self def get_help_topic(self, name_classes=None): '''Get the help topic from the dictionary, given the help class tuple passed in. The single file help header in help_dict is also the entry used in the help topics menu. ''' for key in self.help_dict.keys(): if key in name_classes: return self.help_dict[key][1] return "" def display_help(self): '''Display the single file help screen''' # customize header help_header = "%s: %s" logging.debug("self.screen is =%s", self.screen) if self.screen in self.help_dict: help_header = help_header % (_("Help"), self.help_dict[self.screen][1]) help_text = self.get_help_text(self.help_dict[self.screen][0]) else: help_header = help_header % (_("Help"), _("Not Available")) help_text = _("Help for this screen is not available") self.main_win.set_header_text(help_header) help_text = convert_paragraph(help_text, self.win_size_x - 5) logging.debug("help_text #lines=%d, text is \n%s", len(help_text), help_text) area = WindowArea(x_loc=0, y_loc=1, scrollable_lines=(len(help_text) + 1)) area.lines = self.win_size_y - 1 area.columns = self.win_size_x self.scroll_region = ScrollWindow(area, window=self.center_win) self.scroll_region.add_paragraph(help_text, start_x=(area.x_loc + 3)) self.center_win.activate_object(self.scroll_region) def _show(self): '''Display the screen, either the single file help or help topics.''' logging.debug("in show self.screen=%s", self.screen) if (self.screen is self.__class__.__name__): logging.debug("setting self topics to true:") self.topics = True else: self.topics = False self.screen_last = self.screen logging.debug("setting self.screen_last to %s", self.screen_last) if self.topics: self.display_help_topics() else: self.display_help() def get_help_text(self, filename=None): ''' Get the localized help text for the filename passed in. First check locid directory. If not there, strip off dot extension (fr_FR.UTF-8 becomes fr_FR). If not there, truncate to 2 chars (fr). If not there, use C. ''' if filename is None: return ("") help_file = None try: locid = self.locale path = "%s%s/%s" full_path = path % (HELP_PATH, locid, filename) logging.debug("Accessing help file %s", full_path) if (not os.access(full_path, os.R_OK)): if (locid.find(".") > 0): locid = locid.split(".")[0] full_path = path % (HELP_PATH, locid, filename) logging.debug("Accessing help file %s", full_path) if (not os.access(full_path, os.R_OK)): if (len(locid) > 1): locid = locid[:2] full_path = path % (HELP_PATH, locid, filename) logging.debug("Accessing help file %s", full_path) if (not os.access(full_path, os.R_OK)): locid = "C" full_path = path % (HELP_PATH, locid, filename) logging.debug("Opening help file %s", full_path) help_file = open(full_path, 'r') except IOError: logging.debug("Unable to open help file %s", full_path) help_text = _("Help for this screen is not available") else: help_text = help_file.read() logging.debug("Done reading help file %s", full_path) if help_file: help_file.close() return help_text