Example #1
0
    def __init__(self, controller, systems):
        self.controller = controller

        heading_table = TablePile([
            TableRow([
                Color.info_minor(Text(header))
                for header in ["LABEL", "MODEL", "PUBLISHER", ""]
            ])
        ],
                                  spacing=2)

        trows = []
        systems = sorted(
            systems,
            key=lambda s:
            (s.brand.display_name, s.model.display_name, s.current, s.label))
        for s in systems:
            actions = []
            log.debug('actions: %s', s.actions)
            for act in sorted(s.actions, key=by_preferred_action_type):
                actions.append(
                    Action(label=act.title.capitalize(),
                           value=act,
                           enabled=True))
            menu = ActionMenu(actions)
            connect_signal(menu, 'action', self._system_action, s)
            srow = make_action_menu_row([
                Text(s.label),
                Text(s.model.display_name),
                Text(s.brand.display_name),
                Text("(installed)" if s.current else ""),
                menu,
            ], menu)
            trows.append(srow)

        systems_table = TablePile(trows, spacing=2)
        systems_table.bind(heading_table)
        rows = [
            Pile([heading_table, systems_table]),
        ]

        buttons = []
        if controller.model.current is not None:
            # back to options of current system
            buttons.append(back_btn("BACK", on_press=self.back))

        super().__init__(
            controller.model.current,
            screen(rows=rows,
                   buttons=button_pile(buttons),
                   focus_buttons=False,
                   excerpt=self.excerpt))
Example #2
0
 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)
Example #3
0
    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)
Example #4
0
 def __init__(self, app):
     self.app = app
     rows = [
         TableRow([
             Text(""),
             Text(_("DATE")),
             Text(_("KIND")),
             Text(_("STATUS")),
             Text(""),
         ])
     ]
     self.report_to_row = {}
     self.app.error_reporter.load_reports()
     for report in self.app.error_reporter.reports:
         connect_signal(report, "changed", self._report_changed, report)
         r = self.report_to_row[report] = self.row_for_report(report)
         rows.append(r)
     self.table = TablePile(rows, colspecs={1: ColSpec(can_shrink=True)})
     widgets = [
         Text(_("Select an error report to view:")),
         Text(""),
         self.table,
         Text(""),
         button_pile([close_btn(self)]),
     ]
     super().__init__("", widgets, 2, 2)
Example #5
0
    def _build_table(self):
        widget = self.widget
        if self.field.takes_default_style:
            widget = Color.string_input(widget)

        self.caption_text = Text(self.field.caption)
        self.under_text = Text(self.help)

        if self.field.caption_first:
            self.caption_text.align = 'right'
            first_row = [self.caption_text, _Validator(self, widget)]
        else:
            first_row = [
                _Validator(
                    self,
                    UrwidPadding(widget,
                                 align='right',
                                 width=widget_width(widget))),
                self.caption_text,
            ]

        self._rows = [
            Toggleable(TableRow(row)) for row in [
                first_row,
                [Text(""), self.under_text],
            ]
        ]

        self._table = TablePile(self._rows, spacing=2, colspecs=form_colspecs)
Example #6
0
    def __init__(self, parent, obj):
        self.parent = parent
        self.obj = obj

        lines = [
            Text(
                _("Do you really want to delete the {desc} {label}?").format(
                    desc=obj.desc(), label=obj.label)),
            Text(""),
        ]
        stretchy_index = 0
        fs = obj.fs()
        if fs is not None:
            m = fs.mount()
            if m is not None:
                lines.append(
                    Text(
                        _("It is formatted as {fstype} and mounted at "
                          "{path}").format(fstype=fs.fstype, path=m.path)))
            else:
                lines.append(
                    Text(
                        _("It is formatted as {fstype} and not mounted.").
                        format(fstype=fs.fstype)))
        elif hasattr(obj, 'partitions') and len(obj.partitions()) > 0:
            n = len(obj.partitions())
            if obj.type == "lvm_volgroup":
                if n == 1:
                    things = _("logical volume")
                else:
                    things = _("logical volumes")
            else:
                if n == 1:
                    things = _("partition")
                else:
                    things = _("partitions")
            lines.append(
                Text(
                    _("It contains {n} {things}:").format(n=n, things=things)))
            lines.append(Text(""))
            stretchy_index = len(lines)
            rows = []
            for p, cells in summarize_device(obj):
                if p not in [None, obj]:
                    rows.append(TableRow(cells))
            lines.append(TablePile(rows))
        else:
            lines.append(Text(_("It is not formatted or mounted.")))

        delete_btn = danger_btn(label=_("Delete"), on_press=self.confirm)
        widgets = lines + [
            Text(""),
            button_pile([
                delete_btn,
                other_btn(label=_("Cancel"), on_press=self.cancel),
            ]),
        ]
        super().__init__("", widgets, stretchy_index, len(lines) + 1)
