Пример #1
0
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
Пример #2
0
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
Пример #3
0
class DiskScreen(BaseScreen):
    '''
    Allow the user to select a (valid) disk target for installation
    Display the partition/slice table for the highlighted disk
    
    '''

    HEADER_TEXT = _("Disks")
    PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE
    SIZE_TEXT = _("Recommended size:  %(recommend).1fGB      "
                  "Minimum size: %(min).1fGB")
    DISK_SEEK_TEXT = _("Seeking disks on system")
    FOUND_x86 = _("The following partitions were found on the disk.")
    FOUND_SPARC = _("The following slices were found on the disk.")
    PROPOSED_x86 = _("A partition table was not found. The following is "
                     "proposed.")
    PROPOSED_SPARC = _("A VTOC label was not found. The following "
                       "is proposed.")
    PROPOSED_GPT = _("A GPT labeled disk was found. The following is "
                     "proposed.")
    TOO_SMALL = _("Too small")
    TOO_BIG_WARN = _("Limited to %.1f TB")
    GPT_LABELED = _("GPT labeled disk")
    NO_DISKS = _("No disks found. Additional device drivers may " "be needed.")
    NO_TARGETS = _("%(release)s cannot be installed on any disk") % RELEASE
    TGT_ERROR = _("An error occurred while searching for installation"
                  " targets. Please check the install log and file a bug"
                  " at bugs.openindiana.org.")

    DISK_HEADERS = [(8, _("Type")), (10, _("Size(GB)")), (6, _("Boot")),
                    (9, _("Device")), (15, _("Manufacturer")),
                    (22, _("Notes"))]
    SPINNER = ["\\", "|", "/", "-"]

    DISK_WARNING_HEADER = _("Warning")
    DISK_WARNING_TOOBIG = _("Only the first %.1fTB can be used.")
    DISK_WARNING_GPT = _("You have chosen a GPT labeled disk. Installing "
                         "onto a GPT labeled disk will cause the loss "
                         "of all existing data and the disk will be "
                         "relabeled as SMI.")

    CANCEL_BUTTON = _("Cancel")
    CONTINUE_BUTTON = _("Continue")

    def __init__(self, main_win):
        super(DiskScreen, self).__init__(main_win)
        if platform.processor() == "i386":
            self.found_text = DiskScreen.FOUND_x86
            self.proposed_text = DiskScreen.PROPOSED_x86
        else:
            self.found_text = DiskScreen.FOUND_SPARC
            self.proposed_text = DiskScreen.PROPOSED_SPARC

        disk_header_text = []
        for header in DiskScreen.DISK_HEADERS:
            header_str = fit_text_truncate(header[1],
                                           header[0] - 1,
                                           just="left")
            disk_header_text.append(header_str)
        self.disk_header_text = " ".join(disk_header_text)
        max_note_size = DiskScreen.DISK_HEADERS[5][0]
        self.too_small_text = DiskScreen.TOO_SMALL[:max_note_size]
        max_disk_size = SliceInfo.MAX_VTOC.size_as("tb")
        too_big_warn = DiskScreen.TOO_BIG_WARN % max_disk_size
        self.too_big_warn = too_big_warn[:max_note_size]
        self.disk_warning_too_big = \
            DiskScreen.DISK_WARNING_TOOBIG % max_disk_size

        self.disks = []
        self.existing_pools = []
        self.disk_win = None
        self.disk_detail = None
        self.num_targets = 0
        self.td_handle = None
        self._size_line = None
        self.selected_disk = 0
        self._minimum_size = None
        self._recommended_size = None
        self.do_copy = False  # Flag indicating if install_profile.disk
        # should be copied

    def determine_minimum(self):
        '''Returns minimum install size, fetching first if needed'''
        self.determine_size_data()
        return self._minimum_size

    minimum_size = property(determine_minimum)

    def determine_recommended(self):
        '''Returns recommended install size, fetching first if needed'''
        self.determine_size_data()
        return self._recommended_size

    recommended_size = property(determine_recommended)

    def determine_size_data(self):
        '''Retrieve the minimum and recommended sizes and generate the string
        to present that information.
        
        '''
        if self._minimum_size is None or self._recommended_size is None:
            self._recommended_size = get_recommended_size().size_as("gb")
            self._minimum_size = get_minimum_size().size_as("gb")

    def get_size_line(self):
        '''Returns the line of text displaying the min/recommended sizes'''
        if self._size_line is None:
            size_dict = {
                "recommend": self.recommended_size,
                "min": self.minimum_size
            }
            self._size_line = DiskScreen.SIZE_TEXT % size_dict
        return self._size_line

    size_line = property(get_size_line)

    def wait_for_disks(self):
        '''Block while waiting for libtd to finish. Catch F9 and quit
        if needed
        
        '''
        if self.td_handle is None:
            self.start_discovery()
        self.main_win.actions.pop(curses.KEY_F2, None)
        self.main_win.actions.pop(curses.KEY_F6, None)
        self.main_win.actions.pop(curses.KEY_F3, None)
        self.main_win.show_actions()
        if self.td_handle.is_alive():
            self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1,
                                     self.win_size_x - 3)
            self.main_win.do_update()
            offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2
            spin_index = 0
            self.center_win.window.timeout(250)
            while self.td_handle.is_alive():
                input_key = self.main_win.getch()
                if input_key == curses.KEY_F9:
                    if self.confirm_quit():
                        raise QuitException
                self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5,
                                         offset)
                self.center_win.no_ut_refresh()
                self.main_win.do_update()
                spin_index = (spin_index + 1) % len(DiskScreen.SPINNER)

            self.center_win.window.timeout(-1)
            self.center_win.clear()

        # Get the list of existing zpools on the
        # system and based on that come up with
        # a unique name for the root pool
        index = 1
        pool_name = "rpool"
        while pool_name in self.existing_pools:
            pool_name = "rpool%d" % index
            index += 1

        # Set the SliceInfo.DEFAULT_POOL to the unique
        # pool name
        SliceInfo.DEFAULT_POOL.data = pool_name

    def _show(self):
        '''Create a list of disks to choose from and create the window
        for displaying the partition/slice information from the selected
        disk
        
        '''
        self.wait_for_disks()
        self.num_targets = 0

        if not self.disks:
            self.center_win.add_paragraph(DiskScreen.NO_DISKS,
                                          1,
                                          1,
                                          max_x=(self.win_size_x - 1))
            return

        if isinstance(self.disks[0], BaseException):
            if len(self.disks) == 1:
                raise tgt.TgtError(
                    ("Unexpected error (%s) during target "
                     "discovery. See log for details.") % self.disks[0])
            else:
                self.disks = self.disks[1:]
                logging.warn("Failure in target discovery, but one or more"
                             " disks found. Continuing.")

        boot_disk = self.disks[0]
        for disk in self.disks:
            if (disk.size.size_as("gb") > self.minimum_size):
                self.num_targets += 1
            if disk.boot:
                boot_disk = disk
        self.disks.remove(boot_disk)
        self.disks.insert(0, boot_disk)

        if self.num_targets == 0:
            self.center_win.add_paragraph(DiskScreen.NO_TARGETS,
                                          1,
                                          1,
                                          max_x=(self.win_size_x - 1))
            return

        self.main_win.reset_actions()
        self.main_win.show_actions()

        y_loc = 1
        self.center_win.add_text(DiskScreen.PARAGRAPH, y_loc, 1)

        y_loc += 1
        self.center_win.add_text(self.size_line, y_loc, 1)

        y_loc += 2
        self.center_win.add_text(self.disk_header_text, y_loc, 1)

        y_loc += 1
        self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1,
                                     curses.ACS_HLINE,
                                     textwidth(self.disk_header_text))

        y_loc += 1
        disk_win_area = WindowArea(4,
                                   textwidth(self.disk_header_text) + 2, y_loc,
                                   0)
        disk_win_area.scrollable_lines = len(self.disks) + 1
        self.disk_win = ScrollWindow(disk_win_area, window=self.center_win)

        disk_item_area = WindowArea(1, disk_win_area.columns - 2, 0, 1)
        disk_index = 0
        len_type = DiskScreen.DISK_HEADERS[0][0] - 1
        len_size = DiskScreen.DISK_HEADERS[1][0] - 1
        len_boot = DiskScreen.DISK_HEADERS[2][0] - 1
        len_dev = DiskScreen.DISK_HEADERS[3][0] - 1
        len_mftr = DiskScreen.DISK_HEADERS[4][0] - 1
        for disk in self.disks:
            disk_text_fields = []
            type_field = disk.type[:len_type]
            type_field = ljust_columns(type_field, len_type)
            disk_text_fields.append(type_field)
            disk_size = disk.size.size_as("gb")
            size_field = "%*.1f" % (len_size, disk_size)
            disk_text_fields.append(size_field)
            if disk.boot:
                bootable_field = "+".center(len_boot)
            else:
                bootable_field = " " * (len_boot)
            disk_text_fields.append(bootable_field)
            device_field = disk.name[:len_dev]
            device_field = ljust_columns(device_field, len_dev)
            disk_text_fields.append(device_field)
            if disk.vendor is not None:
                mftr_field = disk.vendor[:len_mftr]
                mftr_field = ljust_columns(mftr_field, len_mftr)
            else:
                mftr_field = " " * len_mftr
            disk_text_fields.append(mftr_field)
            selectable = True
            if disk_size < self.minimum_size:
                note_field = self.too_small_text
                selectable = False
            elif DiskInfo.GPT in disk.label:
                note_field = DiskScreen.GPT_LABELED
            elif disk_size > SliceInfo.MAX_VTOC.size_as("gb"):
                note_field = self.too_big_warn
            else:
                note_field = ""
            disk_text_fields.append(note_field)
            disk_text = " ".join(disk_text_fields)
            disk_item_area.y_loc = disk_index
            disk_list_item = ListItem(disk_item_area,
                                      window=self.disk_win,
                                      text=disk_text,
                                      add_obj=selectable)
            disk_list_item.on_make_active = on_activate
            disk_list_item.on_make_active_kwargs["disk_info"] = disk
            disk_list_item.on_make_active_kwargs["disk_select"] = self
            disk_index += 1
        self.disk_win.no_ut_refresh()

        y_loc += 7
        disk_detail_area = WindowArea(6, 70, y_loc, 1)
        self.disk_detail = DiskWindow(disk_detail_area,
                                      self.disks[0],
                                      window=self.center_win)

        self.main_win.do_update()
        self.center_win.activate_object(self.disk_win)
        self.disk_win.activate_object(self.selected_disk)
        # Set the flag so that the disk is not copied by on_change_screen,
        # unless on_activate gets called as a result of the user changing
        # the selected disk.
        self.do_copy = False

    def on_change_screen(self):
        ''' Assign the selected disk to the InstallProfile, and make note of
        its index (in case the user returns to this screen later)
        
        '''
        if self.disk_detail is not None:
            if self.do_copy or self.install_profile.disk is None:
                disk = self.disk_detail.disk_info
                self.install_profile.disk = deepcopy(disk)
                self.install_profile.original_disk = disk
            self.selected_disk = self.disk_win.active_object

    def start_discovery(self):
        '''Spawn a thread to begin target discovery'''
        logging.debug("spawning target discovery thread")
        self.td_handle = threading.Thread(target=DiskScreen.get_disks,
                                          args=(self.disks,
                                                self.existing_pools))
        logging.debug("starting target discovery thread")
        self.td_handle.start()

    @staticmethod
    def get_disks(disks, pools):
        '''
        Call into target discovery and get disk data. The disks found are
        added to the list passed in by the 'disks' argument
        
        '''
        try:
            td_disks = tgt.discover_target_data()
            for disk in td_disks:
                disks.append(DiskInfo(tgt_disk=disk))
            pools.extend(get_zpool_list())
        # If an exception occurs, regardless of type, log it, add it as the
        # first item in the disk list, and consume it (an uncaught Exception
        # in this threaded code would distort the display).
        # During the call to _show, if an exception occurred, the program
        # aborts gracefully
        # pylint: disable-msg=W0703
        except BaseException, err:
            logging.exception(traceback.format_exc())
            disks.insert(0, err)
