Пример #1
0
    def test_empty(self):
        c = ListRowContainer(columns=1)

        c.render(10)
        result = c.get_lines()

        self.assertEqual(len(result), 0)
Пример #2
0
class ScreenWithListWidget(UIScreen):
    def __init__(self, widgets_count):
        super().__init__()
        self._widgets_count = widgets_count
        self._list_widget = None
        self.container_callback_input = -1

    def refresh(self, args=None):
        super().refresh(args)

        self._list_widget = ListRowContainer(2)
        for i in range(self._widgets_count):
            self._list_widget.add(TextWidget("Test %s" % i), self._callback, i)

        self.window.add(self._list_widget)

    def input(self, args, key):
        self.close()
        if self._list_widget.process_user_input(key):
            return InputState.PROCESSED

        return InputState.DISCARDED

    def _callback(self, data):
        self.container_callback_input = data
Пример #3
0
    def test_list_callback_without_data(self, out_mock, in_mock):
        c = ListRowContainer(1)

        c.add(TextWidget("Test"), self._callback)

        self.assertTrue(c.process_user_input("1"))
        self.assertIsNone(self._callback_called)
Пример #4
0
    def refresh(self, args=None):
        super().refresh(args)

        self._list_widget = ListRowContainer(2)
        for i in range(self._widgets_count):
            self._list_widget.add(TextWidget("Test %s" % i), self._callback, i)

        self.window.add(self._list_widget)
Пример #5
0
    def test_list_correct_input_processing(self, out_mock, in_mock):
        c = ListRowContainer(1)

        self._prepare_callbacks(c, 3)

        self.assertTrue(c.process_user_input("2"))

        self.assertEqual(self._callback_called, 2)
Пример #6
0
    def refresh(self, args=None):
        """This methods fills the self.window list by all the objects
        we want shown on this screen. Title and Spokes mostly."""
        TUIObject.refresh(self, args)

        self._container = ListRowContainer(2, columns_width=39, spacing=2)

        for w in self._spokes_map:
            self._container.add(w, callback=self._item_called, data=w)

        self.window.add_with_separator(self._container)
Пример #7
0
    def test_more_columns_than_widgets(self):
        c = ListRowContainer(columns=3,
                             items=[self.w1],
                             columns_width=40,
                             numbering=False)
        c.render(80)

        expected_result = [u"Můj krásný dlouhý text"]

        res_lines = c.get_lines()
        self.evaluate_result(res_lines, expected_result)
Пример #8
0
    def test_listrow_container(self):
        c = ListRowContainer(columns=2,
                             items=[self.w2, self.w3, self.w5],
                             columns_width=10,
                             spacing=2,
                             numbering=False)
        c.render(25)

        expected_result = [u"Test        Test 2", u"Test 3"]
        res_lines = c.get_lines()

        self.evaluate_result(res_lines, expected_result)
Пример #9
0
    def test_list_container_too_small(self):
        # to be able to render this container we need at least 11 width
        # 8 will take only spacing and then 1 for every column
        c = ListRowContainer(3, spacing=4, numbering=False)

        c.add(TextWidget("This can't be rendered."))
        c.add(
            TextWidget("Because spacing takes more space than maximal width."))
        c.add(TextWidget("Exception will raise."))

        with self.assertRaisesRegex(ValueError, "Columns width is too small."):
            c.render(10)
Пример #10
0
    def test_row_numbering(self):
        # spacing is 3 by default
        c = ListRowContainer(2, [self.w1, self.w2, self.w3, self.w4],
                             columns_width=16)
        c.render(25)

        expected_result = [
            u"1) Můj krásný      2) Test", u"   dlouhý text",
            u"3) Test 2          4) Krásný dlouhý",
            u"                      text podruhé"
        ]
        res_lines = c.get_lines()
        self.evaluate_result(res_lines, expected_result)
    def test_listrow_wrapping(self):
        # spacing is 3 by default
        c = ListRowContainer(2, [self.w1, self.w2, self.w3, self.w4],
                             columns_width=15,
                             numbering=False)
        c.render(25)

        expected_result = [
            "Můj krásný        Test", "dlouhý text",
            "Test 2            Krásný dlouhý", "                  text podruhé"
        ]
        res_lines = c.get_lines()
        self.evaluate_result(res_lines, expected_result)