Example #7
0
 def __init__(self):
     self.table = TablePile([], spacing=1)
     self.device_to_checkbox = {}
     self.device_to_selector = {}
     self.devices = {}  # {device:active|spare}
     self.all_rows = []
     self.no_selector_rows = []
     self.supports_spares = True
     super().__init__(self.table)
Example #8
0
    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)))
Example #9
0
    def _create(self):
        # Create the widget for a nic. This consists of a Pile containing a
        # table, an info line and a blank line. The first row of the table is
        # the one that can be focused and has a menu for manipulating the nic,
        # the other rows summarize its address config.
        #   [ name type notes   â–¸ ]   \
        #     address info            | <- table
        #     more address info       /
        #   mac / vendor info / model info
        #   <blank line>

        actions = []
        for action in NetDevAction:
            meth = getattr(self.parent, '_action_' + action.name)
            opens_dialog = getattr(meth, 'opens_dialog', False)
            if action in self.dev_info.enabled_actions:
                actions.append(
                    (action.str(), True, (action, meth), opens_dialog))

        menu = ActionMenu(actions)
        connect_signal(menu, 'action', self.parent._action, self)

        trows = [
            make_action_menu_row([
                Text("["),
                Text(self.dev_info.name),
                Text(self.dev_info.type),
                Text(self._notes(), wrap='clip'),
                menu,
                Text("]"),
            ], menu)
        ] + self._address_rows()

        self.table = TablePile(trows,
                               colspecs=self.parent.device_colspecs,
                               spacing=2)
        self.table.bind(self.parent.heading_table)

        if self.dev_info.type == "vlan":
            info = _("VLAN {id} on interface {link}").format(
                id=self.dev_info.vlan.id, link=self.dev_info.vlan.link)
        elif self.dev_info.type == "bond":
            info = _("bond master for {interfaces}").format(
                interfaces=', '.join(self.dev_info.bond.interfaces))
        else:
            info = " / ".join([
                self.dev_info.hwaddr,
                self.dev_info.vendor,
                self.dev_info.model,
            ])

        return Pile([
            ('pack', self.table),
            ('pack', Color.info_minor(Text("  " + info))),
            ('pack', Text("")),
        ])
Example #10
0
    def _device_widget(self, dev, netdev_i=None):
        # Create the widget for a nic. This consists of a Pile containing a
        # table, an info line and a blank line. The first row of the table is
        # the one that can be focused and has a menu for manipulating the nic,
        # the other rows summarize its address config.
        #   [ name type notes   â–¸ ]   \
        #     address info            | <- table
        #     more address info       /
        #   mac / vendor info / model info
        #   <blank line>
        if netdev_i is None:
            netdev_i = len(self.cur_netdevs)
        self.cur_netdevs[netdev_i:netdev_i] = [dev]

        actions = []
        for action in NetDevAction:
            meth = getattr(self, '_action_' + action.name)
            opens_dialog = getattr(meth, 'opens_dialog', False)
            if dev.supports_action(action):
                actions.append(
                    (_(action.value), True, (action, meth), opens_dialog))

        menu = ActionMenu(actions)
        connect_signal(menu, 'action', self._action, dev)

        trows = [
            make_action_menu_row([
                Text("["),
                Text(dev.name),
                Text(dev.type),
                Text(self._notes_for_device(dev), wrap='clip'),
                menu,
                Text("]"),
            ], menu)
        ] + self._address_rows_for_device(dev)

        table = TablePile(trows, colspecs=self.device_colspecs, spacing=2)
        self.dev_to_table[dev] = table
        table.bind(self.heading_table)

        if dev.type == "vlan":
            info = _("VLAN {id} on interface {link}").format(**dev.config)
        elif dev.type == "bond":
            info = _("bond master for {interfaces}").format(
                interfaces=', '.join(dev.config['interfaces']))
        else:
            info = " / ".join(
                [dev.info.hwaddr, dev.info.vendor, dev.info.model])

        return Pile([
            ('pack', table),
            ('pack', Color.info_minor(Text("  " + info))),
            ('pack', Text("")),
        ])
