diff --git a/.gitignore b/.gitignore index 1dd9fde6..fd412777 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ target/ # subiquity installer images installer/*.img +logs/* diff --git a/bin/subiquity b/bin/subiquity index 8e4ad3d3..fd38eda3 100755 --- a/bin/subiquity +++ b/bin/subiquity @@ -16,10 +16,11 @@ import argparse import sys -import urwid -import urwid.curses_display -from subiquity.palette import STYLES, STYLES_MONO -from subiquity.controllers.welcome import WelcomeController +import logging +from subiquity.log import setup_logger +from subiquity import __version__ as VERSION +from subiquity.controllers import BaseController as Subiquity +from subiquity.ui.frame import SubiquityUI def parse_options(argv): @@ -34,20 +35,15 @@ def parse_options(argv): def main(): opts = parse_options(sys.argv[1:]) - if opts.run_on_serial: - screen = urwid.curses_display.Screen() - screen.register_palette(STYLES_MONO) - else: - screen = urwid.raw_display.Screen() - screen.set_mouse_tracking(False) - screen.set_terminal_properties(256) - screen.register_palette(STYLES) + setup_logger() + logger = logging.getLogger('subiquity') + logger.info("Starting SUbiquity v{}".format(VERSION)) + logger.info("Arguments passed: {}".format(sys.argv)) - def unhandled_input(key): - if key in ('Q', 'q', 'esc'): - raise urwid.ExitMainLoop() - urwid.MainLoop(WelcomeController().show(), screen=screen, - unhandled_input=unhandled_input).run() + ui = SubiquityUI() + + subiquity_interface = Subiquity(ui, opts) + subiquity_interface.run() if __name__ == '__main__': main() diff --git a/subiquity/controllers/__init__.py b/subiquity/controllers/__init__.py index 87f3306d..9ee58b5f 100644 --- a/subiquity/controllers/__init__.py +++ b/subiquity/controllers/__init__.py @@ -13,31 +13,93 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from abc import ABCMeta, abstractmethod +import logging +import urwid +import urwid.curses_display +from subiquity.routes import Routes +from subiquity.palette import STYLES, STYLES_MONO -class BaseController(metaclass=ABCMeta): - controller_name = None +log = logging.getLogger('subiquity.controller') - @classmethod - def name(cls): - if cls.controller_name: - return cls.controller_name - return cls.__name__.lower() - @abstractmethod - def show(self): - """ Implements show action for the controller +class BaseControllerError(Exception): + """ Basecontroller exception """ + pass - Renders the View for controller. - """ - pass - @abstractmethod - def finish(self): - """ Implements finish action for controller. +class BaseController: + def __init__(self, ui, opts): + self.ui = ui + self.opts = opts - This handles any callback data/procedures required - to move to the next controller or end the install. - """ - pass + def next_controller(self, *args, **kwds): + controller = Routes.next() + controller(self).show(*args, **kwds) + + def prev_controller(self, *args, **kwds): + controller = Routes.prev() + controller(self).show(*args, **kwds) + + def redraw_screen(self): + if hasattr(self, 'loop'): + try: + self.loop.draw_screen() + except AssertionError as e: + log.critical(e) + + def set_alarm_in(self, interval, cb): + self.loop.set_alarm_in(interval, cb) + return + + def update(self, *args, **kwds): + route = Routes.current_idx() + if route == 0: + self.begin() + self.set_alarm_in(1, self.update) + + def exit(self): + raise urwid.ExitMainLoop() + + def header_hotkeys(self, key): + if key in ['q', 'Q']: + self.exit() + + def set_body(self, w): + self.ui.set_body(w) + self.redraw_screen() + + def set_header(self, title, excerpt): + self.ui.set_header(title, excerpt) + self.redraw_screen() + + def set_footer(self, message): + self.ui.set_footer(message) + self.redraw_screen() + + def run(self): + if not hasattr(self, 'loop'): + if self.opts.run_on_serial: + screen = urwid.raw_display.Screen() + screen.register_palette(STYLES_MONO) + else: + screen = urwid.raw_display.Screen() + screen.set_mouse_tracking(False) + screen.set_terminal_properties(256) + screen.register_palette(STYLES) + + self.loop = urwid.MainLoop(self.ui, screen=screen, + unhandled_input=self.header_hotkeys) + + try: + self.begin() + self.loop.run() + except: + log.exception("Exception in controller.run():") + raise + + def begin(self): + """ Initializes the first controller for installation """ + Routes.reset() + initial_controller = Routes.first() + initial_controller(self).show() diff --git a/subiquity/controllers/installpath.py b/subiquity/controllers/installpath.py index c8b3f086..985ccb8d 100644 --- a/subiquity/controllers/installpath.py +++ b/subiquity/controllers/installpath.py @@ -13,18 +13,36 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from subiquity.controllers import BaseController +from subiquity.controllers.policy import ControllerPolicy from subiquity.views.installpath import InstallpathView -from subiquity.models.welcome import InstallpathModel +from subiquity.models.installpath import InstallpathModel +import logging +import subprocess + +log = logging.getLogger('subiquity.installpath') -class InstallpathController(BaseController): +class InstallpathController(ControllerPolicy): """InstallpathController""" - controller_name = "Ubuntu Install selection" - def show(self): + title = "15.10" + excerpt = ("Welcome to Ubuntu! The world’s favourite platform " + "for clouds, clusters and amazing internet things. " + "This is the installer for Ubuntu on servers and " + "internet devices.") + footer = ("Use UP, DOWN arrow keys, and ENTER, to " + "navigate options") + + def show(self, *args, **kwds): + log.debug("Loading install path controller") + self.ui.set_header(self.title, self.excerpt) + self.ui.set_footer(self.footer) model = InstallpathModel() - return InstallpathView(model, self.finish) + self.ui.set_body(InstallpathView(model, self.finish)) + return - def finish(self, install_selection): - raise SystemExit("Install selection: {}".format(install_selection)) + def finish(self, install_selection=None): + # subprocess.check_call("/usr/local/bin/curtin_wrap.sh") + return self.ui.next_controller() + +__controller_class__ = InstallpathController diff --git a/subiquity/controllers/network.py b/subiquity/controllers/network.py new file mode 100644 index 00000000..dc289de4 --- /dev/null +++ b/subiquity/controllers/network.py @@ -0,0 +1,45 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from subiquity.controllers.policy import ControllerPolicy +from subiquity.views.network import NetworkView +from subiquity.models.network import NetworkModel +import logging + +log = logging.getLogger('subiquity.network') + + +class NetworkController(ControllerPolicy): + """InstallpathController""" + + title = "Network connections" + excerpt = ("Configure at least the main interface this server will " + "use to talk to other machines, and preferably provide " + "sufficient access for updates.") + + footer = ("Additional networking info here") + + def show(self, *args, **kwds): + self.ui.set_header(self.title, self.excerpt) + self.ui.set_footer(self.footer) + model = NetworkModel() + self.ui.set_body(NetworkView(model, self.finish)) + return + + def finish(self, interface=None): + log.info("Network Interface choosen: {}".format(interface)) + return self.ui.exit() + +__controller_class__ = NetworkController diff --git a/subiquity/controllers/policy.py b/subiquity/controllers/policy.py new file mode 100644 index 00000000..a77da196 --- /dev/null +++ b/subiquity/controllers/policy.py @@ -0,0 +1,41 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Controller policy """ + +from abc import ABCMeta, abstractmethod + + +class ControllerPolicy(metaclass=ABCMeta): + + def __init__(self, ui): + self.ui = ui + + @abstractmethod + def show(self, *args, **kwds): + """ Implements show action for the controller + + Renders the View for controller. + """ + pass + + @abstractmethod + def finish(self): + """ Implements finish action for controller. + + This handles any callback data/procedures required + to move to the next controller or end the install. + """ + pass diff --git a/subiquity/controllers/welcome.py b/subiquity/controllers/welcome.py index 0fb4bb88..8e1d2119 100644 --- a/subiquity/controllers/welcome.py +++ b/subiquity/controllers/welcome.py @@ -13,20 +13,34 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from subiquity.controllers import BaseController +from subiquity.controllers.policy import ControllerPolicy from subiquity.views.welcome import WelcomeView from subiquity.models.welcome import WelcomeModel -import subprocess +import logging -class WelcomeController(BaseController): +log = logging.getLogger('subiquity.controllers.welcome') + + +class WelcomeController(ControllerPolicy): """WelcomeController""" - controller_name = "Welcome to Ubuntu" + title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!" + excerpt = "Please choose your preferred language" + footer = ("Use UP, DOWN arrow keys, and ENTER, to " + "select your language.") - def show(self): - model = WelcomeModel() - return WelcomeView(model, self.finish) + def show(self, *args, **kwds): + self.ui.set_header(self.title, self.excerpt) + self.ui.set_footer(self.footer) + self.ui.set_body(WelcomeView(WelcomeModel, self.finish)) + return - def finish(self, code, val): - subprocess.check_call("/usr/local/bin/curtin_wrap.sh") - raise SystemExit("Saw res: {}, val: {}".format(code, val)) + def finish(self, language=None): + if language is None: + raise SystemExit("No language selected, exiting as there are no " + "more previous controllers to render.") + WelcomeModel.selected_language = language + log.debug("Welcome Model: {}".format(WelcomeModel())) + return self.ui.next_controller() + +__controller_class__ = WelcomeController diff --git a/subiquity/log.py b/subiquity/log.py new file mode 100644 index 00000000..e70d648e --- /dev/null +++ b/subiquity/log.py @@ -0,0 +1,41 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +import os +from logging.handlers import TimedRotatingFileHandler + + +def setup_logger(name=__name__): + LOGDIR = "logs" + LOGFILE = os.path.join(LOGDIR, "debug.log") + if not os.path.isdir(LOGDIR): + os.makedirs(LOGDIR) + log = TimedRotatingFileHandler(LOGFILE, + when='D', + interval=1, + backupCount=7) + log.setLevel('DEBUG') + log.setFormatter(logging.Formatter( + "%(asctime)s " + "%(name)s:%(lineno)d %(message)s", + datefmt='%m/%d %H:%M')) + log_filter = logging.Filter(name='subiquity') + log.addFilter(log_filter) + + logger = logging.getLogger('') + logger.setLevel('DEBUG') + logger.addHandler(log) + return logger diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py new file mode 100644 index 00000000..6b892840 --- /dev/null +++ b/subiquity/models/filesystem.py @@ -0,0 +1,40 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Filesystem Model + +Provides storage device selection and additional storage +configuration. + +""" + +from subiquity import models + + +class FilesystemModel(models.Model): + """ Model representing storage options + """ + + available_disks = ['/dev/sda', + '/dev/sdb', + '/dev/sdc', + '/dev/sdd', + '/dev/sde'] + + additional_options = ['Connecti iSCSI network disk', + 'Connect Ceph network disk', + 'Create volume group (LVM2)', + 'Create software RAID (MD)', + 'Setup hierarchichal storage (bcache)'] diff --git a/subiquity/models/network.py b/subiquity/models/network.py new file mode 100644 index 00000000..5c203c0c --- /dev/null +++ b/subiquity/models/network.py @@ -0,0 +1,35 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Network Model + +Provides network device listings and extended network information + +""" + +from subiquity import models + + +class NetworkModel(models.Model): + """ Model representing network interfaces + """ + + interfaces = ['em1', + 'em2', + 'bond0'] + + additional_options = ['Set default route', + 'Bond interfaces', + 'Install network driver'] diff --git a/subiquity/models/welcome.py b/subiquity/models/welcome.py index 0e3e2a9e..67c6460c 100644 --- a/subiquity/models/welcome.py +++ b/subiquity/models/welcome.py @@ -28,3 +28,6 @@ class WelcomeModel(models.Model): supported_languages = ['English', 'Belgian', 'German', 'Italian'] selected_language = None + + def __repr__(self): + return "".format(self.selected_language) diff --git a/subiquity/routes.py b/subiquity/routes.py new file mode 100644 index 00000000..e0f5a38b --- /dev/null +++ b/subiquity/routes.py @@ -0,0 +1,74 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from subiquity.controllers.welcome import WelcomeController +from subiquity.controllers.installpath import InstallpathController +from subiquity.controllers.network import NetworkController + + +class RoutesError(Exception): + """ Error in routes """ + pass + + +class Routes: + """ Defines application routes and maps to their controller + + Routes are inserted top down from start to finish. Maintaining + this order is required for routing to work. + """ + routes = [WelcomeController, + InstallpathController, + NetworkController] + current_route_idx = 0 + + @classmethod + def route(cls, idx): + """ Include route listing in controllers """ + try: + _route = cls.routes[idx] + except IndexError: + raise RoutesError("Failed to load Route at index: {}".format(idx)) + return _route + + @classmethod + def current_idx(cls): + """ Returns current route index """ + return cls.current_route_idx + + @classmethod + def reset(cls): + """ Resets current route """ + cls.current_route_idx = 0 + + @classmethod + def first(cls): + """ first controller/start of install """ + return cls.route(0) + + @classmethod + def last(cls): + """ end of install, last controller """ + return cls.route(-1) + + @classmethod + def next(cls): + cls.current_route_idx = cls.current_route_idx + 1 + return cls.route(cls.current_route_idx) + + @classmethod + def prev(cls): + cls.current_route_idx = cls.current_route_idx - 1 + return cls.route(cls.current_route_idx) diff --git a/subiquity/ui/anchors.py b/subiquity/ui/anchors.py index 0158aa69..785fd5e4 100644 --- a/subiquity/ui/anchors.py +++ b/subiquity/ui/anchors.py @@ -15,6 +15,7 @@ from urwid import WidgetWrap, Pile, Text from subiquity.ui.utils import Padding, Color +from subiquity.ui.lists import SimpleList class Header(WidgetWrap): @@ -26,12 +27,9 @@ class Header(WidgetWrap): :returns: Header() """ - title = "Ubuntu Server Installer" - excerpt = "" - - def __init__(self): - title_widget = Padding.push_10(Color.body(Text(self.title))) - excerpt_widget = Padding.push_10(Color.body(Text(self.excerpt))) + def __init__(self, title="Ubuntu Server Installer", excerpt=""): + title_widget = Padding.center_79(Color.body(Text(title))) + excerpt_widget = Padding.center_79(Color.body(Text(excerpt))) pile = Pile([Text(""), title_widget, Text(""), @@ -47,20 +45,23 @@ class Footer(WidgetWrap): """ - message = "" - - def __init__(self): - border = Text("") - message_widget = Padding.push_10(Color.body(Text(self.message))) - status = Pile([border, message_widget]) + def __init__(self, message=""): + message_widget = Padding.center_79(Color.body(Text(message))) + status = Pile([Padding.line_break(""), message_widget]) super().__init__(status) class Body(WidgetWrap): """ Body widget """ + def __init__(self): - self.text = [ - Text("Welcome to the Ubuntu Server Installation", align="center") + text = [ + Padding.line_break(""), + Padding.center_79( + Text("Welcome to the Ubuntu Server Installation", + align="center")), + Padding.line_break("") ] - super().__init__(Pile(self.text)) + w = (SimpleList(text)) + super().__init__(w) diff --git a/subiquity/ui/frame.py b/subiquity/ui/frame.py index 914c38e3..3a17373f 100644 --- a/subiquity/ui/frame.py +++ b/subiquity/ui/frame.py @@ -17,11 +17,25 @@ from urwid import Frame, WidgetWrap from subiquity.ui.anchors import Header, Footer, Body +import logging -class BaseFrame(WidgetWrap): - def __init__(self): - _frame = Frame(Body(), - Header(), - Footer()) - super().__init__(_frame) +log = logging.getLogger('subiquity.ui.frame') + + +class SubiquityUI(WidgetWrap): + def __init__(self, header=None, body=None, footer=None): + self.header = header if header else Header() + self.body = body if body else Body() + self.footer = footer if footer else Footer() + self.frame = Frame(self.body, header=self.header, footer=self.footer) + super().__init__(self.frame) + + def set_header(self, title, excerpt): + self.frame.header = Header(title, excerpt) + + def set_footer(self, message): + self.frame.footer = Footer(message) + + def set_body(self, widget): + self.frame.body = widget diff --git a/subiquity/ui/lists.py b/subiquity/ui/lists.py index fed370b7..6220aa1d 100644 --- a/subiquity/ui/lists.py +++ b/subiquity/ui/lists.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from urwid import ListBox, SimpleListWalker, WidgetWrap, Button +from urwid import ListBox, SimpleListWalker, WidgetWrap class SimpleList(WidgetWrap): @@ -22,6 +22,6 @@ class SimpleList(WidgetWrap): super().__init__(self._build_widget()) def _build_widget(self): - lw = SimpleListWalker([Button(x) for x in self.contents]) + lw = SimpleListWalker([x for x in self.contents]) return ListBox(lw) diff --git a/subiquity/ui/utils.py b/subiquity/ui/utils.py index 4d6ae326..27b70227 100644 --- a/subiquity/ui/utils.py +++ b/subiquity/ui/utils.py @@ -16,7 +16,7 @@ """ UI utilities """ from urwid import Padding as _Padding -from urwid import AttrMap +from urwid import AttrMap, Text from functools import partialmethod from subiquity.palette import STYLES @@ -108,7 +108,7 @@ class Padding: "width of 10 columns")) """ - pass + line_break = partialmethod(Text) def apply_style_map(cls): diff --git a/subiquity/views/installpath.py b/subiquity/views/installpath.py index eed50af4..fd681cb4 100644 --- a/subiquity/views/installpath.py +++ b/subiquity/views/installpath.py @@ -13,32 +13,28 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import logging from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter) from subiquity.ui.lists import SimpleList -from subiquity.ui.anchors import Header, Footer, Body # NOQA -from subiquity.ui.buttons import cancel_btn +from subiquity.ui.buttons import confirm_btn, cancel_btn from subiquity.ui.utils import Padding, Color +log = logging.getLogger('subiquity.installpathView') + + class InstallpathView(WidgetWrap): - def __init__(self, model, cb=None): - Header.title = "15.10" - Header.excerpt = ("Welcome to Ubuntu! The world’s favourite platform " - "for clouds, clusters and amazing internet things. " - "This is the installer for Ubuntu on servers and " - "internet devices.") - Footer.message = ("Use UP, DOWN arrow keys, and ENTER, to " - "navigate options..") + def __init__(self, model, cb): + log.debug("In install path view") self.model = model self.cb = cb self.items = [] - self.layout = [ - Header(), + self.body = [ Padding.center_79(self._build_model_inputs()), + Padding.line_break(""), Padding.center_20(self._build_buttons()), - Footer() ] - super().__init__(ListBox(self.layout)) + super().__init__(ListBox(self.body)) def _build_buttons(self): self.buttons = [ @@ -48,16 +44,17 @@ class InstallpathView(WidgetWrap): return Pile(self.buttons) def _build_model_inputs(self): - sl = SimpleList(self.model.install_paths) - return BoxAdapter(sl, - height=len(self.model.install_paths)) + sl = [] + for ipath in self.model.install_paths: + sl.append(Color.button_primary(confirm_btn(label=ipath, + on_press=self.confirm), + focus_map='button_primary focus')) + + return BoxAdapter(SimpleList(sl), + height=len(sl)) def confirm(self, button): - if self.cb is not None: - return self.cb(True, 'Moving to next controller.') + return self.cb(button.label) def cancel(self, button): - if self.cb is None: - raise SystemExit('Cancelled.') - else: - return self.cb(False, 'Cancelled with callback.') + return self.cb(None) diff --git a/subiquity/views/network.py b/subiquity/views/network.py new file mode 100644 index 00000000..e5f9b311 --- /dev/null +++ b/subiquity/views/network.py @@ -0,0 +1,71 @@ +# Copyright 2015 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, Text) +from subiquity.ui.lists import SimpleList +from subiquity.ui.buttons import confirm_btn, cancel_btn +from subiquity.ui.utils import Padding, Color + + +log = logging.getLogger('subiquity.networkView') + + +class NetworkView(WidgetWrap): + def __init__(self, model, cb): + self.model = model + self.cb = cb + self.items = [] + self.body = [ + Padding.center_79(self._build_model_inputs()), + Padding.line_break(""), + Padding.center_79(self._build_additional_options()), + Padding.line_break(""), + Padding.center_20(self._build_buttons()), + ] + super().__init__(ListBox(self.body)) + + def _build_buttons(self): + buttons = [ + Color.button_secondary(cancel_btn(on_press=self.cancel), + focus_map='button_secondary focus'), + ] + return Pile(buttons) + + def _build_model_inputs(self): + sl = [] + for iface in self.model.interfaces: + sl.append(Color.button_primary(confirm_btn(label=iface, + on_press=self.confirm), + focus_map='button_primary focus')) + sl.append(Padding.push_10(Text("Adapter info"))) + + return BoxAdapter(SimpleList(sl), + height=len(sl)) + + def _build_additional_options(self): + opts = [] + for opt in self.model.additional_options: + opts.append( + Color.button_secondary(confirm_btn(label=opt, + on_press=self.confirm), + focus_map='button_secondary focus')) + return Pile(opts) + + def confirm(self, button): + return self.cb(button.label) + + def cancel(self, button): + return self.cb(None) diff --git a/subiquity/views/welcome.py b/subiquity/views/welcome.py index d8161f21..c1b1c7eb 100644 --- a/subiquity/views/welcome.py +++ b/subiquity/views/welcome.py @@ -15,48 +15,41 @@ from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter) from subiquity.ui.lists import SimpleList -from subiquity.ui.anchors import Header, Footer, Body # NOQA from subiquity.ui.buttons import confirm_btn, cancel_btn from subiquity.ui.utils import Padding, Color class WelcomeView(WidgetWrap): - def __init__(self, model, cb=None): - Header.title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!" - Header.excerpt = "Please choose your preferred language" - Footer.message = ("Use UP, DOWN arrow keys, and ENTER, to " - "select your language.") + def __init__(self, model, cb): self.model = model self.cb = cb self.items = [] - self.layout = [ - Header(), + self.body = [ Padding.center_79(self._build_model_inputs()), + Padding.line_break(""), Padding.center_20(self._build_buttons()), - Footer() ] - super().__init__(ListBox(self.layout)) + super().__init__(ListBox(self.body)) def _build_buttons(self): self.buttons = [ - Color.button_primary(confirm_btn(on_press=self.confirm), - focus_map='button_primary focus'), Color.button_secondary(cancel_btn(on_press=self.cancel), focus_map='button_secondary focus'), ] return Pile(self.buttons) def _build_model_inputs(self): - sl = SimpleList(self.model.supported_languages) - return BoxAdapter(sl, - height=len(self.model.supported_languages)) + sl = [] + for lang in self.model.supported_languages: + sl.append(Color.button_primary( + confirm_btn(label=lang, on_press=self.confirm), + focus_map="button_primary focus")) + + return BoxAdapter(SimpleList(sl), + height=len(sl)) def confirm(self, button): - if self.cb is not None: - return self.cb(True, 'Moving to next controller.') + return self.cb(button.label) def cancel(self, button): - if self.cb is None: - raise SystemExit('Cancelled.') - else: - return self.cb(False, 'Cancelled with callback.') + return self.cb(None)