These patches introduce the EULA spoke for the TUI Initial Setup. Two related patches for the anaconda add the ability to print long widgets in a nice way and remove buggy check that caused exceptions.
The EULA spokes shows two options -- 1) read the license and 2) toggle the "agreed checkbox". The option 1) takes user to the screen displaying the license in parts so that they fit in the screen. Otherwise everything is simple.
Vratislav Podzimek (3): Fix how spokes are collected for the I-S main hub Add TUI Eula spoke (#1000409) Reboot the system if EULA is not agreed (#1000409)
initial_setup/__main__.py | 6 +- initial_setup/tui/hubs/initial_setup_hub.py | 48 ++---------- initial_setup/tui/spokes/eula.py | 112 ++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 initial_setup/tui/spokes/eula.py
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- initial_setup/tui/hubs/initial_setup_hub.py | 46 +++-------------------------- 1 file changed, 4 insertions(+), 42 deletions(-)
diff --git a/initial_setup/tui/hubs/initial_setup_hub.py b/initial_setup/tui/hubs/initial_setup_hub.py index 804acaf..3b6387e 100644 --- a/initial_setup/tui/hubs/initial_setup_hub.py +++ b/initial_setup/tui/hubs/initial_setup_hub.py @@ -1,8 +1,6 @@ +from pyanaconda.constants import FIRSTBOOT_ENVIRON from pyanaconda.ui.tui.hubs import TUIHub -from pyanaconda.ui.tui.spokes import TUISpoke -from pyanaconda.ui.common import collect from initial_setup import product -import os import gettext
__all__ = ["InitialSetupMainHub"] @@ -11,25 +9,6 @@ __all__ = ["InitialSetupMainHub"] _ = lambda x: gettext.ldgettext("initial-setup", x) N_ = lambda x: x
-def collect_spokes(mask_paths): - """Return a list of all spoke subclasses that should appear for a given - category. Look for them in files imported as module_path % basename(f) - - :param mask_paths: list of mask, path tuples to search for classes - :type mask_paths: list of (mask, path) - - :return: list of Spoke classes belonging to category - :rtype: list of Spoke classes - - """ - spokes = [] - for mask, path in mask_paths: - spokes.extend(collect(mask, path, - lambda obj: issubclass(obj, Spoke) and obj.should_run("firstboot", None))) - - return spokes - - class InitialSetupMainHub(TUIHub): categories = ["password", "localization"]
@@ -39,23 +18,6 @@ class InitialSetupMainHub(TUIHub): else: title = _("Initial setup")
- def _collectCategoriesAndSpokes(self): - """collects categories and spokes to be displayed on this Hub - - :return: dictionary mapping category class to list of spoke classes - :rtype: dictionary[category class] -> [ list of spoke classes ] - """ - - ret = {} - - # Collect all the categories this hub displays, then collect all the - # spokes belonging to all those categories. - candidate_spokes = collect_spokes(self.paths["spokes"]) - spokes = [spoke for spoke in candidate_spokes \ - if spoke.should_run("firstboot", self.data)] - - for spoke in spokes: - ret.setdefault(spoke.category, []) - ret[spoke.category].append(spoke) - - return ret + def __init__(self, *args): + TUIHub.__init__(self, *args) + self._environs = [FIRSTBOOT_ENVIRON]
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- initial_setup/tui/hubs/initial_setup_hub.py | 2 +- initial_setup/tui/spokes/eula.py | 112 ++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 initial_setup/tui/spokes/eula.py
diff --git a/initial_setup/tui/hubs/initial_setup_hub.py b/initial_setup/tui/hubs/initial_setup_hub.py index 3b6387e..021a48a 100644 --- a/initial_setup/tui/hubs/initial_setup_hub.py +++ b/initial_setup/tui/hubs/initial_setup_hub.py @@ -10,7 +10,7 @@ _ = lambda x: gettext.ldgettext("initial-setup", x) N_ = lambda x: x
class InitialSetupMainHub(TUIHub): - categories = ["password", "localization"] + categories = ["password", "localization", "system"]
prod_title = product.product_title() if prod_title: diff --git a/initial_setup/tui/spokes/eula.py b/initial_setup/tui/spokes/eula.py new file mode 100644 index 0000000..315d34c --- /dev/null +++ b/initial_setup/tui/spokes/eula.py @@ -0,0 +1,112 @@ +"""EULA TUI spoke for the Initial Setup""" + +import gettext + +from pyanaconda.ui.tui.spokes import NormalTUISpoke +from pyanaconda.ui.tui.simpleline.widgets import TextWidget, CheckboxWidget +from pyanaconda.ui.tui.simpleline.base import UIScreen +from pyanaconda.ui.common import FirstbootOnlySpokeMixIn +from pyanaconda.constants_text import INPUT_PROCESSED +from initial_setup.product import get_license_file_name + +_ = lambda x: gettext.ldgettext("initial-setup", x) +N_ = lambda x: x + +__all__ = ["EULAspoke"] + +class EULAspoke(FirstbootOnlySpokeMixIn, NormalTUISpoke): + """The EULA spoke providing ways to read the license and agree/disagree with it.""" + + title = _("License information") + category = "system" + + def __init__(self, *args, **kwargs): + NormalTUISpoke.__init__(self, *args, **kwargs) + + self._have_eula = True + + def initialize(self): + NormalTUISpoke.initialize(self) + + self._have_eula = bool(get_license_file_name()) + + def refresh(self, args=None): + NormalTUISpoke.refresh(self, args) + + if self._have_eula: + # make the options aligned to the same column (the checkbox has the + # '[ ]' prepended) + self._window += [TextWidget(" 1) %s" % _("Read the License Agreement")), ""] + self._window += [CheckboxWidget(title="2) %s" % _("I accept the license agreement."), + completed=self.data.eula.agreed), ""] + else: + self._window += [TextWidget(_("No license found. Please report this " + "at http://bugzilla.redhat.com")), ""] + + @property + def completed(self): + # Either there is no EULA available, or user agrees/disagrees with it. + return not self._have_eula or self.data.eula.agreed + + @property + def mandatory(self): + # This spoke is always mandatory. + return True + + @property + def status(self): + if not self._have_eula: + return _("No license found") + + return _("License agreed") if self.data.eula.agreed else _("License not agreed") + + def apply(self): + # nothing needed here, the agreed field is changed in the input method + pass + + def input(self, args, key): + try: + keyid = int(key) + except ValueError: + # only number choices are processed here + return key + + if keyid == 1: + # show license + eula_screen = LicenseScreen(self._app) + self.app.switch_screen_with_return(eula_screen) + return INPUT_PROCESSED + elif keyid == 2: + # toggle EULA agreed checkbox by changing ksdata + self.data.eula.agreed = not self.data.eula.agreed + return INPUT_PROCESSED + + # some numerical value that cannot be processed here + return key + +class LicenseScreen(UIScreen): + """Screen showing the License without any input from user requested.""" + + # no title needed, we just want to show the license + title = "" + + def __init__(self, app, screen_height=25): + UIScreen.__init__(self, app, screen_height) + + self._license_file = get_license_file_name() + + def refresh(self, args=None): + UIScreen.refresh(self, args) + + # read the license file and make it one long string so that it can be + # processed by the TextWidget to fit in the screen in a best possible + # way + with open(self._license_file, "r") as fobj: + buf = u"".join(fobj.xreadlines()) + + self._window += [TextWidget(buf), ""] + + def prompt(self, args=None): + # we don't want to prompt user, just close the screen + self.close() + return None
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- initial_setup/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/initial_setup/__main__.py b/initial_setup/__main__.py index ffe5db6..dc6d23f 100644 --- a/initial_setup/__main__.py +++ b/initial_setup/__main__.py @@ -102,7 +102,11 @@ ret = ui.run() # TUI returns False if the app was ended prematurely # all other cases return True or None if ret == False: - sys.exit(0) + if data.eula.agreed: + sys.exit(0) + else: + # EULA not agreed, reboot the system and leave Initial Setup enabled + os.system("reboot")
# Do not execute sections that were part of the original # anaconda kickstart file (== have .seen flag set)
If some widget doesn't fit in the screen, we should print it in multiple parts and prompt user for ENTER to continue.
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/ui/tui/simpleline/base.py | 58 +++++++++++++++++++++++++++++---- pyanaconda/ui/tui/simpleline/widgets.py | 4 +-- 2 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py index 8c2424b..88efd17 100644 --- a/pyanaconda/ui/tui/simpleline/base.py +++ b/pyanaconda/ui/tui/simpleline/base.py @@ -492,13 +492,17 @@ class UIScreen(object): # title line of the screen title = u"Screen.."
- def __init__(self, app): + def __init__(self, app, screen_height = 25): """ :param app: reference to application main class :type app: instance of class App + + :param screen_height: height of the screen (useful for printing long widgets) + :type screen_height: int """
self._app = app + self._screen_height = screen_height
# list that holds the content to be printed out self._window = [] @@ -539,6 +543,40 @@ class UIScreen(object): """Return reference to the window instance. In TUI, just return self.""" return self
+ def _print_long_widget(self, widget): + """Prints a long widget (possibly longer than the screen height) with + user interaction (when needed). + + :param widget: possibly long widget to print + :type widget: Widget instance + + """ + + pos = 0 + lines = widget.get_lines() + num_lines = len(lines) + + if num_lines < self._screen_height - 2: + # widget plus prompt are shorter than screen height, just print the widget + print u"\n".join(lines) + return + + # long widget, print it in steps and prompt user to continue + last_line = num_lines - 1 + while pos <= last_line: + if pos + self._screen_height - 2 > last_line: + # enough space to print the rest of the widget plus regular + # prompt (2 lines) + for line in lines[pos:]: + print line + pos += self._screen_height - 1 + else: + # print part with a prompt to continue + for line in lines[pos:(pos + self._screen_height - 2)]: + print line + self._app.raw_input(_("Press ENTER to continue")) + pos += self._screen_height - 1 + def show_all(self): """Prepares all elements of self._window for output and then prints them on the screen.""" @@ -547,8 +585,11 @@ class UIScreen(object): if hasattr(w, "render"): # pylint: disable-msg=E1101 w.render(self.app.width) - print unicode(w) - + if isinstance(w, Widget): + self._print_long_widget(w) + else: + # not a widget, just print its unicode representation + print unicode(w) show = show_all
def hide(self): @@ -642,9 +683,14 @@ class Widget(object): """ self.clear()
- def __unicode__(self): - """Method to render the screen when printing as unicode string.""" - return u"\n".join(u"".join(l) for l in self._buffer) + def get_lines(self): + """Get lines to write out in order to show this widget. + + :return: lines representing this widget + :rtype: list(unicode) + """ + + return [unicode(u"".join(line)) for line in self._buffer]
def setxy(self, row, col): """Sets cursor position. diff --git a/pyanaconda/ui/tui/simpleline/widgets.py b/pyanaconda/ui/tui/simpleline/widgets.py index 6990c56..438a35f 100644 --- a/pyanaconda/ui/tui/simpleline/widgets.py +++ b/pyanaconda/ui/tui/simpleline/widgets.py @@ -204,10 +204,10 @@ if __name__ == "__main__":
c = ColumnWidget([(15, [t1, t2, t3]), (10, [t4, t5])], spacing = 1) c.render(80) - print unicode(c) + print u"\n".join(c.get_lines())
print 80*"-"
c = ColumnWidget([(20, [t1, t2, t3]), (25, [t4, t5]), (15, [t1, t2, t3])], spacing = 3) c.render(80) - print unicode(c) + print u"\n".join(c.get_lines())
Related: rhbz#1000409
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/ui/tui/simpleline/base.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/pyanaconda/ui/tui/simpleline/base.py b/pyanaconda/ui/tui/simpleline/base.py index 88efd17..941951e 100644 --- a/pyanaconda/ui/tui/simpleline/base.py +++ b/pyanaconda/ui/tui/simpleline/base.py @@ -193,9 +193,7 @@ class App(object): :type args: anything """
- # poll a screen's "ready" status before scheduling it to show - if ui.ready: - self._screens.append((ui, args, self.NOP)) + self._screens.append((ui, args, self.NOP))
self.redraw()
On Tue, 2013-10-08 at 10:25 +0200, Vratislav Podzimek wrote:
These patches introduce the EULA spoke for the TUI Initial Setup. Two related patches for the anaconda add the ability to print long widgets in a nice way and remove buggy check that caused exceptions.
The EULA spokes shows two options -- 1) read the license and 2) toggle the "agreed checkbox". The option 1) takes user to the screen displaying the license in parts so that they fit in the screen. Otherwise everything is simple.
Vratislav Podzimek (3): Fix how spokes are collected for the I-S main hub Add TUI Eula spoke (#1000409) Reboot the system if EULA is not agreed (#1000409)
initial_setup/__main__.py | 6 +- initial_setup/tui/hubs/initial_setup_hub.py | 48 ++---------- initial_setup/tui/spokes/eula.py | 112 ++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 44 deletions(-) create mode 100644 initial_setup/tui/spokes/eula.py
These all look good to me.
anaconda-patches@lists.fedorahosted.org