#!/usr/bin/python
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  $Id: MultiColumnTextList.py 82 2004-07-11 13:01:44Z henning $

from Tkinter import *
import Pmw
import tkFont
import sys

class MultiColumnTextList(Frame):
    "Tkinter.Text wird hier als Tabelle missbraucht"
    colsepwidth = 20
    def __init__(self, master, **kws):
        Frame.__init__(self, master)
        self.rows = []
        self.columnwidths = []
        self.columnstringwidths = {}
        options = ['selectcommand','dblclickcommand','autocolresize']
        self.selectcommand = kws.get('selectcommand', None)
        self.dblclickcommand = kws.get('dblclickcommand', None)
        self.autocolresize = kws.get('autocolresize', 1)
        if kws.get('columnheader', 0):
            kws['columnheader'] = 1
            kws['columnheader_state'] = DISABLED
            kws['columnheader_takefocus'] = 0
            kws['columnheader_highlightthickness'] = 0
            kws['columnheader_cursor'] = self.cget('cursor')
            kws['columnheader_background'] = self.cget('background')
            kws['columnheader_borderwidth'] = 1
        # Remove our own Options from Dict:    
        for opt in options:
            try: del kws[opt]
            except: pass
        if sys.platform != 'win32':
            # Thin Scrollbars:
            kws['horizscrollbar_borderwidth'] = 1
            kws['horizscrollbar_width'] = 10
            kws['vertscrollbar_borderwidth'] = 1
            kws['vertscrollbar_width'] = 10
        self.scrolledtext = Pmw.ScrolledText(self,
            # no distance between text and scrollbars:
            scrollmargin=0,
            text_wrap=NONE,
            text_state=DISABLED,
            text_takefocus=0,
            text_highlightthickness=0,
            text_cursor=self.cget('cursor'),
            **kws)
        self.text = self.scrolledtext.component('text')    
        if kws.get('columnheader', 0):
            self.columnheader = self.scrolledtext.component('columnheader')
        else: self.columnheader = None    
        self.text.bind("<Configure>", self._resize_columns)
        self.text.bind("<Button-1>", self._text_click)
        self.text.bind("<Button-2>", self._text_click)
        self.text.bind("<Button-3>", self._text_click)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.scrolledtext.grid(sticky=W+E+N+S)
        # Create Tag for selected Row (Line):
        self.text.tag_config("rowsel", 
            foreground=self.text.cget('selectforeground'),
            background=self.text.cget('selectbackground'),
            borderwidth=self.text.cget('selectborderwidth'),
            relief=RAISED)
        # Create Tag for Odd Rows (Lines):
        self.text.tag_config("odd", 
            background='#eeeeff')
        # Make Selection invisible:
        self.text.config(
            selectforeground=self.text.cget('foreground'),
            selectbackground=self.text.cget('background'),
            selectborderwidth=0)
        self.font = tkFont.Font(font = self.text.cget('font'))
    def append(self, row):
        "Append a new row"
        self.insert(-1, row)
    def insert(self, idx, row):
        "Insert row before idx"
        self.rows.insert(idx, row)
        colwidths_havechanged = 0
        if self.autocolresize:
            for i in range(len(row)):
                # First, we compare the string length..
                if len(row[i]) > self.columnstringwidths.get(i, 0):
                    self.columnstringwidths[i] = len(row[i])
                    # we measure only the longest string,
                    # because font.measure is VERY TIME CONSUMING!!
                    wd = self.font.measure(row[i])+self.colsepwidth
                    if i > len(self.columnwidths)-1:
                        self.columnwidths.append(wd)
                        colwidths_havechanged = 1
                    elif wd > self.columnwidths[i]:
                        self.columnwidths[i] = wd
                        colwidths_havechanged = 1
        if idx == -1:
            CharIndex = END
            absidx = len(self.rows)-1
        else:
            CharIndex = "%d.0" % (idx+1)
            absidx = idx
        self.text["state"] = NORMAL
        self.text.insert(CharIndex, self._row2textline(row))
        self.text["state"] = DISABLED
        self._updateRowTags(range(absidx-1, len(self.rows)))
        if colwidths_havechanged:
            self._resize_columns()
        if len(self.rows) == 1:
            self.select(0)
    def remove(self, idx):
        "Delete Row at idx"
        self.text["state"] = NORMAL
        self.text.delete("%d.0" % (idx+1), "%d.0" % (idx+2))
        self.text["state"] = DISABLED
        del self.rows[idx]              
        if len(self.rows) == 0:
            self.select(None)
        else:
            self._updateRowTags(range(idx-1, len(self.rows)))
    def clear(self):
        "Delete all Rows."
        self.text["state"] = NORMAL
        self.text.delete("1.0", END)
        self.text["state"] = DISABLED
        self.rows[:] = []
        self.select(None)
    def get(self, idx=None):
        "Return row at idx or list of all rows"
        if idx == None:
            return self.rows
        else:
            try:
                return self.rows[idx]
            except:
                return None
    def length(self):
        "Return number of rows."
        return len(self.rows)
    def selected(self):
        "Return index of selected row."
        return self.selectedrow
    def _row2textline(self, row):
        return "\t".join(row)+"\t\n"
    def _resize_columns(self, event=None):
        "Do column resizing"
        tabs = []
        prevcolend = 0
        for i in range(len(self.columnwidths)-1):
            tabs.append(self.columnwidths[i]+prevcolend)
            prevcolend = tabs[i]        
        # to make the line appear as a complete row,
        # append another tab-stopp at widget width:
        if self.columnwidths: lasttab=max(self.columnwidths[-1]+prevcolend,self.text.winfo_width())
        else: lasttab = self.text.winfo_width()
        tabs.append(lasttab)
        tabs = tuple(map(str, tabs))
        self.text["tabs"] = tabs
        if self.columnheader:
            self.columnheader["tabs"] = tabs
    def _updateRowTags(self, range):
        for i in range:
            if i is not None:
                if i == self.selectedrow:
                    self.text.tag_remove("odd", "%s.0" % (i+1), "%s.end" % (i+1)) 
                    self.text.tag_add("rowsel", "%s.0" % (i+1), "%s.end" % (i+1)) 
                elif i % 2 == 1:
                    self.text.tag_remove("rowsel", "%s.0" % (i+1), "%s.end" % (i+1)) 
                    self.text.tag_add("odd", "%s.0" % (i+1), "%s.end" % (i+1)) 
                else:
                    self.text.tag_remove("rowsel", "%s.0" % (i+1), "%s.end" % (i+1)) 
                    self.text.tag_remove("odd", "%s.0" % (i+1), "%s.end" % (i+1))
    def setcolwidths(self, widths):
        self.columnwidths = widths
        self.columnstringwidths.clear()
        self._resize_columns()
    def setcolwidthsfromstr(self, row): 
        self.columnwidths[:] = []
        for i in range(len(row)):
            wd = self.font.measure(row[i])+self.colsepwidth
            self.columnwidths.append(wd)
        self.columnstringwidths.clear()
        self._resize_columns()
    def setcolheader(self, header):
        self.columnheader["state"] = NORMAL
        self.columnheader.delete('1.0',END)
        self.columnheader.insert('1.0',self._row2textline(header))
        self.columnheader["state"] = DISABLED

    selectedrow = None
    def select(self, idx):
        "Select Row or None."
        if idx >= len(self.rows):
            idx = None
        lastsel = self.selectedrow
        self.selectedrow = idx
        self._updateRowTags([idx, lastsel])
    def see(self, idx):
        "Scroll to line number idx"
        self.text.see("%d.0" % idx)
    _lastmouseclicktime = 0     
    _lastmouseclickrowidx = None
    def _text_click(self, event):
        "User selected a row by click"
        line, char = self.text.index("@%d,%d" % (event.x, event.y)).split('.')
        idx = int(line)-1
        self.select(idx)
        if self.dblclickcommand\
          and event.time - self._lastmouseclicktime <= 400\
          and self._lastmouseclickrowidx == idx:
            self.dblclickcommand()
        elif self.selectcommand:
            self.selectcommand()
        self._lastmouseclicktime = event.time
        self._lastmouseclickrowidx = idx
        

if __name__ == "__main__":
    tk = Tk()
    list = MultiColumnTextList(tk)
    list.pack(fill=BOTH,expand=1)
    list.append(('Datum','Typ','Vorgang','?'))
    list.append(('2003-10-10','Kommentar','Ja, Dies ist mein Test-Kommentar!','What''s that?'))
    list.append(('2003-10-11','Note','Note how the column-widths grow with the content!','Another Cell'))
    list.append(('This','Line','should','HAVE BEEN DELETED!'))
    list.append(('2003-10-11','Test','123','456'))
    list.insert(1, ('This','should appear','as the','second line!'))
    list.remove(4)
    tk.mainloop()
        