Example #11
0
 def __init__(self, parent):
     self.parent = parent
     self.table = TablePile([], spacing=2, colspecs={
         0: ColSpec(rpad=1),
         1: ColSpec(can_shrink=True),
         2: ColSpec(min_width=9),
         4: ColSpec(rpad=1),
         5: ColSpec(rpad=1),
     })
     self._no_mounts_content = Color.info_minor(
         Text(_("No disks or partitions mounted.")))
     super().__init__(self.table)
Example #12
0
 def _build_model_inputs(self):
     self.heading_table = TablePile([
         TableRow([
             Color.info_minor(Text(header)) for header in [
                 "", "NAME", "TYPE", "NOTES", "",
                 ]
             ])
         ],
         spacing=2, colspecs=self.device_colspecs)
     rows = [self.heading_table]
     for dev in self.model.get_all_netdevs():
         rows.append(self._device_widget(dev))
     return rows
Example #13
0
 def show_apply_spinner(self):
     s = Spinner(self.controller.loop)
     s.start()
     c = TablePile([
         TableRow([
             Text(_("Applying changes")),
             s,
             ]),
         ], align='center')
     self.bottom.contents[0:0] = [
         (c, self.bottom.options()),
         (Text(""), self.bottom.options()),
         ]
Example #14
0
def ssh_help_texts(ssh_info):

    texts = [_(SSH_HELP_PROLOGUE), ""]

    if ssh_info is not None:
        if len(ssh_info.ips) > 1:
            texts.append(_(SSH_HELP_MULTIPLE_ADDRESSES))
            texts.append("")
            for ip in ssh_info.ips:
                texts.append(Text(
                    "{}@{}".format(ssh_info.username, ip), align='center'))
        else:
            texts.append(_(SSH_HELP_ONE_ADDRESSES).format(
                username=ssh_info.username, ip=str(ssh_info.ips[0])))
        texts.append("")
        if ssh_info.authorized_key_fingerprints:
            if len(ssh_info.authorized_key_fingerprints) == 1:
                key = ssh_info.authorized_key_fingerprints[0]
                texts.append(Text(_(SSH_HELP_EPILOGUE_ONE_KEY).format(
                    keytype=key.keytype, fingerprint=key.fingerprint)))
            else:
                texts.append(_(SSH_HELP_EPILOGUE_MULTIPLE_KEYS))
                texts.append("")
                rows = []
                for key in ssh_info.authorized_key_fingerprints:
                    rows.append(
                        TableRow([Text(key.keytype), Text(key.fingerprint)]))
                texts.append(TablePile(rows))
            if ssh_info.password_kind == PasswordKind.KNOWN:
                texts.append("")
                texts.append(SSH_HELP_EPILOGUE_KNOWN_PASS_KEYS.format(
                    password=ssh_info.password))
            elif ssh_info.password_kind == PasswordKind.UNKNOWN:
                texts.append("")
                texts.append(SSH_HELP_EPILOGUE_UNKNOWN_PASS_KEYS)
        else:
            if ssh_info.password_kind == PasswordKind.KNOWN:
                texts.append(SSH_HELP_EPILOGUE_KNOWN_PASS_NO_KEYS.format(
                    password=ssh_info.password))
            elif ssh_info.password_kind == PasswordKind.UNKNOWN:
                texts.append(SSH_HELP_EPILOGUE_UNKNOWN_PASS_NO_KEYS)
        texts.append("")
        texts.append(Text(summarize_host_keys([
            (key.keytype, key.fingerprint)
            for key in ssh_info.host_key_fingerprints
            ])))
    else:
        texts.append("")
        texts.append(_(SSH_HELP_NO_ADDRESSES))

    return texts
Example #15
0
 def __init__(self, parent):
     self.parent = parent
     self.table = TablePile(
         [],
         spacing=2,
         colspecs={
             0: ColSpec(rpad=2),
             1: ColSpec(rpad=2),
             2: ColSpec(rpad=2),
             3: ColSpec(rpad=2),
         })
     self._no_zdev_content = Color.info_minor(
         Text(_("No zdev devices found.")))
     super().__init__(self.table)
