try out tkForms for fun (no profit)

Aaron Watters (aaron@funcity.njit.edu)
Fri, 7 Apr 1995 20:17:00 GMT

Okay guys, I'm miffed. Reading comp.lang.python is one of
my daily highlights. You nuts really couldn't come up with
anything today? Not anything!?

Well, I hope you guys on the mailing list have disk space
because I'm going to cram a lot of junk onto your machine
as punishment!
======

I've been playing with a Tkinter based forms interface I've
developed, and thought you-all might like to have a look. It
might serve as a starting point or a hint or something, although
it's not all that sophisticated, so I'm sure any programmer
worth his salt will want to mess with it.

Basically tkForms.py defines a Form class (with related utility
classes) which creates callable objects that pop a form on your screen
when called and return a dictionary corresponding to the values
entered in the form when the user presses the "end" button. There are
also some hooks for automatic field value verification. I've found
this strategy easy and productive.

There is a bit more info in the header for the tkForms.py source
below.

I include two enclosures: first a silly demo script that pretends to
get parameters for the unix "chmod" command using the form and prints
out the command it thinks you wanted, second the tkForms.py source,
which includes a test function with more demos which prints the
returned dictionaries: testforms(), with output which looks something
like this (except for the forms, of course).

funcity% python
Python 1.1.1 (Mar 27 1995)
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>> from tkForms import *
>>> testforms()
({'wally': 'wdefault', 'grump': 'gfd'}, {})
({'wally': 'wdefault', 'grump': 'gfd'}, {})
age : 12
sally : smart
physique : {'pale': 1, 'tall': 0, 'muscular': 0, 'fat': 1}
grump : gfd
enter your last will and testament here :
bill : lazy
iq : 100
line : {'on the wild side': 0, 'take a walk': 1, 'I said, hey babe': 0}
X value : None
wally : wdefault
None
funcity%

Please give it a try. It should run "out of the box" if you've
got tkInter installed.

Aaron Watters
Department of Computer and Information Sciences
New Jersey Institute of Technology
University Heights
Newark, NJ 07102
phone (201)596-2666
fax (201)596-5777
home phone (908)545-3367
email: aaron@vienna.njit.edu

===
"And the colored girls go `do da do do do da doot...'
-Lew Reed, "A walk on the wild side"

==== SILLY DEMO ENCLOSURE

# chmod form interface using tkForms
# this form gets parameters for chmod and prints the command
# it thinks corresponds to them. Sloppy but cute.
from tkForms import *

whoABBREV = {"user":"u", "group":"g", "others":"o", "all":"a"}
opABBREV = {"add":"+", "remove":"-", "assign":"="}
permABBREV = {"read":"r", "write":"w", "exec":"x", "direxec":"X",
"sticky":"t"}
whochoices = whoABBREV.keys()
opchoices = opABBREV.keys()
permchoices = permABBREV.keys()

def opmapper(op):
return ChoicesDescr(op, permchoices, [])

def getpermstring(permdict):
result = ""
for perm in permchoices:
if permdict[perm]:
result = result + permABBREV[perm]
return result

def getopstring(formdict):
result = ""
for op in opchoices:
permdict = formdict[op]
permstring = getpermstring(permdict)
if permstring:
result = result + opABBREV[op] + permstring
return result

def changeMode():
global mainWidget
fileField = FieldDesc("file name", "file.txt")
whoField = RadioField("who", whochoices)
opFields = map(opmapper, opchoices)
theForm = Form("chmod", [fileField, whoField]+opFields)
out = theForm(mainWidget)
if out == None: print "aborted"
else:
dict = out[0]
filename = dict["file name"]
who = whoABBREV[dict["who"]]
opstring = getopstring(dict)
command = "chmod "+who+opstring+" "+filename
print command # should use sys.exec()

# start up stuff
mainWidget = Frame()
Pack.config(mainWidget)
start = Button(mainWidget, {"text":"do chmod", "command":changeMode})
start.pack()
end = Button(mainWidget, {"text":"end", "command":"exit"})
end.pack()
mainWidget.mainloop()

