Exemplo n.º 1
0
 def _showhide_other(self, show):
     if show and not self._other_showing:
         self._w.contents.append(
             (Columns([(1, Text("/")),
                       Color.string_input(self._other)]),
              self._w.options('pack')))
         self._other_showing = True
     elif not show and self._other_showing:
         del self._w.contents[-1]
         self._other_showing = False
 def add_event(self, text):
     walker = self.event_listbox.base_widget.body
     if len(walker) > 0:
         # Remove the spinner from the line it is currently on, if
         # there is one.
         walker[-1] = walker[-1][0]
     # Add spinner to the line we are inserting.
     new_line = Columns([('pack', Text(text)), ('pack', self.spinner)],
                        dividechars=1)
     self._add_line(self.event_listbox, new_line)
Exemplo n.º 3
0
 def make_body(self):
     self.error_text = Text("", align="center")
     return Pile([
         Text(_("Please press one of the following keys:")),
         Text(""),
         Columns([Text(s, align="center") for s in self.step.symbols],
                 dividechars=1),
         Text(""),
         Color.info_error(self.error_text),
     ])
Exemplo n.º 4
0
 def make_body(self):
     self.error_text = Text("", align="center")
     self.pile = Pile([
         ('pack', Text(_("Please press one of the following keys:"))),
         ('pack', Text("")),
         ('pack',
          Columns([Text(s, align="center") for s in self.step.symbols],
                  dividechars=1)),
     ])
     return self.pile
Exemplo n.º 5
0
 def _build_bondmode_configuration(self):
     log.debug('bond: _build_bondmode_configuration')
     items = [
         Text("BOND CONFIGURATION"),
         Columns([("weight", 0.2, Text("Bonding Mode", align="right")),
                  ("weight", 0.3, Color.string_input(self.bond_mode))],
                 dividechars=4),
     ]
     log.debug('bond_mode: items: {}'.format(items))
     return Pile(items)
Exemplo n.º 6
0
    def _build_model_inputs(self):
        partitioned_disks = []

        def format_volume(label, part):
            size = humanize_size(part.size)
            if part.fs() is None:
                fstype = '-'
                mountpoint = '-'
            elif part.fs().mount() is None:
                fstype = part.fs().fstype
                mountpoint = '-'
            else:
                fstype = part.fs().fstype
                mountpoint = part.fs().mount().path
            if part.type == 'disk':
                part_btn = menu_btn(label, on_press=self._click_disk)
            else:
                part_btn = menu_btn(label,
                                    on_press=self._click_part,
                                    user_arg=part)
            return Columns([
                (25, part_btn),
                (9, Text(size, align="right")),
                Text(fstype),
                Text(mountpoint),
            ], 2)

        if self.disk.fs() is not None:
            partitioned_disks.append(format_volume("entire disk", self.disk))
        else:
            for part in self.disk.partitions():
                partitioned_disks.append(
                    format_volume("Partition {}".format(part.number), part))
        if self.disk.free > 0:
            free_space = humanize_size(self.disk.free)
            if len(self.disk.partitions()) > 0:
                label = "Add another partition"
            else:
                label = "Add first partition"
            add_btn = menu_btn(label, on_press=self.add_partition)
            partitioned_disks.append(
                Columns([
                    (25, add_btn),
                    (9, Text(free_space, align="right")),
                    Text("free space"),
                ], 2))
        if len(self.disk.partitions()) == 0 and \
           self.disk.available:
            text = ("Format or create swap on entire "
                    "device (unusual, advanced)")
            partitioned_disks.append(Text(""))
            partitioned_disks.append(
                menu_btn(label=text, on_press=self.format_entire))

        return partitioned_disks
Exemplo n.º 7
0
 def event_start(self, context_id, context_parent_id, message):
     self.event_finish(context_parent_id)
     walker = self.event_listbox.base_widget.body
     spinner = Spinner(self.controller.app.aio_loop)
     spinner.start()
     new_line = Columns([
         ('pack', Text(message)),
         ('pack', spinner),
         ], dividechars=1)
     self.ongoing[context_id] = len(walker)
     self._add_line(self.event_listbox, new_line)