Пример #4
0
class DiskWindow(InnerWindow):
    '''Display and edit disk information, including partitions and slices'''
    
    STATIC_PARTITION_HEADERS = [(12, _("Primary"), _("Logical")),
                                (9, _("Size(GB)"), _("Size(GB)"))]
    
    EDIT_PARTITION_HEADERS = [(13, _("Primary"), _("Logical")),
                              (9, _("Size(GB)"), _("Size(GB)")),
                              (7, _(" Avail"), _(" Avail"))]
    
    STATIC_SLICE_HEADERS = [(13, _("Slice"), _("Slice")),
                            (2, "#", "#"),
                            (9, _("Size(GB)"), _("Size(GB)"))]
    
    EDIT_SLICE_HEADERS = [(13, _("Slice"), _("Slice")),
                          (2, "#", "#"),
                          (9, _("Size(GB)"), _("Size(GB)")),
                          (7, _(" Avail"), _(" Avail"))]
    
    ADD_KEYS = {curses.KEY_LEFT : no_action,
                curses.KEY_RIGHT : no_action}
    
    DEAD_ZONE = 3
    SCROLL_PAD = 2
    
    MIN_SIZE = None
    REC_SIZE = None
    
    SIZE_PRECISION = UI_PRECISION.size_as("gb")
    DESTROYED_MARK = EditField.ASTERISK_CHAR
    
    def __init__(self, area, disk_info, editable=False,
                 error_win=None, reset=None, **kwargs):
        '''See also InnerWindow.__init__
        
        disk_info (required) - A DiskInfo object containing the data to be
        represented. Also accepts PartitionInfo objects (for displaying slice
        data within that partition). If disk_info has partition(s), those are
        displayed. If not, but it has slices, then those are displayed. If
        neither partition data nor slice data are available, a ValueError is
        raised. This window makes a copy of the disk_info, as well as keeping
        a reference to the original for the purposes of resetting at a later
        time.
        
        headers (required) - List of tuples to populate the header of this
        window with. The first item in each tuple should be the width of the
        header, the second item should be the left side header.
        
        editable (optional) - If True, the window will be created such that
        data is editable.
        
        '''
        self.headers = None
        self.orig_ext_part_field = None
        self.orig_logicals_active = False
        self.ext_part_field = None
        self.error_win = error_win
        self.editable = editable
        self.win_width = None
        self.left_win = None
        self.right_win = None
        self.list_area = None
        self.edit_area = None
        super(DiskWindow, self).__init__(area, add_obj=editable, **kwargs)
        self.left_header_string = None
        self.right_header_string = None
        self._orig_data = None
        self._reset = reset
        self.disk_info = None
        self.has_partition_data = True
        self.key_dict[curses.KEY_LEFT] = self.on_arrow_key
        self.key_dict[curses.KEY_RIGHT] = self.on_arrow_key
        if self.editable:
            self.key_dict[curses.KEY_F5] = self.change_type
        
        if getattr(disk_info, "do_revert", False):
            self.reset()
        else:
            self.set_disk_info(disk_info)
    
    def _init_win(self, window):
        '''Require at least 70 columns and 6 lines to fit current needs for
        display of partitions and slices. Builds two inner ScrollWindows for
        displaying/editing the data.
        
        '''
        if self.area.columns < 70:
            raise ValueError, "Insufficient space - area.columns < 70"
        if self.area.lines < 6:
            raise ValueError, "Insufficient space - area.lines < 6"
        self.win_width = (self.area.columns - DiskWindow.DEAD_ZONE
                          + DiskWindow.SCROLL_PAD) / 2
        
        super(DiskWindow, self)._init_win(window)
        
        win_area = WindowArea(self.area.lines - 1, self.win_width, 2, 0)
        win_area.scrollable_lines = self.area.lines - 2
        self.left_win = ScrollWindow(win_area, window=self, add_obj=False)
        self.left_win.color = None
        self.left_win.highlight_color = None
        win_area.x_loc = self.win_width + DiskWindow.DEAD_ZONE
        win_area.scrollable_lines = 2 * PartitionInfo.MAX_LOGICAL_PARTITIONS
        self.right_win = ScrollWindow(win_area, window=self, add_obj=False)
        self.right_win.color = None
        self.right_win.highlight_color = None
    
    def set_disk_info(self, disk_info):
        '''Set up this DiskWindow to represent disk_info'''
        if getattr(disk_info, "partitions", False):
            self.has_partition_data = True
        elif disk_info.slices:
            self.has_partition_data = False