============== tkForms.py the forms interface source
from Tkinter import *
from string import atoi
from types import StringType, IntType

# a Form is a callable object with associated button and feild
# widgets.
#
# F = Form("name", [name, age, sex], [paynow, showSexyPicture],
# "finish", "abort")
#
# makes a form F with field descriptors name, age, sex button descriptors
# paynow, showSexyPicture,
# "terminate normally" button labelled "finish" and quit
# button labelled "abort"
#
# Result = F(master_widget)
# where master_widget is a properly initialized widget will
# cause the form to appear on the screen with buttons across the
# top and fields arranged vertically benieth. A message label
# area will be placed at the bottom of the form.
#
# The form will (attempt to) exit when the user presses the "terminate" or
# "abort" buttons. If the user presses "abort" the form will
# terminate returning Result=None, with all values entered in the form
# discarded.
#
# If the user presses "terminate" the Form will extract the values
# entered in the form from the fields, check their validity using
# the field descriptors, and if they are all valid return a pair of
# dictionaries Result=(results, {}). The dictionary results will map
# field-name -> value, where value is the value extracted from the
# form for the field with name field-name. The second dictionary
# is left for possible upward compatibility for better error handling
# -- at the moment it should always be empty.
#
# If the user presses "terminate" and one or more of the fields has
# an invalid value (says its descriptor) then the Form will report
# an error message in the message area and not exit.
#
# Additional buttons on the form are used to implement possible other
# actions (write to file, read from file, etcetera).
#
# For example b1 = FieldApplyButton("validate",checkUser,"username")
# generates a button descriptor which corresponds to a button on a
# Form which, when pressed will extract the current value for
# "username" from the form and call
# checkUser(value, widget, messagearea)
# The checkUser function can check that the value is a honest to god
# user, possibly create more descendents for the widget, if it likes,
# and display some sort of a message in the message area such as
# messagearea("checkUser function not complete, sorry.")
# which will show up in the message area of the form where the button
# was pressed.
#
# Field Descriptors are objects that define the type of a field,
# how its value should be checked, how it should be displayed,
# and how it should be read in and converted from the representation
# on a form.
# For example:
# sex = RadioField("gender", ["Female","Mail","Other"])
# describes a field named "gender" which will be represented as
# a radio field array of buttons, with possible return values
# "Female", "Mail" (sic), or "Other" which also serve as the
# labels for the button array, and default value "Female".
# A form with this field might return a dictionary like:
# results = { ..., "gender":"Other", ...}
#
# For Example:
# age = AnInt("edad", 20, -10, 2000)
# describes an integer field "edad" with default 20 and accepted range -10,
# 2000. The Form will not return normally unless "edad" maps to an
# accepted integer -10<x<2000.
# A form with this field might return a dictionary like:
# results = { ..., "edad":10, ...}
# That's all for now, hope it helps.

# a Form class that permits multiple "instances" to be active
#
class Form:

def __init__(self, name="A form",
fieldslist, buttonslist=[], endName="go",
quitName="quit"):
self.name = name
self.fieldslist = fieldslist
self.buttonslist = buttonslist
self.endName = endName
self.quitName = quitName

# for call, make an active version of self, and call it.
def __call__(self, master=None, dict={}, returndict={}, domessages=1):
active = FormActive( self.name, self.fieldslist,
self.buttonslist, self.endName, self.quitName)
return active(master, dict, returndict, domessages)

# function to display a possibly large error message
#
def ReportError(errortext, name = "Error message",
longname = "Error encountered in processing",
master = None, filename="error.text"):
if not errortext or type(errortext)!=StringType:
raise TypeError, "bad error text in ReportError"
#errorfield = ROText(longname, errortext)
errorform = \
TextOutputBuffer(name, longname, filename, [], errortext)
# = FormActive(name, [errorfield], [], None, "OK")
errorform(master, {}, {}, 1)
# don't return a value