Exemplo n.º 8
0
 def as_row(self, longest_caption):
     if self.pile is not None:
         raise RuntimeError("do not call as_row more than once!")
     self._longest_caption = longest_caption
     self.help_text.set_text(self.help)
     cols = [
         (self._longest_caption, Text("")),
         self.help_text,
     ]
     self.pile = Pile([self._cols(), Columns(cols, dividechars=2)])
     return self.pile
Exemplo n.º 9
0
 def _build_menu(self):
     items = []
     for label, sig in self.model.get_menu():
         items.append(
             Columns([("weight", 0.2, Text("")),
                      ("weight", 0.3,
                       Color.menu_button(
                           menu_btn(label=label,
                                    on_press=self.confirm,
                                    user_data=sig)))]))
     return Pile(items)
Exemplo n.º 10
0
 def _build_model_inputs(self):
     sl = [
         Columns(
             [
                 ("weight", 0.2, Text("Email address:", align="right")),
                 ("weight", 0.3, Color.string_input(self.email)),
             ],
             dividechars=4
         ),
     ]
     return Pile(sl)
Exemplo n.º 11
0
    def __init__(self, parent, snap, cur_channel):
        self.parent = parent
        self.snap = snap
        self.channels = []
        self.needs_focus = True

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

        radio_group = []
        for csi in snap.channels:
            notes = '-'
            if csi.confinement != "strict":
                notes = csi.confinement
            btn = StarRadioButton(radio_group,
                                  "{}:".format(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"))
            self.channels.append(
                Color.menu_button(
                    TableRow([
                        btn,
                        Text(csi.version),
                        Text("({})".format(csi.revision)),
                        Text(humanize_size(csi.size)),
                        Text(notes),
                    ])))

        self.lb_channels = Table(self.channels,
                                 container_maker=NoTabCyclingListBox)

        title = Columns([
            Text(snap.name),
            ('pack',
             Text(_("Publisher: {}").format(snap.publisher), align='right')),
        ],
                        dividechars=1)

        contents = [
            ('pack', title),
            ('pack', Text("")),
            ('pack', Text(snap.summary)),
            ('pack', Text("")),
            self.lb_description,  # overwritten in render()
            ('pack', Text("")),
            ('weight', 1, self.lb_channels),
        ]
        self.description_index = contents.index(self.lb_description)
        self.pile = Pile(contents)
        super().__init__(self.pile)
Exemplo n.º 12
0
 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.help_text = Text(self.help, align="center")
     cols = [
         (self._longest_caption, Text("")),
         self.help_text,
     ]
     self.pile = Pile([self._cols(), Columns(cols, dividechars=2)])
     return self.pile
Exemplo n.º 13
0
 def __init__(self, parent, snap, max_name_len, max_publisher_len):
     self.parent = parent
     self.snap = snap
     self.box = StarCheckBox(snap.name, on_state_change=self.state_change)
     self.name_and_publisher_width = (max_name_len +
                                      self.box.reserve_columns +
                                      max_publisher_len + 2)
     self.two_column = Color.menu_button(
         Columns([
             (max_name_len + self.box.reserve_columns, self.box),
             Text(snap.summary, wrap='clip'),
         ],
                 dividechars=1))
     self.three_column = Color.menu_button(
         Columns([
             (max_name_len + 4, self.box),
             (max_publisher_len, Text(snap.publisher)),
             Text(snap.summary, wrap='clip'),
         ],
                 dividechars=1))
     super().__init__(self.two_column)
Exemplo n.º 14
0
 def _cols(self):
     text = Text(self.caption, align="right")
     if self._enabled:
         input = Color.string_input(_Validator(self, self.widget))
     else:
         input = self.widget
     cols = [(self._longest_caption, text), input]
     cols = Columns(cols, dividechars=2)
     if self._enabled:
         return cols
     else:
         return WidgetDisable(Color.info_minor(cols))
Exemplo n.º 15
0
 def __init__(self):
     self.mode = "spinning"
     self.spinner = Spinner()
     self.label = Text("", wrap='clip')
     cols = Color.progress_incomplete(Columns([
         (1, Text("")),
         self.label,
         (1, Text("")),
         (1, self.spinner),
         (1, Text("")),
         ]))
     super().__init__(cols)
Exemplo n.º 16
0
    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
Exemplo n.º 17
0
 def event_start(self, context, message):
     self.event_finish(context.parent)
     walker = self.event_listbox.base_widget.body
     indent = context.full_name().count('/') - 2
     if context.get('is-install-context'):
         indent -= 1
     spinner = Spinner(self.controller.app.aio_loop)
     spinner.start()
     new_line = Columns([
         ('pack', Text('  ' * indent + message)),
         ('pack', spinner),
         ], dividechars=1)
     self.ongoing[context] = len(walker)
     self._add_line(self.event_listbox, new_line)
Exemplo n.º 18
0
    def __init__(self, opts, index=0):
        self._icon = ClickableThing(Text(""))
        self._padding = UrwidPadding(
            AttrWrap(
                Columns([
                    (1, Text('[')),
                    self._icon,
                    (3, Text('\N{BLACK DOWN-POINTING SMALL TRIANGLE} ]')),
                ],
                        dividechars=1), 'menu_button', 'menu_button focus'))

        options = []
        for opt in opts:
            options.append(Option(opt))

        self.options = options
        self._set_index(index)
        super().__init__(_Launcher(self, self._padding))
Exemplo n.º 19
0
 def _cols(self):
     text = Text(self.caption, align="right")
     if self._enabled:
         input = Color.string_input(_Validator(self, self.widget))
     else:
         input = self.widget
     if self.help is not None:
         help = Help(self.parent_view, self.help)
     else:
         help = Text("")
     cols = [
         (self._longest_caption, text),
         input,
         (3, help),
     ]
     cols = Columns(cols, dividechars=2)
     if self._enabled:
         return cols
     else:
         return WidgetDisable(Color.info_minor(cols))
Exemplo n.º 20
0
 def show_overlay(self, overlay_widget, **kw):
     args = dict(align='center',
                 width=('relative', 60),
                 min_width=80,
                 valign='middle',
                 height='pack')
     PADDING = 3
     # Don't expect callers to account for the padding if
     # they pass a fixed width.
     if 'width' in kw:
         if isinstance(kw['width'], int):
             kw['width'] += 2 * PADDING
     args.update(kw)
     top = Pile([
         ('pack', Text("")),
         Columns([(PADDING, Text("")), overlay_widget,
                  (PADDING, Text(""))]),
         ('pack', Text("")),
     ])
     self._w = Overlay(top_w=top, bottom_w=disabled(self._w), **args)
Exemplo n.º 21
0
    def _build_filesystem_list(self):
        log.debug('FileSystemView: building part list')
        cols = []
        mount_point_text = _("MOUNT POINT")
        longest_path = len(mount_point_text)
        for m in sorted(self.model._mounts, key=lambda m: m.path):
            path = m.path
            longest_path = max(longest_path, len(path))
            for p, *dummy 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.")))])
        size_text = _("SIZE")
        type_text = _("TYPE")
        size_width = max(len(size_text), 9)
        type_width = max(len(type_text), self.model.longest_fs_name)
        cols.insert(
            0,
            (None, mount_point_text, size_text, type_text, _("DEVICE TYPE")))
        pl = []
        for dummy, 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)), (size_width, b),
                         (type_width, Text(c)),
                         Text(d)], 4))
        return Pile(pl)
