tuikit/tableview.py
author Radek Brich <radek.brich@devl.cz>
Sat, 08 Oct 2011 17:16:07 +0200
changeset 21 8553a6bd2d82
child 22 6ca8b2d221c3
permissions -rw-r--r--
Add TableView plus demo.

# -*- coding: utf-8 -*-

import math

from tuikit.widget import EventSource, Widget
from tuikit.common import Coords


class TableModel(EventSource):
    def __init__(self, list_of_lists):
        EventSource.__init__(self)
        self.addevents('change')
        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='', expand=True, sizereq=1,
        header=False, readonly=False, maxlength=None):
        '''Create column with default values.'''
        
        self.title = title
        '''Column title'''
        
        self.expand = expand
        '''Use 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 = 0
        '''Computed size of column.'''
        
        self.header = header
        '''Is this header column? (bold, readonly)'''
        
        self.readonly = readonly
        '''Allow edit?'''
        
        self.maxlength = maxlength
        '''Maximum length of value (for EditField).'''


class TableView(Widget):
    def __init__(self, model=None, width=20, height=20):
        Widget.__init__(self, width, height)
        
        # model
        self._model = None
        self.setmodel(model)
        
        self.columns = []
        self.spacing = 1
        
        self.offset = Coords()
        
        self.connect('draw', self.on_draw)
        self.connect('keypress', self.on_keypress)
    
    def getmodel(self):
        return self._model
    
    def setmodel(self, value):
        if self._model:
            self._model.disconnect('change', self.redraw)
        self._model = value
        if self._model:
            self._model.connect('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.size.w - 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
    
    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):
        for col, data in zip(self.columns, row):
            if col.header:
                screen.pushcolor('strong')
            screen.puts(x, y, data[:col.size])
            if col.header:
                screen.popcolor()
            x += col.size + self.spacing
    
    def on_draw(self, screen, x, y):
        screen.pushcolor('normal') 
        head_size = 1
        numrows = min(self.model.getcount() - self.offset.y, self.size.h - head_size)
        rows = self.model.getrows(self.offset.y, self.offset.y + numrows)
        self.compute_column_sizes()
        self.draw_head(screen, x, y)
        y += head_size
        for row in rows:
            self.draw_row(screen, x, y, row)
            y += 1
        screen.popcolor()        

    def on_keypress(self, keyname, char):
        if keyname:
            if keyname == 'up':    self.move_up()
            if keyname == 'down':  self.move_down()
            if keyname == 'left':  self.move_left()
            if keyname == 'right': self.move_right()
        self.redraw()