#        else:
#            return
        
        if self.has_partition_data:
            if self.editable:
                self.headers = DiskWindow.EDIT_PARTITION_HEADERS
                self.list_area = WindowArea(1, self.headers[0][0] + 
                                            self.headers[1][0],
                                            0, DiskWindow.SCROLL_PAD)
                self.edit_area = WindowArea(1, self.headers[1][0], 0,
                                            self.headers[0][0])
            else:
                self.headers = DiskWindow.STATIC_PARTITION_HEADERS
        elif disk_info.slices:
            if self.editable:
                self.headers = DiskWindow.EDIT_SLICE_HEADERS
                self.list_area = WindowArea(1, self.headers[0][0] +
                                            self.headers[1][0] +
                                            self.headers[2][0],
                                            0, DiskWindow.SCROLL_PAD)
                self.edit_area = WindowArea(1, self.headers[2][0], 0,
                                            self.headers[0][0] +
                                            self.headers[1][0])
            else:
                self.headers = DiskWindow.STATIC_SLICE_HEADERS
        
        self._orig_data = disk_info
        self.disk_info = deepcopy(disk_info)
        self.disk_info.add_unused_parts()
        
        self.left_win.clear()
        self.right_win.clear()
        self.window.erase()
        self.print_headers()
        
        if self.editable:
            self.active_object = None
            self.build_edit_fields()
            self.right_win.bottom = max(0, len(self.right_win.all_objects) -
                                        self.right_win.area.lines)
            if self.has_partition_data:
                self.orig_ext_part_field = None
                for obj in self.left_win.objects:
                    if (obj.data_obj.is_extended()):
                        self.orig_ext_part_field = obj
                        self.orig_logicals_active = True
                        break
        else:
            self.print_data()
    
    def print_headers(self):
        '''Print the headers for the displayed data.
        
        header[0] - The width of this column. header[1] and header[2] are
                    trimmed to this size
        header[1] - The internationalized text for the left window
        header[2] - The internationalized text for the right window
        
        '''
        self.left_header_string = []
        self.right_header_string = []
        for header in self.headers:
            left_header_str = header[1]
            right_header_str = header[2]
            # Trim the header to fit in the column width,
            # splitting columns with at least 1 space
            # Pad with extra space(s) to align the columns
            left_header_str = fit_text_truncate(left_header_str,
                                                header[0]-1, just="left")
            self.left_header_string.append(left_header_str)
            right_header_str = fit_text_truncate(right_header_str,
                                                header[0]-1, just="left")
            self.right_header_string.append(right_header_str)
        self.left_header_string = " ".join(self.left_header_string)
        self.right_header_string = " ".join(self.right_header_string)
        logging.debug(self.left_header_string)
        self.add_text(self.left_header_string, 0, DiskWindow.SCROLL_PAD)
        right_win_offset = (self.win_width + DiskWindow.DEAD_ZONE +
                            DiskWindow.SCROLL_PAD)
        self.add_text(self.right_header_string, 0, right_win_offset)
        self.window.hline(1, DiskWindow.SCROLL_PAD, curses.ACS_HLINE,
                          textwidth(self.left_header_string))
        self.window.hline(1, right_win_offset, curses.ACS_HLINE,
                          textwidth(self.right_header_string))
        self.no_ut_refresh()
    
    def print_data(self):
        '''Print static (non-editable) data.
        
        Slices - fill the left side, then remaining slices on the right side.
        If for some reason not all slices fit, indicate how many more slices
        there area
        
        Partitions - Put standard partitions on the left, logical partitions
        on the right
        
        '''
        part_index = 0
        if self.has_partition_data:
            max_parts = PartitionInfo.MAX_STANDARD_PARTITIONS
        else:
            max_parts = min(len(self.disk_info.slices),
                                self.left_win.area.lines)
        win = self.left_win
        y_loc = 0
        for next_part in self.disk_info.get_parts():
            if y_loc >= max_parts:
                if win is self.left_win:
                    win = self.right_win
                    y_loc = 0
                    max_parts = win.area.lines
                else:
                    if self.has_partition_data:
                        num_extra = len(self.disk_info.partitions) - part_index
                        more_parts_txt = _("%d more partitions") % num_extra
                    else:
                        num_extra = len(self.disk_info.slices) - part_index
                        more_parts_txt = _("%d more slices") % num_extra
                    win.add_text(more_parts_txt, win.area.lines, 3)
                    break
            x_loc = DiskWindow.SCROLL_PAD
            field = 0
            win.add_text(next_part.get_description(), y_loc, x_loc,
                         self.headers[field][0] - 1)
            x_loc += self.headers[field][0]
            field += 1
            if not self.has_partition_data:
                win.add_text(str(next_part.number), y_loc, x_loc,
                             self.headers[field][0] - 1)
                x_loc += self.headers[field][0]
                field += 1
            win.add_text("%*.1f" % (self.headers[field][0]-1,
                                    next_part.size.size_as("gb")),
                                    y_loc, x_loc,
                                    self.headers[field][0]-1)
            x_loc += self.headers[field][0]
            y_loc += 1
            field += 1
            part_index += 1
        self.right_win.use_vert_scroll_bar = False
        self.no_ut_refresh()
    
    def build_edit_fields(self):
        '''Build subwindows for editing partition sizes
        
        For slices, fill the left side, then the right (right side scrolling as
        needed, though this shouldn't happen unless the number of slices on
        disk exceeds 8 for some reason)
        
        For partitions, fill the left side up to MAX_STANDARD_PARTITIONS,
        and place all logical partitions on the right.
        
        '''
        if self.has_partition_data:
            max_left_parts = PartitionInfo.MAX_STANDARD_PARTITIONS
        else:
            max_left_parts = min(len(self.disk_info.slices),
                                 self.left_win.area.lines)
        part_iter = iter(self.disk_info.get_parts())
        try:
            next_part = part_iter.next()
            self.objects.append(self.left_win)
            for y_loc in range(max_left_parts):
                self.list_area.y_loc = y_loc
                self.create_list_item(next_part, self.left_win, self.list_area)
                next_part.orig_type = next_part.type
                next_part = part_iter.next()
            self.objects.append(self.right_win)
            for y_loc in range(self.right_win.area.scrollable_lines):
                self.list_area.y_loc = y_loc
                self.create_list_item(next_part, self.right_win,
                                      self.list_area)
                next_part.orig_offset = next_part.offset.size_as("gb")
                next_part.orig_size = next_part.size.size_as("gb")
                next_part = part_iter.next()
        except StopIteration:
            if len(self.right_win.all_objects) <= self.right_win.area.lines:
                self.right_win.use_vert_scroll_bar = False
            self.right_win.no_ut_refresh()
        else:
            raise ValueError("Could not fit all partitions in DiskWindow")
        self.no_ut_refresh()
    
    def create_list_item(self, next_part, win, list_area):
        '''Add an entry for next_part (a PartitionInfo or SliceInfo) to
        the DiskWindow
        
        '''
        next_part.is_dirty = False
        next_part.restorable = True
        next_part.orig_offset = next_part.offset.size_as("gb")
        next_part.orig_size = next_part.size.size_as("gb")
        next_part.orig_type = next_part.type
        list_item = ListItem(list_area, window=win, data_obj=next_part)
        list_item.key_dict.update(DiskWindow.ADD_KEYS)
        list_item.on_make_inactive = on_leave_part_field
        list_item.on_make_inactive_kwargs = {"field" : list_item}
        edit_field = EditField(self.edit_area, window=list_item,
                               numeric_pad=" ",
                               validate=decimal_valid,
                               on_exit=on_exit_edit,
                               error_win=self.error_win,
                               add_obj=False,
                               data_obj=next_part)
        edit_field.right_justify = True
        edit_field.validate_kwargs["disk_win"] = self
        edit_field.key_dict.update(DiskWindow.ADD_KEYS)
        self.update_part(part_field=list_item)
        return list_item
    
    def update_part(self, part_info=None, part_field=None):
        '''Sync changed partition data to the screen.'''
        if part_field is None:
            if part_info is None:
                raise ValueError("Must supply either part_info or part_field")
            part_field = self.find_part_field(part_info)[1]
        elif part_info is None:
            part_info = part_field.data_obj
        elif part_field.data_obj is not part_info:
            raise ValueError("part_field must be a ListItem associated with "
                             "part_info")
        if not isinstance(part_field, ListItem):
            raise TypeError("part_field must be a ListItem associated with "
                            "part_info")
        if self.has_partition_data:
            desc_text = part_info.get_description()
        else:
            desc_length = self.headers[0][0] - 1
            desc_text = "%-*.*s %i" % (desc_length, desc_length,
                                       part_info.get_description(),
                                       part_info.number)
        part_field.set_text(desc_text)
        edit_field = part_field.all_objects[0]
        edit_field.set_text("%.1f" % part_info.size.size_as("gb"))
        self.mark_if_destroyed(part_field)
        self._update_edit_field(part_info, part_field, edit_field)

        self.update_avail_space(part_info=part_info)
        if self.has_partition_data:
            if part_info.is_extended():
                self.ext_part_field = part_field
    
    def _update_edit_field(self, part_info, part_field, edit_field):
        '''If the partition/slice is editable, add it to the .objects list.
        If it's also the part_field that's currently selected, then activate
        the edit field.
        
        '''
        if part_info.editable(self.disk_info):
            part_field.objects = [edit_field]
            active_win = self.get_active_object()
            if active_win is not None:
                if active_win.get_active_object() is part_field:
                    part_field.activate_object(edit_field)
        else:
            edit_field.make_inactive()
            part_field.objects = []
            part_field.active_object = None

    def mark_if_destroyed(self, part_field):
        '''Determine if the partition/slice represented by part_field has
        changed such that its contents will be destroyed.
        
        '''
        part_info = part_field.data_obj
        destroyed = part_info.destroyed()
        self.mark_destroyed(part_field, destroyed)
    
    def mark_destroyed(self, part_field, destroyed):
        '''If destroyed is True, add an asterisk indicating that the
        partition or slice's content will be destroyed during installation.
        Otherwise, clear the asterisk
        
        '''
        y_loc = part_field.area.y_loc
        x_loc = part_field.area.x_loc - 1
        if part_field in self.right_win.objects:
            win = self.right_win
        else:
            win = self.left_win
        if destroyed:
            win.window.addch(y_loc, x_loc, DiskWindow.DESTROYED_MARK,
                             win.color_theme.inactive)
        else:
            win.window.addch(y_loc, x_loc, InnerWindow.BKGD_CHAR)
    
    def update_avail_space(self, part_number=None, part_info=None):
        '''Update the 'Avail' column for the specified slice or partition.
        If no number is given, all avail columns are updated
        
        '''
        if part_number is None and part_info is None:
            self._update_all_avail_space()
        else:
            self._update_avail_space(part_number, part_info)

    def _update_all_avail_space(self):
        '''Update the 'Avail' column for all slices or partitions.'''
        idx = 0
        for item in self.left_win.objects:
            self.update_avail_space(idx)
            idx += 1
        for item in self.right_win.objects:
            self.update_avail_space(idx)
            idx += 1
        y_loc = idx - len(self.left_win.objects)
        if self.has_partition_data:
            x_loc = self.headers[0][0] + self.headers[1][0] + 1
            field = 2
        else:
            x_loc = (self.headers[0][0] + self.headers[1][0] +
                     self.headers[2][0] + 1)
            field = 3
        if y_loc > 0:
            self.right_win.add_text(" " * self.headers[field][0],
                                    y_loc, x_loc)
        elif y_loc == 0 and self.has_partition_data:
            # Blank out the size fields of removed (non-original)
            # logical partitions
            orig_logicals = len(self._orig_data.get_logicals())
            for right_y_loc in range(orig_logicals,
                                self.right_win.area.scrollable_lines):
                self.right_win.add_text(" " * self.headers[field][0],
                                        right_y_loc, x_loc)

    def _update_avail_space(self, part_number=None, part_info=None):
        '''Update the 'Avail' column for the specified slice or partition.'''
        if part_number is None:
            win, item = self.find_part_field(part_info)
        elif part_number < len(self.left_win.objects):
            win = self.left_win
            item = win.objects[part_number]
        else:
            win = self.right_win
            item = win.objects[part_number - len(self.left_win.objects)]
        if self.has_partition_data:
            x_loc = self.headers[0][0] + self.headers[1][0] + 1
            field = 2
        else:
            x_loc = (self.headers[0][0] + self.headers[1][0] +
                     self.headers[2][0] + 1)
            field = 3
        y_loc = item.area.y_loc
        part = item.data_obj
        max_space = part.get_max_size(self.disk_info)
        max_space = "%*.1f" % (self.headers[field][0], max_space)
        win.add_text(max_space, y_loc, x_loc)
    
    def find_part_field(self, part_info):
        '''Given a PartitionInfo or SliceInfo object, find the associated
        ListItem. This search compares by reference, and will only succeed
        if you have a handle to the exact object referenced by the ListItem
        
        '''
        for win in [self.left_win, self.right_win]:
            for item in win.objects:
                if item.data_obj is part_info:
                    return win, item
        raise ValueError("Part field not found")
    
    def reset(self, dummy=None):
        '''Reset disk_info to _orig_data.
        Meaningful only for editable DiskWindows
        
        '''
        if self.editable:
            if self._reset is not None:
                self.set_disk_info(self._reset)
            else:
                self.set_disk_info(self._orig_data)
            self.activate_solaris_data()
    
    def activate_solaris_data(self):
        '''Find the Solaris Partition / ZFS Root Pool Slice and activate it.
        See also DiskInfo.get_solaris_data()
        
        '''
        if self.editable:
            solaris_part = self.disk_info.get_solaris_data()
            if solaris_part is None:
                logging.debug("No Solaris data, activating default")
                self.activate_object()
                self.right_win.scroll(scroll_to_line=0)
                return
            disk_order = self.disk_info.get_parts().index(solaris_part)
            logging.debug("solaris disk at disk_order = %s", disk_order)
            if disk_order < len(self.left_win.objects):
                logging.debug("activating in left_win")
                self.left_win.activate_object(disk_order)
                self.activate_object(self.left_win)
                self.right_win.scroll(scroll_to_line=0)
            else:
                activate = disk_order - len(self.left_win.objects)
                logging.debug('activating in right win')
                self.right_win.activate_object_force(activate,
                                                     force_to_top=True)
                self.activate_object(self.right_win)
                left_active = self.left_win.get_active_object()
                if left_active is not None:
                    left_active.make_inactive()
    
    def make_active(self):
        '''On activate, select the solaris partition or ZFS root pool,
        instead of defaulting to 0
        
        '''
        self.set_color(self.highlight_color)
        self.activate_solaris_data()
    
    def on_arrow_key(self, input_key):
        '''
        On curses.KEY_LEFT: Move from the right win to the left win
        On curses.KEY_RIGHT: Move from the left to the right
        
        '''
        if (input_key == curses.KEY_LEFT and
            self.get_active_object() is self.right_win and
            len(self.left_win.objects) > 0):
            
            active_object = self.right_win.get_active_object().area.y_loc
            if (active_object >= len(self.left_win.objects)):
                active_object = len(self.left_win.objects) - 1
            self.activate_object(self.left_win)
            self.left_win.activate_object(active_object)
            return None
        elif (input_key == curses.KEY_RIGHT and
              self.get_active_object() is self.left_win and
              len(self.right_win.objects) > 0):
            active_line = (self.left_win.active_object + 
                             self.right_win.current_line[0])
            active_object = None
            force_to_top = False
            for obj in self.right_win.objects:
                if obj.area.y_loc >= active_line:
                    active_object = obj
                    off_screen = (self.right_win.current_line[0] +
                                  self.right_win.area.lines)
                    if active_object.area.y_loc > off_screen:
                        force_to_top = True
                    break
            if active_object is None:
                active_object = 0
            self.left_win.activate_object(-1, loop=True)
            self.activate_object(self.right_win)
            self.right_win.activate_object_force(active_object,
                                                 force_to_top=force_to_top)
            return None
        return input_key
    
    def no_ut_refresh(self, abs_y=None, abs_x=None):
        '''Refresh self, left win and right win explicitly'''
        super(DiskWindow, self).no_ut_refresh()
        self.left_win.no_ut_refresh(abs_y, abs_x)
        self.right_win.no_ut_refresh(abs_y, abs_x)
    
    def change_type(self, dummy):
        '''Cycle the type for the currently active object, and
        update its field
        
        '''
        logging.debug("changing type")
        part_field = self.get_active_object().get_active_object()
        part_info = part_field.data_obj
        old_type = part_info.type
        if part_info.restorable:
            part_info.cycle_type(self.disk_info, [part_info.orig_type])
        else:
            part_info.cycle_type(self.disk_info)
        new_type = part_info.type
        if old_type != new_type:
            max_size = part_info.get_max_size(self.disk_info)
            if part_info.restorable:
                part_info.is_dirty = (new_type != part_info.orig_type)
            if old_type == part_info.UNUSED:
                if part_info.orig_type == part_info.UNUSED:
                    part_info.size = "%.1fgb" % max_size
                    part_info.adjust_offset(self.disk_info)
                else:
                    part_info.size = part_info.previous_size
            if part_info.is_dirty and part_info.size.size_as("gb") > max_size:
                part_info.size = "%.1fgb" % max_size
                part_info.adjust_offset(self.disk_info)
            if self.has_partition_data:
                if old_type in PartitionInfo.EXTENDED:
                    self.deactivate_logicals()
                elif part_info.is_extended():
                    self.create_extended(part_field)
                elif part_info.is_logical():
                    last_logical = self.right_win.objects[-1].data_obj

                    if (old_type == PartitionInfo.UNUSED and
                        self.right_win.objects[-1] is part_field):
                        logging.debug("part is logical, old type unused, "
                                      "last field")
                        self.append_unused_logical()
                    elif (len(self.right_win.objects) > 1 and
                          new_type == PartitionInfo.UNUSED and
                          self.right_win.objects[-2] is part_field and
                          last_logical.type == PartitionInfo.UNUSED):
                        # If we cycle the second to last partition to Unused,
                        # combine it with the last unused partition
                        remove = self.right_win.objects[-1]
                        self.right_win.remove_object(remove)
                    self.update_part(part_field=self.ext_part_field)
            self.update_part(part_field=part_field)
        logging.log(LOG_LEVEL_INPUT, "part updated to:\n%s", part_info)
        self.update_avail_space()
        part_field.no_ut_refresh()
        return None
    
    def deactivate_logicals(self):
        '''Marks as destroyed all logicals in the original extended partition,
        and sets them as unselectable. Additionally, completely removes
        any logical partitions added by the user.
        
        '''
        if self.orig_logicals_active:
            original_logicals = len(self._orig_data.get_logicals())
            self.orig_logicals_active = False
        else:
            original_logicals = 0
        logging.log(LOG_LEVEL_INPUT, "orig logicals = %s", original_logicals)
        self.disk_info.remove_logicals()
        for obj in self.right_win.objects[:original_logicals]:
            self.mark_destroyed(obj, True)
        for obj in self.right_win.objects[original_logicals:]:
            obj.clear()
            self.right_win.remove_object(obj)

        if self.right_win in self.objects:
            self.objects.remove(self.right_win)
        self.right_win.objects = []
        self.right_win.active_object = None
        scroll = len(self.right_win.all_objects) > self.right_win.area.lines
        self.right_win.use_vert_scroll_bar = scroll
        self.right_win.no_ut_refresh()
    
    def create_extended(self, ext_part_field):
        '''If this is the original extended partition, restore the original
        logical partitions. Otherwise, create a single unused logical
        partition.
        
        '''
        if not ext_part_field.data_obj.modified():
            self.right_win.clear()
            self.orig_logicals_active = True
            logicals = deepcopy(self._orig_data.get_logicals())
            self.disk_info.partitions.extend(logicals)
            for idx, logical in enumerate(logicals):
                self.list_area.y_loc = idx
                self.create_list_item(logical, self.right_win, self.list_area)
            if self.right_win not in self.objects:
                self.objects.append(self.right_win)
            self.right_win.activate_object_force(0, force_to_top=True)
            self.right_win.make_inactive()
            self.right_win.no_ut_refresh()
        else:
            # Leave old data be, create new Unused logical partition
            if self.right_win not in self.objects:
                self.objects.append(self.right_win)
            self.append_unused_logical()
    
    def append_unused_logical(self):
        '''Adds a single Unused logical partition to the right window'''
        new_part = self.disk_info.append_unused_logical()
        self.list_area.y_loc = len(self.right_win.all_objects)
        bottom = self.list_area.y_loc - self.right_win.area.lines + 1
        self.right_win.bottom = max(0, bottom)
        self.create_list_item(new_part, self.right_win, self.list_area)
        scroll = len(self.right_win.all_objects) > self.right_win.area.lines
        self.right_win.use_vert_scroll_bar = scroll
        self.right_win.no_ut_refresh()