# same, but not an error
def FYIMessage(messagetext, name = "FYI",
longname = "For your information", master = None,
filename="FYI.text"):
ReportError(messagetext, name, longname, master, filename)

# set up various parameters for a widget
def setupThing(thing, relief="flat", bw="4", pack={"side":"top"}):
thing["relief"] = relief
thing["borderwidth"] = bw
thing.pack(pack)

# since this class uses internal state to pass information
# it should only be active ONCE in each instance.
# (multiple activity will cause undefined behavior)
#
class FormActive:

# if endName is None only quit will be used
#
def __init__(self, name="FormActive", fieldslist,
buttonslist=[], endName="go", quitName="quit"):
#if len(fieldslist)<1:
# raise ValueError, "form must have at least one field"
self.name = name
self.fieldslist = fieldslist
self.buttonslist = buttonslist
self.endName = endName
self.quitName = quitName
self.returndict = self.errorsdict = None

bareapack = fareapack = mareapack = {"side":"top"}
bareabw = fareabw = mareabw = "4"
(barearel, farearel, marearel) = ("raised", "flat", "sunken")

buttonpack = {"side":"left"}

def setupButton(self, button, name, command):
button["text"] = name
button["command"] = command
button.pack(self.buttonpack)

fieldpack = {"side":"top", "fill":"x"}

MainMaker = Toplevel # use frame as top level

# if domessages is false no message buffer will be constructed
# (primarily for read only forms).
def __call__(self, master=None, dict={}, returndict={}, domessages=1):
# set up dictionaries to return, using default values
errorsdict = {}
fieldslist = self.fieldslist
# copy values from dict into return dict
# SHOULD CHECK FOR VALID TYPE HERE...
for (name, value) in dict.items():
returndict[name] = value
# use defaults if not overridden
for f in fieldslist:
name = f.name
if not returndict.has_key(name):
returndict[name] = f.default
self.returndict = returndict
self.errorsdict = errorsdict
self.aborted = 1 # assume aborted unless reset

# set up a main widget
self.Main = Main = self.MainMaker(master)
# hack to get a reasonable title sometimes
if self.MainMaker == Toplevel:
Main.title(self.name)

# set up list of buttons on top
buttonsarea = Frame(Main)
setupThing(buttonsarea, self.barearel, self.bareabw, self.bareapack)

# set up a vertical sequence of fields
fieldsarea = Frame(Main)
setupThing(fieldsarea, self.farearel, self.fareabw, self.fareapack)

# at bottom set up a message area, only if requested
if domessages:
self.messagearea = messagearea = Messenger(Main,"initialized")
setupThing(messagearea, self.marearel, self.mareabw, self.mareapack)
else:
self.messagearea = dummymessagearea

# set up variable for quitting/ending
endVar = self.endVar = StringVar()
endVar.set("running")

# set up
# quit button
quitbutton = Button(buttonsarea)
# when quitting mark self as aborted, destroy main
self.setupButton(quitbutton, self.quitName, self.quit)
# end button only if not None
if self.endName != None:
endbutton = Button(buttonsarea)
self.setupButton(endbutton, self.endName, self.finishup)

# other buttons
buttonslist = self.buttonslist
for i in range(len(buttonslist)):
bd = buttonslist[i]
# set up the button to call bd.action on self
thisbutton = Button(buttonsarea)
bd.setup(thisbutton, (lambda b=bd, s=self: b(s)))
thisbutton.pack(self.buttonpack)

# set up the fields -- should add stuff for tabbing
fieldsdict = {}
for f in fieldslist:
name = f.name
thing = f.widget(fieldsarea)
thing.widget.pack(self.fieldpack)
fieldsdict[name] = thing
self.fieldswidgets = fieldsdict

# wait on endVar
Main.waitvar(endVar)
# when endvar is set, terminate...
self.Main.destroy()

# return result
if self.aborted: return None
return (self.returndict, self.errorsdict)

# quit just marks self as aborted, maybe more cleanup also?
def quit(self):
self.messagearea("quitting")
self.aborted = 1
self.endVar.set("quitting")
self.Main.update()

