예제 #1
0
    def _show(self):
        '''Prepare a text summary and display it to the user in a ScrollWindow
        
        '''

        global LOGGER
        LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)
        
        self.sysconfig = solaris_install.sysconfig.profile.from_engine()
        
        y_loc = 1
        y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc)
        
        y_loc += 1
        summary_text = self.build_summary()

        LOGGER.info("The following configuration is used for "
                    "installation: %s\n", summary_text)
        # 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)
예제 #2
0
    def _show(self):
        '''Prepare a text summary and display it to the user in a ScrollWindow
        
        '''

        global LOGGER
        LOGGER = logging.getLogger(INSTALL_LOGGER_NAME)

        self.sysconfig = solaris_install.sysconfig.profile.from_engine()

        y_loc = 1
        y_loc += self.center_win.add_paragraph(SummaryScreen.PARAGRAPH, y_loc)

        y_loc += 1
        summary_text = self.build_summary()

        LOGGER.info(
            "The following configuration is used for "
            "installation: %s\n", summary_text)
        # 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)
예제 #3
0
    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)
예제 #4
0
 def _show(self):
     '''Display the static paragraph WELCOME_TEXT and all
        applicable bullet items'''
     sc_options = get_sc_options_from_doc()
     max_width = self.win_size_x - WelcomeScreen.INDENT - 1
     text = convert_paragraph(WelcomeScreen.WELCOME_TEXT, max_width)
     # list configuration groups in a comma-separated list with
     # bullet on first line and indentation on subsequent lines
     grouplist = list()
     if configure_group(SC_GROUP_NETWORK):
         grouplist.append(_("network"))
     elif configure_group(SC_GROUP_IDENTITY):
         grouplist.append(_("system hostname"))
     if configure_group(SC_GROUP_LOCATION):
         grouplist.append(_("time zone"))
     if configure_group(SC_GROUP_DATETIME):
         grouplist.append(_("date and time"))
     if configure_group(SC_GROUP_USERS):
         grouplist.append(_("user and root accounts"))
     if configure_group(SC_GROUP_NS):
         grouplist.append(_("name services"))
     grouplist = ", ".join(grouplist)
     grouplist = convert_paragraph(grouplist,
                                   max_width - len(WelcomeScreen.BULLET))
     for ln in range(len(grouplist)):
         if ln == 0:
             text.append(WelcomeScreen.BULLET + grouplist[ln])
         else:
             text.append(WelcomeScreen.BULLET_INDENT + grouplist[ln])
     # display navigation instructions and profile path
     fmt = {"scprof": sc_options.profile}
     text.extend(convert_paragraph(WelcomeScreen.NAVIPRO_TEXT % fmt,
                                   max_width))
     # indent and align while bulletting
     for bullet in WelcomeScreen.BULLET_ITEMS:
         btext = convert_paragraph(bullet,
                                   max_width - len(WelcomeScreen.BULLET))
         for ln in range(len(btext)):
             if ln == 0:
                 text.append(WelcomeScreen.BULLET + btext[ln])
             else:
                 text.append(WelcomeScreen.BULLET_INDENT + btext[ln])
     # prepare welcome text in entire window for scrolling
     area = WindowArea(x_loc=0, y_loc=1, scrollable_lines=(len(text) + 1))
     area.lines = self.win_size_y - 1
     area.columns = self.win_size_x
     scroll_region = ScrollWindow(area, window=self.center_win)
     scroll_region.add_paragraph(text, start_x=WelcomeScreen.INDENT)
     self.center_win.activate_object(scroll_region)
