Propagate "quit" event, do not just terminate application. Resize: flag widgets to be resized, do resizes only once before draw. Draw: flag widgets to be redrawn, do not draw everything on any event.
# -*- coding: utf-8 -*-
import math
import logging
from tuikit.events import Event, Emitter
from tuikit.widget import Widget
from tuikit.common import Coords
class TableModel(Emitter):
def __init__(self, list_of_lists):
self.add_events('change', Event)
self.data = list_of_lists
def getcount(self):
'''Get number of rows.'''
return len(self.data)
def getrows(self, begin, end):
'''Get rows from begin to end, including begin, excluding end.'''
return self.data[begin:end]
def update(self, row, col, val):
self.data[row][col] = val
class Column:
'''Columns description.'''
def __init__(self, title='', header=False, expand=True, sizereq=1,
readonly=False, maxlength=None):
'''Create column with default values.'''
self.title = title
'''Title is displayed in heading before first row.'''
self.header = header
'''Header column is highlighted, values in this column cannot be edited.'''
self.expand = expand
'''If true, this column will autoresize to consume any free space.'''
self.sizereq = sizereq
'''Size request. Meaning depends on value of expand:
When false, sizereq is number of characters.
When true, sizereq is relative size ratio.
'''
self.size = None
'''Computed size of column.'''
self.index = None
'''Computed index.'''
self.readonly = readonly
'''If not readonly, values in this column can be changed by user.'''
self.maxlength = maxlength
'''Maximum length of value (for EditField).'''
class TableView(Widget):
def __init__(self, model=None):
Widget.__init__(self)
self._default_size.update(20, 20)
self.allow_focus = True
# model
self._model = None
self.setmodel(model)
self.columns = []
self.spacing = 1
self.rowcount = 0
self.headsize = 1
self.offset = Coords()
#: Active cell (cursor)
self.acell = Coords()
self.add_events(
'scroll', Event,
'areasize', Event)
def getmodel(self):
return self._model
def setmodel(self, value):
if self._model:
self._model.remove_handler('change', self.redraw)
self._model = value
if self._model:
self._model.add_handler('change', self.redraw)
model = property(getmodel, setmodel)
def addcolumn(self, *args, **kwargs):
for col in args:
self.columns.append(col)
if len(args) == 0:
col = Column(**kwargs)
self.columns.append(col)
def compute_column_sizes(self):
total_space = self.width - self.spacing * len(self.columns)
no_expand_cols = [col for col in self.columns if not col.expand]
no_expand_size = sum([col.sizereq for col in no_expand_cols])
expand_cols = [col for col in self.columns if col.expand]
expand_num = len(expand_cols)
expand_size = total_space - no_expand_size
# compute size of cols without expand
for col in no_expand_cols:
col.size = col.sizereq
# compute size of cols with expand
if no_expand_size > total_space + expand_num:
for col in expand_cols:
col.size = 1
else:
total_req = sum([col.sizereq for col in expand_cols])
remaining_space = 0.
for col in expand_cols:
frac, intp = math.modf(expand_size * col.sizereq / total_req)
col.size = int(intp)
remaining_space += frac
if remaining_space > 0.99:
remaining_space -= 1.
col.size += 1
# compute indexes
idx = 0
for col in self.columns:
if not col.header:
col.index = idx
idx += 1
def draw_head(self, screen, x, y):
screen.pushcolor('strong')
for col in self.columns:
screen.puts(x, y, col.title[:col.size])
x += col.size + self.spacing
screen.popcolor()
def draw_row(self, screen, x, y, row, highlight):
for col, data in zip(self.columns, row):
if col.header:
screen.pushcolor('strong')
elif col.index in highlight:
screen.pushcolor('active')
else:
screen.pushcolor('normal')
screen.puts(x, y, data[:col.size])
screen.popcolor()
x += col.size + self.spacing
def on_draw(self, ev):
ev.driver.pushcolor('normal')
ev.driver.fill_clip()
self.rowcount = self.model.getcount()
numrows = min(self.rowcount - self.offset.y, self.height - self.headsize)
rows = self.model.getrows(self.offset.y, self.offset.y + numrows)
self.compute_column_sizes()
self.draw_head(ev.driver, ev.x, ev.y)
y = ev.y + self.headsize
for row in rows:
highlight = []
if self.offset.y + rows.index(row) == self.acell.y:
highlight.append(self.acell.x)
self.draw_row(ev.driver, ev.x, y, row, highlight)
y += 1
ev.driver.popcolor()
def on_keypress(self, ev):
key_map = {
'up': self.move_up,
'down': self.move_down,
'left': self.move_left,
'right': self.move_right,
'pageup': self.move_pageup,
'pagedown': self.move_pagedown}
if ev.keyname in key_map:
key_map[ev.keyname]()
self.redraw()
return True
def set_yofs(self, yofs):
if yofs > self.rowcount - (self.height - self.headsize):
yofs = self.rowcount - (self.height - self.headsize)
if yofs < 0:
yofs = 0
self.offset.y = yofs
self.emit('scroll')
def move_up(self):
if self.acell.y > 0:
self.acell.y -= 1
if self.acell.y < self.offset.y:
self.set_yofs(self.acell.y)
return True
return False
def move_down(self):
log=logging.getLogger('tuikit')
log.debug('height %d', self.height)
if self.acell.y < self.rowcount - 1:
self.acell.y += 1
if self.acell.y > self.offset.y + (self.height - self.headsize - 1):
self.set_yofs(self.acell.y - (self.height - self.headsize - 1))
return True
return False
def move_pageup(self):
if self.acell.y >= self.height - self.headsize - 1:
self.acell.y -= self.height - self.headsize - 1
self.set_yofs(self.offset.y - (self.height - self.headsize - 1))
else:
self.acell.y = 0
self.set_yofs(0)
def move_pagedown(self):
if self.acell.y <= self.rowcount - (self.height - self.headsize - 1):
self.acell.y += self.height - self.headsize - 1
self.set_yofs(self.offset.y + (self.height - self.headsize - 1))
else:
self.acell.y = self.rowcount - 1
self.set_yofs(self.acell.y)
def move_left(self):
if self.acell.x > 0:
self.acell.x -= 1
return True
return False
def move_right(self):
if self.acell.x < len([col for col in self.columns if not col.header]) - 1:
self.acell.x += 1
return True
return False