|
Hi Attached is a grid class I wrote which may help you. Regards Paul Laszlo Nagy wrote: I have a gtk.Table inside a scrolled window. I have many rows and columns in the Table. Each cell in the table contains a widget (typically a button, but not necessarily.) Some of the cells on the edges of the table are headers. They should always be visible even when the user scrolls in the middle of the Table.How can I achieve that? If this cannot be done with gtk.Table, can you please suggest another widget that can do the job for me? (If the cells cannot hold Widgets but can receive events then it may be a good solution...) Number of cells can be anything from 10 to 10 000. Thanks, Laszlo _______________________________________________ pygtk mailing list [email protected] http://www.daa.com.au/mailman/listinfo/pygtk Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/ |
import gtk
import pango
class ScrollGrid:
"""
A scrolled grid widget
"""
def __init__(self, **args):
defaults = {
"scrn": None, # A VBox
"width": 600,
"height": 400,
"heads": [], # General Headings
"cols": None, # Column Headings
"rows": None, # Number of rows
"form": [], # Format of data fields as follows:
# Code - As per the Format class
# Size - Total size . Decimals
# Event - True or False
"data": [], # Main table data
"event": None, # A function to execute
"butt": [], # Additional buttons
"pbar": None} # Progressbar object
for nam in args.keys():
defaults[nam] = args[nam]
for nam in defaults.keys():
exec "self.%s = defaults[nam]" % nam
self.doVariables()
self.doProcess()
def doVariables(self):
if type(self.butt) == tuple:
self.butt = list(self.butt)
self.butt.append(("gtk-close", self.closeProcess))
def doProcess(self):
# main headings
colr = self.scrn.get_style().copy().bg[gtk.STATE_ACTIVE]
for head in self.heads:
ebox = gtk.EventBox()
ebox.modify_bg(gtk.STATE_NORMAL, colr)
self.scrn.pack_start(ebox, False, False, 0)
labl = gtk.Label()
ebox.add(labl)
if head:
labl.set_markup("<b>%s</b>" % head.replace("&", "&"))
# create a heading scrolled window
self.hwin = gtk.ScrolledWindow()
self.hwin.set_size_request(self.width, -1)
self.hwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
self.hadj = self.hwin.get_hadjustment()
self.scrn.pack_start(self.hwin, True, True, 0)
# create an eventbox
ebox = gtk.EventBox()
ebox.modify_bg(gtk.STATE_NORMAL, colr)
self.hwin.add_with_viewport(ebox)
# create a table for the column headings
headtab = gtk.Table(rows=len(self.cols), columns=len(self.form),
homogeneous=False)
ebox.add(headtab)
# column headings
for y, col in enumerate(self.cols):
for x, c in enumerate(col):
fmt = "%s-%ss" % ("%", int(self.form[x][1]))
head = gtk.Label()
head.set_markup(fmt % c.replace("&", "&"))
headtab.attach(head, x, x+1, y, y+1, 0, 0, 0, 0)
exec "self.hlabl_%s_%s = head" % (y, x)
# create a body scrolled window
self.bwin = gtk.ScrolledWindow()
self.bwin.set_size_request(self.width, self.height)
self.bwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
self.badj = self.bwin.get_hadjustment()
self.badj.connect("notify::value", self.doHAdjustment)
self.scrn.pack_start(self.bwin, True, True, 0)
# create a table for the body details
bodytab = gtk.Table(rows=self.rows, columns=len(self.cols[0]),
homogeneous=False)
self.bwin.add_with_viewport(bodytab)
# populate the table
# column data
col1 = gtk.gdk.color_parse("white")
col2 = self.scrn.get_style().copy().bg[gtk.STATE_NORMAL]
colr = col2
for y, dat in enumerate(self.data):
if self.pbar:
self.pbar.displayProgress()
if colr == col2:
colr = col1
else:
colr = col2
for x, d in enumerate(dat):
ebox = gtk.EventBox()
ebox.modify_bg(gtk.STATE_NORMAL, colr)
val = Format(d, self.form[x][0], self.form[x][1])
if val.err:
val = Format(d, "NA", self.form[x][1])
labl = gtk.Label(val.disp)
if len(self.form[x]) == 4 and val.work < 0:
labl.modify_fg(gtk.STATE_NORMAL,
gtk.gdk.color_parse(self.form[x][3]))
ebox.add(labl)
if self.form[x][2]:
if not self.event:
ebox.connect("button_release_event", self.execCmd, y, x)
else:
ebox.connect(self.event[0], self.event[1], dat[-1], x)
bodytab.attach(ebox, x, x+1, y, y+1, 0, 0, 0, 0)
exec "self.blabl_%s_%s = labl" % (y, x)
exec "self.bcell_%s_%s = ebox" % (y, x)
self.last = y
# create a hbox to hold the buttons
hbox = gtk.HBox(False, 0)
# create a label and spinbox for the font
self.scrn.pack_start(hbox, False, False, 10)
sbl = gtk.Label("Font Size")
sbl.set_alignment(0, 0.5)
hbox.pack_start(sbl, False, True, 0)
sba = gtk.Adjustment(10, 6, 12, 1, 0, 0)
sbw = gtk.SpinButton(sba, 0, 0)
sbw.set_wrap(True)
hbox.pack_start(sbw, False, True, 0)
sba.connect("value_changed", self.doSetFont, sbw)
# create the buttons
for num, but in enumerate(self.butt):
exec "but%s = gtk.Button(stock=but[0])" % num
exec "but%s.connect('clicked', self.execCmd, num)" % num
exec "but%s.set_flags(gtk.CAN_DEFAULT)" % num
exec "hbox.pack_start(but%s, True, True, 0)" % num
exec "but%s.show()" % num
self.scrn.show_all()
if self.pbar:
self.pbar.closeProgress()
def doSetFont(self, *args):
while gtk.events_pending():
gtk.main_iteration()
size = args[1].get_value_as_int()
font = pango.FontDescription("Courier %s" % size)
for y, col in enumerate(self.cols):
for x, c in enumerate(col):
exec "self.hlabl_%s_%s.modify_font(font)" % (y, x)
for y in range(0, self.last + 1):
for x, c in enumerate(col):
try:
exec "self.blabl_%s_%s.modify_font(font)" % (y, x)
except:
pass
def doHAdjustment(self, *args):
self.hadj.set_value(args[0].get_value())
def hideCol(self, col):
for row in range(0, self.last + 1):
try:
exec "self.hlabl_%s_%s.hide()" % (row, col)
except:
pass
try:
exec "self.bcell_%s_%s.hide()" % (row, col)
except:
pass
def showCol(self, col):
for row in range(0, self.last + 1):
try:
exec "self.hlabl_%s_%s.show()" % (row, col)
except:
pass
try:
exec "self.bcell_%s_%s.show()" % (row, col)
except:
pass
def execCmd(self, widget, *args):
if self.butt[args[0]][1]:
self.butt[args[0]][1]()
def closeProcess(self, widget=None, *args):
for child in self.scrn.get_children():
child.destroy()
# There must be a main loop in the calling class/module
gtk.main_quit()
class Format:
"""
A class to format data
Data Types
============================================================================
CB = Check Box
D0 = Date (CCYYMMDD)
D1 = Date (CCYYMMDD)
d1 = Date (CCYYMMDD) Alowing Zero
D2 = Current Period Date (CCYYMM)
d2 = Current Period Date (CCYYMM) Allowing Zero
D3 = Date (DD-MMM-YYYY)
DT = Date and Time (DD-MMM-YYYY-HH:MM)
FF = File or Directory Format
LA = Lower Case Alphanumeric (Must also have 'size' set)
La = Lower Case Alphanumeric Right Justified (Must also have 'size' set)
HA = Normal Alphanumeric Hidden as for Passwords
NA = Normal Alphanumeric (Must also have 'size' set)
Na = Normal Alphanumeric Right Justified (Must also have 'size' set)
RW = Raw Data, No formatting or checking
SD = Signed Decimal (size includes sign and decimals)
Sd = Convert Integer to Signed Decimal (size includes sign and decimals)
SI = Signed Integer (size includes sign)
SL = Signed Long Integer (size includes sign)
TM = Time (HH:MM)
TS = Timestamp (CCYY-MM-DD HH:MM:SS)
TV = Text View - This is only used with TartanDialog for input purposes
Tv = Text View - This is only used with TartanDialog for input purposes
TX = Text or Blob Alphanumeric Variable Length- Storing Text Views etc.
UA = Upper Case Alphanumeric (Must also have 'size' set)
Ua = Upper Case Alphanumeric Right Justified (Must also have 'size' set)
UD = Unsigned Decimal (size includes decimals)
Ud = Convert Integer to Unsigned Decimal (size includes decimals)
UI = Unsigned Integer
UL = Unsigned Long Integer
US = Unsigned Serial
"""
def __init__(self, data, type, size):
self.mthnum = {
"JAN":1, "FEB":2, "MAR":3, "APR":4, "MAY":5, "JUN":6,
"JUL":7, "AUG":8, "SEP":9, "OCT":10, "NOV":11, "DEC":12}
#=======================================================================
# Work around data types from TartanDialog which are 3 characters long
#=======================================================================
if len(type) == 3:
type = type[1:]
#=======================================================================
# Work around for None
#=======================================================================
if data == None:
if type[1].lower() in ("a", "v", "w", "x"):
self.data = ""
else:
self.data = "0"
#=======================================================================
# Raw Data Type
#=======================================================================
if type == "RW":
self.err = ""
self.work = self.disp = self.data = data
return
#=======================================================================
# Timestamp
#=======================================================================
if type == "TS":
self.err = ""
self.work = self.disp = self.data = data
return
#=======================================================================
# Work around for previous format error
#=======================================================================
elif not type[0] == "T" and type[1].upper() != "A" and data == "None":
self.data = str(0)
#=======================================================================
else:
self.data = str(data)
self.data = self.data.strip()
self.type = type
self.size = size
if type in ("D1","d1","D2","d2","DT","dT","TM"):
if self.data[-1:] == "L":
self.data = self.data[:-1]
elif type in ("Sd", "Ud"):
self.data = str(float(long(self.data) / 100.0))
elif type[1] in ("I","L"):
self.data = self.data.split(".")[0]
if self.data[-1:] == "L":
self.data = self.data[:-1]
elif self.type[1].lower() in ("a", "v", "x"):
self.data = self.data.replace("\\", "/")
self.processData()
def processData(self):
self.quo = int(self.size / 1)
self.rem = int(round((self.size % 1) * 10))
self.disp = None
self.work = None
self.err = ""
if self.type in ("D0","d0"):
# Work around for previous format error
#======================================
if self.mthnum.has_key(self.data[3:6].upper()):
self.chkD3_Date()
else:
self.chkD0_Date()
elif self.type in ("D1","d1"):
self.chkD1_Date()
elif self.type in ("D2","d2"):
self.chkD2_Date()
elif self.type in ("D3","d3"):
self.chkD3_Date()
elif self.type in ("DT","dT"):
self.chkDT_Date()
elif self.type == "TM":
self.chkTM_Time()
elif self.type[1] in ("F","A","a"):
dat = self.data[:self.quo]
if self.type[1] in ("F","A"):
if self.data == "":
self.work = dat
self.disp = " " * self.quo
else:
if self.type[0] == "L":
dat = dat.lower()
elif self.type[0] == "U":
dat = dat.upper()
self.work = dat
self.disp = dat.ljust(self.quo)
elif self.type[1] == "a":
try:
if dat[-1:] == "L":
raise exception
dat = str(long(dat))
if dat[-1:] == "L":
dat = dat[:-1]
self.work = dat.rjust(self.quo)
self.disp = dat.rjust(self.quo)
except:
if self.type[0] == "L":
dat = dat.lower()
elif self.type[0] == "U":
dat = dat.upper()
self.work = dat
self.disp = dat.ljust(self.quo)
elif self.type == "UI":
self.unsignedInteger()
elif self.type == "US":
self.unsignedInteger()
elif self.type == "SI":
self.signedInteger()
elif self.type == "UL":
self.unsignedLong()
elif self.type == "SL":
self.signedLong()
elif self.type in ("UD", "Ud"):
self.unsignedDecimal()
elif self.type in ("SD", "Sd"):
self.signedDecimal()
elif self.type in ("TV", "Tv", "TX"):
self.work = self.data
self.disp = self.data
else:
self.err = "Invalid Input, Retry (%s)" % self.type
def chkD0_Date(self):
date = self.data.replace("-", "")
self.disp = date
try:
self.work = int(date.replace("-", ""))
except:
self.work = date
def chkD1_Date(self):
date = self.data.replace("-", "")
if date in ("", "0", "00000000") and (self.type == "d1"):
self.work = 0
self.disp = "0000-00-00"
else:
try:
if len(date) < 5 or len(date) > 8:
raise Exception
yy = int(date[0:(len(date) - 4)])
mm = int(date[(len(date) - 4):(len(date) - 2)])
dd = int(date[(len(date) - 2):len(date)])
if yy < 100 and yy > 30:
yy += 1900
elif yy < 100:
yy += 2000
if mm < 01 or mm > 12:
self.err = "Invalid Date (D1-1)"
return
chkmth = copy.deepcopy(mthnam)
if mm == 2:
if (yy % 4) == 0:
chkmth[2][2] = 29
else:
chkmth[2][2] = 28
if dd < 1 or dd > chkmth[mm][2]:
self.err = "Invalid Date (D1-2)"
return
self.work = (yy * 10000) + (mm * 100) + dd
self.disp = "%04i-%02i-%02i" % (yy, mm, dd)
if len(self.disp) < self.size:
self.disp = self.disp + \
(" " * (int(self.size/1) - len(self.disp)))
except:
self.err = "Invalid Date (D1-3)"
def chkD2_Date(self):
date = self.data.replace("-", "")
if date in ("", "0", "000000") and (self.type == "d2"):
self.work = 0
self.disp = "0000-00"
else:
try:
if len(date) < 3 or len(date) > 6:
raise Exception
yy = int(date[0:(len(date) - 2)])
mm = int(date[(len(date) - 2):len(date)])
if yy < 100 and yy > 30:
yy += 1900
elif yy < 100:
yy += 2000
if mm < 01 or mm > 12:
self.err = "Invalid Month (D2-1)"
else:
self.work = (yy * 100) + mm
self.disp = "%04i-%02i" % (yy, mm)
if len(self.disp) < self.size:
self.disp = self.disp + \
(" " * (int(self.size/1) - len(self.disp)))
except:
self.err = "Invalid Current Date (D2-2)"
def chkD3_Date(self):
date = self.data
self.work = date
self.disp = date
try:
yy = int(date[7:11])
mm = self.mthnum[date[3:6].upper()]
dd = int(date[0:2])
if yy < 100 and yy > 30:
yy += 1900
elif yy < 100:
yy += 2000
chkmth = copy.deepcopy(mthnam)
if mm == 2:
if (yy % 4) == 0:
chkmth[2][2] = 29
else:
chkmth[2][2] = 28
if mm < 01 or mm > 12:
self.err = "Invalid Month (D3-1)"
elif dd < 1 or dd > chkmth[mm][2]:
self.err = "Invalid Day (D3-2)"
else:
self.isoDate = (yy * 10000) + (mm*100) + dd
except:
self.err = "Invalid Date (D3-3)"
def chkDT_Date(self):
date = self.data.replace("-", "")
date = self.data.replace(":", "")
if date in ("", "0", "00000000") and (self.type == "dT"):
self.work = 0
self.disp = "0000-00-00-00:00"
else:
try:
if len(date) < 9 or len(date) > 12:
raise Exception
yy = int(date[0:(len(date) - 8)])
mm = int(date[(len(date) - 8):(len(date) - 6)])
dd = int(date[(len(date) - 6):(len(date) - 4)])
hh = int(date[(len(date) - 4):(len(date) - 2)])
tt = int(date[(len(date) - 2):len(date)])
if yy < 100 and yy > 30:
yy += 1900
elif yy < 100:
yy += 2000
if mm < 01 or mm > 12:
self.err = "Invalid Date (D1-1)"
return
chkmth = copy.deepcopy(mthnam)
if mm == 2:
if (yy % 4) == 0:
chkmth[2][2] = 29
else:
chkmth[2][2] = 28
if dd < 1 or dd > chkmth[mm][2]:
self.err = "Invalid Date (D1-2)"
return
self.work = (yy*100000000)+(mm*1000000)+(dd*10000)+(hh*100)+tt
self.disp = "%04i-%02i-%02i-%02i:%02i" % (yy, mm, dd, hh, tt)
if len(self.disp) < self.size:
self.disp = self.disp + \
(" " * (int(self.size/1) - len(self.disp)))
except:
self.err = "Invalid Date (DT)"
def chkTM_Time(self):
time = self.data.replace(":", "")
try:
if len(time) < 1 or len(time) > 4:
raise Exception
time = int(time)
hh = time / 100
mm = time % 100
self.work = (hh*100)+mm
self.disp = "%02i:%02i" % (hh, mm)
if len(self.disp) < self.size:
self.disp = self.disp+(" "*(int(self.size/1)-len(self.disp)))
except:
self.err = "Invalid Time (TM)"
def unsignedInteger(self):
if self.data == "":
self.data = "0"
try:
if self.data.find("-") != -1:
self.err = "Invalid UnSigned Integer (UI-)"
else:
self.work = int(self.data)
self.disp = self.data.rjust(self.quo)
if len(self.disp) > self.quo:
self.err = "Invalid UnSigned Integer (UIs)"
except:
self.err = "Invalid UnSigned Integer (UI)"
def signedInteger(self):
if self.data == "":
self.data = "0"
try:
s = self.data.find("-")
if s == (len(self.data) - 1):
self.data = self.data.replace("-", "")
self.data = "-" + self.data
self.work = int(self.data)
s = self.data.find("-")
if s != -1:
self.data = self.data.replace("-", "")
self.data = self.data + "-"
else:
self.data = self.data + " "
self.disp = self.data.rjust(self.quo)
if len(self.disp) > self.quo:
self.err = "Invalid UnSigned Integer (UIs)"
except:
self.err = "Invalid Signed Integer (SI)"
def unsignedLong(self):
if self.data == "":
self.data = "0"
try:
if self.data.find("-") != -1:
self.err = "Invalid UnSigned Long (UI-)"
else:
self.work = long(self.data)
self.disp = self.data.rjust(self.quo)
if len(self.disp) > self.quo:
self.err = "Invalid UnSigned Long (ULs)"
except:
self.err = "Invalid UnSigned Long (UL)"
def signedLong(self):
if self.data == "":
self.data = "0"
try:
s = self.data.find("-")
if s == (len(self.data) - 1):
self.data = self.data.replace("-", "")
self.data = "-" + self.data
self.work = long(self.data)
s = self.data.find("-")
if s != -1:
self.data = self.data.replace("-", "")
self.data = self.data + "-"
else:
self.data = self.data + " "
self.disp = self.data.rjust(self.quo)
if len(self.disp) > self.quo:
self.err = "Invalid UnSigned Long (ULs)"
except:
self.err = "Invalid Signed Long (SL)"
def unsignedDecimal(self):
if self.data == "":
self.data = "0.0"
try:
if self.data.find("-") != -1:
self.err = "Invalid UnSigned Decimal (UD-)"
else:
if self.data.find(".") == -1:
self.data = self.data + "." + (self.rem*"0")
self.work = float(self.data)
dec = self.data.split(".", self.rem)
if len(dec[0]) > self.quo - (self.rem + 1):
self.err = "Invalid Unsigned Decimal (UDs)"
elif len(dec[1]) > self.rem:
self.err = "Invalid Unsigned Decimal (UDd)"
else:
self.data = self.data + ((self.rem - len(dec[1]))*"0")
self.disp = self.data.rjust(self.quo)
d = len(self.disp) - self.quo
if d > 0:
self.disp = self.disp[d:]
except:
self.err = "Invalid UnSigned Decimal (UD)"
def signedDecimal(self):
if self.data == "":
self.data = "0.0"
try:
s = self.data.find("-")
if s == (len(self.data) - 1):
self.data = self.data.replace("-", "")
self.data = "-" + self.data
if self.data.find(".") == -1:
self.data = self.data + "." + (self.rem*"0")
self.work = float(self.data)
dec = self.data.split(".", self.rem)
if len(dec[0]) > self.quo - (self.rem + 1):
self.err = "Invalid Signed Decimal (SDs)"
elif len(dec[1]) > self.rem:
self.err = "Invalid Signed Decimal (SDd)"
else:
self.data = self.data + ((self.rem - len(dec[1]))*"0")
s = self.data.find("-")
if s != -1:
self.data = self.data.replace("-", "")
self.data = self.data + "-"
else:
self.data = self.data + " "
self.disp = self.data.rjust(self.quo)
d = len(self.disp) - self.quo
if d > 0:
self.disp = self.disp[d:]
except:
self.err = "Invalid Signed Decimal (SD)"
def clicked(*args):
print "row", args[2], "col", args[3]
def quit(*args):
print args
if __name__ == "__main__":
heads = ("ABC Company (Pty) Limited", "",
"Financial Statements for the Period Ending 2008-02-29")
cols = (
(
"",
"-------------", "-- Month-01 -","------------ ",
"-------------", "-- Month-02 -","------------ ",
"-------------", "-- Month-03 -","------------ ",
"-------------", "-- Month-04 -","------------ ",
"-------------", "-- Month-05 -","------------ ",
"-------------", "-- Month-06 -","------------ ",
"-------------", "-- Month-07 -","------------ ",
"-------------", "-- Month-08 -","------------ ",
"-------------", "-- Month-09 -","------------ ",
"-------------", "-- Month-10 -","------------ ",
"-------------", "-- Month-11 -","------------ ",
"-------------", "-- Month-12 -","------------ ",
"-------------", "--- Totals --","------------ "),
(
"Description",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget ",
"Current-Year ", " Last-Year ", " Budget "))
form = (
("NA", 30, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False),
("SD", 13.2, True), ("SD", 13.2, False), ("SD", 13.2, False))
data = [("One",100,100,100,200,200,200,300,300,300,400,400,400,500,500,500,
600,600,600,700,700,700,800,800,800,900,900,900,1000,1000,1000,
1100,1100,1100,1200,1200,1200,7800,7800,7800)] * 30
win = gtk.Window()
win.set_size_request(800, 600)
win.get_settings().set_string_property('gtk-font-name', 'courier 10','')
scrn = gtk.VBox()
win.add(scrn)
size = win.get_size()
width = int(size[0] * .95)
height = height=int(size[1] * .70)
swin = ScrollGrid(scrn=scrn, width=width, height=height, heads=heads,
rows=3, cols=cols, form=form, data=data, event=("button_release_event",
clicked))
win.show_all()
gtk.main()
_______________________________________________ pygtk mailing list [email protected] http://www.daa.com.au/mailman/listinfo/pygtk Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/
