diff --git a/subiquity/controllers/filesystem.py b/subiquity/controllers/filesystem.py index 580f7d07..19f00b83 100644 --- a/subiquity/controllers/filesystem.py +++ b/subiquity/controllers/filesystem.py @@ -44,7 +44,6 @@ from subiquity.models.filesystem import ( from subiquity.ui.views import ( FilesystemView, GuidedDiskSelectionView, - GuidedFilesystemView, ) from subiquity.ui.views.filesystem.probing import ( SlowProbing, @@ -183,12 +182,18 @@ class FilesystemController(BaseController): # performed would be tricky. Possibly worth doing though! Just # not today. self.stop_listening_udev() - self.ui.set_body(GuidedFilesystemView(self)) + self.ui.set_body(GuidedDiskSelectionView(self)) pr = self._crash_reports.get(False) if pr is not None: self.app.show_error_report(pr) if self.answers['guided']: - self.guided(self.answers.get('guided-method', 'direct')) + disk = self.model.all_disks()[self.answers['guided-index']] + method = self.answers.get('guided-method') + self.ui.body.form.guided_choice.value = { + 'disk': disk, + 'use_lvm': method == "lvm", + } + self.ui.body.done(self.ui.body.form) elif self.answers['manual']: self.manual() diff --git a/subiquity/ui/views/__init__.py b/subiquity/ui/views/__init__.py index 8dfd1052..f6e552af 100644 --- a/subiquity/ui/views/__init__.py +++ b/subiquity/ui/views/__init__.py @@ -16,7 +16,6 @@ from .filesystem import ( FilesystemView, GuidedDiskSelectionView, - GuidedFilesystemView, ) from .identity import IdentityView from .installprogress import ProgressView @@ -26,7 +25,6 @@ from .zdev import ZdevView __all__ = [ 'FilesystemView', 'GuidedDiskSelectionView', - 'GuidedFilesystemView', 'IdentityView', 'KeyboardView', 'ProgressView', diff --git a/subiquity/ui/views/filesystem/__init__.py b/subiquity/ui/views/filesystem/__init__.py index f0d1d3f4..cf66c905 100644 --- a/subiquity/ui/views/filesystem/__init__.py +++ b/subiquity/ui/views/filesystem/__init__.py @@ -21,9 +21,8 @@ configuration. """ from .filesystem import FilesystemView -from .guided import GuidedDiskSelectionView, GuidedFilesystemView +from .guided import GuidedDiskSelectionView __all__ = [ 'FilesystemView', 'GuidedDiskSelectionView', - 'GuidedFilesystemView', ] diff --git a/subiquity/ui/views/filesystem/guided.py b/subiquity/ui/views/filesystem/guided.py index dd9b3459..50588663 100644 --- a/subiquity/ui/views/filesystem/guided.py +++ b/subiquity/ui/views/filesystem/guided.py @@ -17,25 +17,25 @@ import logging from urwid import ( connect_signal, - Text, ) -from subiquitycore.ui.buttons import ( - back_btn, - cancel_btn, - ok_btn, +from subiquitycore.ui.form import ( + BooleanField, + ChoiceField, + Form, + NO_CAPTION, + NO_HELP, + RadioButtonField, + SubForm, + SubFormField, ) +from subiquitycore.ui.selector import Option from subiquitycore.ui.table import ( - ColSpec, - TableListBox, + TablePile, TableRow, ) from subiquitycore.ui.utils import ( - button_pile, - ClickableIcon, - Color, - CursorOverride, - screen, + rewrap, ) from subiquitycore.view import BaseView @@ -48,120 +48,107 @@ from .helpers import summarize_device log = logging.getLogger("subiquity.ui.views.filesystem.guided") - -text = _("""The installer can guide you through partitioning an entire disk \ +text = _("""The installer can guide you through partitioning an entire disk either directly or using LVM, or, if you prefer, you can do it manually. -If you choose to partition an entire disk you will still have a chance to \ +If you choose to partition an entire disk you will still have a chance to review and modify the results.""") -class GuidedFilesystemView(BaseView): +class GuidedChoiceForm(SubForm): + + disk = ChoiceField(caption=NO_CAPTION, help=NO_HELP, choices=["x"]) + use_lvm = BooleanField(_("Set up this disk as an LVM group"), help=NO_HELP) + + def __init__(self, parent): + super().__init__(parent) + options = [] + tables = [] + for disk in parent.model.all_disks(): + for obj, cells in summarize_device(disk): + table = TablePile([TableRow(cells)]) + tables.append(table) + options.append(Option((table, obj is disk, obj))) + t0 = tables[0] + for t in tables[1:]: + t0.bind(t) + self.disk.widget.options = options + self.disk.widget.index = 0 + + +class GuidedForm(Form): + + group = [] + + guided = RadioButtonField(group, _("Use an entire disk"), help=NO_HELP) + guided_choice = SubFormField(GuidedChoiceForm, "", help=NO_HELP) + custom = RadioButtonField(group, _("Custom storage layout"), help=NO_HELP) + + cancel_label = _("Back") + + def __init__(self, model): + self.model = model + super().__init__() + connect_signal(self.guided.widget, 'change', self._toggle_guided) + + def _toggle_guided(self, sender, new_value): + self.guided_choice.enabled = new_value + + +HELP = _(""" + +The "Use an entire disk" option installs Ubuntu onto the selected disk, +replacing any partitions and data already there. + +If the platform requires it, a bootloader partition is created on the disk. + +If you choose to use LVM, two partitions are then created, one for /boot and +one covering the rest of the disk. A LVM volume group is created containing +the large partition. A 4 gigabyte logical volume is created for the root +filesystem. It can easily be enlarged with standard LVM command line tools. + +If you do not choose to use LVM, a single partition is created covering the +rest of the disk which is then formatted as ext4 and mounted at /. + +In either case, you will still have a chance to review and modify the results. + +If you choose to use a custom storage layout, no changes are made to the disks +and you will have to, at a minimum, select a boot disk and mount a filesystem +at /. + +""") + + +class GuidedDiskSelectionView (BaseView): title = _("Filesystem setup") def __init__(self, controller): self.controller = controller - direct = ok_btn( - _("Use An Entire Disk"), on_press=self.guided, user_arg="direct") - lvm = ok_btn( - _("Use An Entire Disk And Set Up LVM"), on_press=self.guided, - user_arg="lvm") - manual = ok_btn(_("Manual"), on_press=self.manual) - back = back_btn(_("Back"), on_press=self.cancel) - super().__init__(screen( - rows=[button_pile([direct, lvm, manual, back]), Text("")], - buttons=None, - focus_buttons=False, - excerpt=text)) + self.form = GuidedForm(model=controller.model) - def manual(self, btn): - log.debug("GuidedFilesystemView.manual") - self.controller.manual() + connect_signal(self.form, 'submit', self.done) + connect_signal(self.form, 'cancel', self.cancel) - def guided(self, btn, method): - log.debug("GuidedFilesystemView.guided") - self.controller.guided(method) + super().__init__(self.form.as_screen( + focus_buttons=False, excerpt=rewrap(_(text)))) + + def local_help(self): + return (_("Help on guided storage configuration"), rewrap(_(HELP))) + + def done(self, sender): + results = sender.as_data() + if results['custom']: + self.controller.manual() + else: # results['guided'] + self.choose_disk(**results['guided_choice']) def cancel(self, btn=None): self.controller.cancel() - -excerpts = { - 'direct': _("""The selected guided partitioning scheme creates the \ -required bootloader partition on the chosen disk and then creates a single \ -partition covering the rest of the disk, formatted as ext4 and mounted at '/'.\ -"""), - - 'lvm': _("""The LVM guided partitioning scheme creates three \ -partitions on the selected disk: one as required by the bootloader, one \ -for '/boot', and one covering the rest of the disk. - -A LVM volume group is created containing the large partition. A \ -4 gigabyte logical volume is created for the root filesystem. \ -It can easily be enlarged with standard LVM command line tools."""), -} - - -def _wrap_button_row(row): - return CursorOverride(Color.done_button(row), 2) - - -class GuidedDiskSelectionView(BaseView): - - title = _("Filesystem setup") - - def __init__(self, model, controller, method): - self.model = model - self.controller = controller - self.method = method - cancel = cancel_btn(_("Cancel"), on_press=self.cancel) - rows = [] - for disk in self.model.all_disks(): - for obj, cells in summarize_device(disk): - wrap = Color.info_minor - if obj is disk: - start, end = '[', ']' - arrow = '\N{BLACK RIGHT-POINTING SMALL TRIANGLE}' - if disk.size >= dehumanize_size("6G"): - arrow = ClickableIcon(arrow) - connect_signal( - arrow, 'click', self.choose_disk, disk) - wrap = _wrap_button_row - else: - start, arrow, end = '', '', '' - if isinstance(arrow, str): - arrow = Text(arrow) - rows.append(wrap(TableRow( - [Text(start)] + cells + [arrow, Text(end)]))) - rows.append(TableRow([Text("")])) - super().__init__(screen( - TableListBox(rows[:-1], spacing=2, colspecs={ - 0: ColSpec(rpad=1), - 2: ColSpec(can_shrink=True), - 4: ColSpec(min_width=9), - 5: ColSpec(rpad=1), - }, align='center'), - button_pile([cancel]), - focus_buttons=False, - excerpt=( - excerpts[method] - + "\n\n" - + _("Choose the disk to install to:")))) - - def cancel(self, btn=None): - self.controller.start_ui() - - def choose_disk(self, btn, disk): + def choose_disk(self, disk, use_lvm): self.controller.reformat(disk) - if self.method == "direct": - result = { - "size": disk.free_for_partitions, - "fstype": "ext4", - "mount": "/", - } - self.controller.partition_disk_handler(disk, None, result) - elif self.method == 'lvm': + if use_lvm: if DeviceAction.MAKE_BOOT in disk.supported_actions: self.controller.make_boot_disk(disk) self.controller.create_partition( @@ -175,11 +162,9 @@ class GuidedDiskSelectionView(BaseView): size=disk.free_for_partitions, fstype=None, )) - vg = self.controller.create_volgroup( - spec=dict( - name="ubuntu-vg", - devices=set([part]), - )) + spec = dict(name="ubuntu-vg", devices=set([part])) + # create volume group on partition + vg = self.controller.create_volgroup(spec) self.controller.create_logical_volume( vg=vg, spec=dict( size=dehumanize_size("4G"), @@ -188,5 +173,10 @@ class GuidedDiskSelectionView(BaseView): mount="/", )) else: - raise Exception("unknown guided method '{}'".format(self.method)) + result = { + "size": disk.free_for_partitions, + "fstype": "ext4", + "mount": "/", + } + self.controller.partition_disk_handler(disk, None, result) self.controller.manual() diff --git a/subiquity/ui/views/filesystem/tests/test_guided.py b/subiquity/ui/views/filesystem/tests/test_guided.py deleted file mode 100644 index a370f63b..00000000 --- a/subiquity/ui/views/filesystem/tests/test_guided.py +++ /dev/null @@ -1,45 +0,0 @@ -import unittest -from unittest import mock - -import urwid - -from subiquitycore.testing import view_helpers - -from subiquity.controllers.filesystem import FilesystemController -from subiquity.ui.views.filesystem.guided import GuidedFilesystemView - - -class GuidedFilesystemViewTests(unittest.TestCase): - - def make_view(self): - controller = mock.create_autospec(spec=FilesystemController) - return GuidedFilesystemView(controller) - - def test_initial_focus(self): - view = self.make_view() - focus_path = view_helpers.get_focus_path(view) - for w in reversed(focus_path): - if isinstance(w, urwid.Button): - if w.label == "Use An Entire Disk": - return - else: - self.fail("Guided button not focus") - - def test_click_guided(self): - view = self.make_view() - button = ( - view_helpers.find_button_matching(view, "^Use An Entire Disk$")) - view_helpers.click(button) - view.controller.guided.assert_called_once_with('direct') - - def test_click_manual(self): - view = self.make_view() - button = view_helpers.find_button_matching(view, "^Manual$") - view_helpers.click(button) - view.controller.manual.assert_called_once_with() - - def test_click_back(self): - view = self.make_view() - button = view_helpers.find_button_matching(view, "^Back$") - view_helpers.click(button) - view.controller.cancel.assert_called_once_with() diff --git a/subiquitycore/ui/table.py b/subiquitycore/ui/table.py index ac9e88c8..a1a24f9c 100644 --- a/subiquitycore/ui/table.py +++ b/subiquitycore/ui/table.py @@ -363,6 +363,14 @@ class AbstractTable(WidgetWrap): row.width = total_width row.base_widget.set_widths(widths) + def get_natural_width(self): + rows = [] + for table in self.group: + rows.extend(table.table_rows) + widths, total_width, has_unpacked = _compute_widths_for_size( + 100000, rows, self.colspecs, self.spacing) + return total_width + def rows(self, size, focus): self._compute_widths_for_size(size) return super().rows(size, focus)