# finishup gets and verifies values from each field, sets up
# return dictionaries...
def finishup(self):
self.messagearea("finishing up")
self.getDictionaries()
if self.errorsdict:
return # don't complete, message area should report error
self.aborted = 0
self.endVar.set("ending")
self.Main.update()

# get and check the values for form, store them in
# self.returndict (for successful ones)
# self.errorsdict (for failures)
#
def getDictionaries(self):
returndict = self.returndict
errorsdict = {}
fieldslist = self.fieldslist
fieldswidgets = self.fieldswidgets
for f in fieldslist:
name = f.name
thing = fieldswidgets[name]
value = f.getvalue(thing)
cvalue = f.convertvalue(value)
# check for success/failure
if cvalue != None:
value = cvalue[0]
returndict[name] = value
else:
errorsdict[name] = value
self.messagearea(f.errormessage(value))
self.returndict = returndict
self.errorsdict = errorsdict

# Messenger is a callable object that displays a message
class Messenger(Label):

def __init__(self, master, message):
Label.__init__(self,master)
self["text"] = message

def __call__(self, string):
self["text"] = string
self.update()

# dummy message area doesn't do anything
# (used, for example, for read only forms)
#
def dummymessagearea(string):
pass

# buttondescriptor is a callable object that does something
# with an active form.
# superclass only responds to a button push by reporting it
# in the message area
#
class ButtonDesc:

def __init__(self, name): # ??? more ???
self.name = name

# all buttons should inherit this.
def setup(self, button, action):
button["text"] = self.name
button["command"] = action

def __call__(self, activeform):
# dummy for now
activeform.messagearea( self.name+" pressed" )

# FieldApplyButton gets a field value from a form and
# applies a function to it
# function should accept (value, widget, messagearea),
# can use widget as master to display results or report errors
# can use message area for short messages
#
class FieldApplyButton(ButtonDesc):

def __init__(self, name, function, field):
self.name = name
self.function = function
self.field = field

def __call__(self, activeform):
# get the dictionaries for the form
activeform.getDictionaries()
msg = activeform.messagearea
main = activeform.Main
# find the field
field = self.field
(returns, errors) = (activeform.returndict, activeform.errorsdict)
if returns.has_key(field):
value = returns[field]
return self.function(value, main, msg)
elif errors.has_key[field]:
value = errors[value]
msg(`field`+" has bad value "+`value`)
else:
msg("cannot find field "+`field`)

# FieldModifyButton modifies a field value using a function
# function should accept (Fwidget, widget, messagearea)
# messagearea for final messages
# Fwidget is the VALUE widget associated with the field
# widget is the form widget to use as master if needed.
#
class FieldModifyButton(ButtonDesc):

def __init__(self, name, function, fieldname):
self.name = name
self.function = function
self.fieldname = fieldname

def __call__(self, activeform):
# get the widget for the field etc...
msg = activeform.messagearea
main = activeform.Main
fieldname = self.fieldname
fieldswidgets = activeform.fieldswidgets
if fieldswidgets.has_key(fieldname):
fwidget = fieldswidgets[ fieldname ].valuewidget
self.function(fwidget, main, msg)
else:
msg("cannot find field: "+`fieldname`)

# simple button just calls a function when pressed
# the function should take widget and messagearea
class SimpleButton(ButtonDesc):

def __init__(self, name, function):
self.name = name
self.function = function

def __call__(self, a):
self.function(a.Main, a.messagearea)

# field should be an object which
# -creates a widgetholder containing widget (for vertical presentation)
# -gets the data string from the widget
# -checks the validity of string, converts it...
# -possibly manages user interactions...
# superclass is simple NONEMPTY named string
# doesn't check for correct length of input (yet)
#
# DUMMY CLASS TO AVOID CIRCULAR REFS
class widgetholder:
def __init__(self, widget, valuewidget):
self.widget = widget
self.valuewidget = valuewidget

class FieldDesc:

# default value is invalid!
def __init__(self, name, default="", length=20):
self.name = name
self.default = default
self.length = length
if len(default)>length:
raise ValueError, "bad length for default value"\
+`(default,length)`

wpack = {"side":"left"}
def widget(self, master):
# a little hacky: use the generated widget to hold state info
widget = Frame(master)
namearea = Label(widget)
namearea["text"] = self.name
namearea.pack(self.wpack)
valuearea = self.MakeValueArea(widget)
valuearea.pack(self.wpack)
return widgetholder(widget, valuearea)

def MakeValueArea(self, widget):
valuearea = Entry(widget)
valuearea["width"] = `self.length`
valuearea["borderwidth"] = "2"
valuearea["relief"] = "sunken"
valuearea.insert(At(0), self.default)
return valuearea

def getvalue(self, widgetholder):
return widgetholder.valuewidget.get()

# return [convertedValue] on success, else None
# STRING MUST BE NONEMPTY...
def convertvalue(self, string):
if string:
return [string]
return None

def errormessage(self,value):
if value:
return self.name+": no error?"
return self.name+" must be non empty"

NEString = FieldDesc # nonempty string class alias

class AnyString(FieldDesc):
def convertvalue(self, str):
return [str]
def errormessage(self, v):
return "?no error? for "+self.name

# Choices presented as a horizontal sequence of Checkbuttons
# returns a dictionary: choice -> boolean
# where boolean is true if choice is checked.
# ChoicesDescr("physique", ["tall","muscular","fat","pale"], ["fat","pale"])
# will set fat and pale as on by default with default returning
# {"tall":0, "muscular":0, "fat":1, "pale":1}
# DICTIONARY RETURNED SHOULD NOT BE MODIFIED (MAY BE DEFAULT)
#
class ChoicesDescr(AnyString):
def __init__(self, name, choicelist, DefaultOn):
self.name = name
self.choicelist = choicelist
self.DefaultOn = DefaultOn
default = {}
for choice in choicelist:
default[choice] = 0
for choice in DefaultOn:
if not default.has_key(choice):
raise ValueError, \
"default on value not in choice list"+\
`(choicelist, DefaultOn)`
default[choice] = 1
self.default = default

rpack = {"side":"left"}

def MakeValueArea(self, widget):
clist = self.choicelist
ChoiceFrame = Frame(widget)
variables = {}
for choice in clist:
V = variables[choice] = BooleanVar()
V.set(0)
for choice in self.DefaultOn:
variables[choice].set(1)
ChoiceFrame.variables = variables
cbuttons = clist[:]
crange = range(len(clist))
for i in crange:
cb = Checkbutton(ChoiceFrame,
{"text": clist[i],
"offvalue": 0, "onvalue": 1,
"variable": variables[clist[i]],
Pack: self.rpack} )
cbuttons[i] = cb # for debug
return ChoiceFrame

def getvalue(self, widgetholder):
variables = widgetholder.valuewidget.variables
value = {}
for choice in self.choicelist:
value[choice] = variables[choice].get()
return value

def convertedvalue(self, value):
return [value]

# same as above, but choice feild is vertical
class VChoicesDescr(ChoicesDescr):
rpack = {"side":"top", "fill": "both"}

# radiobutton field arranged horizontally of string choices
class RadioField(AnyString):

# default is the first in the list of options
def __init__(self, name, optionsStrings):
self.name = name
if len(optionsStrings)<2: raise ValueError, "bad option string"
self.options = optionsStrings
self.default = optionsStrings[0]

rpack = {"side":"left"}

def MakeValueArea(self, widget):
Opts = self.options
RadioFrame = Frame(widget)
V = RadioFrame.Variable = StringVar()
V.set(Opts[0])
RadioFrame.pack()
rpack = self.rpack
optrange = range(len(Opts))
rbuttons = optrange[:]
for i in optrange:
rb = Radiobutton(RadioFrame,
{"text": Opts[i],
"variable": V,
"value": Opts[i],
Pack: rpack} )
rbuttons[i] = rb # for debug
return RadioFrame

