#!/usr/bin/python # Copyright (C) 2004 Henning Jacobs # # 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("", self._resize_columns) self.text.bind("", self._text_click) self.text.bind("", self._text_click) self.text.bind("", 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()