Пример #5
0
class DiskScreen(BaseScreen):
    '''
    Allow the user to select a (valid) disk target for installation
    Display the partition/slice table for the highlighted disk
    
    '''
    
    HEADER_TEXT = _("Disks")
    PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE
    SIZE_TEXT = _("Recommended size:  %(recommend).1fGB      "
                  "Minimum size: %(min).1fGB")
    DISK_SEEK_TEXT = _("Seeking disks on system")
    FOUND_x86 = _("The following partitions were found on the disk.")
    FOUND_SPARC = _("The following slices were found on the disk.")
    PROPOSED_x86 = _("A partition table was not found. The following is "
                     "proposed.")
    PROPOSED_SPARC = _("A VTOC label was not found. The following "
                       "is proposed.")
    PROPOSED_GPT = _("A GPT labeled disk was found. The following is "
                     "proposed.")
    TOO_SMALL = _("Too small")
    TOO_BIG_WARN = _("Limited to %.1f TB")
    GPT_LABELED = _("GPT labeled disk")
    NO_DISKS = _("No disks found. Additional device drivers may "
                 "be needed.")
    NO_TARGETS = _("%(release)s cannot be installed on any disk") % RELEASE
    TGT_ERROR = _("An error occurred while searching for installation"
                  " targets. Please check the install log and file a bug"
                  " at bugs.dvl.pt.")
    
    DISK_HEADERS = [(8, _("Type")),
                    (10, _("Size(GB)")),
                    (6, _("Boot")),
                    (9, _("Device")),
                    (15, _("Manufacturer")),
                    (22, _("Notes"))]
    SPINNER = ["\\", "|", "/", "-"]
    
    DISK_WARNING_HEADER = _("Warning")
    DISK_WARNING_TOOBIG = _("Only the first %.1fTB can be used.")
    DISK_WARNING_GPT = _("You have chosen a GPT labeled disk. Installing "
                         "onto a GPT labeled disk will cause the loss "
                         "of all existing data and the disk will be "
                         "relabeled as SMI.")

    CANCEL_BUTTON = _("Cancel")
    CONTINUE_BUTTON = _("Continue")
    
    def __init__(self, main_win):
        super(DiskScreen, self).__init__(main_win)
        if platform.processor() == "i386":
            self.found_text = DiskScreen.FOUND_x86
            self.proposed_text = DiskScreen.PROPOSED_x86
        else:
            self.found_text = DiskScreen.FOUND_SPARC
            self.proposed_text = DiskScreen.PROPOSED_SPARC
        
        disk_header_text = []
        for header in DiskScreen.DISK_HEADERS:
            header_str = fit_text_truncate(header[1], header[0]-1, just="left")
            disk_header_text.append(header_str)
        self.disk_header_text = " ".join(disk_header_text)
        max_note_size = DiskScreen.DISK_HEADERS[5][0]
        self.too_small_text = DiskScreen.TOO_SMALL[:max_note_size]
        max_disk_size = SliceInfo.MAX_VTOC.size_as("tb")
        too_big_warn = DiskScreen.TOO_BIG_WARN % max_disk_size
        self.too_big_warn = too_big_warn[:max_note_size]
        self.disk_warning_too_big = \
            DiskScreen.DISK_WARNING_TOOBIG % max_disk_size
        
        self.disks = []
        self.existing_pools = []
        self.disk_win = None
        self.disk_detail = None
        self.num_targets = 0
        self.td_handle = None
        self._size_line = None
        self.selected_disk = 0
        self._minimum_size = None
        self._recommended_size = None
        self.do_copy = False # Flag indicating if install_profile.disk
                             # should be copied
    
    def determine_minimum(self):
        '''Returns minimum install size, fetching first if needed'''
        self.determine_size_data()
        return self._minimum_size
    
    minimum_size = property(determine_minimum)
    
    def determine_recommended(self):
        '''Returns recommended install size, fetching first if needed'''
        self.determine_size_data()
        return self._recommended_size
    
    recommended_size = property(determine_recommended)
    
    def determine_size_data(self):
        '''Retrieve the minimum and recommended sizes and generate the string
        to present that information.
        
        '''
        if self._minimum_size is None or self._recommended_size is None:
            self._recommended_size = get_recommended_size().size_as("gb")
            self._minimum_size = get_minimum_size().size_as("gb")
    
    def get_size_line(self):
        '''Returns the line of text displaying the min/recommended sizes'''
        if self._size_line is None:
            size_dict = {"recommend" : self.recommended_size,
                         "min" : self.minimum_size}
            self._size_line = DiskScreen.SIZE_TEXT % size_dict
        return self._size_line
    
    size_line = property(get_size_line)
    
    def wait_for_disks(self):
        '''Block while waiting for libtd to finish. Catch F9 and quit
        if needed
        
        '''
        if self.td_handle is None:
            self.start_discovery()
        self.main_win.actions.pop(curses.KEY_F2, None)
        self.main_win.actions.pop(curses.KEY_F6, None)
        self.main_win.actions.pop(curses.KEY_F3, None)
        self.main_win.show_actions()
        if self.td_handle.is_alive():
            self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1,
                                     self.win_size_x - 3)
            self.main_win.do_update()
            offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2
            spin_index = 0
            self.center_win.window.timeout(250)
            while self.td_handle.is_alive():
                input_key = self.main_win.getch()
                if input_key == curses.KEY_F9:
                    if self.confirm_quit():
                        raise QuitException
                self.center_win.add_text(DiskScreen.SPINNER[spin_index], 5,
                                         offset)
                self.center_win.no_ut_refresh()
                self.main_win.do_update()
                spin_index = (spin_index + 1) % len(DiskScreen.SPINNER)

            self.center_win.window.timeout(-1)
            self.center_win.clear()

        # Get the list of existing zpools on the
        # system and based on that come up with 
        # a unique name for the root pool 
        index = 1
        pool_name = "rpool"
        while pool_name in self.existing_pools:
            pool_name = "rpool%d" % index
            index += 1

        # Set the SliceInfo.DEFAULT_POOL to the unique
        # pool name
        SliceInfo.DEFAULT_POOL.data = pool_name

    def _show(self):
        '''Create a list of disks to choose from and create the window
        for displaying the partition/slice information from the selected
        disk
        
        '''
        self.wait_for_disks()
        self.num_targets = 0
        
        if not self.disks:
            self.center_win.add_paragraph(DiskScreen.NO_DISKS, 1, 1,
                                          max_x=(self.win_size_x - 1))
            return
        
        if isinstance(self.disks[0], BaseException):
            if len(self.disks) == 1:
                raise tgt.TgtError(("Unexpected error (%s) during target "
                                    "discovery. See log for details.") %
                                    self.disks[0])
            else:
                self.disks = self.disks[1:]
                logging.warn("Failure in target discovery, but one or more"
                             " disks found. Continuing.")
        
        boot_disk = self.disks[0]
        for disk in self.disks:
            if (disk.size.size_as("gb") > self.minimum_size):
                self.num_targets += 1
            if disk.boot:
                boot_disk = disk
        self.disks.remove(boot_disk)
        self.disks.insert(0, boot_disk)
        
        if self.num_targets == 0:
            self.center_win.add_paragraph(DiskScreen.NO_TARGETS, 1, 1,
                                          max_x=(self.win_size_x - 1))
            return
        
        self.main_win.reset_actions()
        self.main_win.show_actions()
        
        y_loc = 1
        self.center_win.add_text(DiskScreen.PARAGRAPH, y_loc, 1)
        
        y_loc += 1
        self.center_win.add_text(self.size_line, y_loc, 1)
        
        y_loc += 2
        self.center_win.add_text(self.disk_header_text, y_loc, 1)
        
        y_loc += 1
        self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1,
                                     curses.ACS_HLINE,
                                     textwidth(self.disk_header_text))
        
        y_loc += 1
        disk_win_area = WindowArea(4, textwidth(self.disk_header_text) + 2,
                                   y_loc, 0)
        disk_win_area.scrollable_lines = len(self.disks) + 1
        self.disk_win = ScrollWindow(disk_win_area,
                                     window=self.center_win)
        
        disk_item_area = WindowArea(1, disk_win_area.columns - 2, 0, 1)
        disk_index = 0
        len_type = DiskScreen.DISK_HEADERS[0][0] - 1
        len_size = DiskScreen.DISK_HEADERS[1][0] - 1
        len_boot = DiskScreen.DISK_HEADERS[2][0] - 1
        len_dev = DiskScreen.DISK_HEADERS[3][0] - 1
        len_mftr = DiskScreen.DISK_HEADERS[4][0] - 1
        for disk in self.disks:
            disk_text_fields = []
            type_field = disk.type[:len_type]
            type_field = ljust_columns(type_field, len_type)
            disk_text_fields.append(type_field)
            disk_size = disk.size.size_as("gb")
            size_field = "%*.1f" % (len_size, disk_size)
            disk_text_fields.append(size_field)
            if disk.boot:
                bootable_field = "+".center(len_boot)
            else:
                bootable_field = " " * (len_boot)
            disk_text_fields.append(bootable_field)
            device_field = disk.name[:len_dev]
            device_field = ljust_columns(device_field, len_dev)
            disk_text_fields.append(device_field)
            if disk.vendor is not None:
                mftr_field = disk.vendor[:len_mftr]
                mftr_field = ljust_columns(mftr_field, len_mftr)
            else:
                mftr_field = " " * len_mftr
            disk_text_fields.append(mftr_field)
            selectable = True
            if disk_size < self.minimum_size:
                note_field = self.too_small_text
                selectable = False
            elif DiskInfo.GPT in disk.label:
                note_field = DiskScreen.GPT_LABELED
            elif disk_size > SliceInfo.MAX_VTOC.size_as("gb"):
                note_field = self.too_big_warn
            else:
                note_field = ""
            disk_text_fields.append(note_field)
            disk_text = " ".join(disk_text_fields)
            disk_item_area.y_loc = disk_index
            disk_list_item = ListItem(disk_item_area, window=self.disk_win,
                                      text=disk_text, add_obj=selectable)
            disk_list_item.on_make_active = on_activate
            disk_list_item.on_make_active_kwargs["disk_info"] = disk
            disk_list_item.on_make_active_kwargs["disk_select"] = self
            disk_index += 1
        self.disk_win.no_ut_refresh()
        
        y_loc += 7
        disk_detail_area = WindowArea(6, 70, y_loc, 1)
        self.disk_detail = DiskWindow(disk_detail_area, self.disks[0],
                                      window=self.center_win)
        
        self.main_win.do_update()
        self.center_win.activate_object(self.disk_win)
        self.disk_win.activate_object(self.selected_disk)
        # Set the flag so that the disk is not copied by on_change_screen,
        # unless on_activate gets called as a result of the user changing
        # the selected disk.
        self.do_copy = False
    
    def on_change_screen(self):
        ''' Assign the selected disk to the InstallProfile, and make note of
        its index (in case the user returns to this screen later)
        
        '''
        if self.disk_detail is not None:
            if self.do_copy or self.install_profile.disk is None:
                disk = self.disk_detail.disk_info
                self.install_profile.disk = deepcopy(disk)
                self.install_profile.original_disk = disk
            self.selected_disk = self.disk_win.active_object
    
    def start_discovery(self):
        '''Spawn a thread to begin target discovery'''
        logging.debug("spawning target discovery thread")
        self.td_handle = threading.Thread(target=DiskScreen.get_disks,
                                          args=(self.disks,
                                                self.existing_pools))
        logging.debug("starting target discovery thread")
        self.td_handle.start()
    
    @staticmethod
    def get_disks(disks, pools):
        '''
        Call into target discovery and get disk data. The disks found are
        added to the list passed in by the 'disks' argument
        
        '''
        try:
            td_disks = tgt.discover_target_data()
            for disk in td_disks:
                disks.append(DiskInfo(tgt_disk=disk))
            pools.extend(get_zpool_list())
        # If an exception occurs, regardless of type, log it, add it as the
        # first item in the disk list, and consume it (an uncaught Exception
        # in this threaded code would distort the display).
        # During the call to _show, if an exception occurred, the program
        # aborts gracefully
        # pylint: disable-msg=W0703
        except BaseException, err:
            logging.exception(traceback.format_exc())
            disks.insert(0, err)