Пример #12
0
    def test_list_container_too_small_turn_off_numbering(self):
        # to be able to render this container we need 11 width + three times numbers (3 characters) = 20
        # 8 will take only spacing and then 1 for every column
        c = ListRowContainer(3, spacing=4, numbering=True)

        c.add(TextWidget("This can't be rendered."))
        c.add(
            TextWidget("Because spacing takes more space than maximal width."))
        c.add(
            TextWidget(
                "Exception will raise with info to turn off numbering."))

        with self.assertRaisesRegex(
                ValueError, "Increase column width or disable numbering."):
            c.render(19)
Пример #13
0
    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        self._container = ListRowContainer(1)

        log.debug("license found")
        # make the options aligned to the same column (the checkbox has the
        # '[ ]' prepended)
        self._container.add(
            TextWidget("%s\n" % _("Read the License Agreement")),
            self._show_license_screen_callback)

        self._container.add(
            CheckboxWidget(title=_("I accept the license agreement."),
                           completed=self.data.eula.agreed),
            self._license_accepted_callback)
        self.window.add_with_separator(self._container)
Пример #14
0
    def refresh(self, args=None):
        """Refresh method is called always before the screen will be printed.

        All items for printing should be updated or created here.
        """
        # Init window container. The windows container will be erased here.
        # The window container is the base container. Everything for rendering should be put
        # into this container, including other containers.
        super().refresh(args)

        # Add the screen header message before our items.
        header = TextWidget("Please complete all the spokes to continue")
        header = CenterWidget(header)
        self.window.add_with_separator(header, blank_lines=2)

        # Create the empty container.
        # It will add numbering, process user input and positioning for us.
        self._container = ListRowContainer(2)

        # Create widget to get user name.
        widget = self._create_name_widget()
        # Add widget, callback, arguments to the container.
        #
        # widget - Widget we want to render. It will be numbered automatically.
        #          Could be container if needed.
        # callback - This callback will be called by the ListRowContainer.process_user_input()
        #            method when a user press the number of this item. Callback will get args
        #            passed as 3rd argument.
        # args - Argument for callback.
        self._container.add(widget, self._push_screen_callback,
                            self._name_spoke)

        # Create surname widget and add it to the container.
        widget = self._create_surname_widget()
        self._container.add(widget, self._push_screen_callback,
                            self._surname_spoke)

        # Create password widget and add it to the container.
        widget = self._create_password_widget()
        self._container.add(widget, self._push_screen_callback,
                            self._password_spoke)

        # Add the ListRowContainer container to the WindowContainer container.
        self.window.add_with_separator(self._container)
Пример #15
0
    def refresh(self, args=None):
        """This methods fills the self.window list by all the objects
        we want shown on this screen. Title and Spokes mostly."""
        TUIObject.refresh(self, args)

        self._container = ListRowContainer(2, columns_width=39, spacing=2)

        for w in self._spokes_map:
            self._container.add(w, callback=self._item_called, data=w)

        self.window.add_with_separator(self._container)
Пример #16
0
    def test_newline_wrapping(self):
        widgets = [
            TextWidget("Hello"),
            TextWidget("Wrap\nthis\ntext"),
            TextWidget("Hi"),
            TextWidget("Hello2")
        ]

        c = ListRowContainer(3,
                             widgets,
                             columns_width=6,
                             spacing=1,
                             numbering=False)
        c.render(80)

        expected_result = [
            u"Hello  Wrap   Hi", u"       this", u"       text", u"Hello2"
        ]
        res_lines = c.get_lines()
        self.evaluate_result(res_lines, expected_result)
Пример #17
0
    def test_custom_numbering(self):
        # spacing is 3 by default
        c = ListRowContainer(2, [self.w1, self.w2, self.w3, self.w4],
                             columns_width=20)
        c.key_pattern = KeyPattern("a {:d} a ")
        c.render(25)

        expected_result = [
            u"a 1 a Můj krásný       a 2 a Test", u"      dlouhý text",
            u"a 3 a Test 2           a 4 a Krásný dlouhý",
            u"                             text podruhé"
        ]
        res_lines = c.get_lines()
        self.evaluate_result(res_lines, expected_result)
