Source code for lookatme.widgets.table

"""
Defines a basic Table widget for urwid
"""


from collections import defaultdict
import urwid


from lookatme.render.markdown_block import render_text
import lookatme.config as config
from lookatme.utils import styled_text
from lookatme.widgets.clickable_text import ClickableText


[docs]class Table(urwid.Pile): """Create a table from a list of headers, alignment values, and rows. """ signals = ["change"] def __init__(self, rows, headers=None, aligns=None): """Create a new table :param list columns: The rows to use for the table :param list headers: (optional) Headers for the table :param list aligns: (optional) Alignment values for each column """ self.table_rows = rows self.table_headers = headers self.table_aligns = aligns if headers is not None: self.num_columns = len(headers) elif headers is None: self.num_columns = len(rows[0]) else: raise ValueError( "Invalid table specification: could not determine # of columns" ) def header_modifier(cell): return ClickableText(styled_text(cell, "bold"), align=cell.align) if self.table_headers is not None: self.rend_headers = self.create_cells( [self.table_headers], modifier=header_modifier ) else: self.rend_headers = [] self.rend_rows = self.create_cells(self.table_rows) self.column_maxes = self.calc_column_maxes() cell_spacing = config.STYLE["table"]["column_spacing"] self.total_width = sum(self.column_maxes.values()) + ( cell_spacing * (self.num_columns - 1) ) # final rows final_rows = [] # put headers in Columns if self.table_headers is not None: header_columns = [] for idx, header in enumerate(self.rend_headers[0]): header_with_div = urwid.Pile([ self.watch(header), urwid.Divider(config.STYLE["table"]["header_divider"]), ]) header_columns.append((self.column_maxes[idx], header_with_div)) final_rows.append(urwid.Columns(header_columns, cell_spacing)) for row_idx, rend_row in enumerate(self.rend_rows): row_columns = [] for idx, rend_cell in enumerate(rend_row): row_columns.append((self.column_maxes[idx], self.watch(rend_cell))) column_row = urwid.Columns(row_columns, cell_spacing) final_rows.append(column_row) urwid.Pile.__init__(self, final_rows)
[docs] def render(self, *args, **kwargs): """Do whatever needs to be done to render the table """ self.set_column_maxes() return urwid.Pile.render(self, *args, **kwargs)
[docs] def watch(self, w): """Watch the provided widget w for changes """ def wrapper(*args, **kwargs): self._invalidate() self._emit("change") urwid.connect_signal(w, "change", wrapper) return w
def _invalidate(self): self.set_column_maxes() urwid.Pile._invalidate(self)
[docs] def set_column_maxes(self): """Calculate and set the column maxes for this table """ self.column_maxes = self.calc_column_maxes() cell_spacing = config.STYLE["table"]["column_spacing"] self.total_width = sum(self.column_maxes.values()) + ( cell_spacing * (self.num_columns - 1) ) for columns, info in self.contents: # row should be a Columns instance new_columns = [] for idx, column_items in enumerate(columns.contents): column_widget, column_info = column_items new_columns.append(( column_widget, (column_info[0], self.column_maxes[idx], column_info[2]), )) columns.contents = new_columns
[docs] def calc_column_maxes(self): column_maxes = defaultdict(int) for row in self.rend_headers + self.rend_rows: for idx, cell in enumerate(row): if idx > self.num_columns: break column_maxes[idx] = max(column_maxes[idx], len(cell.text)) return column_maxes
[docs] def create_cells(self, body_rows, modifier=None): """Create the rows for the body, optionally calling a modifier function on each created cell Text. The modifier must accept an urwid.Text object and must return an urwid.Text object. """ res = [] for row in body_rows: rend_row = [] for idx, cell in enumerate(row): if idx >= self.num_columns: break rend_cell = render_text(text=cell) rend_cell.align = self.table_aligns[idx] or "left" if modifier is not None: rend_cell = modifier(rend_cell) rend_row.append(rend_cell) res.append(rend_row) return res