Exemplo n.º 22
0
 def format_volume(label, part):
     size = humanize_size(part.size)
     if part.fs() is None:
          fstype = '-'
          mountpoint = '-'
     elif part.fs().mount() is None:
         fstype = part.fs().fstype
         mountpoint = '-'
     else:
         fstype = part.fs().fstype
         mountpoint = part.fs().mount().path
     if part.type == 'disk':
         part_btn = menu_btn(label, on_press=self._click_disk)
     else:
         part_btn = menu_btn(label, on_press=self._click_part, user_arg=part)
     return Columns([
         (label_width, part_btn),
         (9, Text(size, align="right")),
         Text(fstype),
         Text(mountpoint),
     ], 2)
Exemplo n.º 23
0
 def __init__(self, parent):
     self.parent = parent
     close_text = "(close)"
     close = ActionBackButton(close_text)
     connect_signal(close, "click", self.close)
     group = [Color.menu_button(close)]
     width = len(close_text)
     for i, action in enumerate(self.parent._actions):
         if action.enabled:
             if isinstance(action.label, Widget):
                 btn = action.label
             elif action.opens_dialog:
                 btn = Color.menu_button(ActionMenuOpenButton(action.label))
             else:
                 btn = Color.menu_button(ActionMenuButton(action.label))
             width = max(width, len(btn.base_widget.label))
             connect_signal(btn.base_widget, 'click', self.click,
                            action.value)
         else:
             label = action.label
             if isinstance(label, Widget):
                 label = label.base_widget.label
             width = max(width, len(label))
             if action.opens_dialog:
                 rhs = "\N{BLACK RIGHT-POINTING SMALL TRIANGLE}"
             else:
                 rhs = ""
             btn = Columns([
                 ('fixed', 1, Text("")),
                 Text(label),
                 ('fixed', 1, Text(rhs)),
             ],
                           dividechars=1)
             btn = AttrWrap(btn, 'info_minor')
         group.append(btn)
     self.width = width
     super().__init__(LineBox(ListBox(group)))