def getvalue(self, widgetholder):
return widgetholder.valuewidget.Variable.get()

def convertedvalue(self, string):
return [string]

class VRadioField(RadioField):
rpack = {"side":"top", "fill":"both"}

# VERTICAL CHECKBUTTON FIELD

# evaluable python expression
# error handling not clean here...
class PyExpr(FieldDesc):
def convertvalue(self, str):
try:
return [eval(str)]
except:
return None
def errormessage(self, val):
return self.name+" must be a python expression"

# integer with option inclusive upper and lower bounds.
#
class AnInt(FieldDesc):
def __init__(self, name, default=0, length=10, lower=None, upper=None):
self.name = name
self.length = length
self.lower = lower
self.upper = upper
# check the default
if type(default) != IntType \
or (lower!=None and type(lower)!=IntType)\
or (upper!=None and type(upper)!=IntType):
raise TypeError, \
"AnInt requires integer default and integer or None bounds"+\
`(default,lower,upper)`
# check the bounds
if upper!=None and lower!=None and upper<lower:
raise ValueError, "Invalid bounds for AnInt: "+`(lower,upper)`
# record default as string representation
self.default = `default`

def convertvalue(self, string):
try:
if not string: raise ValueError, "dummy"
intval = atoi(string)
except ValueError:
return None
if self.lower!=None and intval<self.lower: return None
if self.upper!=None and intval>self.upper: return None
return [intval]

def errormessage(self,value):
try:
if not value: raise ValueError, "dummy"
intval = atoi(value)
except ValueError:
return self.name+" must be an int"
if self.lower!=None and intval<self.lower:
return self.name+" must be >= "+`self.lower`
if self.upper!=None and intval>self.upper:
return self.name+" must be <= "+`self.upper`

# reseting the contents of a Text widget
def ResetText(Textwidget, newtext=""):
Textwidget.delete("1.0", AtEnd())
Textwidget.insert(AtEnd(), newtext)

# editable text
#
class AnyText(AnyString):

def __init__(self, name, default=""):
self.name = name
self.default = default

wpack = {"side": "top"}

def MakeValueArea(self, widget):
valuearea = Text(widget)
valuearea["borderwidth"] = "4"
valuearea["relief"] = "raised"
valuearea.insert(AtEnd(), self.default)
return valuearea

def getvalue(self, widgetholder):
text = widgetholder.valuewidget
str = text.get("1.0","end")
return str

# read only text area
# I DON'T KNOW HOW TO IMPLEMENT THIS, IT'S A STUB RIGHT NOW
class ROText(AnyText):
pass

# stuff for making an editable text form
# no edit buttons in this incarnation
class EditAbleForm(Form):

stdEditButtons = []

def __init__(self, name = "editable", header = "edit this buffer",
morebuttons = [], text = ""):
EditField = AnyText(header, text)
self.header = header
Form.__init__(self, name, [EditField],
self.stdEditButtons + morebuttons, # buttons
None, # no end name
"done")

# beep the user (also prints a newline.... fix it sometime)
def beep():
print "\007"*3

def writeToFile(text, widget, msgarea, defaultfile):
#print "writeToFile ", text
# write modes
a = "append"
n = "new only"
ov = "new or overwrite"
# filename field name
fn = "file name"

# make a form to ask user for filename etc
modefield = RadioField("mode", [n, ov, a])
filenamefield = FieldDesc(fn, defaultfile)
fnForm = Form("write where?", [modefield, filenamefield], [])
pair = fnForm(widget)
if pair == None: return defaultfile# aborted
result = pair[0]
mode = result["mode"]
name = result[fn]
# check for existence, if needed
if mode in [n, a]:
fileexists = 1
try:
f = open(name, "r")
f.close()
except:
fileexists = 0
if mode == a and not fileexists:
beep()
msgarea("cannot append: "+`name`+" doesn't exist")
return name
elif mode == n and fileexists:
beep()
msgarea("cannot write as new: "+`name`+" exists")
return name
# open the file
if mode == a:
f = open(name, "a")
else:
f = open(name, "w")
#print "writing..."+name
f.write(text)
f.close()
msgarea("written to: "+`name`)
return name