Example #16
0
 def __init__(self, parent, disk):
     self.parent = parent
     dinfo = disk.info_for_display()
     rows = []
     for label, key in labels_keys:
         v = str(dinfo[key])
         rows.append(TableRow([Text(label, align='right'), Text(v)]))
     widgets = [
         TablePile(rows, colspecs={1: ColSpec(can_shrink=True)}),
         Text(""),
         button_pile([done_btn(_("Close"), on_press=self.close)]),
         ]
     title = _("Info for {}").format(disk.label)
     super().__init__(title, widgets, 0, 2)
Example #17
0
 def __init__(self, parent, show_available):
     self.parent = parent
     self.show_available = show_available
     self.table = TablePile([],  spacing=2, colspecs={
         0: ColSpec(rpad=1),
         2: ColSpec(can_shrink=True),
         4: ColSpec(min_width=9),
         5: ColSpec(rpad=1),
     })
     if show_available:
         text = _("No available devices")
     else:
         text = _("No used devices")
     self._no_devices_content = Color.info_minor(Text(text))
     super().__init__(self.table)
Example #18
0
 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
     connect_signal(self.use_lvm.widget, 'change', self._toggle)
     self.lvm_options.enabled = self.use_lvm.value
Example #19
0
    def _build_table(self):
        widget = self.widget
        if self.field.takes_default_style:
            widget = Color.string_input(widget)

        self.caption_text = Text(self.field.caption, align="right")
        self.under_text = Text(self.help)

        self._rows = [
            Toggleable(TableRow(row)) for row in [
                [self.caption_text,
                 _Validator(self, widget)],
                [Text(""), self.under_text],
            ]
        ]

        self._table = TablePile(self._rows, spacing=2, colspecs=form_colspecs)
Example #20
0
 def _guidance(self):
     todos = []
     if not self.model.is_root_mounted():
         todos.append(_("Mount a filesystem at /"))
     if self.model.needs_bootloader_partition():
         todos.append(_("Select a boot disk"))
     if not todos:
         return None
     rows = [
         TableRow([
             Text(_("To continue you need to:")),
             Text(todos[0]),
         ]),
     ]
     for todo in todos[1:]:
         rows.append(TableRow([Text(""), Text(todo)]))
     rows.append(TableRow([Text("")]))
     return TablePile(rows)
Example #21
0
 def __init__(self, app, parent):
     rows = []
     for key, text in GLOBAL_KEYS:
         rows.append(TableRow([Text(_(key)), Text(_(text))]))
     if app.opts.dry_run:
         for key, text in DRY_RUN_KEYS:
             rows.append(TableRow([Text(_(key)), Text(_(text))]))
     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(parent)]),
         ]
     super().__init__(_("Shortcut Keys"), widgets, 0, 2)
Example #22
0
 def __init__(self, parent):
     super().__init__(parent)
     options = []
     tables = []
     initial = -1
     for disk in parent.model.all_disks():
         for obj, cells in summarize_device(disk):
             table = TablePile([TableRow(cells)])
             tables.append(table)
             enabled = False
             if obj is disk and disk.size > 6 * (2**30):
                 enabled = True
                 if initial < 0:
                     initial = len(options)
             options.append(Option((table, enabled, obj)))
     t0 = tables[0]
     for t in tables[1:]:
         t0.bind(t)
     self.disk.widget.options = options
     self.disk.widget.index = initial
     connect_signal(self.use_lvm.widget, 'change', self._toggle)
     self.lvm_options.enabled = self.use_lvm.value
Example #23
0
 def __init__(self, parent, show_available):
     self.parent = parent
     self.show_available = show_available
     self.table = TablePile(
         [],
         spacing=2,
         colspecs={
             0: ColSpec(rpad=1),
             1: ColSpec(can_shrink=True),
             2: ColSpec(min_width=9),
             3: ColSpec(rpad=1),
             4: ColSpec(rpad=1),
         })
     if show_available:
         text = _("No available devices")
     else:
         text = _("No used devices")
     self._no_devices_content = Color.info_minor(Text(text))
     super().__init__(self.table)
     self.refresh_model_inputs()
     # I don't really know why this is required:
     self.table._select_first_selectable()
