def __init__(self, controller): self.controller = controller self.spinner = Spinner(controller.loop) self.event_listwalker = SimpleFocusListWalker([]) self.event_listbox = ListBox(self.event_listwalker) self.event_linebox = MyLineBox(self.event_listbox) self.event_buttons = button_pile([other_btn("View full log", on_press=self.view_log)]) event_body = [ ('pack', Text("")), ('weight', 1, Padding.center_79(self.event_linebox)), ('pack', Text("")), ('pack', self.event_buttons), ('pack', Text("")), ] self.event_pile = Pile(event_body) self.log_listwalker = SimpleFocusListWalker([]) self.log_listbox = ListBox(self.log_listwalker) log_linebox = MyLineBox(self.log_listbox, _("Full installer output")) log_body = [ ('weight', 1, log_linebox), ('pack', button_pile([other_btn(_("Close"), on_press=self.close_log)])), ] self.log_pile = Pile(log_body) super().__init__(self.event_pile)
def __init__(self, model, controller): self.model = model self.controller = controller self.items = [] self.error = Text("", align='center') self.model_inputs = Pile(self._build_model_inputs()) self.additional_options = Pile(self._build_additional_options()) self.body = [ self.model_inputs, Padding.center_79(self.additional_options), Padding.line_break(""), ] self.lb = Padding.center_90(ListBox(self.body)) self.footer = Pile([ Text(""), self._build_buttons(), Text(""), ]) self.error_showing = False self.frame = Pile([ ('pack', Text("")), self.lb, ('pack', self.footer)]) self.lb.original_widget._select_last_selectable() self.frame.focus_position = 2 super().__init__(self.frame)
def __init__(self, model, controller): self.model = model self.controller = controller self.dev_to_row = {} self.cur_netdevs = [] self.error = Text("", align='center') self.device_table = TablePile(self._build_model_inputs(), spacing=2, colspecs={ 0: ColSpec(rpad=1), 4: ColSpec(can_shrink=True, rpad=1), }) self._create_bond_btn = menu_btn(_("Create bond"), on_press=self._create_bond) bp = button_pile([self._create_bond_btn]) bp.align = 'left' self.listbox = ListBox([self.device_table] + [ bp, ]) self.bottom = Pile([ Text(""), self._build_buttons(), Text(""), ]) self.error_showing = False self.frame = Pile([('pack', Text("")), ('pack', Padding.center_79(Text(_(self.excerpt)))), ('pack', Text("")), Padding.center_90(self.listbox), ('pack', self.bottom)]) self.frame.set_focus(self.bottom) super().__init__(self.frame)
def __init__(self, model, controller, name): self.model = model self.controller = controller self.dev = self.model.get_netdev_by_name(name) self.title = _("Network interface {} WIFI configuration").format(name) self.form = WLANForm() connect_signal(self.form, 'submit', self.done) connect_signal(self.form, 'cancel', self.cancel) if self.dev.configured_ssid is not None: self.form.ssid.value = self.dev.configured_ssid if self.dev.configured_wifi_psk is not None: self.form.psk.value = self.dev.configured_wifi_psk self.ssid_row = self.form.ssid.as_row(self.form.longest_caption) self.psk_row = self.form.psk.as_row(self.form.longest_caption) self.inputs = Pile(self._build_iface_inputs()) self.error = Text("") self.body = Pile([ ('pack', Text("")), ListBox([Padding.center_79(self.inputs)]), ('pack', Pile([ ('pack', Text("")), Padding.center_79(Color.info_error(self.error)), self.form.buttons, ('pack', Text("")), ])), ]) self.orig_w = None super().__init__(self.body)
def __init__(self, model, controller, name): self.model = model self.controller = controller self.dev = self.model.get_netdev_by_name(name) self.form = WLANForm() connect_signal(self.form, 'submit', self.done) connect_signal(self.form, 'cancel', self.cancel) if self.dev.configured_ssid is not None: self.form.ssid.value = self.dev.configured_ssid if self.dev.configured_wifi_psk is not None: self.form.psk.value = self.dev.configured_wifi_psk self.ssid_row = self.form.ssid.as_row(self, self.form.longest_caption) self.psk_row = self.form.psk.as_row(self, self.form.longest_caption) self.inputs = Pile(self._build_iface_inputs()) self.error = Text("") self.body = [ Padding.center_79(self.inputs), Padding.line_break(""), Padding.center_79(Color.info_error(self.error)), Padding.line_break(""), Padding.fixed_10(Pile([self.form.done_btn, self.form.cancel_btn])), ] self.orig_w = None super().__init__(ListBox(self.body))
def _build_filesystem_list(self): log.debug('FileSystemView: building part list') cols = [] longest_path = len("MOUNT POINT") for m in sorted(self.model._mounts, key=lambda m:m.path): path = m.path longest_path = max(longest_path, len(path)) for p, *_ in reversed(cols): if path.startswith(p): path = [('info_minor', p), path[len(p):]] break cols.append((m.path, path, humanize_size(m.device.volume.size), m.device.fstype, m.device.volume.desc())) for fs in self.model._filesystems: if fs.fstype == 'swap': cols.append((None, 'SWAP', humanize_size(fs.volume.size), fs.fstype, fs.volume.desc())) if len(cols) == 0: return Pile([Color.info_minor( Text("No disks or partitions mounted."))]) cols.insert(0, (None, "MOUNT POINT", "SIZE", "TYPE", "DEVICE TYPE")) pl = [] for _, a, b, c, d in cols: if b == "SIZE": b = Text(b, align='center') else: b = Text(b, align='right') pl.append(Columns([(longest_path, Text(a)), (9, b), (self.model.longest_fs_name, Text(c)), Text(d)], 4)) return Pile(pl)
def __init__(self, model, controller): log.debug('FileSystemView init start()') self.model = model self.controller = controller self.items = [] self.body = [ Text(_("FILE SYSTEM SUMMARY")), Text(""), Padding.push_4(self._build_filesystem_list()), Text(""), Text(_("AVAILABLE DEVICES")), Text(""), Padding.push_4(self._build_available_inputs()), #self._build_menu(), #Text(""), #Text("USED DISKS"), #Text(""), #self._build_used_disks(), #Text(""), ] self.lb = Padding.center_95(ListBox(self.body)) self.footer = Pile([ Text(""), self._build_buttons(), Text(""), ]) self.frame = Pile([ ('pack', Text("")), self.lb, ('pack', self.footer)]) if self.model.can_install(): self.lb.original_widget._select_last_selectable() self.frame.focus_position = 2 super().__init__(self.frame) log.debug('FileSystemView init complete()')
def _build_iface_selection(self): log.debug('bond: _build_iface_selection') items = [Text("INTERFACE SELECTION")] all_iface_names = self.model.get_all_interface_names() avail_ifnames = [ iface for iface in all_iface_names if not self.model.iface_is_bonded(iface) ] log.debug('available for bonding: {}'.format(avail_ifnames)) if len(avail_ifnames) == 0: log.debug('Nothing available...') return Pile([Color.info_minor(Text("No available interfaces."))]) for ifname in avail_ifnames: device = self.model.get_interface(ifname) device_speed = self.model.iface_get_speed(ifname) iface_string = "{} {}, {}".format(device.ifname, device.ip4, device_speed) log.debug('bond: iface_string={}'.format(iface_string)) self.selected_ifaces.append(CheckBox(iface_string)) items += self.selected_ifaces log.debug('iface_select: items: {}'.format(items)) return Pile(items)
def __init__(self, model, controller, opts, loop): self.model = model self.controller = controller self.opts = opts self.loop = loop self.items = [] self.email = EmailEditor() self.error = Text("", align="center") self.progress = Text("", align="center") body = [ ('pack', Text("")), ListBox([ self._build_model_inputs(), Padding.line_break(""), Padding.center_79( Color.info_minor( Text("If you do not have an account, visit " "https://login.ubuntu.com to create one."))), Padding.line_break(""), Padding.center_90(Color.info_error(self.error)), Padding.center_90(self.progress), ]), ('pack', Pile([ ('pack', Text("")), button_pile(self._build_buttons()), ('pack', Text("")), ])), ] super().__init__(Pile(body))
def __init__(self, controller): self.controller = controller self.spinner = Spinner(controller.loop) self.reboot_btn = Toggleable( ok_btn(_("Reboot Now"), on_press=self.reboot)) self.exit_btn = cancel_btn(_("Exit To Shell"), on_press=self.quit) self.view_log_btn = other_btn(_("View full log"), on_press=self.view_log) self.event_listbox = ListBox() self.event_linebox = MyLineBox(self.event_listbox) self.event_buttons = button_pile([self.view_log_btn]) event_body = [ ('pack', Text("")), ('weight', 1, Padding.center_79(self.event_linebox, min_width=76)), ('pack', Text("")), ('pack', self.event_buttons), ('pack', Text("")), ] self.event_pile = Pile(event_body) self.log_listbox = ListBox() log_linebox = MyLineBox(self.log_listbox, _("Full installer output")) log_body = [ ('weight', 1, log_linebox), ('pack', button_pile([other_btn(_("Close"), on_press=self.close_log)])), ] self.log_pile = Pile(log_body) super().__init__(self.event_pile)
def __init__(self, controller): self.controller = controller self.ongoing = {} # context -> line containing a spinner self.reboot_btn = Toggleable(ok_btn( _("Reboot Now"), on_press=self.reboot)) self.view_error_btn = cancel_btn( _("View error report"), on_press=self.view_error) self.view_log_btn = other_btn( _("View full log"), on_press=self.view_log) self.continue_btn = other_btn( _("Continue"), on_press=self.continue_) self.event_listbox = ListBox() self.event_linebox = MyLineBox(self.event_listbox) self.event_buttons = button_pile([self.view_log_btn]) event_body = [ ('weight', 1, Padding.center_79(self.event_linebox, min_width=76)), ('pack', Text("")), ('pack', self.event_buttons), ('pack', Text("")), ] self.event_pile = Pile(event_body) self.log_listbox = ListBox() log_linebox = MyLineBox(self.log_listbox, _("Full installer output")) log_body = [ ('weight', 1, log_linebox), ('pack', button_pile([other_btn(_("Close"), on_press=self.close_log)])), ] self.log_pile = Pile(log_body) super().__init__(self.event_pile)
def _build_model_inputs(self): items = [ Columns([("weight", 0.2, Text("iSCSI Server Host", align="right")), ("weight", 0.3, Color.string_input(self.iscsi_host))], dividechars=4), Columns( [("weight", 0.2, Text("Connect anonymously", align="right")), ("weight", 0.3, Color.string_input(Pile(self.connect_anon.group)))], dividechars=4), Columns( [("weight", 0.2, Text("Connect as user", align="right")), ("weight", 0.3, Color.string_input(self.connect_username))], dividechars=4), Columns( [("weight", 0.2, Text("Password", align="right")), ("weight", 0.3, Color.string_input(self.connect_password))], dividechars=4), Columns( [("weight", 0.2, Text("Require server auth", align="right")), ("weight", 0.3, Color.string_input(Pile(self.server_auth.group)))], dividechars=4), Columns( [("weight", 0.2, Text("Server identity", align="right")), ("weight", 0.3, Color.string_input(self.server_username))], dividechars=4), Columns( [("weight", 0.2, Text("Server password", align="right")), ("weight", 0.3, Color.string_input(self.server_password))], dividechars=4) ] return Pile(items)
def __init__(self, size, existing, initial, back): mountpoint_to_devpath_mapping = self.model.get_mountpoint_to_devpath_mapping( ) if existing is not None: fs = existing.fs() if fs is not None: if existing.flag != "boot": initial['fstype'] = self.model.fs_by_name[fs.fstype] mount = fs.mount() if mount is not None: initial['mount'] = mount.path if mount.path in mountpoint_to_devpath_mapping: del mountpoint_to_devpath_mapping[mount.path] else: initial['fstype'] = self.model.fs_by_name[None] self.form = self.form_cls(mountpoint_to_devpath_mapping, size, initial) self.back = back connect_signal(self.form, 'submit', self.done) connect_signal(self.form, 'cancel', self.cancel) partition_box = Padding.center_50(ListBox(self.make_body())) super().__init__( Pile([ ('pack', Text("")), partition_box, ('pack', Pile([ ('pack', Text("")), self.form.buttons, ('pack', Text("")), ])), ]))
def __init__(self, model, controller, opts): self.model = model self.controller = controller self.signal = controller.signal self.opts = opts self.items = [] self.form = IdentityForm() connect_signal(self.form, 'submit', self.done) connect_signal(self.form.confirm_password.widget, 'change', self._check_password) self.ssh_import_confirmed = True body = Pile([ ('pack', Text("")), Padding.center_90(ListBox([self.form.as_rows(self)])), ('pack', Pile([ ('pack', Text("")), button_pile([self.form.done_btn]), ('pack', Text("")), ], focus_item=1)), ]) super().__init__(body)
def __init__(self, controller, netdev_infos): self.controller = controller self.dev_name_to_table = {} self.cur_netdev_names = [] self.error = Text("", align='center') self.device_colspecs = { 0: ColSpec(rpad=1), 3: ColSpec(min_width=15), 4: ColSpec(can_shrink=True, rpad=1), } self.heading_table = TablePile([ TableRow([ Color.info_minor(Text(header)) for header in [ "", "NAME", "TYPE", "NOTES", "", ] ]) ], spacing=2, colspecs=self.device_colspecs) self.device_pile = Pile([self.heading_table]) for dev_info in netdev_infos: self.new_link(dev_info) self._create_bond_btn = menu_btn(_("Create bond"), on_press=self._create_bond) bp = button_pile([self._create_bond_btn]) bp.align = 'left' rows = [ self.device_pile, bp, ] self.buttons = button_pile([ done_btn("TBD", on_press=self.done), # See _route_watcher back_btn(_("Back"), on_press=self.cancel), ]) self.bottom = Pile([ ('pack', self.buttons), ]) self.error_showing = False super().__init__( screen(rows=rows, buttons=self.bottom, focus_buttons=True, excerpt=_(self.excerpt)))
def _build_model_inputs(self): netdevs = self.model.get_all_netdevs() ifname_width = 8 # default padding if netdevs: ifname_width += max(map(lambda dev: len(dev.name), netdevs)) if ifname_width > 20: ifname_width = 20 iface_menus = [] # Display each interface -- name in first column, then configured IPs # in the second. log.debug('interfaces: {}'.format(netdevs)) for dev in netdevs: col_1 = [] col_2 = [] col_1.append( menu_btn(label=dev.name, on_press=self.on_net_dev_press)) if dev.type == 'wlan': col_2.extend(_build_wifi_info(dev)) if len(dev.actual_ip_addresses) == 0 and (dev.type == 'eth' and not dev.is_connected): col_2.append(Color.info_primary(Text(_("Not connected")))) col_2.extend(_build_gateway_ip_info_for_version(dev, 4)) col_2.extend(_build_gateway_ip_info_for_version(dev, 6)) # Other device info (MAC, vendor/model, speed) template = '' if dev.hwaddr: template += '{} '.format(dev.hwaddr) # TODO is this to translate? if dev.is_bond_slave: template += '(Bonded) ' # TODO to check if this is affected by translations if not dev.vendor.lower().startswith('unknown'): vendor = textwrap.wrap(dev.vendor, 15)[0] template += '{} '.format(vendor) if not dev.model.lower().startswith('unknown'): model = textwrap.wrap(dev.model, 20)[0] template += '{} '.format(model) if dev.speed: template += '({})'.format(dev.speed) col_2.append(Color.info_minor(Text(template))) iface_menus.append( Columns([(ifname_width, Pile(col_1)), Pile(col_2)], 2)) return iface_menus
def __init__(self, app, report, interrupting=True): self.app = app self.report = report self.interrupting = interrupting self.btns = { 'cancel': other_btn( _("Cancel upload"), on_press=self.cancel_upload), 'close': close_btn(self, _("Close report")), 'continue': close_btn(self, _("Continue")), 'debug_shell': other_btn( _("Switch to a shell"), on_press=self.debug_shell), 'restart': other_btn( _("Restart the installer"), on_press=self.restart), 'submit': other_btn( _("Send to Canonical"), on_press=self.submit), 'submitted': disabled(other_btn(_("Sent to Canonical"))), 'view': other_btn( _("View full report"), on_press=self.view_report), } w = 0 for n, b in self.btns.items(): w = max(w, widget_width(b)) for n, b in self.btns.items(): self.btns[n] = Padding(b, width=w, align='center') self.spinner = Spinner(app.aio_loop, style='dots') self.pile = Pile([]) self._report_changed() super().__init__("", [self.pile], 0, 0) connect_signal(self, 'closed', self.spinner.stop)
def __init__(self, bottom_w, stretchy): self.bottom_w = bottom_w self.stretchy = stretchy self.listbox = ListBox([stretchy.stretchy_w]) def entry(i, w): if i == stretchy.stretchy_index: return ('weight', 1, self.listbox) else: return ('pack', w) inner_pile = Pile( [entry(i, w) for (i, w) in enumerate(stretchy.widgets)]) inner_pile.focus_position = stretchy.focus_index # this Filler/Padding/LineBox/Filler/Padding construction # seems ridiculous but it works. self.top_w = urwid.Filler(urwid.Padding(urwid.LineBox( urwid.Filler(urwid.Padding(inner_pile, left=2, right=2), top=1, bottom=1, height=('relative', 100)), title=stretchy.title), left=3, right=3), top=1, bottom=1, height=('relative', 100))
def __init__(self, parent, device): self.parent = parent self.device = device title = _("Network interface {nic} WIFI configuration").format( nic=device.name) self.form = WLANForm() connect_signal(self.form, 'submit', self.done) connect_signal(self.form, 'cancel', self.cancel) ssid, psk = self.device.configured_ssid if ssid: self.form.ssid.value = ssid if psk: self.form.psk.value = psk self.ssid_row = self.form.ssid._table self.psk_row = self.form.psk._table self.ssid_row.bind(self.psk_row) self.inputs = Pile(self._build_iface_inputs()) self.error = Text("") widgets = [ self.inputs, Padding.center_79(Color.info_error(self.error)), self.form.buttons, ] super().__init__(title, widgets, 0, 0)
def _build_disk_selection(self): log.debug('lvm: _build_disk_selection') items = [Text("DISK SELECTION")] # lvm can use empty whole disks, or empty partitions avail_disks = self.model.get_empty_disk_names() avail_parts = self.model.get_empty_partition_names() avail_devs = sorted(avail_disks + avail_parts) if len(avail_devs) == 0: return items.append( [Color.info_minor(Text("No available disks."))]) for dname in avail_devs: device = self.model.get_disk(dname) if device.path != dname: # we've got a partition lvmdev = device.get_partition(dname) else: lvmdev = device disk_sz = humanize_size(lvmdev.size) disk_string = "{} {}, {}".format(dname, disk_sz, device.model) log.debug('lvm: disk_string={}'.format(disk_string)) self.selected_disks.append(CheckBox(disk_string)) items += self.selected_disks return Pile(items)
def __init__(self, controller): self.controller = controller super().__init__(Pile([ ListBox([Text('')]), # need to have a listbox or something else "stretchy" here or urwid complains. ('pack', button_pile([ok_btn("OK", on_press=self.confirm)])), ('pack', Text("")), ], focus_item=1))
def __init__(self, app): rows = [] keys = GLOBAL_KEYS if app.opts.run_on_serial: keys += SERIAL_GLOBAL_HELP_KEYS for key, text in keys: rows.append(TableRow([Text(_(key)), Text(_(text))])) if app.opts.dry_run: dro = _('(dry-run only)') for key, text in DRY_RUN_KEYS: rows.append(TableRow([Text(_(key)), Text(_(text) + ' ' + dro)])) table = TablePile(rows, spacing=2, colspecs={1: ColSpec(can_shrink=True)}) widgets = [ Pile([ ('pack', Text(rewrap(GLOBAL_KEY_HELP))), ('pack', Text("")), ('pack', table), ]), Text(""), button_pile([close_btn(app, self)]), ] super().__init__(_("Shortcut Keys"), widgets, 0, 2)
def _build_buttons(self): log.debug('lvm: _build_buttons') cancel = cancel_btn(on_press=self.cancel) done = done_btn(on_press=self.done) buttons = [Color.button(done), Color.button(cancel)] return Pile(buttons)
def __init__(self, mountpoint_to_devpath_mapping): opts = [] first_opt = None max_len = max(map(len, common_mountpoints)) for i, mnt in enumerate(common_mountpoints): devpath = mountpoint_to_devpath_mapping.get(mnt) if devpath is None: if first_opt is None: first_opt = i opts.append((mnt, True, mnt)) else: opts.append(("%-*s (%s)" % (max_len, mnt, devpath), False)) if first_opt is None: first_opt = len(opts) opts.append((_('other'), True, OTHER)) opts.append(('---', False)), opts.append((_('leave unmounted'), True, LEAVE_UNMOUNTED)) self._selector = Selector(opts, first_opt) connect_signal(self._selector, 'select', self._select_mount) self._other = _MountEditor() super().__init__(Pile([self._selector])) self._other_showing = False if self._selector.value is OTHER: # This can happen if all the common_mountpoints are in use. self._showhide_other(True)
def _build_model_inputs(self): items = [ Columns( [ ("weight", 0.2, Text("Ceph MON", align="right")), ("weight", 0.3, Color.string_input(self.ceph_mon)) ], dividechars=4 ), Columns( [ ("weight", 0.2, Text("Username", align="right")), ("weight", 0.3, Color.string_input(self.username)) ], dividechars=4 ), Columns( [ ("weight", 0.2, Text("Key", align="right")), ("weight", 0.3, Color.string_input(self.ceph_key)) ], dividechars=4 ) ] return Pile(items)
def __init__(self, mountpoints, ok_for_slash_boot): opts = [] first_opt = None for i, mnt in enumerate(common_mountpoints): if not ok_for_slash_boot and mnt == "/boot": opts.append((mnt, False)) elif mnt not in mountpoints: if first_opt is None: first_opt = i opts.append((mnt, True, mnt)) else: opts.append((mnt, False)) if first_opt is None: first_opt = len(opts) opts.append((_('other'), True, OTHER)) opts.append(('---', False)), opts.append((_('leave unmounted'), True, LEAVE_UNMOUNTED)) self._selector = Selector(opts, first_opt) connect_signal(self._selector, 'select', self._select_mount) self._other = _MountEditor() super().__init__(Pile([self._selector])) self._other_showing = False if self._selector.value is OTHER: # This can happen if all the common_mountpoints are in use. self._showhide_other(True)
def __init__(self, model, controller, name): self.model = model self.controller = controller self.dev = self.model.get_netdev_by_name(name) self._build_widgets() super().__init__( Pile([ ('pack', Text("")), Padding.center_79(ListBox(self._build_body())), ('pack', Pile([ ('pack', Text("")), self._build_buttons(), ('pack', Text("")), ])), ]))
def __init__(self, parent, existing=None): self.parent = parent self.existing = existing raid_names = {raid.name for raid in parent.model.all_raids()} if existing is None: title = _('Create software RAID ("MD") disk') label = _('Create') x = 0 while True: name = 'md{}'.format(x) if name not in raid_names: break x += 1 initial = { 'devices': {}, 'name': name, 'level': raidlevels_by_value["raid1"], 'size': '-', } else: raid_names.remove(existing.name) title = _('Edit software RAID disk "{name}"').format( name=existing.name) label = _('Save') name = existing.name if name.startswith('md/'): name = name[3:] devices = {} for d in existing.devices: devices[d] = 'active' for d in existing.spare_devices: devices[d] = 'spare' initial = { 'devices': devices, 'name': name, 'level': raidlevels_by_value[existing.raidlevel] } possible_components = get_possible_components( self.parent.model, existing, initial['devices'], lambda dev: dev.ok_for_raid) form = self.form = RaidForm( self.parent.model, possible_components, initial, raid_names) form.devices.widget.set_supports_spares( initial['level'].supports_spares) form.buttons.base_widget[0].set_label(label) connect_signal(form.level.widget, 'select', self._select_level) connect_signal(form.devices.widget, 'change', self._change_devices) connect_signal(form, 'submit', self.done) connect_signal(form, 'cancel', self.cancel) rows = form.as_rows() super().__init__( title, [Pile(rows), Text(""), self.form.buttons], 0, 0)
def __init__(self, parent, ssh_data, key_material, fingerprints): self.parent = parent self.ssh_data = ssh_data self.key_material = key_material ok = ok_btn(label=_("Yes"), on_press=self.ok) cancel = cancel_btn(label=_("No"), on_press=self.cancel) if len(fingerprints) > 1: title = _("Confirm SSH keys") header = _("Keys with the following fingerprints were fetched. " "Do you want to use them?") else: title = _("Confirm SSH key") header = _("A key with the following fingerprint was fetched. " "Do you want to use it?") fingerprints = Pile([Text(fingerprint) for fingerprint in fingerprints]) super().__init__( title, [ Text(header), Text(""), fingerprints, Text(""), button_pile([ok, cancel]), ], 2, 4)
def as_row(self, view, longest_caption): if self.pile is not None: raise RuntimeError("do not call as_row more than once!") self.parent_view = view self._longest_caption = longest_caption self.pile = Pile([self._cols()]) return self.pile