def clearText(Textwidget, widget, msgarea):
ResetText(Textwidget, "")
msgarea("text cleared")

# prompt user for file to read from, return contents of file
def readFromFile(Textwidget, widget, msgarea, defaultfile):
fn = "file name"
# make a form for asking which file to read
filenamefield = FieldDesc(fn, defaultfile)
fnForm = Form("read from where?", [filenamefield], [])
pair = fnForm(widget)
if pair == None: return defaultfile# aborted
result = pair[0]
name = result[fn]
try:
f = open(name, "r")
text = f.read()
f.close()
except IOError:
beep()
msgarea("Couldn't open/read: "+`name`)
return name
# put the text in the text area
Textwidget.insert("insert", text)
msgarea("inserted from: "+`name`)
return name

# wrapper for calling writeToFile/readFromFile
class RWAction:
def __init__(self, defaultfilename, ReadOrWrite):
self.default = defaultfilename
self.ReadOrWrite = ReadOrWrite
def __call__(self, t, w, m):
self.default = self.ReadOrWrite(t,w,m,self.default)
return self.default

class TextOutputBuffer(EditAbleForm):

def __init__(self, name = "Output Buffer", header = "please type",
defaultfilename = "junk.text", morebuttons=[],
text=""):
self.defaultfile = defaultfilename
wAction = RWAction(defaultfilename, writeToFile)
writeButton = \
FieldApplyButton("write to file",
wAction, header)
morebuttons = morebuttons + [writeButton]
EditAbleForm.__init__(self, name, header, morebuttons, text)

# add ability to clear
#
class TextInputBuffer(TextOutputBuffer):

def __init__(self, name = "OutputBuffer", header ="please type",
defaultfilename = "junk.text", morebuttons=[],
text = ""):
rAction = RWAction(defaultfilename, readFromFile)
readButton = \
FieldModifyButton("insert from file", rAction, header)
clearButton = \
FieldModifyButton("CLEAR", clearText, header)
morebuttons = morebuttons + [readButton,clearButton]
TextOutputBuffer.__init__(
self, name, header, defaultfilename, morebuttons, text)

def testforms():
global mainWidget
mainWidget = Frame()
Pack.config(mainWidget)
start = Button(mainWidget, {"text":"press", "command":go})
start.pack()
end = Button(mainWidget, {"text":"end", "command":"exit"})
end.pack()
mainWidget.mainloop()

def go():
global b1, b2, f1, f3, F, F2, t1, e1, i1, i2, rf, F3
b1 = ButtonDesc("lola")
b2 = ButtonDesc("fred")
f1 = FieldDesc("grump","gfd")
f2 = FieldDesc("wally","wdefault")
F = Form("F", [f1,f2], [])
F2 = Form("F2", [f1, f2], [b1,b2])

t1 = AnyText("enter your last will and testament here")
e1 = PyExpr("X value", "None")
i1 = AnInt("age", 12, 10, -10, 120)
i2 = AnInt("iq", 100, 10, 0, 660)
rf = RadioField("bill", ["lazy", "boring", "stupid"])
vrf = VRadioField("sally", ["smart","pretty","dangerous"])
cf = ChoicesDescr("physique", ["tall","muscular","fat","pale"],
["fat","pale"])
l1 = "I said, hey babe"
l2 = "take a walk"
l3 = "on the wild side"
vcf = VChoicesDescr("line",[l1,l2,l3],[l2])
F3 = Form("F3", [f1,f2,i1,e1,i2,t1,rf,vrf,cf,vcf], [b1,b2])
EF = TextInputBuffer()

print F(mainWidget)
print F2(mainWidget)
x = F3(mainWidget)
if x:
for (a,b) in x[0].items():
print a, " : ", b
else: print x
print EF(mainWidget)