Пример #6
0
class ZpoolScreen(BaseScreen):
    '''
    Allow the user to select a (valid) zpool target for installation
    '''

    HEADER_TEXT = _("Pools")
    PARAGRAPH = _("Where should %(release)s be installed?") % RELEASE
    SIZE_TEXT = _("Recommended size:  %(recommend).1fGB      "
                  "Minimum size: %(min).1fGB")
    POOL_SEEK_TEXT = _("Seeking pools on system")
    TOO_SMALL = _("Too small")
    NO_POOLS = _("No pools found. ")
    NO_TARGETS = _("%(release)s cannot be installed on any pool") % RELEASE
    TGT_ERROR = _("An error occurred while searching for installation"
                  " targets. Please check the install log and file a bug"
                  " at bugs.openindiana.org.")
    OVERWRITE_BOOT_CONFIGURATION_LABEL = _(
        "Overwrite pool's boot configuration")
    BE_LABEL = _("Select BE name:")
    FILESYSTEM_EXISTS_ERROR = _(
        "ZFS file system"
        " %(pool_name)s/ROOT/%(be_name)s already exists")
    BE_NAME_EMPTY_ERROR = _("Boot environment name is empty")
    BE_NAME_UNALLOWED_ERROR = _(
        "Boot environment name contains unallowed symbols")

    POOL_HEADERS = [(25, _("Name")), (10, _("Size(GB)")), (16, _("Notes"))]

    BE_SCREEN_LEN = 32
    ITEM_OFFSET = 2

    def __init__(self, main_win):
        super(ZpoolScreen, self).__init__(main_win)

        pool_header_text = []
        for header in ZpoolScreen.POOL_HEADERS:
            header_str = fit_text_truncate(header[1],
                                           header[0] - 1,
                                           just="left")
            pool_header_text.append(header_str)
        self.pool_header_text = " ".join(pool_header_text)

        self.existing_pools = []
        self.num_targets = 0
        max_note_size = ZpoolScreen.POOL_HEADERS[2][0]
        self.too_small_text = ZpoolScreen.TOO_SMALL[:max_note_size]
        self._size_line = None
        self.selected_pool = 0
        self._minimum_size = None
        self._recommended_size = None
        self.pool_win = None

        max_field = max(textwidth(ZpoolScreen.BE_LABEL),
                        textwidth(ZpoolScreen.BE_NAME_EMPTY_ERROR),
                        textwidth(ZpoolScreen.BE_NAME_UNALLOWED_ERROR),
                        textwidth(ZpoolScreen.NO_POOLS))

        self.max_text_len = int((self.win_size_x - ZpoolScreen.BE_SCREEN_LEN -
                                 ZpoolScreen.ITEM_OFFSET) / 2)
        self.text_len = min(max_field + 1, self.max_text_len)
        self.list_area = WindowArea(1, self.text_len, 0,
                                    ZpoolScreen.ITEM_OFFSET)

        self.edit_area = WindowArea(1, ZpoolScreen.BE_SCREEN_LEN + 1, 0,
                                    self.text_len)
        err_x_loc = 2
        err_width = (self.text_len + ZpoolScreen.BE_SCREEN_LEN)
        self.error_area = WindowArea(1, err_width + 1, 0, err_x_loc)
        self.be_name_list = None
        self.be_name_edit = None
        self.be_name_err = None

        self.boot_configuration_item = None
        self.do_copy = False  # Flag indicating if install_profile.pool_name
        # should be copied

    def determine_minimum(self):
        '''Returns minimum install size, fetching first if needed'''
        self.determine_size_data()
        return self._minimum_size

    minimum_size = property(determine_minimum)

    def determine_recommended(self):
        '''Returns recommended install size, fetching first if needed'''
        self.determine_size_data()
        return self._recommended_size

    recommended_size = property(determine_recommended)

    def determine_size_data(self):
        '''Retrieve the minimum and recommended sizes and generate the string
        to present that information.
        
        '''
        if self._minimum_size is None or self._recommended_size is None:
            self._recommended_size = get_recommended_size().size_as("gb")
            self._minimum_size = get_minimum_size().size_as("gb")

    def get_size_line(self):
        '''Returns the line of text displaying the min/recommended sizes'''
        if self._size_line is None:
            size_dict = {
                "recommend": self.recommended_size,
                "min": self.minimum_size
            }
            self._size_line = ZpoolScreen.SIZE_TEXT % size_dict
        return self._size_line

    size_line = property(get_size_line)

    def _show(self):
        '''Create a list of pools to choose from, ask user to select BE
        name and if we should overwrite pool's boot configuration
        
        '''
        if not self.install_profile.install_to_pool:
            raise SkipException

        if len(self.existing_pools) == 0:
            self.existing_pools.extend(get_zpool_list())
        self.num_targets = 0

        if len(self.existing_pools) == 0:
            self.center_win.add_paragraph(ZpoolScreen.NO_POOLS,
                                          1,
                                          1,
                                          max_x=(self.win_size_x - 1))
            return

        for pool in self.existing_pools:
            free_gb = get_zpool_free_size(pool) / 1024 / 1024 / 1024
            if (get_zpool_free_size(pool) / 1024 / 1024 / 1024 >
                    self.minimum_size):
                self.num_targets += 1
            else:
                logging.info("Skipping pool %s: need %d GB, free %d GB" %
                             (pool, self.minimum_size, free_gb))

        if self.num_targets == 0:
            self.center_win.add_paragraph(ZpoolScreen.NO_TARGETS,
                                          1,
                                          1,
                                          max_x=(self.win_size_x - 1))
            return

        self.main_win.reset_actions()
        self.main_win.show_actions()

        y_loc = 1
        self.center_win.add_text(ZpoolScreen.PARAGRAPH, y_loc, 1)

        y_loc += 1
        self.center_win.add_text(self.size_line, y_loc, 1)

        y_loc += 2
        self.center_win.add_text(self.pool_header_text, y_loc, 1)

        y_loc += 1
        self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1,
                                     curses.ACS_HLINE,
                                     textwidth(self.pool_header_text))

        y_loc += 1
        pool_win_area = WindowArea(4,
                                   textwidth(self.pool_header_text) + 2, y_loc,
                                   0)
        pool_win_area.scrollable_lines = len(self.existing_pools) + 1
        self.pool_win = ScrollWindow(pool_win_area, window=self.center_win)

        pool_item_area = WindowArea(1, pool_win_area.columns - 2, 0, 1)
        pool_index = 0
        len_name = ZpoolScreen.POOL_HEADERS[0][0] - 1
        len_size = ZpoolScreen.POOL_HEADERS[2][0] - 1
        for pool in self.existing_pools:
            pool_text_fields = []
            name_field = pool[:len_name]
            name_field = ljust_columns(name_field, len_name)
            pool_text_fields.append(name_field)
            pool_size = get_zpool_free_size(pool) / 1024 / 1024 / 1024
            size_field = "%*.1f" % (len_size, pool_size)
            pool_text_fields.append(size_field)
            selectable = True
            if pool_size < self.minimum_size:
                note_field = self.too_small_text
                selectable = False
            else:
                note_field = ""
            pool_text_fields.append(note_field)
            pool_text = " ".join(pool_text_fields)
            pool_item_area.y_loc = pool_index
            pool_list_item = ListItem(pool_item_area,
                                      window=self.pool_win,
                                      text=pool_text,
                                      add_obj=selectable)
            pool_list_item.on_make_active = on_activate
            pool_list_item.on_make_active_kwargs["pool_select"] = self

            pool_index += 1
        self.pool_win.no_ut_refresh()

        y_loc += 7
        self.list_area.y_loc = y_loc
        y_loc += 2
        self.error_area.y_loc = y_loc
        self.be_name_err = ErrorWindow(self.error_area, window=self.center_win)
        self.be_name_list = ListItem(self.list_area,
                                     window=self.center_win,
                                     text=ZpoolScreen.BE_LABEL)
        self.be_name_edit = EditField(self.edit_area,
                                      window=self.be_name_list,
                                      validate=be_name_valid,
                                      error_win=self.be_name_err,
                                      text=self.install_profile.be_name)

        y_loc += 2
        boot_configuration_width = textwidth(
            ZpoolScreen.OVERWRITE_BOOT_CONFIGURATION_LABEL) + 5
        cols = int((self.win_size_x - boot_configuration_width) / 2)
        boot_configuration_area = WindowArea(1, boot_configuration_width,
                                             y_loc, cols)

        self.boot_configuration_item = MultiListItem(
            boot_configuration_area,
            window=self.center_win,
            text=ZpoolScreen.OVERWRITE_BOOT_CONFIGURATION_LABEL,
            used=self.install_profile.overwrite_boot_configuration)

        self.boot_configuration_item.on_select = on_select_obc
        self.boot_configuration_item.on_select_kwargs["pool_select"] = self

        self.main_win.do_update()
        self.center_win.activate_object(self.pool_win)
        self.pool_win.activate_object(self.selected_pool)
        # Set the flag so that the pool is not copied by on_change_screen,
        # unless on_activate gets called as a result of the user changing
        # the selected pool.
        self.do_copy = False

    def on_change_screen(self):
        ''' Assign the selected pool to the InstallProfile, and make note of
        its index (in case the user returns to this screen later)
        
        '''
        if self.pool_win:
            if self.do_copy or self.install_profile.pool_name is None:
                self.install_profile.pool_name = self.existing_pools[
                    self.pool_win.active_object]
            self.selected_pool = self.pool_win.active_object
            self.install_profile.be_name = self.be_name_edit.get_text()

    def validate(self):
        if not self.pool_win:
            raise UIMessage(ZpoolScreen.NO_POOLS)
        pool_name = self.existing_pools[self.pool_win.active_object]
        be_name = self.be_name_edit.get_text()
        if not be_name:
            raise UIMessage(ZpoolScreen.BE_NAME_EMPTY_ERROR)

        be_names = get_zpool_be_names(pool_name)
        if be_name in be_names:
            filesystem_dict = {"pool_name": pool_name, "be_name": be_name}
            raise UIMessage(ZpoolScreen.FILESYSTEM_EXISTS_ERROR %
                            filesystem_dict)