예제 #5
0
 def _show(self):
     '''Display the static paragraph WELCOME_TEXT and all
        applicable bullet items'''
     sc_options = get_sc_options_from_doc()
     max_width = self.win_size_x - WelcomeScreen.INDENT - 1
     text = convert_paragraph(WelcomeScreen.WELCOME_TEXT, max_width)
     # list configuration groups in a comma-separated list with
     # bullet on first line and indentation on subsequent lines
     grouplist = list()
     if configure_group(SC_GROUP_NETWORK):
         grouplist.append(_("network"))
     elif configure_group(SC_GROUP_IDENTITY):
         grouplist.append(_("system hostname"))
     if configure_group(SC_GROUP_LOCATION):
         grouplist.append(_("time zone"))
     if configure_group(SC_GROUP_DATETIME):
         grouplist.append(_("date and time"))
     if configure_group(SC_GROUP_USERS):
         grouplist.append(_("user and root accounts"))
     if configure_group(SC_GROUP_NS):
         grouplist.append(_("name services"))
     grouplist = ", ".join(grouplist)
     grouplist = convert_paragraph(grouplist,
                                   max_width - len(WelcomeScreen.BULLET))
     for ln in range(len(grouplist)):
         if ln == 0:
             text.append(WelcomeScreen.BULLET + grouplist[ln])
         else:
             text.append(WelcomeScreen.BULLET_INDENT + grouplist[ln])
     # display navigation instructions and profile path
     fmt = {"scprof": sc_options.profile}
     text.extend(convert_paragraph(WelcomeScreen.NAVIPRO_TEXT % fmt,
                                   max_width))
     # indent and align while bulletting
     for bullet in WelcomeScreen.BULLET_ITEMS:
         btext = convert_paragraph(bullet,
                                   max_width - len(WelcomeScreen.BULLET))
         for ln in range(len(btext)):
             if ln == 0:
                 text.append(WelcomeScreen.BULLET + btext[ln])
             else:
                 text.append(WelcomeScreen.BULLET_INDENT + btext[ln])
     # prepare welcome text in entire window for scrolling
     area = WindowArea(x_loc=0, y_loc=1, scrollable_lines=(len(text) + 1))
     area.lines = self.win_size_y - 1
     area.columns = self.win_size_x
     scroll_region = ScrollWindow(area, window=self.center_win)
     scroll_region.add_paragraph(text, start_x=WelcomeScreen.INDENT)
     self.center_win.activate_object(scroll_region)
예제 #6
0
    def _show(self):
        '''Prepare a text summary from the DOC 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
        self.sysconfig = solaris_install.sysconfig.profile.from_engine()
        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)
예제 #7
0
    def _show(self):
        '''Prepare a text summary from the DOC 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
        self.sysconfig = solaris_install.sysconfig.profile.from_engine()
        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)
