tuikit/tableview.py
author Radek Brich <radek.brich@devl.cz>
Mon, 10 Oct 2011 22:20:59 +0200
changeset 25 f69a1f0382ce
parent 22 6ca8b2d221c3
child 32 088b92ffb119
permissions -rw-r--r--
Partial DriverPygame.putch() implementation. Add patch for PyGame implementing font.render_glyph(). Add test for PyGame font module. Add special key definitions to DriverPygame.

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

import math
import logging

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='', 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, width=20, height=20):
        Widget.__init__(self, width, height)
        
        # model
        self._model = None
        self.setmodel(model)
        
        self.columns = []
        self.spacing = 1
        self.rowcount = 0
        self.headsize = 1
        
        self.offset = Coords()
        self.acell = Coords()
        '''Active cell (cursor).'''
        
        self.connect('draw', self.on_draw)
        self.connect('keypress', self.on_keypress)
        
        self.addevents('scroll', 'areasize')
    
    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
        
        # 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, screen, x, y):
        screen.pushcolor('normal') 
        self.rowcount = self.model.getcount()
        numrows = min(self.rowcount - self.offset.y, self.size.h - self.headsize)
        rows = self.model.getrows(self.offset.y, self.offset.y + numrows)
        self.compute_column_sizes()
        self.draw_head(screen, x, y)
        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(screen, x, y, row, highlight)
            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()
            if keyname == 'pageup':  self.move_pageup()
            if keyname == 'pagedown':  self.move_pagedown()
        self.redraw()

    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.handle('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