Пример #18
0
class EULASpoke(FirstbootOnlySpokeMixIn, NormalTUISpoke):
    """The EULA spoke providing ways to read the license and agree/disagree with it."""

    category = LicensingCategory

    def __init__(self, *args, **kwargs):
        NormalTUISpoke.__init__(self, *args, **kwargs)
        self.title = _("License information")
        self._container = None

    def initialize(self):
        NormalTUISpoke.initialize(self)

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        self._container = ListRowContainer(1)

        log.debug("license found")
        # make the options aligned to the same column (the checkbox has the
        # '[ ]' prepended)
        self._container.add(
            TextWidget("%s\n" % _("Read the License Agreement")),
            self._show_license_screen_callback)

        self._container.add(
            CheckboxWidget(title=_("I accept the license agreement."),
                           completed=self.data.eula.agreed),
            self._license_accepted_callback)
        self.window.add_with_separator(self._container)

    @property
    def completed(self):
        # Either there is no EULA available, or user agrees/disagrees with it.
        return self.data.eula.agreed

    @property
    def mandatory(self):
        # This spoke is always mandatory.
        return True

    @property
    def status(self):
        return _("License accepted") if self.data.eula.agreed else _(
            "License not accepted")

    @classmethod
    def should_run(cls, environment, data):
        # the EULA spoke should only run in Initial Setup
        if eula_available() and FirstbootOnlySpokeMixIn.should_run(
                environment, data):
            # don't run if we are in reconfig mode and the EULA has already been accepted
            if data and data.firstboot.firstboot == FIRSTBOOT_RECONFIG and data.eula.agreed:
                log.debug(
                    "not running license spoke: reconfig mode & license already accepted"
                )
                return False
            return True
        return False

    def apply(self):
        # nothing needed here, the agreed field is changed in the input method
        pass

    def input(self, args, key):
        if not self._container.process_user_input(key):
            return key

        return InputState.PROCESSED

    @staticmethod
    def _show_license_screen_callback(data):
        # show license
        log.debug("showing the license")
        eula_screen = LicenseScreen()
        ScreenHandler.push_screen(eula_screen)

    def _license_accepted_callback(self, data):
        # toggle EULA agreed checkbox by changing ksdata
        log.debug("license accepted state changed to: %s",
                  self.data.eula.agreed)
        self.data.eula.agreed = not self.data.eula.agreed
        self.redraw()