예제 #8
0
class HelpScreen(BaseScreen):
    '''Show localized help file pertaining to last traversed screen or
    help topics list from which to choose a desired help topic.

    '''
    def __init__(self, main_win, help_header, help_index, intro):
        super(HelpScreen, self).__init__(main_win)

        try:
            self.locale = locale.setlocale(locale.LC_MESSAGES, "")
        except locale.Error:
            terminalui.LOGGER.warning("System configured with invalid "
                                      "locale(5), falling back to C.")
            self.locale = locale.setlocale(locale.LC_MESSAGES, "C")

        terminalui.LOGGER.debug("locale=%s", self.locale)

        self.help_header = help_header
        self.help_index = help_index
        self.intro = intro

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

    def setup_help_data(self, screens):
        '''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>)

        help_info contains tuples:
           (tuple of screen names, format of text)

        '''
        self.help_dict = {}
        self.help_info = []

        for screen in screens:
            if screen.help_data[0]:
                key = screen.__class__.__name__ + screen.instance
                self.help_dict[key] = screen.help_data
                self.help_info.append((key, " " + screen.help_format))

        terminalui.LOGGER.debug("self.help_dict=%s", self.help_dict)
        terminalui.LOGGER.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

        '''

        terminalui.LOGGER.debug("in set_actions self.class_name=%s",
                                self.__class__.__name__)

        # change F6 description
        self.main_win.help_action.text = self.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,
                               self.main_win.continue_action.text,
                               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(self.help_header)
        y_loc = 1

        y_loc += self.center_win.add_paragraph(self.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)
        terminalui.LOGGER.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
        terminalui.LOGGER.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]
            topic_title = self.help_dict[info[0]][1]
            help_topic = topic_format % topic_title
            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)
            terminalui.LOGGER.debug("self.screen_last=%s", self.screen_last)
            if self.screen_last == info[0]:
                terminalui.LOGGER.debug("Set cur_help_idx = %s", idx)
                self.cur_help_idx = idx
        terminalui.LOGGER.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 presses F2 on help topics screen.
        Results in show being called again to display single file help
        of chosen topic.

        '''
        terminalui.LOGGER.debug("continue_action:%s",
                                self.scroll_region.active_object)
        cur_topic = self.scroll_region.active_object
        self.screen = self.help_info[cur_topic][0]
        terminalui.LOGGER.debug("continue_action self.screen=%s", self.screen)
        self.topics = False
        return self

    def display_help(self):
        '''Display the single file help screen'''
        # customize header
        help_header = "%s: %s"
        terminalui.LOGGER.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)
        terminalui.LOGGER.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.'''

        terminalui.LOGGER.debug("in show self.screen=%s", self.screen)

        if (self.screen == self.__class__.__name__):
            terminalui.LOGGER.debug("setting self topics to true:")
            self.topics = True
        else:
            self.topics = False
            self.screen_last = self.screen
            terminalui.LOGGER.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 not filename:
            return ""

        help_file = None
        try:
            for locid in self._get_locids():
                full_path = filename % locid
                terminalui.LOGGER.debug("Accessing help file %s", full_path)
                if os.access(full_path, os.R_OK):
                    break
            terminalui.LOGGER.debug("Opening help file %s", full_path)
            with open(full_path) as help_file:
                help_text = help_file.read()
                terminalui.LOGGER.debug("Done reading help file %s", full_path)
        except IOError:
            terminalui.LOGGER.debug("Unable to open help file %s", full_path)
            help_text = _("Help for this screen is not available")
        return help_text

    def _get_locids(self):
        '''Generate a list of possible locales - folders to check for
        help screen text in. Used by get_help_text() in conjunction with
        a screen's indicated help text file path.

        The list will include one or more of:
            * The current locale (e.g., "en_US.UTF-8")
            * The current locale, stripped of encoding (e.g., "en_US")
            * The current language, stripped of locale (e.g., "en")
            * The default locale ("C")

        '''
        locale = self.locale
        locids = [locale]
        if "." in locale:
            locale = locale.split(".")[0]
            locids.append(locale)
        if len(locale) > 2:
            locids.append(locale[:2])
        locids.append("C")
        return locids
예제 #9
0
class MainWindow(object):
    '''Represent initscr (the whole screen), and break it into a border,
    header, and central region. Map F# keystrokes to Actions

    '''
    def __init__(self,
                 initscr,
                 screen_list,
                 default_actions,
                 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.actions = None

        # _default_actions keeps a "pristine" copy of the actions
        self._default_actions = default_actions

        # default_actions is copied from _default_actions and may
        # get modified during the course of display of a screen.
        # reset_actions() is responsible for copying the pristine copy
        # into this variable.
        self.default_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

        '''
        # A shallow copy of each Action is desired to properly preserve
        # the Action's reference to a given bound method.
        actions = [copy.copy(action) for action in self._default_actions]
        self.default_actions = actions
        self.set_default_actions()

    @property
    def continue_action(self):
        return self.actions[curses.KEY_F2]

    @property
    def back_action(self):
        return self.actions[curses.KEY_F3]

    @property
    def help_action(self):
        return self.actions[curses.KEY_F6]

    @property
    def quit_action(self):
        return self.actions[curses.KEY_F9]

    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 = {}
        for action in self.default_actions:
            self.actions[action.key] = 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, redraw_keys=[InnerWindow.REPAINT_KEY]):
        '''Call down into central_area to get a keystroke, and, if necessary,
        update the footer to switch to using the Esc- prefixes.
        Redraw the screen if any of redraw keys is pressed.

        '''
        input_key = self._active_win.getch()
        # Redraw whole screen if one of 'redraw' keys has been pressed.
        if input_key in redraw_keys:
            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(current_screen.redraw_keys)
            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