import numbers
from .. import Element
def empty_grid(n_rows, n_columns):
if n_rows < 0 or not isinstance(n_rows, numbers.Integral):
raise TypeError('number of rows must be non-negative integer')
if n_columns < 0 or not isinstance(n_columns, numbers.Integral):
raise TypeError('number of columns must be non-negative integer')
return [[None for j in range(n_columns)] for i in range(n_rows)]
def smallest_fitting_dimensions(cells):
return (len(cells), (max(len(row) for row in cells) if cells else 0))
[docs]class Grid(Element):
"""A two-dimensional grid of elements.
A grid's number of rows and columns are given by `n_rows` and `n_columns`.
Those properties may also be set, to change the number of rows and columns.
Grids are indexable by pairs of non-negative integers, e.g.
>>> my_grid[0, 0]
>>> my_grid[3, 2] = Text('hi')
>>> my_grid[1, 2] = None
>>> del my_grid[3, 3]
"""
def __init__(self, cells=(), n_rows=None, n_columns=None, **kwargs):
super(Grid, self).__init__(tag_name='table', **kwargs)
self.css['border-spacing'] = '0'
self.css['border-collapse'] = 'collapse'
if not all(all(isinstance(x, Element) or x is None for x in row) for row in cells):
raise TypeError('cell contents must be Elements')
if not (cells or (n_rows is not None and n_columns is not None)):
raise ValueError("can't guess dimensions for Grid")
self._n_rows = 0
self._n_columns = 0
self._cells = []
if cells:
self.n_rows, self.n_columns = smallest_fitting_dimensions(cells)
else:
self.n_rows, self.n_columns = n_rows, n_columns
for (i, row) in enumerate(cells):
for (j, cell) in enumerate(row):
if cell is not None:
self[i,j] = cell
@property
def n_rows(self):
return self._n_rows
@n_rows.setter
def n_rows(self, value):
if value < 0 or not isinstance(value, numbers.Integral):
raise TypeError('number of rows must be non-negative integer')
if value < self.n_rows:
for i in range(value, self.n_rows):
for j in range(self.n_columns):
if self[i,j] is not None:
del self[i,j]
for tr in self.tag.childNodes[value:]:
self.tag.removeChild(tr)
self._cells = self._cells[:value]
else:
while len(self._cells) < value:
self._cells.append([None]*self.n_columns)
tr = self._new_tr()
for j in range(self.n_columns):
td = self._new_td()
tr.appendChild(td)
self.tag.appendChild(tr)
self._n_rows = value
self.mark_dirty()
@property
def n_columns(self):
return self._n_columns
@n_columns.setter
def n_columns(self, value):
if value < 0 or not isinstance(value, numbers.Integral):
raise TypeError('number of columns must be non-negative integer')
if value < self.n_columns:
for i in range(self.n_rows):
for j in range(value, self.n_columns):
if self[i,j] is not None:
del self[i,j]
self._cells[i] = self._cells[i][:value]
for td in self.tag.childNodes[i].childNodes[value:]:
self.tag.childNodes[i].removeChild(td)
else:
for i, row in enumerate(self._cells):
while len(row) < value:
row.append(None)
td = self._new_td()
self.tag.childNodes[i].appendChild(td)
self._n_columns = value
self.mark_dirty()
def _new_tr(self):
return self.tag.ownerDocument.createElement('tr')
def _new_td(self):
td = self.tag.ownerDocument.createElement('td')
td.setAttribute('style', 'border: 1px solid black')
return td
def __getitem__(self, indices):
(i, j) = indices
if isinstance(i, slice):
rows = self._cells[i]
return [row[j] for row in rows]
else:
return self._cells[i][j]
def __setitem__(self, indices, child):
(i, j) = indices
if isinstance(i, slice) or isinstance(j, slice):
raise NotImplementedError("slice assignment to Grids not yet supported")
td = self.tag.childNodes[i].childNodes[j]
old_child = self._cells[i][j]
if old_child is not None:
td.removeChild(td.childNodes[0])
self._cells[i][j] = child
td.appendChild(child.tag)
self.mark_dirty()
def __delitem__(self, indices):
(i, j) = indices
if isinstance(i, slice) or isinstance(j, slice):
raise NotImplementedError("slice deletion from Grids not yet supported")
old_child = self._cells[i][j]
self._cells[i][j] = None
old_child.tag.parentNode.removeChild(old_child.tag)
self.mark_dirty()
@classmethod
def make_column(cls, *elements, **kwargs):
return cls(cells=[[e] for e in elements], **kwargs)
@classmethod
def make_row(cls, *elements, **kwargs):
return cls(cells=[elements], **kwargs)