Пример #19
0
class TUIHub(TUIObject, common.Hub):
    """Base Hub class implementing the pyanaconda.ui.common.Hub interface.
       It uses text based categories to look for relevant Spokes and manages
       all the spokes it finds to have the proper category.

       :param categories: list all the spoke categories to be displayed in this Hub
       :type categories: list of strings



       .. inheritance-diagram:: TUIHub
          :parts: 3
    """

    categories = []

    def __init__(self, data, storage, payload, instclass):
        TUIObject.__init__(self, data)
        common.Hub.__init__(self, storage, payload, instclass)
        self.title = N_("Default HUB title")
        self._container = None

        self._spokes_map = []  # hold all the spokes in the right ordering
        self._spokes = {}      # holds spokes referenced by their class name
        self._spoke_count = 0

        # we want user input
        self.input_required = True

    def setup(self, args="anaconda"):
        TUIObject.setup(self, args)
        environment = args
        cats_and_spokes = self._collectCategoriesAndSpokes()
        categories = cats_and_spokes.keys()

        for c in sorted(categories, key=lambda i: i.title):

            hub_spokes = []
            for spoke_class in cats_and_spokes[c]:
                # Do the checks for the spoke and create the spoke
                if spoke_class.should_run(environment, self.data):
                    spoke = spoke_class(self.data, self.storage, self.payload, self.instclass)

                    if spoke.showable:
                        spoke.initialize()
                    else:
                        log.warning("Spoke %s initialization failure!", spoke.__class__.__name__)
                        del spoke
                        continue

                    if spoke.indirect:
                        continue

                    hub_spokes.append(spoke)

            # sort created spokes and add them to result structures
            for spoke in sorted(hub_spokes, key=lambda s: s.title):

                self._spoke_count += 1
                self._spokes_map.append(spoke)
                self._spokes[spoke.__class__.__name__] = spoke

        if self._spoke_count:
            # initialization of all expected spokes has been started, so notify the controller
            hub_controller = lifecycle.get_controller_by_name(self.__class__.__name__)
            if hub_controller:
                hub_controller.all_modules_added()
            else:
                log.error("Initialization controller for hub %s expected but missing.", self.__class__.__name__)

        # only schedule the hub if it has some spokes
        return self._spoke_count != 0

    def refresh(self, args=None):
        """This methods fills the self.window list by all the objects
        we want shown on this screen. Title and Spokes mostly."""
        TUIObject.refresh(self, args)

        self._container = ListRowContainer(2, columns_width=39, spacing=2)

        for w in self._spokes_map:
            self._container.add(w, callback=self._item_called, data=w)

        self.window.add_with_separator(self._container)

    def _item_called(self, data):
        item = data
        ScreenHandler.push_screen(item)

    def input(self, args, key):
        """Handle user input. Numbers are used to show a spoke, the rest is passed
        to the higher level for processing."""

        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            # If we get a continue, check for unfinished spokes.  If unfinished
            # don't continue
            # TRANSLATORS: 'c' to continue
            if key == Prompt.CONTINUE:
                for spoke in self._spokes.values():
                    if not spoke.completed and spoke.mandatory:
                        print(_("Please complete all spokes before continuing"))
                        return InputState.DISCARDED
            # TRANSLATORS: 'h' to help
            elif key == Prompt.HELP:
                if self.has_help:
                    help_path = ihelp.get_help_path(self.helpFile, self.instclass, True)
                    ScreenHandler.push_screen_modal(HelpScreen(help_path))
                    self.redraw()
                    return InputState.PROCESSED
            return key

    def prompt(self, args=None):
        """Show an alternative prompt if the hub contains only one spoke.
        Otherwise it is not readily apparent that the user needs to press
        1 to enter the single visible spoke.

        :param args: optional argument passed from switch_screen calls
        :type args: anything

        :return: returns text to be shown next to the prompt for input or None
                 to skip further input processing
        :rtype: str|None
        """
        prompt = super(TUIHub, self).prompt(args)

        if self._spoke_count == 1:
            prompt.add_option("1", _("to enter the %(spoke_title)s spoke") % {"spoke_title": list(self._spokes.values())[0].title})

        if self.has_help:
            prompt.add_help_option()

        return prompt