Exemplo n.º 24
0
 def __init__(self, parent, cur_index):
     self.parent = parent
     group = []
     for i, option in enumerate(self.parent._options):
         if option.enabled:
             btn = ClickableIcon(" " + option.label)
             connect_signal(btn, 'click', self.click, i)
             if i == cur_index:
                 rhs = '\N{BLACK LEFT-POINTING SMALL TRIANGLE} '
             else:
                 rhs = '  '
             btn = Columns([
                 btn,
                 (2, Text(rhs)),
             ])
             btn = AttrWrap(btn, 'menu_button', 'menu_button focus')
         else:
             btn = Text(" " + option.label)
             btn = AttrWrap(btn, 'info_minor')
         btn = UrwidPadding(btn, width=self.parent._padding.width)
         group.append(btn)
     list_box = ListBox(group)
     list_box.base_widget.focus_position = cur_index
     super().__init__(Color.body(LineBox(list_box)))
Exemplo n.º 25
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)
Exemplo n.º 26
0
 def col3(col1, col2, col3):
     inputs.append(Columns([(40, col1), (10, col2), (10, col3)], 2))
Exemplo n.º 27
0
class SSHImport(WidgetWrap, WantsToKnowFormField):

    signals = ['change']

    _helps = {
        None: _("You can import your SSH keys from Github, Launchpad or Ubuntu One."),
        "gh": _("Enter your github username."),
        "lp": _("Enter your Launchpad username."),
        "sso": _("Enter an email address associated with your Ubuntu One account."),
        }

    def __init__(self):
        choices = [
            (_("No"), True, None),
            (_("from Github"), True, "gh"),
            (_("from Launchpad"), True, "lp"),
            (_("from Ubuntu One account"), True, "sso"),
            ]
        self.selector = Selector(choices)
        connect_signal(self.selector, 'select', self._select)
        self.username = UsernameEditor()
        self.email = EmailEditor()
        connect_signal(self.username, 'change', self._change)
        self.cols = Columns([
            self.selector,
            (1, Text("")),
            (2, Color.body(Text(""))),
            Color.body(Text(""))])
        super().__init__(self.cols)

    def _change(self, sender, val):
        self._emit('change', val)

    def set_bound_form_field(self, bff):
        self.bff = bff
        self.username.set_bound_form_field(bff)
        # Get things set up for the initial selection.
        self._select(self.selector, None)

    def _select(self, sender, val):
        label = sender.option_by_value(val).label
        self.cols.contents[0] = (self.cols.contents[0][0], self.cols.options('given', len(label) + 4))
        if val is not None:
            if val == 'sso':
                editor = self.email
            else:
                editor = self.username
            self.cols.contents[3] = (editor, self.cols.options())
            self.cols[1].set_text(":")
            self.cols.focus_position = 3
        else:
            self.username.set_edit_text("")
            self.cols[1].set_text("")
            self.cols.contents[3] = (Color.body(Text("")), self.cols.options())
        self.bff.help = self._helps[val]

    @property
    def value(self):
        v = self.selector.value
        if v is not None:
            return v + ":" + self.username.value
