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 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()