Пример #20
0
class Hub(UIScreen):
    def __init__(self):
        super().__init__("Hub")

        # Container will be used for spokes positioning. Container is always created in
        # the refresh() method.
        self._container = None

        self._create_spokes()

    def _create_spokes(self):
        """Create spokes and use their value."""

        # Create name spoke
        self._name_spoke = SetNameScreen("First name", "John")
        # Create surname spoke
        self._surname_spoke = SetNameScreen("Surname", "Doe")
        # Create the PasswordDialog advanced widget for getting password from a user.
        self._password_spoke = PasswordDialog()

    def refresh(self, args=None):
        """Refresh method is called always before the screen will be printed.

        All items for printing should be updated or created here.
        """
        # Init window container. The windows container will be erased here.
        # The window container is the base container. Everything for rendering should be put
        # into this container, including other containers.
        super().refresh(args)

        # Add the screen header message before our items.
        header = TextWidget("Please complete all the spokes to continue")
        header = CenterWidget(header)
        self.window.add_with_separator(header, blank_lines=2)

        # Create the empty container.
        # It will add numbering, process user input and positioning for us.
        self._container = ListRowContainer(2)

        # Create widget to get user name.
        widget = self._create_name_widget()
        # Add widget, callback, arguments to the container.
        #
        # widget - Widget we want to render. It will be numbered automatically.
        #          Could be container if needed.
        # callback - This callback will be called by the ListRowContainer.process_user_input() method
        #            when a user press the number of this item. Callback will get args passed as 3rd argument.
        # args - Argument for callback.
        self._container.add(widget, self._push_screen_callback,
                            self._name_spoke)

        # Create surname widget and add it to the container.
        widget = self._create_surname_widget()
        self._container.add(widget, self._push_screen_callback,
                            self._surname_spoke)

        # Create password widget and add it to the container.
        widget = self._create_password_widget()
        self._container.add(widget, self._push_screen_callback,
                            self._password_spoke)

        # Add the ListRowContainer container to the WindowContainer container.
        self.window.add_with_separator(self._container)

    def _create_name_widget(self):
        """Create name spoke widget.

        Add the actual value below the spoke name.
        """
        msg = "First name"
        if self._name_spoke.value:
            msg += "\n{}".format(self._name_spoke.value)

        return TextWidget(msg)

    def _create_surname_widget(self):
        """Create surname spoke widget.

        Add the actual value below the spoke name.
        """
        msg = "Surname"
        if self._surname_spoke.value:
            msg += "\n{}".format(self._surname_spoke.value)

        return TextWidget(msg)

    def _create_password_widget(self):
        """Create password spoke widget.

        Add the "Password set" text below the spoke name if set.
        """
        msg = "Password"
        if self._password_spoke.answer:
            msg += "\nPassword set."

        return TextWidget(msg)

    def input(self, args, key):
        """Run spokes based on the user choice."""
        # Find out if a user pressed number for an existing widget and call the callback attached
        # to it with arguments passed in the refresh() method.
        # Return False if the input is not related to the widget.
        if self._container.process_user_input(key):
            # Do not process other input if spoke is entered.
            return InputState.PROCESSED
        # Block continue ('c') if everything is not set.
        elif key == Prompt.CONTINUE:
            if self._name_spoke and self._surname_spoke and self._password_spoke.answer:
                return key
            else:  # catch 'c' key if not everything set
                return InputState.DISCARDED
        else:
            return key

    def _push_screen_callback(self, target_screen):
        """Push target screen as new screen.

        Target screen is passed in as an argument in the refresh() method.
        """
        ScreenHandler.push_screen(target_screen)

    def prompt(self, args=None):
        """Add information to prompt for user."""
        prompt = super().prompt(args)
        # Give user hint that he can press 1, 2 or 3 to enter spokes.
        prompt.add_option("1,2,3", "to enter spokes")
        return prompt
Пример #21
0
    def test_add_new_container(self):
        c = ListRowContainer(columns=2,
                             items=[TextWidget("Ahoj")],
                             columns_width=15,
                             spacing=0,
                             numbering=False)

        expected_result = [u"Ahoj"]

        c.render(80)
        self.evaluate_result(c.get_lines(), expected_result)

        c.add(TextWidget("Nový widget"))
        c.add(TextWidget("Hello"))

        expected_result = [u"Ahoj           Nový widget", u"Hello"]

        c.render(80)
        self.evaluate_result(c.get_lines(), expected_result)
Пример #22
0
    def test_list_container_without_width(self):
        column_count = 3
        spacing_width = 3
        c = ListRowContainer(column_count,
                             spacing=spacing_width,
                             numbering=False)

        c.add(TextWidget("AAAA"))
        c.add(TextWidget("BBBB"))
        c.add(TextWidget("CCCCC"))  # this line is too long
        c.add(TextWidget("DDDD"))

        expected_col_width = 4
        expected_spacing_sum = 2 * spacing_width  # three columns so 2 spacing between them
        render_width = (column_count *
                        expected_col_width) + expected_spacing_sum
        c.render(render_width)

        expected_result = [u"AAAA   BBBB   CCCC", u"              C", u"DDDD"]

        res_lines = c.get_lines()
        self.evaluate_result(res_lines, expected_result)
Пример #23
0
    def test_list_input_processing_none(self, out_mock, in_mock):
        c = ListRowContainer(1)

        self._prepare_callbacks(c, 2)

        self.assertFalse(c.process_user_input(None))
Пример #24
0
    def test_list_input_processing_negative_number(self, out_mock, in_mock):
        c = ListRowContainer(1)

        self._prepare_callbacks(c, 3)

        self.assertFalse(c.process_user_input("-2"))