Example #24
0
    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'

        rows = [
            self.device_table,
            bp,
        ]

        buttons = button_pile([
                    done_btn(_("Done"), on_press=self.done),
                    back_btn(_("Back"), on_press=self.cancel),
                    ])
        self.bottom = Pile([
            ('pack', buttons),
        ])

        self.error_showing = False

        super().__init__(screen(
            rows=rows,
            buttons=self.bottom,
            focus_buttons=True,
            excerpt=self.excerpt))
Example #25
0
    def _build_table(self):
        widget = self.widget
        if self.field.takes_default_style:
            widget = Color.string_input(widget)

        if self.help is not NO_HELP:
            self.under_text = Text(self.help)
        else:
            self.under_text = Text("")
        if self.field.caption is NO_CAPTION:
            first_row = [(2, _Validator(self, widget))]
            second_row = [(2, self.under_text)]
        else:
            self.caption_text = Text(_(self.field.caption))

            if self.field.caption_first:
                self.caption_text.align = 'right'
                first_row = [self.caption_text, _Validator(self, widget)]
            else:
                first_row = [
                    _Validator(
                        self,
                        UrwidPadding(widget,
                                     align='right',
                                     width=widget_width(widget))),
                    self.caption_text,
                ]
            second_row = [Text(""), self.under_text]

        rows = [first_row]
        if self.help is not NO_HELP:
            rows.append(second_row)

        self._rows = [Toggleable(TableRow(row)) for row in rows]

        self._table = TablePile(self._rows, spacing=2, colspecs=form_colspecs)
Example #26
0
    def __init__(self, parent, snap, cur_channel):
        self.parent = parent
        self.snap = snap
        self.needs_focus = True

        self.description = Text(snap.description.replace('\r', '').strip())
        self.lb_description = ListBox([self.description])

        latest_update = datetime.datetime.min
        radio_group = []
        channel_rows = []
        for csi in snap.channels:
            latest_update = max(latest_update, csi.released_at)
            btn = StarRadioButton(radio_group,
                                  csi.channel_name,
                                  state=csi.channel_name == cur_channel,
                                  on_state_change=self.state_change,
                                  user_data=SnapSelection(
                                      channel=csi.channel_name,
                                      is_classic=csi.confinement == "classic"))
            channel_rows.append(
                Color.menu_button(
                    TableRow([
                        btn,
                        Text(csi.version),
                        Text("(" + csi.revision + ")"),
                        Text(humanize_size(csi.size)),
                        Text(format_datetime(csi.released_at)),
                        Text(csi.confinement),
                    ])))

        first_info_row = TableRow([
            (3, Text([
                ('info_minor', "LICENSE: "),
                snap.license,
            ],
                     wrap='clip')),
            (3,
             Text([
                 ('info_minor', "LAST UPDATED: "),
                 format_datetime(latest_update),
             ])),
        ])
        heading_row = Color.info_minor(
            TableRow([
                Text("CHANNEL"),
                (2, Text("VERSION")),
                Text("SIZE"),
                Text("PUBLISHED"),
                Text("CONFINEMENT"),
            ]))
        colspecs = {
            1: ColSpec(can_shrink=True),
        }
        info_table = TablePile([
            first_info_row,
            TableRow([Text("")]),
            heading_row,
        ],
                               spacing=2,
                               colspecs=colspecs)
        self.lb_channels = NoTabCyclingTableListBox(channel_rows,
                                                    spacing=2,
                                                    colspecs=colspecs)
        info_table.bind(self.lb_channels)
        self.info_padding = Padding.pull_1(info_table)

        publisher = [('info_minor header', "by: "), snap.publisher]
        if snap.verified:
            publisher.append(('verified header', ' \N{check mark}'))

        self.title = Columns([
            Text(snap.name),
            ('pack', Text(publisher, align='right')),
        ],
                             dividechars=1)

        contents = [
            ('pack', Text(snap.summary)),
            ('pack', Text("")),
            self.lb_description,  # overwritten in render()
            ('pack', Text("")),
            ('pack', self.info_padding),
            ('pack', Text("")),
            ('weight', 1, self.lb_channels),
        ]
        self.description_index = contents.index(self.lb_description)
        self.pile = Pile(contents)
        super().__init__(self.pile)