Exemplo n.º 28
0
 def col2(col1, col2):
     inputs.append(Columns([(40, col1), col2], 2))
Exemplo n.º 29
0
 def col1(col1):
     inputs.append(Columns([(40, col1)], 1))
Exemplo n.º 30
0
class TableRow(WidgetWrap):
    """A row in a table.

    A wrapper around a Columns. The widths will be set when rendered.
    """

    # To allow for variable padding between columns, we can't use
    # Columns.dividechars. Instead, the padding is implemented as
    # extra columns. This complicates things because "column i"
    # becomes a bit ambiguous -- does it mean the i'th column from the
    # user's POV, or does it mean the i'th column in the Columns from
    # urwid's POV? This code tries to refer to the former as the "user
    # index" and the latter as the "underlying index". Mapping from
    # one to the other is pretty simple: underlying-index = 2*user-index.

    def __init__(self, cells):
        """cells is a list of [widget] or [(colspan, widget)].

        colspan is assumed to be 1 if omitted.
        """
        self.cells = []
        cols = []
        for cell in cells:
            colspan = 1
            if isinstance(cell, tuple):
                colspan, cell = cell
            assert colspan > 0
            self.cells.append((colspan, cell))
            cols.append(cell)
            cols.append(urwid.Text(""))
        del cols[-1]
        self.columns = Columns(cols)
        super().__init__(self.columns)

    def selectable(self):
        for w, _ in self._w.contents:
            if w.selectable():
                return True
        return False

    def _user_indices_cells(self):
        """Yield the user indices each cell spans and the cell.
        """
        user_i = 0
        for colspan, cell in self.cells:
            yield range(user_i, user_i+colspan), cell
            user_i += colspan

    def get_natural_widths(self, unpacked_cols):
        """Return a mapping {underlying-index:natural-width}.

        Cells spanning multiple columns are ignored (handled in
        adjust_for_spanning_cells).
        """
        widths = {}
        for user_indices, cell in self._user_indices_cells():
            if len(user_indices) == 1 and user_indices[0] not in unpacked_cols:
                widths[2*user_indices[0]] = widget_width(cell)
        return widths

    def adjust_for_spanning_cells(self, unpacked_user_indices, widths):
        """Make sure columns are wide enough for cells with colspan > 1.

        This very roughly follows the approach in
        https://www.w3.org/TR/CSS2/tables.html#width-layout.
        """
        for user_indices, cell in self._user_indices_cells():
            if set(user_indices) & unpacked_user_indices:
                continue
            user_indices = [
                user_i for user_i in user_indices if widths[2*user_i] > 0]
            if len(user_indices) <= 1:
                continue
            cur_width = _width(widths, user_indices)
            cell_width = widget_width(cell)
            if cur_width < cell_width:
                # Attempt to widen each column by about the same amount.
                # But widen the first few columns by more if that's
                # whats needed.
                div, mod = divmod(cell_width - cur_width, len(user_indices))
                for i, user_j in enumerate(user_indices):
                    widths[2*user_j] += div + int(i < mod)

    def set_widths(self, widths):
        """Configure row to given widths.

        `widths` is a mapping {underlying-index:width}. An index being
        missing means let the column shrink, a width being 0 means omit
        the column entirely.
        """
        cols = []
        for user_indices, cell in self._user_indices_cells():
            try:
                width = _width(widths, user_indices)
            except KeyError:
                opt = self.columns.options('weight', 1)
            else:
                if width == 0:
                    continue
                opt = self.columns.options('given', width)
            cols.append((cell, opt))
            n = widths.get(2*max(user_indices) + 1, 0)
            if n:
                cols.append((urwid.Text(""), self.columns.options('given', n)))
        self.columns.contents[:] = cols