Пример #25
0
class TUIHub(TUIObject, common.Hub):
    """Base Hub class implementing the pyanaconda.ui.common.Hub interface.
       It uses text based categories to look for relevant Spokes and manages
       all the spokes it finds to have the proper category.

       :param categories: list all the spoke categories to be displayed in this Hub
       :type categories: list of strings



       .. inheritance-diagram:: TUIHub
          :parts: 3
    """

    categories = []

    def __init__(self, data, storage, payload):
        TUIObject.__init__(self, data)
        common.Hub.__init__(self, storage, payload)
        self.title = N_("Default HUB title")
        self._container = None

        self._spokes_map = []  # hold all the spokes in the right ordering
        self._spokes = {}  # holds spokes referenced by their class name
        self._spoke_count = 0

        # we want user input
        self.input_required = True

    def setup(self, args="anaconda"):
        TUIObject.setup(self, args)
        environment = args
        cats_and_spokes = self._collectCategoriesAndSpokes()
        categories = cats_and_spokes.keys()

        # display categories by sort order or class name if their
        # sort order is the same
        for c in common.sort_categories(categories):

            hub_spokes = []
            for spoke_class in cats_and_spokes[c]:
                # Do the checks for the spoke and create the spoke
                if spoke_class.should_run(environment, self.data):
                    spoke = spoke_class(self.data, self.storage, self.payload)

                    if spoke.showable:
                        spoke.initialize()
                    else:
                        log.warning("Spoke %s initialization failure!",
                                    spoke.__class__.__name__)
                        del spoke
                        continue

                    if spoke.indirect:
                        continue

                    hub_spokes.append(spoke)

            # sort created spokes and add them to result structures
            for spoke in sorted(hub_spokes, key=lambda s: s.title):

                self._spoke_count += 1
                self._spokes_map.append(spoke)
                self._spokes[spoke.__class__.__name__] = spoke

        if self._spoke_count:
            # initialization of all expected spokes has been started, so notify the controller
            hub_controller = lifecycle.get_controller_by_name(
                self.__class__.__name__)
            if hub_controller:
                hub_controller.all_modules_added()
            else:
                log.error(
                    "Initialization controller for hub %s expected but missing.",
                    self.__class__.__name__)

        # only schedule the hub if it has some spokes
        return self._spoke_count != 0

    def refresh(self, args=None):
        """This methods fills the self.window list by all the objects
        we want shown on this screen. Title and Spokes mostly."""
        TUIObject.refresh(self, args)

        self._container = ListRowContainer(2, columns_width=39, spacing=2)

        for w in self._spokes_map:
            self._container.add(w, callback=self._item_called, data=w)

        self.window.add_with_separator(self._container)

    def _item_called(self, data):
        item = data
        ScreenHandler.push_screen(item)

    def input(self, args, key):
        """Handle user input. Numbers are used to show a spoke, the rest is passed
        to the higher level for processing."""

        if self._container.process_user_input(key):
            return InputState.PROCESSED
        else:
            # If we get a continue, check for unfinished spokes.  If unfinished
            # don't continue
            # TRANSLATORS: 'c' to continue
            if key == Prompt.CONTINUE:
                for spoke in self._spokes.values():
                    if not spoke.completed and spoke.mandatory:
                        print(
                            _("Please complete all spokes before continuing"))
                        return InputState.DISCARDED
            # TRANSLATORS: 'h' to help
            elif key == Prompt.HELP:
                if self.has_help:
                    help_path = get_help_path(self.helpFile, True)
                    ScreenHandler.push_screen_modal(HelpScreen(help_path))
                    return InputState.PROCESSED_AND_REDRAW
            return key

    def prompt(self, args=None):
        """Show an alternative prompt if the hub contains only one spoke.
        Otherwise it is not readily apparent that the user needs to press
        1 to enter the single visible spoke.

        :param args: optional argument passed from switch_screen calls
        :type args: anything

        :return: returns text to be shown next to the prompt for input or None
                 to skip further input processing
        :rtype: str|None
        """
        prompt = super().prompt(args)

        if self._spoke_count == 1:
            prompt.add_option(
                "1",
                _("to enter the %(spoke_title)s spoke") %
                {"spoke_title": list(self._spokes.values())[0].title})

        if self.has_help:
            prompt.add_help_option()

        return prompt
Пример #26
0
    def test_list_wrong_input_processing(self, out_mock, in_mock):
        c = ListRowContainer(1)

        self._prepare_callbacks(c, 3)

        self.assertFalse(c.process_user_input("c"))