def test_active_object(self): '''Test that arrow key changes active object''' lines = 4 extra_lines = 9 scroll_win = ScrollWindow(WindowArea(lines, 70, 0, 0, scrollable_lines=lines + extra_lines), color_theme=ColorTheme(force_bw=True)) scroll_win.active_object = 0 myobj0 = MockEditField() myobj0.area = MockInnerWin() myobj0.area.y_loc = 1 myobj0.active = True myobj1 = MockEditField() myobj1.area = MockInnerWin() myobj1.area.y_loc = 3 myobj1.active = False scroll_win.objects.append(myobj0) scroll_win.objects.append(myobj1) key = scroll_win.on_arrow_key(curses.KEY_DOWN) self.assertEquals(key, None) self.assertEquals(scroll_win.active_object, 1)
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 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 test_scroll_no_args(self): '''Test that scroll called with no args throws ValueError''' scroll_win = ScrollWindow(WindowArea(70, 70, 0, 0, scrollable_lines=75), color_theme=ColorTheme(force_bw=True)) self.assertTrue(scroll_win.get_use_vert_scroll_bar()) self.assertEquals(scroll_win.current_line[0], 0) self.assertRaises(ValueError, scroll_win.scroll)
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 test_scroll_one_col(self): '''Test to scroll one column ''' scroll_win = ScrollWindow(WindowArea(70, 70, 0, 0, scrollable_columns=75), color_theme=ColorTheme(force_bw=True)) self.assertTrue(scroll_win.get_use_horiz_scroll_bar()) self.assertEquals(scroll_win.current_line[1], 0) scroll_win.scroll(columns=1) self.assertEquals(scroll_win.current_line[1], 1)
def test_scroll_to_right(self): '''Test to scroll multiple columns to right of scrollarea''' cols = 70 extra_cols = 5 scroll_win = ScrollWindow(WindowArea(70, cols, 0, 0, scrollable_columns=cols+extra_cols), color_theme=ColorTheme(force_bw=True)) scroll_win.scroll(scroll_to_column=5) self.assertTrue(scroll_win.at_right())
def test_scroll_to_bottom(self): '''Test to scroll multiple lines to bottom of scrollarea''' lines = 70 extra_lines = 5 scroll_win = ScrollWindow(WindowArea(lines, 70, 0, 0, scrollable_lines=lines + extra_lines), color_theme=ColorTheme(force_bw=True)) scroll_win.scroll(scroll_to_line=5) self.assertTrue(scroll_win.at_bottom())
def test_no_ut_refresh(self): '''Ensure deep_refresh is updating nested window screen location ''' scroll_win = ScrollWindow(WindowArea(60, 70, 0, 0, scrollable_columns=75), color_theme=ColorTheme(force_bw=True)) scroll_win.window = MockInnerWin() scroll_win.area.lower_right_y = 20 scroll_win.area.lower_right_x = 20 myscroll = ScrollWindow(WindowArea(10, 10, 0, 0, scrollable_columns=15), color_theme=ColorTheme(force_bw=True)) myscroll.window = MockInnerWin() myscroll.area.lower_right_y = 16 myscroll.area.lower_right_x = 18 scroll_win.objects.append(myscroll) scroll_win.area.y_loc = 3 scroll_win.area.x_loc = 5 abs_y = 12 abs_x = 15 scroll_win.latest_yx = (abs_y, abs_x) scroll_win.no_ut_refresh() self.assertEquals(myscroll.latest_yx[0], scroll_win.area.y_loc + abs_y) self.assertEquals(myscroll.latest_yx[1], scroll_win.area.x_loc + abs_x)
def test_act_obj_indexerr_not_edge(self): '''Test arrow key, active object, IndexError, not at edge''' lines = 4 extra_lines = 9 scroll_win = ScrollWindow(WindowArea(lines, 70, 0, 0, scrollable_lines=lines + extra_lines), color_theme=ColorTheme(force_bw=True)) scroll_win.active_object = 0 scroll_win.objects.append(object()) key = scroll_win.on_arrow_key(curses.KEY_DOWN) self.assertEquals(scroll_win.current_line[0], 1) self.assertEquals(key, None)
def test_scroll_past_right(self): '''Test to scroll past right, should end up at right ''' cols = 70 extra_cols = 5 scroll_win = ScrollWindow(WindowArea(70, cols, 0, 0, scrollable_columns=cols+extra_cols), color_theme=ColorTheme(force_bw=True)) self.assertTrue(scroll_win.at_left()) self.assertFalse(scroll_win.at_right()) scroll_win.scroll(columns=10) self.assertFalse(scroll_win.at_left()) self.assertTrue(scroll_win.at_right())
def test_scroll_past_bottom(self): '''Test to scroll past bottom, should end up at bottom ''' lines = 70 extra_lines = 5 scroll_win = ScrollWindow(WindowArea(lines, 70, 0, 0, scrollable_lines=lines + extra_lines), color_theme=ColorTheme(force_bw=True)) self.assertTrue(scroll_win.at_top()) self.assertFalse(scroll_win.at_bottom()) scroll_win.scroll(lines=10) self.assertFalse(scroll_win.at_top()) self.assertTrue(scroll_win.at_bottom())
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 _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)
def _show(self): '''Create a list of NICs to choose from. If more than 15 NICs are found, create a scrolling region to put them in ''' self.nic = self.install_profile.nic if self.nic.type != NetworkInfo.MANUAL: raise SkipException if len(self.ether_nics) == 1: self.set_nic_in_profile(self.ether_nics[0]) raise SkipException try: selected_nic_name = self.nic.nic_name except AttributeError: selected_nic_name = "" y_loc = 1 y_loc += self.center_win.add_paragraph(NICSelect.PARAGRAPH, y_loc) selected_nic = 0 y_loc += 1 max_nics = min(NICSelect.MAX_NICS, self.center_win.area.lines - y_loc) if len(self.ether_nics) > max_nics: columns = self.win_size_x - NICSelect.LIST_OFFSET win_area = WindowArea(lines=max_nics, columns=columns, y_loc=y_loc, x_loc=NICSelect.LIST_OFFSET, scrollable_lines=len(self.ether_nics)) window = ScrollWindow(win_area, window=self.center_win) y_loc = 0 else: window = self.center_win for nic in self.ether_nics: self.list_area.y_loc = y_loc self.list_area.columns = len(nic) + 1 list_item = ListItem(self.list_area, window=window, text=nic, data_obj=nic) if nic == selected_nic_name: selected_nic = list_item y_loc += 1 self.main_win.do_update() self.center_win.activate_object(selected_nic)
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 test_horiz_scrollbar_created(self): '''Ensure horizontal scrollbar is created or not appropriately''' scroll_win = ScrollWindow(WindowArea(70, 70, 0, 0, scrollable_columns=75), color_theme=ColorTheme(force_bw=True)) self.assertTrue(scroll_win.get_use_horiz_scroll_bar()) scroll_win = ScrollWindow(WindowArea(70, 70, 0, 0, scrollable_columns=69), color_theme=ColorTheme(force_bw=True)) self.assertFalse(scroll_win.get_use_horiz_scroll_bar())
def test_vert_scrollbar_created(self): '''Ensure vertical scrollbar is created or not appropriately''' scroll_win = ScrollWindow(WindowArea(70, 70, 0, 0, scrollable_lines=75), color_theme=ColorTheme(force_bw=True)) self.assertTrue(scroll_win.get_use_vert_scroll_bar()) scroll_win = ScrollWindow(WindowArea(70, 70, 0, 0, scrollable_lines=70), color_theme=ColorTheme(force_bw=True)) self.assertFalse(scroll_win.get_use_vert_scroll_bar())
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 ''' if self.install_profile.install_to_pool: raise SkipException 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_use = DiskScreen.DISK_HEADERS[0][0] - 1 len_type = DiskScreen.DISK_HEADERS[1][0] - 1 len_size = DiskScreen.DISK_HEADERS[2][0] - 1 len_boot = DiskScreen.DISK_HEADERS[3][0] - 1 len_dev = DiskScreen.DISK_HEADERS[4][0] - 1 len_mftr = DiskScreen.DISK_HEADERS[5][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 = "" # Use first selectable disk if no disk was used yet if disk.used is None and not self.one_disk_used and selectable: disk.used = True self.one_disk_used = True disk_text_fields.append(note_field) disk_text = " ".join(disk_text_fields) disk_item_area.y_loc = disk_index disk_list_item = MultiListItem(disk_item_area, window=self.disk_win, text=disk_text, add_obj=selectable, used=disk.used) 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_list_item.on_select = on_select disk_list_item.on_select_kwargs["disk_info"] = disk disk_list_item.on_select_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_select gets called as a result of the user changing # the selected disks. self.do_copy = False
def test_scroll_down_up_arrow(self): '''Test to scroll down and up with arrow key ''' lines = 4 extra_lines = 9 scroll_win = ScrollWindow(WindowArea(lines, 70, 0, 0, scrollable_lines=lines + extra_lines), color_theme=ColorTheme(force_bw=True)) scroll_win.active_object = None self.assertTrue(scroll_win.at_top()) key = scroll_win.on_arrow_key(curses.KEY_DOWN) self.assertEqual(key, None) self.assertFalse(scroll_win.at_top()) key = scroll_win.on_arrow_key(curses.KEY_UP) self.assertEqual(key, None) self.assertTrue(scroll_win.at_top()) key = scroll_win.on_arrow_key(curses.KEY_UP) self.assertEqual(key, curses.KEY_UP) self.assertTrue(scroll_win.at_top()) scroll_win.scroll(extra_lines-2) self.assertFalse(scroll_win.at_bottom()) scroll_win.on_arrow_key(curses.KEY_DOWN) self.assertTrue(scroll_win.at_bottom()) scroll_win.scroll(-(extra_lines-2)) self.assertFalse(scroll_win.at_top()) scroll_win.on_arrow_key(curses.KEY_UP) self.assertTrue(scroll_win.at_top())
def test_scroll_right_left_arrow(self): '''Test to scroll right and left with arrow key ''' cols = 3 extra_cols = 5 scroll_win = ScrollWindow(WindowArea(10, cols, 0, 0, scrollable_columns=cols+extra_cols), color_theme=ColorTheme(force_bw=True)) scroll_win.active_object = None self.assertTrue(scroll_win.at_left()) key = scroll_win.on_arrow_key(curses.KEY_RIGHT) self.assertEqual(key, None) self.assertFalse(scroll_win.at_left()) key = scroll_win.on_arrow_key(curses.KEY_LEFT) self.assertEqual(key, None) self.assertTrue(scroll_win.at_left()) key = scroll_win.on_arrow_key(curses.KEY_LEFT) self.assertEqual(key, curses.KEY_LEFT) self.assertTrue(scroll_win.at_left()) scroll_win.scroll(columns=extra_cols-2) self.assertFalse(scroll_win.at_left()) self.assertFalse(scroll_win.at_right()) scroll_win.on_arrow_key(curses.KEY_RIGHT) self.assertTrue(scroll_win.at_right()) scroll_win.scroll(columns=-(extra_cols-2)) self.assertFalse(scroll_win.at_left()) self.assertFalse(scroll_win.at_right()) scroll_win.on_arrow_key(curses.KEY_LEFT) self.assertTrue(scroll_win.at_left())
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()
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)
def _show(self): '''Create the list of time zones''' logging.debug("self.screen %s", self.screen) if self.install_profile.system is None: self.install_profile.system = SystemInfo() self.sys_info = self.install_profile.system self.cur_country = self.sys_info.tz_country self.cur_continent = self.sys_info.tz_region if self.cur_continent == SystemInfo.UTC and self.screen != "regions": raise SkipException self.center_win.border_size = TimeZone.BORDER_WIDTH if self.screen == TimeZone.LOCATIONS: self.cur_timezone_parent = self.cur_continent elif self.screen == TimeZone.TIMEZONE: self.cur_timezone_parent = self.cur_country logging.debug("cur_continent %s, cur_country %s", self.cur_continent, self.cur_country) y_loc = 1 y_loc += self.center_win.add_paragraph(self.intro, y_loc) y_loc += 1 menu_item_max_width = self.win_size_x - TimeZone.SCROLL_SIZE self.center_win.add_text(self.title, y_loc, TimeZone.SCROLL_SIZE) y_loc += 1 self.center_win.window.hline(y_loc, 3, curses.ACS_HLINE, 40) y_loc += 1 tz_list = self.get_timezones(self.cur_continent, self.cur_country) area = WindowArea(x_loc=0, y_loc=y_loc, scrollable_lines=len(tz_list) + 1) area.lines = self.win_size_y - (y_loc + 1) area.columns = self.win_size_x logging.debug("area.lines=%d, area.columns=%d", area.lines, area.columns) self.scroll_region = ScrollWindow(area, window=self.center_win) utc = 0 if self.screen == TimeZone.REGIONS: utc_area = WindowArea(1, len(TimeZone.UTC_TEXT) + 1, 0, TimeZone.SCROLL_SIZE) utc_item = ListItem(utc_area, window=self.scroll_region, text=TimeZone.UTC_TEXT, data_obj=SystemInfo.UTC) utc = 1 # add the entries to the screen for idx, timezone in enumerate(tz_list): logging.log(LOG_LEVEL_INPUT, "tz idx = %i name= %s", idx, tz_list[idx]) hilite = min(menu_item_max_width, len(timezone) + 1) win_area = WindowArea(1, hilite, idx + utc, TimeZone.SCROLL_SIZE) list_item = ListItem(win_area, window=self.scroll_region, text=timezone, data_obj=timezone) y_loc += 1 self.main_win.do_update() self.center_win.activate_object(self.scroll_region) logging.debug("self.cur_timezone_idx=%s", self.cur_timezone_idx) self.scroll_region.activate_object_force(self.cur_timezone_idx, force_to_top=True)
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
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
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)
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 TimeZone(BaseScreen): '''Allow user to select timezone based on already selected continent and country. ''' UTC_TEXT = _("UTC/GMT") SCROLL_SIZE = 2 BORDER_WIDTH = (0, 3) REGIONS = "regions" LOCATIONS = "locations" TIMEZONE = "timezone" def __init__(self, main_win, screen=None): super(TimeZone, self).__init__(main_win) self.sys_info = None if screen is None: self.screen = TimeZone.TIMEZONE else: self.screen = screen self.tz_tuples = None self.tz_list = None self.cur_timezone_idx = 0 self.cur_timezone_parent = None self.last_timezone_parent = None self.cur_continent = None self.cur_country = None self.scroll_region = None self.last_country = None self.last_continent = None if self.screen == TimeZone.TIMEZONE: self.header_text = _("Time Zone") self.intro = _("Select your time zone.") self.title = _("Time Zones") elif self.screen == TimeZone.LOCATIONS: self.header_text = _("Time Zone: Locations") self.intro = _("Select the location that contains your time zone.") self.title = _("Locations") else: self.header_text = _("Time Zone: Regions") self.intro = _("Select the region that contains your time zone.") self.title = _("Regions") def _show(self): '''Create the list of time zones''' logging.debug("self.screen %s", self.screen) if self.install_profile.system is None: self.install_profile.system = SystemInfo() self.sys_info = self.install_profile.system self.cur_country = self.sys_info.tz_country self.cur_continent = self.sys_info.tz_region if self.cur_continent == SystemInfo.UTC and self.screen != "regions": raise SkipException self.center_win.border_size = TimeZone.BORDER_WIDTH if self.screen == TimeZone.LOCATIONS: self.cur_timezone_parent = self.cur_continent elif self.screen == TimeZone.TIMEZONE: self.cur_timezone_parent = self.cur_country logging.debug("cur_continent %s, cur_country %s", self.cur_continent, self.cur_country) y_loc = 1 y_loc += self.center_win.add_paragraph(self.intro, y_loc) y_loc += 1 menu_item_max_width = self.win_size_x - TimeZone.SCROLL_SIZE self.center_win.add_text(self.title, y_loc, TimeZone.SCROLL_SIZE) y_loc += 1 self.center_win.window.hline(y_loc, 3, curses.ACS_HLINE, 40) y_loc += 1 tz_list = self.get_timezones(self.cur_continent, self.cur_country) area = WindowArea(x_loc=0, y_loc=y_loc, scrollable_lines=len(tz_list) + 1) area.lines = self.win_size_y - (y_loc + 1) area.columns = self.win_size_x logging.debug("area.lines=%d, area.columns=%d", area.lines, area.columns) self.scroll_region = ScrollWindow(area, window=self.center_win) utc = 0 if self.screen == TimeZone.REGIONS: utc_area = WindowArea(1, len(TimeZone.UTC_TEXT) + 1, 0, TimeZone.SCROLL_SIZE) utc_item = ListItem(utc_area, window=self.scroll_region, text=TimeZone.UTC_TEXT, data_obj=SystemInfo.UTC) utc = 1 # add the entries to the screen for idx, timezone in enumerate(tz_list): logging.log(LOG_LEVEL_INPUT, "tz idx = %i name= %s", idx, tz_list[idx]) hilite = min(menu_item_max_width, len(timezone) + 1) win_area = WindowArea(1, hilite, idx + utc, TimeZone.SCROLL_SIZE) list_item = ListItem(win_area, window=self.scroll_region, text=timezone, data_obj=timezone) y_loc += 1 self.main_win.do_update() self.center_win.activate_object(self.scroll_region) logging.debug("self.cur_timezone_idx=%s", self.cur_timezone_idx) self.scroll_region.activate_object_force(self.cur_timezone_idx, force_to_top=True) def get_timezones(self, continent, country_code): '''Get the timezone info, a list of tuples, each with: [0] timezone name [1] timezone name descriptive [2] localized timezone name ''' logging.debug("get_timezones continent=%s", continent) logging.debug("get_timezones country_code=%s", country_code) logging.debug( "get_timezones self.cur_timezone_parent=%s," " self.last_timezone_parent=%s", self.cur_timezone_parent, self.last_timezone_parent) if (self.tz_list is None or self.cur_timezone_parent != self.last_timezone_parent): self.tz_list = [] self.cur_timezone_idx = 0 # pass get_tz_info the correct parameters: # none - to get regions/continents # continent ("America") - to get countries # continent and country code ("America", "US") - to get the # timezones if self.screen == TimeZone.REGIONS: self.tz_tuples = get_tz_info() elif self.screen == TimeZone.LOCATIONS: self.tz_tuples = get_tz_info(continent) else: self.tz_tuples = get_tz_info(continent, country_code) # get name to display. First choice is localized name, then # descriptive name, then plain name logging.debug("number of timezones=%i", len(self.tz_tuples)) for item in self.tz_tuples: if item[2]: logging.debug("tz2 = %s", item[2]) self.tz_list.append(item[2]) elif item[1]: logging.debug("tz1 = %s", item[1]) self.tz_list.append(item[1]) else: logging.debug("tz0 = %s", item[0]) self.tz_list.append(item[0]) return self.tz_list def on_change_screen(self): '''Save the chosen timezone's index and name when leaving the screen''' self.cur_timezone_idx = self.scroll_region.active_object idx = self.cur_timezone_idx self.last_timezone_parent = self.cur_timezone_parent if self.screen == TimeZone.REGIONS: if (self.scroll_region.get_active_object().data_obj == SystemInfo.UTC): self.sys_info.tz_region_idx = 0 self.sys_info.tz_region = SystemInfo.UTC self.sys_info.tz_country = SystemInfo.UTC self.sys_info.tz_timezone = SystemInfo.UTC self.sys_info.tz_display_name = TimeZone.UTC_TEXT else: self.sys_info.tz_region_idx = idx self.sys_info.tz_region = self.tz_tuples[idx - 1][0] logging.debug("on_change_screen sys_info.tz_region: %s", self.sys_info.tz_region) elif self.screen == TimeZone.LOCATIONS: self.sys_info.tz_country_idx = idx self.sys_info.tz_country = self.tz_tuples[idx][0] self.last_continent = self.cur_continent logging.debug("on_change_screen sys_info.tz_country: %s", self.sys_info.tz_country) logging.debug("on_change_screen sys_info.tz_country_idx: %s", self.sys_info.tz_country_idx) else: self.sys_info.tz_timezone_idx = idx self.sys_info.tz_timezone = self.tz_tuples[idx][0] selected_tz = self.scroll_region.get_active_object().data_obj self.sys_info.tz_display_name = selected_tz self.last_country = self.cur_country self.cur_timezone_idx = self.scroll_region.active_object logging.debug("on_change_screen self.sys_info.tz_timezone: %s", self.sys_info.tz_timezone) logging.debug("on_change_screen self.sys_info.tz_timezone_idx:%s", self.sys_info.tz_timezone_idx)
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 = [(6, _("Use")), (8, _("Type")), (10, _("Size(GB)")), (6, _("Boot")), (9, _("Device")), (15, _("Manufacturer")), (16, _("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. ") DISK_WARNING_NEED_ONE_DISK = _("You haven't selected a disk for installation " "of the operating system.") 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[6][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.one_disk_used = False 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.disks # 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 # Export all zpools, which could be imported due to zpool install export_zpools() 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 ''' if self.install_profile.install_to_pool: raise SkipException 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_use = DiskScreen.DISK_HEADERS[0][0] - 1 len_type = DiskScreen.DISK_HEADERS[1][0] - 1 len_size = DiskScreen.DISK_HEADERS[2][0] - 1 len_boot = DiskScreen.DISK_HEADERS[3][0] - 1 len_dev = DiskScreen.DISK_HEADERS[4][0] - 1 len_mftr = DiskScreen.DISK_HEADERS[5][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 = "" # Use first selectable disk if no disk was used yet if disk.used is None and not self.one_disk_used and selectable: disk.used = True self.one_disk_used = True disk_text_fields.append(note_field) disk_text = " ".join(disk_text_fields) disk_item_area.y_loc = disk_index disk_list_item = MultiListItem(disk_item_area, window=self.disk_win, text=disk_text, add_obj=selectable, used=disk.used) 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_list_item.on_select = on_select disk_list_item.on_select_kwargs["disk_info"] = disk disk_list_item.on_select_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_select gets called as a result of the user changing # the selected disks. 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 len(self.install_profile.disks) == 0: self.install_profile.disks = [] for disk in self.disks: if disk.used: self.install_profile.disks.append(deepcopy(disk)) self.install_profile.original_disks.append(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)
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