class SnapInfoView(WidgetWrap): # This is mostly like a Pile but it tries to be a bit smart about # how to distribute space between the description and channel list # (which can both be arbitrarily long or short). If both are long, # the channel list is given a third of the space. If there is # space for both, they are packed into the upper part of the view. 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 = NoTabCyclingTableListBox(self.channels) 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) def state_change(self, sender, state, selection): if state: self.parent.snap_boxes[self.snap.name].set_state(True) self.parent.to_install[self.snap.name] = selection def render(self, size, focus): maxcol, maxrow = size rows_available = maxrow pack_option = self.pile.options('pack') for w, o in self.pile.contents: if o == pack_option: rows_available -= w.rows((maxcol, ), focus) rows_wanted_description = self.description.rows((maxcol, ), False) rows_wanted_channels = len(self.channels) if rows_wanted_channels + rows_wanted_description <= rows_available: description_rows = rows_wanted_description else: if rows_wanted_description < 2 * rows_available / 3: description_rows = rows_wanted_description else: channel_rows = min(rows_wanted_channels, int(rows_available / 3)) description_rows = rows_available - channel_rows self.pile.contents[self.description_index] = (self.lb_description, self.pile.options( 'given', description_rows)) if description_rows >= rows_wanted_description: self.lb_description.base_widget._selectable = False else: self.lb_description.base_widget._selectable = True if self.needs_focus: self.pile._select_first_selectable() self.needs_focus = False return self.pile.render(size, focus)
class SnapInfoView(WidgetWrap): # This is mostly like a Pile but it tries to be a bit smart about # how to distribute space between the description and channel list # (which can both be arbitrarily long or short). If both are long, # the channel list is given a third of the space. If there is # space for both, they are packed into the upper part of the view. 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) def state_change(self, sender, state, selection): if state: self.parent.snap_boxes[self.snap.name].set_state(True) self.parent.to_install[self.snap.name] = selection def render(self, size, focus): maxcol, maxrow = size rows_available = maxrow pack_option = self.pile.options('pack') for w, o in self.pile.contents: if o == pack_option: rows_available -= w.rows((maxcol, ), focus) rows_wanted_description = self.description.rows((maxcol - 1, ), False) rows_wanted_channels = 0 for row in self.lb_channels._w.original_widget.body: rows_wanted_channels += row.rows((maxcol, ), False) log.debug('rows_available %s', rows_available) log.debug('rows_wanted_description %s rows_wanted_channels %s', rows_wanted_description, rows_wanted_channels) if rows_wanted_channels + rows_wanted_description <= rows_available: description_rows = rows_wanted_description channel_rows = rows_wanted_channels else: if rows_wanted_description < 2 * rows_available / 3: description_rows = rows_wanted_description channel_rows = rows_available - description_rows else: channel_rows = max( min(rows_wanted_channels, int(rows_available / 3)), 3) log.debug('channel_rows %s', channel_rows) description_rows = rows_available - channel_rows self.pile.contents[self.description_index] = (self.lb_description, self.pile.options( 'given', description_rows)) if description_rows >= rows_wanted_description: self.lb_description.base_widget._selectable = False else: self.lb_description.base_widget._selectable = True if channel_rows >= rows_wanted_channels: self.info_padding.right = 0 else: self.info_padding.right = 1 if self.needs_focus: self.pile._select_first_selectable() self.needs_focus = False return self.pile.render(size, focus)