#!/usr/bin/env python
#
# $Id: Table.py,v 1.5 2002/04/30 11:09:11 doughellmann Exp $
#
# Copyright 2001 Doug Hellmann.
#
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Doug
# Hellmann not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
"""A Table widget for displaying tabular data.
"""
__rcs_info__ = {
#
# Creation Information
#
'module_name' : '$RCSfile: Table.py,v $',
'rcs_id' : '$Id: Table.py,v 1.5 2002/04/30 11:09:11 doughellmann Exp $',
'creator' : 'Doug Hellmann <doug@hellfly.net>',
'project' : 'PmwContribD',
'created' : 'Sat, 05-May-2001 13:26:12 EDT',
#
# Current Information
#
'author' : '$Author: doughellmann $',
'version' : '$Revision: 1.5 $',
'date' : '$Date: 2002/04/30 11:09:11 $',
}
#
# Import system modules
#
import Tkinter, Canvas, tkFont
import UserList
import string
import math
#
# Import Local modules
#
from ShadowBox import ShadowBox
class BorderLessCanvasGroup(Canvas.Group):
"""A subclass of Canvas.Group which knows how to do bbox.
"""
def __init__(self, canvas, width=1):
self.width = width
Canvas.Group.__init__(self, canvas)
def bbox(self):
"Returns the bounding box of the items in the group."
objs = self.canvas.find_withtag(self.tag)
#ulx,uly,lrx,lry = 0,0, 0,0
ulx,uly,lrx,lry = 100000,100000, 0,0
for item in objs:
x1,y1,x2,y2 = self.canvas.bbox(item)
x1 = x1 + self.width
y1 = y1 + self.width
x2 = x2 - self.width
y2 = y2 - self.width
if x1 < ulx: ulx = x1
if x2 > lrx: lrx = x2
if y1 < uly: uly = y1
if y2 > lry: lry = y2
return ( (ulx,uly), (lrx,lry) )
#
# Module
#
class Column:
"""A single Column in a Table.
"""
def __init__(
self,
title,
datasource,
width,
rowheight,
column=0,
titletextcolor='black',
titlebackground='grey',
celltextcolor='black',
cellbackground='white',
celloutline='black',
titlealignment='center',
cellalignment='sw',
cellpadding=2,
icellpadding=1,
font=('Helvetica', '-12'),
*args, **kw):
#
self.titlecell = None
# where we get our data
self._datasource = datasource
# how we display the data
self.title = title
# this column number
self.column = column
# auto-calculate width if we have not been given it
self.font = font
if width == 0:
self.width = self.auto_width()
else:
self.width = width
self.rowheight = rowheight
self.titletextcolor=titletextcolor
self.titlebackground=titlebackground
self.celltextcolor=celltextcolor
self.cellbackground=cellbackground
self.celloutline=celloutline
self.titlealignment = titlealignment
self.cellalignment = cellalignment
self.cellpadding = cellpadding
self.icellpadding = icellpadding
self.visibleCells = {}
return
def auto_width(self):
"""Calculate the width of this column automatically.
The algorithm assumes that 'm' is the longest character. The
screen width of the 'm' character in the current font is used
to estimate the screen width of all of the row values, and the
longest estimate is used as the default width.
"""
f = tkFont.Font(font=self.font)
w = f.measure('m')
# now find longest string in data
maxwidth = len(self.title)
for row in range(-1, len(self._datasource)):
maxwidth = max(maxwidth, len(self.getRowStr(row)))
return maxwidth * w
def __str__(self):
return str(self.title)
def getRowStr(self, row):
"""Get the string to display for a specified row.
"""
if row == -1:
return self.title
return str(self._datasource(row, self.column))
def numRows(self):
"How many rows does the column have?"
return len(self._datasource)
def __len__(self):
return self.numRows()
def columnWidth(self):
"How wide is the column on the screen?"
return self.width
def hideCanvasObjects(self, rowRange, offScreenFlag):
"""Hide the objects related to the rows in the specified range.
If the offScreenFlag is true, also hide the title cell for the
column.
"""
#print '"%s".hideCanvasObjects(%s, %s)' % (self,
# rowRange,
# offScreenFlag)
if offScreenFlag:
self.titlecell.delete()
self.titlecell = None
for cellNum in rowRange:
try:
cell = self.visibleCells[cellNum]
if cell:
cell.delete()
self.visibleCells[cellNum] = None
except KeyError:
pass
return
def showCanvasObjects(self, canvas, leftEdgeX, startRow, endRow):
"""Show the objects related to the rows in the specified range.
This is the primary "paint" method.
"""
#print '"%s".showCanvasObjects leftX = %d, rows: %d-%d)' % \
# (self, leftEdgeX, startRow, endRow)
#
# Deal with the title cell. It should never
# scroll off of the screen, and might not
# exist when we start.
#
if not self.titlecell:
# self.createCanvasObjects(canvas)
self.titlecell = self.createTitleCell(canvas, self.title)
leftEdgeX = leftEdgeX + self.cellpadding
bbox = self.titlecell.bbox()
deltaX = (leftEdgeX - bbox[0][0])
deltaY = (0 - bbox[0][1])
self.titlecell.move( deltaX, deltaY )
#print 'new title cell bbox is ', self.titlecell.bbox()
#
# Now handle the regular cells. Determine
# the initial yOffset based on the height
# of the title cell.
#
yOffset = self.cellpadding + self.rowheight + self.cellpadding
for row in range(startRow, endRow):
#
# Check for an existing cell representation
#
try:
cell = self.visibleCells[row]
except KeyError:
cell = None
#
# If there is no cell, create it.
#
if not cell:
try:
cell = self.createCell(canvas, row, leftEdgeX, yOffset)
except IndexError:
#
# The data source will cause an IndexError
# when we try to go too far through the
# rows.
#
break
self.visibleCells[row] = cell
#
# Set the yOffset so that the next cell
# will be just below this one
#
## yOffset = cell.bbox()[1][1] + self.cellpadding
yOffset += self.rowheight + self.cellpadding
else:
#
# If there was a cell, move it into
# position.
#
bbox = cell.bbox()
ulx = bbox[0][0]
uly = bbox[0][1]
#deltaX = leftEdgeX - ulx
deltaX = leftEdgeX - ulx
deltaY = yOffset - uly
if deltaX or deltaY:
cell.move(deltaX, deltaY)
#
# Set the yOffset so that the next cell
# will be just below this one
#
yOffset = yOffset + self.rowheight + self.cellpadding
return
def alignmentPoint(self, alignment, bbox):
"""Find the point on the 'bbox' around which we are aligning an object.
The 'alignment' value is a string such as would be passed as
an alignment argument to 'pack' or another geometry manager.
If 'alignment' includes
's' - (South) -- Use the bottom of the 'bbox' for the Y coordinate.
'n' - (North) -- Use the top of the 'bbox' for the Y coordinate.
'w' - (West) -- Use the left side of the 'bbox' for the X coordinate.
'e' - (East) -- Use the right side of the 'bbox' for the X coordinate.
If no instruction is found for one of the coordinates, the
default behavior is to use the center of the 'bbox' for that
axis.
"""
ulx = bbox[0][0] + self.icellpadding
uly = bbox[0][1] + self.icellpadding
lrx = bbox[1][0] - self.icellpadding
lry = bbox[1][1] - self.icellpadding
xcenter = (ulx + lrx)/2
ycenter = (uly + lry)/2
#print 'getting alignment point for %s in ' % alignment, bbox
if alignment == 'center':
return (xcenter, ycenter)
x = xcenter
y = ycenter
if 's' in alignment:
y = lry
elif 'n' in alignment:
y = uly
else:
y = ycenter
if 'w' in alignment:
x = ulx
elif 'e' in alignment:
x = lrx
else:
x = xcenter
return (x,y)
def createCell(self, canvas, row, ulx, uly):
"""
Create the cell containing the information in
the specified row of the table.
"""
width = self.width
height = self.rowheight
#
# Create the outline rectangle for
# the cell
#
text=self.getRowStr(row)
newCell = Canvas.Rectangle(
canvas,
ulx, uly,
ulx + width, uly + height,
fill=self.cellbackground,
#outline=self.cellbackground,
outline=self.celloutline,
width=1,
#width=2,
#width=0,
)
#
# Create the text for the cell
#
point = self.alignmentPoint(self.cellalignment, newCell.bbox())
newCellText = Canvas.CanvasText(
canvas,
point[0], point[1],
text=text,
fill=self.celltextcolor,
anchor=self.cellalignment,
)
#
# Create a group through which both
# cell objects can be managed
#
newCellGroup = BorderLessCanvasGroup(canvas)
newCell.addtag(newCellGroup)
newCellText.addtag(newCellGroup)
return newCellGroup
def createTitleCell(self, canvas, label):
"""Create the cell for the column title.
"""
titleCell = ShadowBox(
canvas,
ulx=0,
uly=0,
width=self.width,
#height=self.rowheight + (self.cellpadding * 2),
height=self.rowheight,
background=self.titlebackground,
)
point = self.alignmentPoint('center', titleCell.bbox())
titleText = Canvas.CanvasText(
canvas,
point[0], point[1],
text=label,
fill=self.titletextcolor,
)
titleCell.add_object(titleText)
return titleCell
# def createCanvasObjects(self, canvas):
# """
# Create the on-screen objects to display
# this column.
# """
# self.titlecell = self.createTitleCell(canvas, self.title)
# return
class RowLabelColumn(Column):
"""Column for holding the row labels.
"""
def __init__(self, *args, **kw):
apply(Column.__init__, (self,) + args, kw)
def createTitleCell(self, canvas, label):
titleCell = ShadowBox(
canvas,
ulx=0,
uly=0,
width=self.width,
height=self.rowheight,
background=self.titlebackground,
)
return titleCell
def createCell(self, canvas, row, ulx, uly):
newCell = Column.createTitleCell(self, canvas, str(row+1))
(x1,y1),(x2,y2) = newCell.bbox()
newCell.move( ulx - x1, uly - y1 )
return newCell
class ColumnDataSource(UserList.UserList):
"""A data source for a Column.
"""
def __call__(self, row, col=0):
#return self.data[row]
return self[row]
class Table(Tkinter.Frame):
"""A widget for displaying tabular data.
"""
def __init__(
self,
parent,
cellpadding=2,
rowheight=15,
xfreeze=1,
font=('Helvetica', '-12'),
rowlabels=1,
keybindings=1,
*args, **kw):
"""Construct a Table.
Arguments
parent -- Parent Tk widget.
cellpadding=2 -- Space in pixels between cells.
rowheight=15 -- Height in pixels of rows.
xfreeze=1 -- Number of columns from the left edge to hold
locked on the screen. These columns do not
scroll horizontally.
font=('Helvetica', '-12') -- Font specification for contents of cells.
rowlabels=1 -- Boolean controlling whether rows are labeled
as columns are.
keybindings=1 -- Boolean controlling whether default
keybindings are applied.
"""
#
# Get our specific parameters
#
self.cellpadding = cellpadding
self.rowheight = rowheight
#
# Handle the rest of the init
#
apply(Tkinter.Frame.__init__, (self, parent) + args, kw)
#
# Create some member data variables
#
self.rightColumn = 0
self.topRow = 0
self.bottomRow = 0
self.numRows = 1
self._columns = []
self.xfreeze = max(xfreeze, rowlabels)
self.leftColumn = self.xfreeze
self.font = font
self.rowlabels = rowlabels
self.createwidgets()
if keybindings:
self._bind(self.canvas)
return
def createwidgets(self):
"Create the widget components of the Table."
#
# Scrollbars
#
self.horizSB = Tkinter.Scrollbar(
self,
orient='horizontal',
command=self._xview,
)
self.horizSB.pack(
side=Tkinter.BOTTOM,
expand=Tkinter.NO,
fill=Tkinter.X,
)
self.vertSB = Tkinter.Scrollbar(
self,
orient='vertical',
command=self._yview,
)
self.vertSB.pack(
side=Tkinter.RIGHT,
expand=Tkinter.NO,
fill=Tkinter.Y,
)
#
# Canvas drawing area
#
self.canvas = Tkinter.Canvas(
self,
background='white',
xscrollcommand=self.horizSB.set,
yscrollcommand=self.vertSB.set,
)
self.canvas.pack(
side=Tkinter.TOP,
anchor=Tkinter.W,
expand=Tkinter.YES,
fill=Tkinter.BOTH,
)
self.canvas.bind('<Configure>', self.configureEventHandler)
self.titleSlopRectangle = None
self.cellSlopRectangle = None
return
def _bind(self, widget):
"Apply default keybindings to allow convenient movement with keyboard."
widget.focus_set()
widget.bind('<Next>', self.scrolldownpage)
widget.bind('<Prior>', self.scrolluppage)
widget.bind('<Down>', self.scrolldownline)
widget.bind('<Up>', self.scrollupline)
widget.bind('<Home>', self.scrollbegin)
widget.bind('<End>', self.scrollend)
widget.bind('<Right>', self.scrollright)
widget.bind('<Left>', self.scrollleft)
widget.bind('<Control-Right>', self.scrollrightpage)
widget.bind('<Control-Left>', self.scrollleftpage)
return
def scrolldownpage(self, event):
"Handle page down event."
self._yview('scroll', '1', 'page')
return
def scrolluppage(self, event):
"Handle page up event."
self._yview('scroll', '-1', 'page')
return
def scrolldownline(self, event):
"Handle scroll down event."
self._yview('scroll', '1', 'units')
return
def scrollupline(self, event):
"Handle scroll up event."
self._yview('scroll', '-1', 'units')
return
def scrollright(self, event):
"Handle scroll right event."
self._xview('scroll', '1', 'units')
return
def scrollleft(self, event):
"Handle scroll left event."
self._xview('scroll', '-1', 'units')
return
def scrollrightpage(self, event):
"Handle page right event."
self._xview('scroll', '1', 'page')
return
def scrollleftpage(self, event):
"Handle page left event."
self._xview('scroll', '-1', 'page')
return
def scrollend(self, event):
"Handle 'end' event."
self._yview('moveto', '1.0')
return
def scrollbegin(self, event):
"Handle 'begin' event."
self._yview('moveto', '0.0')
return
def addcolumn(self, column):
"""Add the column to the list of columns managed by this Table.
"""
self._columns.append(column)
self.numRows = max([self.numRows, column.numRows()])
return
def configureEventHandler(self, event=None):
"""Event handler for configure event on the canvas.
"""
rightColumn = self._findRightColumnFromLeft(self.leftColumn,1)
leftColumn = self._findLeftColumnFromRight(rightColumn)
rightColumn = self._findRightColumnFromLeft(self.leftColumn)
bottomRow = self._findBottomRowFromTop(self.topRow)
topRow = self._findTopRowFromBottom(bottomRow)
self.setVisibleArea(
leftColumn, rightColumn,
topRow, bottomRow
)
self.canvas.after_idle(self.updateScrollbars)
return
def clearScreenBeforeMove(self, newLeft, newRight, newTop, newBottom):
"""Clear anything that used to be in the display range but isn't any more.
"""
# print '\nclearScreenBeforeMove(%d, %d, %d, %d)' % (newLeft,
# newRight,
# newTop,
# newBottom)
# print ' current settings: (%d, %d, %d, %d)' % (
# self.leftColumn, self.rightColumn,
# self.topRow, self.bottomRow)
if ( (self.leftColumn >= newLeft) and
(self.rightColumn <= newRight) and
(self.topRow >= newTop) and
(self.bottomRow <= newBottom)
):
# no changes needed
# print 'no clearing needed'
return
#
# Figure out the column situation
#
colRange = None
rowRange = None
if ( (newLeft <= self.leftColumn) and (newRight < self.rightColumn) ):
#print '\tmoved left'
# moved view window to the left
# so we need to clear off the right edge
colRange = xrange(newRight, self.rightColumn)
xcolRange = xrange(self.leftColumn, newRight)
elif ( (newRight >= self.rightColumn) and (newLeft > self.leftColumn) ):
#print '\tmoved right'
# moved view window to the right
# so we need to clear off the left edge
colRange = xrange(self.leftColumn, newLeft)
xcolRange = xrange(newRight, self.rightColumn)
#
# Figure out the row situation
#
if ( (newTop <= self.topRow) and (newBottom < self.bottomRow) ):
#print '\tmoved up'
# moved view window up
# so we need to clear off the bottom
rowRange = xrange(newBottom, self.bottomRow)
elif ( (newBottom >= self.bottomRow) and (newTop > self.topRow) ):
#print '\tmoved down'
# moved view window DOWN
# so we need to clear off the top
rowRange = xrange(self.topRow, newTop)
#
# Clear stuff from the screen
#
# Watch out in case both x and y have changed
if colRange != None and rowRange != None:
# erase all rows in colRange
self._hideCanvasObjects(xrange(self.topRow, self.bottomRow),
colRange, newLeft, newRight)
# set colRange so code below will result in all
# cells in xcolRange, rowRange will be erased
colRange = xcolRange
# now None means all columns/rows
if rowRange == None:
rowRange = xrange(self.topRow, self.bottomRow)
if colRange == None:
colRange = xrange(self.leftColumn, self.rightColumn)
# erase what is left to erase
self._hideCanvasObjects(rowRange, colRange, newLeft, newRight)
# do any frozen columns
for columnNum in range(self.xfreeze):
column = self._columns[columnNum]
self._columns[columnNum].hideCanvasObjects(rowRange, 0)
return
def _hideCanvasObjects(self, rowRange, colRange, newLeft, newRight):
"""Hide the objects related to the columns and rows in the specified range.
"""
# print 'erasing rows/cols:', rowRange, colRange
for columnNum in colRange:
column = self._columns[columnNum]
if (columnNum < newLeft) or (columnNum >= newRight):
offScreen = 1
else:
offScreen = 0
self._columns[columnNum].hideCanvasObjects(rowRange, offScreen)
return
def refresh(self):
"""Refresh screen.
For example, after sorting the datasource.
"""
rowRange = xrange(self.topRow, self.bottomRow)
colRange = xrange(self.leftColumn, self.rightColumn)
self._hideCanvasObjects(rowRange, colRange,
self.leftColumn, self.rightColumn)
self.setVisibleArea(self.leftColumn, self.rightColumn,
self.topRow, self.bottomRow)
return
def setVisibleArea(self, left, right, top, bottom):
"""Update which columns are displayed.
"""
#
# Clear what just moved off of the screen
#
self.clearScreenBeforeMove(left, right, top, bottom)
#
# Update the internal state
#
self.topRow = top
self.bottomRow = bottom
self.leftColumn = left
self.rightColumn = right
#
# Update the scrollbars
#
self.updateScrollbars()
#
# Update the canvas
#
offsetX = self.cellpadding
# do any frozen columns
for columnNum in range(self.xfreeze):
column = self._columns[columnNum]
column.showCanvasObjects(self.canvas, offsetX, top, bottom)
offsetX = offsetX + column.width + self.cellpadding
# now do non-frozen columns
for columnNum in range(left, right):
column = self._columns[columnNum]
column.showCanvasObjects(self.canvas, offsetX, top, bottom)
offsetX = offsetX + column.width + self.cellpadding
#slop for the title
if not self.titleSlopRectangle:
self.titleSlopRectangle = Canvas.Rectangle(
self.canvas,
0, 0,
10, 10,
fill='grey',
outline='grey',
)
bbox = self._columns[left].titlecell.bbox()
slopHeight = bbox[1][1] - bbox[0][1] - 1
self.titleSlopRectangle.coords(
( (offsetX, 0),
(self.canvas.winfo_width(),
slopHeight)
)
)
self.titleSlopRectangle.tkraise()
# slop for the cells
if not self.cellSlopRectangle:
self.cellSlopRectangle = Canvas.Rectangle(
self.canvas,
0, 0,
10, 10,
fill='white',
outline='white'
)
bbox = self.titleSlopRectangle.bbox()
self.cellSlopRectangle.coords (
( (bbox[0][0],bbox[1][1]),
(bbox[1][0],self.canvas.winfo_height())
)
)
return
def updateScrollbars(self):
"""
Compute the values for start and stop of the scrollbars
and call their set methods to set them correctly.
"""
self.updateHScrollbar()
self.updateVScrollbar()
return
def updateVScrollbar(self):
"""Update the settings of the vertical scrollbar.
"""
bottomRow = self._findBottomRowFromTop(self.topRow)
if bottomRow == 0:
top = 0
bottom = 0
else:
top = self.topRow * 1.0 / (self.numRows + 1)
bottom = bottomRow * 1.0 / (self.numRows + 1)
self.vertSB.set( top, bottom )
return
def updateHScrollbar(self):
"""Update the settings of the horizontal scrollbar.
"""
rightColumn = self._findRightColumnFromLeft(self.leftColumn, 1)
numColumns = len(self._columns) - self.xfreeze
if rightColumn == 0:
left = 0
right = 0
else:
left = (self.leftColumn - self.xfreeze) * 1.0 / numColumns
right = (rightColumn - self.xfreeze) * 1.0 / numColumns
self.horizSB.set( left, right )
return
def _findBottomRowFromTop(self, topRow):
"Determine the last visible row on the screen, given the first."
height = self.canvas.winfo_height()
visibleRows = math.floor(height / (self.rowheight +
self.cellpadding))
lastRow = min(self.numRows+1, topRow + visibleRows)
return lastRow
def _findTopRowFromBottom(self, bottomRow):
"Determine the first visible row on the screen, given the last."
height = self.canvas.winfo_height()
visibleRows = math.ceil(height / (self.rowheight +
self.cellpadding))
firstRow = max(0, bottomRow - visibleRows)
return firstRow
def _findRightColumnFromLeft(self, leftColumn, fullonly=0):
"""
Given a left column number, determine the
right-most column which can be displayed if
that column is the left-most column on the
screen.
"""
if not self._columns:
rightColumn = 0
else:
width = self.canvas.winfo_width()
for x in range(self.xfreeze):
column = self._columns[x]
width = width - (column.columnWidth() +
self.cellpadding)
rightColumn = leftColumn
columnWidths = 0
while (
(columnWidths <= width) and
(rightColumn < len(self._columns))
):
column = self._columns[rightColumn]
widthIncr = column.columnWidth()
#print 'widthincr', widthIncr
#print 'columnWidths', columnWidths
#print 'spacing', self.cellpadding
columnWidths = (columnWidths +
widthIncr +
self.cellpadding)
rightColumn = rightColumn + 1
if columnWidths > width and fullonly:
rightColumn = max(0, rightColumn-1)
return rightColumn
def _findLeftColumnFromRight(self, rightColumn):
"""
Given a right column number, determine the
left-most column which can be displayed if
that column is the right-most on the screen.
"""
if not self._columns:
return 0
else:
width = self.canvas.winfo_width()
for x in range(self.xfreeze):
column = self._columns[x]
width = width - (column.columnWidth() +
self.cellpadding)
leftColumn = rightColumn - 1
columnWidths = 0
while (
(columnWidths <= width) and
(leftColumn >= self.xfreeze)
):
column = self._columns[leftColumn]
widthIncr = column.columnWidth()
columnWidths = (columnWidths +
widthIncr +
self.cellpadding)
leftColumn = leftColumn - 1
leftColumn = leftColumn + 1
if columnWidths > width:
leftColumn = leftColumn + 1
if leftColumn < self.xfreeze:
leftColumn = self.xfreeze
return leftColumn
def _xview(self, mode, value, units=None):
"""Callback for horizontal scrollbar.
"""
# print 'xview', mode, value, units
if mode == 'moveto':
# fraction of number of columns
value = max(0, min(string.atof(value), 1.0))
newLeft = (self.xfreeze +
int( math.floor( value * (len(self._columns) - self.xfreeze))))
# Do not let the indicator move too far to the right
newRight = self._findRightColumnFromLeft(newLeft, 1)
while (newLeft >= self.xfreeze and
newRight == self._findRightColumnFromLeft(newLeft, 1) ):
newLeft = newLeft - 1
newLeft = newLeft + 1
elif units == 'units':
# increment one column at a time
moveBy = string.atoi(value)
if (self.leftColumn == self.xfreeze) and (moveBy < 0):
return
currentRight = self._findRightColumnFromLeft(self.leftColumn, 1)
if (currentRight == len(self._columns)) and (moveBy > 0):
return
newLeft = self.leftColumn + moveBy
elif mode == 'scroll':
# move one page at a time
moveBy = string.atoi(value)
import sys
if moveBy > 0:
newLeft = self._findRightColumnFromLeft(self.leftColumn)
newLeft = max(self.xfreeze, newLeft-1)
newRight = self._findRightColumnFromLeft(newLeft, 1)
while ( newRight == self._findRightColumnFromLeft(newLeft, 1) ):
newLeft = newLeft - 1
newLeft = newLeft + 1
else:
newLeft = self._findLeftColumnFromRight(self.leftColumn)
else:
print 'WHAT?'
# Do some range/optimization checking
if newLeft < self.xfreeze:
newLeft = self.xfreeze
if newLeft == self.leftColumn:
return
# get right column including partial
newRight = self._findRightColumnFromLeft(newLeft)
# Change the left column and fix up the scrollbar
self.setVisibleArea(newLeft, self._findRightColumnFromLeft(newLeft),
self.topRow, self.bottomRow)
return
def _yview(self, mode, value, units=None):
"""Callback for vertical scrollbar.
"""
if mode == 'moveto':
# fraction of number of columns
newTop = int( math.floor( string.atof(value) *
self.numRows))
if newTop < 0: newTop = 0
newBottom = self._findBottomRowFromTop(newTop)
newTop = self._findTopRowFromBottom(newBottom)
elif units == 'units':
# increment one column at a time
moveBy = string.atoi(value)
if (self.topRow == 0) and (moveBy < 0):
return
currentBottom = self._findBottomRowFromTop(self.topRow)
if (currentBottom == self.numRows+1) and (moveBy > 0):
return
newTop = self.topRow + moveBy
else:
# move one page at a time
moveBy = string.atoi(value)
if moveBy > 0:
newTop = self._findBottomRowFromTop(self.topRow)
newBottom = self._findBottomRowFromTop(newTop)
newTop = self._findTopRowFromBottom(newBottom)
else:
newTop = self._findTopRowFromBottom(self.topRow)
if newTop < 0: newTop = 0
# Do some range/optimization checking
if newTop == self.topRow:
return
# Change the left column and fix up the scrollbar
self.setVisibleArea(self.leftColumn, self.rightColumn,
newTop, self._findBottomRowFromTop(newTop))
return
def load(self, source, titles=None):
"""Load Table with data from source.
Source should implement the following methods::
__call__(self, row=0, col=0) # return data for row/col
__len__(self) # return number of rows in the datasource
columns(self) # return number of columns in the datasource
"""
# insert column for rowlabels if we need them
if self.rowlabels:
self.addcolumn( RowLabelColumn(
title='',
datasource=[],
width=50,
rowheight=self.rowheight,
cellalignment='se',
cellpadding=self.cellpadding))
# add a column for each title
for i in range(source.columns()):
if titles == None:
title='Col %d' % (1+i)
else:
title=titles[i]
self.addcolumn( Column(
title=title,
datasource=source,
column=i,
width=0,
font=self.font,
rowheight=self.rowheight,
cellalignment='se',
cellpadding=self.cellpadding))
return
if __name__ == '__main__':
TESTROWS=50
import GuiAppD
class TestDataSource(ColumnDataSource):
def __init__(self, base=0):
ColumnDataSource.__init__(self)
for i in range(TESTROWS):
self.data.append(str(base+i))
return
class TestApp(GuiAppD.GuiAppD):
def createInterface(self):
pad=1
rowheight=15
self.table = Table(self.interior(), cellpadding=pad, xfreeze=1)
self.table.addcolumn( RowLabelColumn(
title='',
datasource=TestDataSource(),
width=50,
rowheight=rowheight,
cellalignment='se',
cellpadding=pad,
))
for i in range(10):
self.table.addcolumn( Column(
title='Col %d' % i,
datasource=TestDataSource(base=10**i),
#width=50 + (10 * i),
width=0,
rowheight=rowheight,
cellalignment='se',
cellpadding=pad,
) )
self.table.pack(fill=Tkinter.BOTH,
expand=Tkinter.YES)
return
class DataSource:
def __init__(self, data, formats=None):
self.data = data
self.formats = formats
self.guess_len = 0
def __call__(self, row=0, col=0):
if self.formats != None:
return self.formats[col](self.data[row][col])
else:
return repr(self.data[row][col])
def __len__(self):
return len(self.data)
def columns(self):
return len(self.data[0])
def sort(self):
self.data.sort()
def reverse(self):
self.data.reverse()
class TestApp2(GuiAppD.GuiAppD):
usebuttonbox=1
def createInterface(self):
pad=1
rowheight=15
self.table = Table(self.interior(), cellpadding=pad,
rowlabels=1, rowheight=15, font='fixed')
data = []
for i in range(TESTROWS):
row = []
for j in range(10):
row.append(i + 10**j)
data.append(row)
self.source = DataSource(data)
self.table.load(self.source)
self.table.pack(fill=Tkinter.BOTH,
expand=Tkinter.YES)
self.buttonAdd('Sort',
'sort data',
'sort data', command=self.sort)
self.buttonAdd('Reverse',
'reverse data',
'reverse data', command=self.reverse)
return
def sort(self):
self.source.sort()
self.table.refresh()
def reverse(self):
self.source.reverse()
self.table.refresh()
TestApp2().run()
|