Initial revision
diff --git a/Demo/sgi/audio/README b/Demo/sgi/audio/README
new file mode 100644
index 0000000..02a3701
--- /dev/null
+++ b/Demo/sgi/audio/README
@@ -0,0 +1,8 @@
+Programs that demonstrate the use of the audio device on the SGI 4D/25.
+These require the built-in module 'audio'.
+
+XXX This hardware is already obsolete; see ../al for examples of audio
+XXX on the Indigo and 4D/35.
+
+play		Read a sound sample from a file and play it through the
+		speaker.  Options to set volume, sampling rate etc.
diff --git a/Demo/sgi/audio/play.py b/Demo/sgi/audio/play.py
new file mode 100755
index 0000000..adc7625
--- /dev/null
+++ b/Demo/sgi/audio/play.py
@@ -0,0 +1,75 @@
+#! /usr/local/python
+
+import sys
+import audio
+
+import string
+import getopt
+import auds
+
+debug = []
+
+DEF_RATE = 3
+
+def main():
+	#
+	gain = 100
+	rate = 0
+	starter = audio.write
+	stopper = 0
+	#
+	optlist, args = getopt.getopt(sys.argv[1:], 'adg:r:')
+	#
+	for optname, optarg in optlist:
+		if 0:
+			pass
+		elif optname == '-d':
+			debug.append(1)
+		elif optname == '-g':
+			gain = string.atoi(optarg)
+			if not (0 < gain < 256):
+				raise optarg.error, '-g gain out of range'
+		elif optname == '-r':
+			rate = string.atoi(optarg)
+			if not (1 <= rate <= 3):
+				raise optarg.error, '-r rate out of range'
+		elif optname == '-a':
+			starter = audio.start_playing
+			stopper = audio.wait_playing
+	#
+	audio.setoutgain(gain)
+	audio.setrate(rate)
+	#
+	if not args:
+		play(starter, rate, auds.loadfp(sys.stdin))
+	else:
+		real_stopper = 0
+		for file in args:
+			if real_stopper:
+				real_stopper()
+			play(starter, rate, auds.load(file))
+			real_stopper = stopper
+
+def play(starter, rate, data):
+	magic = data[:4]
+	if magic == '0008':
+		mrate = 3
+	elif magic == '0016':
+		mrate = 2
+	elif magic == '0032':
+		mrate = 1
+	else:
+		mrate = 0
+	if mrate:
+		data = data[4:]
+	else:
+		mrate = DEF_RATE
+	if not rate: rate = mrate
+	audio.setrate(rate)
+	starter(data)
+
+try:
+	main()
+finally:
+	audio.setoutgain(0)
+	audio.done()
diff --git a/Demo/sgi/audio_stdwin/README b/Demo/sgi/audio_stdwin/README
new file mode 100644
index 0000000..6d96fe1
--- /dev/null
+++ b/Demo/sgi/audio_stdwin/README
@@ -0,0 +1,19 @@
+Three programs that provide a user interface based upon STDWIN to the
+audio device of the SGI 4D/25.  These scripts also demonstrate the power
+of a set of window interface classes implemented in Python that simplify
+the construction of all sorts of buttons, etc.
+
+XXX This hardware is already obsolete; see ../al for examples of audio
+XXX on the Indigo and 4D/35.
+
+jukebox		Browses a directory full of sound samples and lets you
+		play selected ones.  (Probably not fully functional, it
+		requires a conversion program.)
+
+rec		A tape recorder that lets you record a sound sample,
+		play it back, and save it to a file.  Various options to
+		set sampling rate, volume etc.  When idle it doubles
+		as a VU meter.
+
+vumeter		A VU meter that displays a history of the volume of
+		sound recently sampled from the microphone.
diff --git a/Demo/sgi/audio_stdwin/jukebox.py b/Demo/sgi/audio_stdwin/jukebox.py
new file mode 100755
index 0000000..b223992
--- /dev/null
+++ b/Demo/sgi/audio_stdwin/jukebox.py
@@ -0,0 +1,321 @@
+#! /usr/local/python
+
+# JUKEBOX: browse directories full of sampled sound files.
+#
+# One or more "list windows" display the files and subdirectories of
+# the arguments.  Double-clicking on a subdirectory opens a new window
+# displaying its contents (and so on recursively).  Double clicking
+# on a file plays it as a sound file (assuming it is one).
+#
+# Playing is asynchronous: the application keeps listening to events
+# while the sample is playing, so you can change the volume (gain)
+# during playing, cancel playing or start a new sample right away.
+#
+# The control window displays the current output gain and a primitive
+# "stop button" to cancel the current play request.
+#
+# Sound files must currently be in Dik Winter's compressed Mac format.
+# Since decompression is costly, decompressed samples are saved in
+# /usr/tmp/@j* until the application is left.  The files are read
+# afresh each time, though.
+
+import audio
+import sunaudio
+import commands
+import getopt
+import path
+import posix
+import rand
+import stdwin
+from stdwinevents import *
+import string
+import sys
+
+from WindowParent import WindowParent
+from HVSplit import VSplit
+from Buttons import PushButton
+from Sliders import ComplexSlider
+
+# Pathnames
+
+HOME_BIN_SGI = '/ufs/guido/bin/sgi/'	# Directory where macsound2sgi lives
+DEF_DB = '/ufs/dik/sounds/Mac/HCOM'	# Default directory of sounds
+
+
+# Global variables
+
+class struct: pass		# Class to define featureless structures
+
+G = struct()			# Holds writable global variables
+
+
+# Main program
+
+def main():
+	G.synchronous = 0	# If set, use synchronous audio.write()
+	G.debug = 0		# If set, print debug messages
+	G.gain = 75		# Output gain
+	G.rate = 3		# Sampling rate
+	G.busy = 0		# Set while asynchronous playing is active
+	G.windows = []		# List of open windows (except control)
+	G.mode = 'mac'		# Macintosh mode
+	G.tempprefix = '/usr/tmp/@j' + `rand.rand()` + '-'
+	#
+	optlist, args = getopt.getopt(sys.argv[1:], 'dg:r:sSa')
+	for optname, optarg in optlist:
+		if   optname == '-d':
+			G.debug = 1
+		elif optname == '-g':
+			G.gain = string.atoi(optarg)
+			if not (0 < G.gain < 256):
+				raise optarg.error, '-g gain out of range'
+		elif optname == '-r':
+			G.rate = string.atoi(optarg)
+			if not (1 <= G.rate <= 3):
+				raise optarg.error, '-r rate out of range'
+		elif optname == '-s':
+			G.synchronous = 1
+		elif optname == '-S':
+			G.mode = 'sgi'
+		elif optname == '-a':
+			G.mode = 'sun'
+	#
+	if not args:
+		args = [DEF_DB]
+	#
+	G.cw = opencontrolwindow()
+	for dirname in args:
+		G.windows.append(openlistwindow(dirname))
+	#
+	#
+	savegain = audio.getoutgain()
+	try:
+		# Initialize stdaudio
+		audio.setoutgain(0)
+		audio.start_playing('')
+		dummy = audio.wait_playing()
+		audio.setoutgain(0)
+		maineventloop()
+	finally:
+		audio.setoutgain(savegain)
+		audio.done()
+		clearcache()
+
+def maineventloop():
+	mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
+	while G.windows:
+		type, w, detail = event = stdwin.getevent()
+		if w == G.cw.win:
+			if type == WE_CLOSE:
+				return
+			G.cw.dispatch(event)
+		else:
+			if type == WE_DRAW:
+				w.drawproc(w, detail)
+			elif type in mouse_events:
+				w.mouse(w, type, detail)
+			elif type == WE_CLOSE:
+				w.close(w)
+				del w, event
+			else:
+				if G.debug: print type, w, detail
+
+# Control window -- to set gain and cancel play operations in progress
+
+def opencontrolwindow():
+	cw = WindowParent().create('Jukebox', (0, 0))
+	v = VSplit().create(cw)
+	#
+	gain = ComplexSlider().define(v)
+	gain.setminvalmax(0, G.gain, 255)
+	gain.settexts('  ', '  ')
+	gain.sethook(gain_setval_hook)
+	#
+	stop = PushButton().definetext(v, 'Stop')
+	stop.hook = stop_hook
+	#
+	cw.realize()
+	return cw
+
+def gain_setval_hook(self):
+	G.gain = self.val
+	if G.busy: audio.setoutgain(G.gain)
+
+def stop_hook(self):
+	if G.busy:
+		audio.setoutgain(0)
+		dummy = audio.stop_playing()
+		G.busy = 0
+
+
+# List windows -- to display list of files and subdirectories
+
+def openlistwindow(dirname):
+	list = posix.listdir(dirname)
+	list.sort()
+	i = 0
+	while i < len(list):
+		if list[i] == '.' or list[i] == '..':
+			del list[i]
+		else:
+			i = i+1
+	for i in range(len(list)):
+		name = list[i]
+		if path.isdir(path.join(dirname, name)):
+			list[i] = list[i] + '/'
+	width = maxwidth(list)
+	width = width + stdwin.textwidth(' ')	# XXX X11 stdwin bug workaround
+	height = len(list) * stdwin.lineheight()
+	stdwin.setdefwinsize(width, min(height, 500))
+	w = stdwin.open(dirname)
+	stdwin.setdefwinsize(0, 0)
+	w.setdocsize(width, height)
+	w.drawproc = drawlistwindow
+	w.mouse = mouselistwindow
+	w.close = closelistwindow
+	w.dirname = dirname
+	w.list = list
+	w.selected = -1
+	return w
+
+def maxwidth(list):
+	width = 1
+	for name in list:
+		w = stdwin.textwidth(name)
+		if w > width: width = w
+	return width
+
+def drawlistwindow(w, area):
+	d = w.begindrawing()
+	d.erase((0, 0), (1000, 10000))
+	lh = d.lineheight()
+	h, v = 0, 0
+	for name in w.list:
+		d.text((h, v), name)
+		v = v + lh
+	showselection(w, d)
+
+def hideselection(w, d):
+	if w.selected >= 0:
+		invertselection(w, d)
+
+def showselection(w, d):
+	if w.selected >= 0:
+		invertselection(w, d)
+
+def invertselection(w, d):
+	lh = d.lineheight()
+	h1, v1 = p1 = 0, w.selected*lh
+	h2, v2 = p2 = 1000, v1 + lh
+	d.invert(p1, p2)
+
+def mouselistwindow(w, type, detail):
+	(h, v), clicks, button = detail[:3]
+	d = w.begindrawing()
+	lh = d.lineheight()
+	if 0 <= v < lh*len(w.list):
+		i = v / lh
+	else:
+		i = -1
+	if w.selected <> i:
+		hideselection(w, d)
+		w.selected = i
+		showselection(w, d)
+	if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
+		name = path.join(w.dirname, w.list[i])
+		if name[-1:] == '/':
+			if clicks == 2:
+				G.windows.append(openlistwindow(name[:-1]))
+		else:
+			playfile(name)
+
+def closelistwindow(w):
+	remove(G.windows, w)
+
+def remove(list, item):
+	for i in range(len(list)):
+		if list[i] == item:
+			del list[i]
+			break
+
+
+# Playing tools
+
+cache = {}
+
+def clearcache():
+	for x in cache.keys():
+		try:
+			sts = posix.system('rm -f ' + cache[x])
+			if sts:
+				print cmd
+				print 'Exit status', sts
+		except:
+			print cmd
+			print 'Exception?!'
+		del cache[x]
+
+def playfile(name):
+	if G.mode <> 'mac':
+		tempname = name
+	elif cache.has_key(name):
+		tempname = cache[name]
+	else:
+		tempname = G.tempprefix + `rand.rand()`
+		cmd = HOME_BIN_SGI + 'macsound2sgi'
+		cmd = cmd + ' ' + commands.mkarg(name)
+		cmd = cmd + ' >' + tempname
+		if G.debug: print cmd
+		sts = posix.system(cmd)
+		if sts:
+			print cmd
+			print 'Exit status', sts
+			stdwin.fleep()
+			return
+		cache[name] = tempname
+	fp = open(tempname, 'r')
+	try:
+		hdr = sunaudio.gethdr(fp)
+	except sunaudio.error, msg:
+		hdr = ()
+	if hdr:
+		data_size = hdr[0]
+		data = fp.read(data_size)
+		# XXX this doesn't work yet, need to convert from uLAW!!!
+		del fp
+	else:
+		del fp
+		data = readfile(tempname)
+	if G.debug: print len(data), 'bytes read from', tempname
+	if G.busy:
+		G.busy = 0
+		dummy = audio.stop_playing()
+	#
+	# Completely reset the audio device
+	audio.setrate(G.rate)
+	audio.setduration(0)
+	audio.setoutgain(G.gain)
+	#
+	if G.synchronous:
+		audio.write(data)
+		audio.setoutgain(0)
+	else:
+		try:
+			audio.start_playing(data)
+			G.busy = 1
+		except:
+			stdwin.fleep()
+	del data
+
+def readfile(filename):
+	return readfp(open(filename, 'r'))
+
+def readfp(fp):
+	data = ''
+	while 1:
+		buf = fp.read(102400) # Reads most samples in one fell swoop
+		if not buf:
+			return data
+		data = data + buf
+
+main()
diff --git a/Demo/sgi/audio_stdwin/rec.py b/Demo/sgi/audio_stdwin/rec.py
new file mode 100755
index 0000000..0caba89
--- /dev/null
+++ b/Demo/sgi/audio_stdwin/rec.py
@@ -0,0 +1,268 @@
+#! /ufs/guido/bin/sgi/python
+
+import sys
+import audio
+import stdwin
+
+import string
+import getopt
+
+from stdwinevents import *
+from Buttons import *
+from Sliders import *
+#from Soundogram import Soundogram
+from VUMeter import VUMeter
+from WindowParent import WindowParent, MainLoop
+from HVSplit import HSplit, VSplit
+
+class TimeOutToggleButton(ToggleButton):
+	def define(self, parent):
+		self = ToggleButton.define(self, parent)
+		self.parent.need_timer(self)
+		self.timer_hook = 0
+		return self
+	def timer(self):
+		if self.timer_hook:
+			self.timer_hook(self)
+
+K = 1024
+BUFSIZE = 30*8*K
+Rates = [0, 32*K, 16*K, 8*K]
+Magics = ['', '0032', '0016', '0008']
+
+class Struct: pass
+G = Struct()
+
+def main():
+	#
+	# Turn off scroll bars
+	#
+	stdwin.setdefscrollbars(0, 0)
+	#
+	# Set default state
+	#
+	G.gain = 60
+	G.rate = 3
+	G.nomuting = 0
+	G.savefile = '@rec'
+	#
+	# Set default values
+	#
+	G.data = ''
+	G.playing = 0
+	G.recording = 0
+	G.sogram = 0
+	#
+	# Parse options
+	#
+	optlist, args = getopt.getopt(sys.argv[1:], 'mdg:r:')
+	#
+	for optname, optarg in optlist:
+		if 0: # (So all cases start with elif)
+			pass
+		elif optname == '-d':
+			G.debug = 1
+		elif optname == '-g':
+			G.gain = string.atoi(optarg)
+			if not (0 < G.gain < 256):
+				raise optarg.error, '-g gain out of range'
+		elif optname == '-m':
+			G.nomuting = (not G.nomuting)
+		elif optname == '-r':
+			G.rate = string.atoi(optarg)
+			if not (1 <= G.rate <= 3):
+				raise optarg.error, '-r rate out of range'
+	#
+	if args:
+		G.savefile = args[0]
+	#
+	# Initialize the sound package
+	#
+	audio.setoutgain(G.nomuting * G.gain)	# Silence the speaker
+	audio.setrate(G.rate)
+	#
+	# Create the WindowParent and VSplit
+	#
+	G.window = WindowParent().create('Recorder', (0, 0))
+	w = G.vsplit = VSplit().create(G.window)
+	#
+	# VU-meter
+	#
+	G.vubtn = VUMeter().define(w)
+	#
+	# Radiobuttons for rates
+	#
+	r1btn = RadioButton().definetext(w, '32 K/sec')
+	r1btn.on_hook = rate_hook
+	r1btn.rate = 1
+	#
+	r2btn = RadioButton().definetext(w, '16 K/sec')
+	r2btn.on_hook = rate_hook
+	r2btn.rate = 2
+	#
+	r3btn = RadioButton().definetext(w, '8 K/sec')
+	r3btn.on_hook = rate_hook
+	r3btn.rate = 3
+	#
+	radios = [r1btn, r2btn, r3btn]
+	r1btn.group = r2btn.group = r3btn.group = radios
+	for r in radios:
+		if r.rate == G.rate: r.select(1)
+	#
+	# Other controls
+	#
+	G.recbtn = TimeOutToggleButton().definetext(w, 'Record')
+	G.recbtn.on_hook = record_on_hook
+	G.recbtn.timer_hook = record_timer_hook
+	G.recbtn.off_hook = record_off_hook
+	#
+	G.mutebtn = CheckButton().definetext(w, 'Mute')
+	G.mutebtn.select(not G.nomuting)
+	G.mutebtn.hook = mute_hook
+	#
+	G.playbtn = TimeOutToggleButton().definetext(w, 'Playback')
+	G.playbtn.on_hook = play_on_hook
+	G.playbtn.timer_hook = play_timer_hook
+	G.playbtn.off_hook = play_off_hook
+	#
+	G.gainbtn = ComplexSlider().define(w)
+	G.gainbtn.settexts('  Volume: ', '  ')
+	G.gainbtn.setminvalmax(0, G.gain, 255)
+	G.gainbtn.sethook(gain_hook)
+	#
+	G.sizebtn = Label().definetext(w, `len(G.data)` + ' bytes')
+	#
+	#G.showbtn = PushButton().definetext(w, 'Sound-o-gram...')
+	#G.showbtn.hook = show_hook
+	#
+	G.savebtn = PushButton().definetext(w, 'Save...')
+	G.savebtn.hook = save_hook
+	#
+	G.quitbtn = PushButton().definetext(w, 'Quit')
+	G.quitbtn.hook = quit_hook
+	G.playbtn.enable(0)
+	G.savebtn.enable(0)
+	#G.showbtn.enable(0)
+	start_vu()
+	G.window.realize()
+	#
+	# Event loop
+	#
+	MainLoop()
+
+# XXX Disabled...
+def show_hook(self):
+	savetext = self.text
+	self.settext('Be patient...')
+	close_sogram()
+	stdwin.setdefwinsize(400, 300)
+	win = stdwin.open('Sound-o-gram')
+	G.sogram = Soundogram().define(win, G.data)
+	win.buttons = [G.sogram]
+	self.settext(savetext)
+
+def close_sogram():
+	if G.sogram:
+		# Break circular references
+		G.sogram.win.buttons[:] = []
+		del G.sogram.win
+		G.sogram = 0
+
+def mute_hook(self):
+	G.nomuting = (not self.selected)
+	audio.setoutgain(G.nomuting * G.gain)
+
+def rate_hook(self):
+	G.rate = self.rate
+	audio.setrate(G.rate)
+
+def record_on_hook(self):
+	stop_vu()
+	close_sogram()
+	audio.setrate(G.rate)
+	audio.setoutgain(G.nomuting * G.gain)
+	audio.start_recording(BUFSIZE)
+	G.recording = 1
+	G.playbtn.enable(0)
+	G.window.settimer(10 * BUFSIZE / Rates[G.rate])
+
+def record_timer_hook(self):
+	if G.recording:
+		if audio.poll_recording():
+			self.hilite(0)
+			record_off_hook(self)
+		else:
+			self.parent.settimer(5)
+
+def record_off_hook(self):
+	if not G.recording:
+		return
+	G.data = audio.stop_recording()
+	G.recording = 0
+	G.sizebtn.settext(`len(G.data)` + ' bytes')
+	audio.setoutgain(G.nomuting * G.gain)
+	G.playbtn.enable((len(G.data) > 0))
+	G.savebtn.enable((len(G.data) > 0))
+	#G.showbtn.enable((len(G.data) > 0))
+	G.window.settimer(0)
+	start_vu()
+
+def play_on_hook(self):
+	stop_vu()
+	audio.setrate(G.rate)
+	audio.setoutgain(G.gain)
+	audio.start_playing(G.data)
+	G.playing = 1
+	G.recbtn.enable(0)
+	G.window.settimer(max(10 * len(G.data) / Rates[G.rate], 1))
+
+def play_timer_hook(self):
+	if G.playing:
+		if audio.poll_playing():
+			self.hilite(0)
+			play_off_hook(self)
+		else:
+			self.parent.settimer(5)
+
+def play_off_hook(self):
+	if not G.playing:
+		return
+	x = audio.stop_playing()
+	G.playing = 0
+	audio.setoutgain(G.nomuting * G.gain)
+	G.recbtn.enable(1)
+	G.window.settimer(0)
+	start_vu()
+
+def gain_hook(self):
+	G.gain = self.val
+	if G.playing or G.nomuting: audio.setoutgain(G.gain)
+
+def save_hook(self):
+	if not G.data:
+		stdwin.fleep()
+	else:
+		prompt = 'Store sampled data on file: '
+		try:
+			G.savefile = stdwin.askfile(prompt, G.savefile, 1)
+		except KeyboardInterrupt:
+			return
+		try:
+			fp = open(G.savefile, 'w')
+			fp.write(Magics[G.rate] + G.data)
+		except:
+			stdwin.message('Cannot create ' + file)
+
+def stop_vu():
+	G.vubtn.stop()
+
+def start_vu():
+	G.vubtn.start()
+
+def quit_hook(self):
+	G.window.delayed_destroy()
+
+try:
+	main()
+finally:
+	audio.setoutgain(0)
diff --git a/Demo/sgi/audio_stdwin/vumeter.py b/Demo/sgi/audio_stdwin/vumeter.py
new file mode 100755
index 0000000..bfee66e
--- /dev/null
+++ b/Demo/sgi/audio_stdwin/vumeter.py
@@ -0,0 +1,35 @@
+#! /usr/local/python
+
+import audio
+import stdwin
+
+from VUMeter import VUMeter
+from WindowParent import WindowParent
+import MainLoop
+
+NBUFS=20
+BUFSIZE = NBUFS*48
+SCALE=128
+
+class MyVUMeter(VUMeter):
+	def init_reactivity(self):
+		self.parent.need_mouse(self)
+	def mouse_down(self, detail):
+		if self.enabled:
+			self.stop()
+		else:
+			self.start()
+	def mouse_move(self, detail): pass
+	def mouse_up(self, detail): pass
+
+def main():
+	audio.setrate(3)
+	audio.setoutgain(0)
+	w = WindowParent().create('VU Meter', (200, 100))
+	v = MyVUMeter().define(w)
+	v.start()
+	w.realize()
+	while 1:
+		w.dispatch(stdwin.getevent())
+
+main()
diff --git a/Demo/sgi/flp/test_cb.fd b/Demo/sgi/flp/test_cb.fd
new file mode 100755
index 0000000..e83fd1f
--- /dev/null
+++ b/Demo/sgi/flp/test_cb.fd
@@ -0,0 +1,75 @@
+Magic: 12321
+
+Internal Form Definition File
+    (do not change)
+
+Number of forms: 1
+
+=============== FORM ===============
+Name: main_form
+Width: 170.000000
+Height: 190.000000
+Number of Objects: 4
+
+--------------------
+class: 1
+type: 1
+box: 0.000000 0.000000 170.000000 190.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: 
+name: 
+callback: 
+argument: 
+
+--------------------
+class: 11
+type: 0
+box: 10.000000 140.000000 150.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Button 1
+name: button1
+callback: button1CB
+argument: 0
+
+--------------------
+class: 11
+type: 0
+box: 10.000000 100.000000 150.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Button 2
+name: button2
+callback: button2CB
+argument: 0
+
+--------------------
+class: 11
+type: 6
+box: 10.000000 10.000000 150.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: EXIT
+name: exitbutton
+callback: exitbuttonCB
+argument: 0
+
+==============================
+create_the_forms
diff --git a/Demo/sgi/flp/test_cb.py b/Demo/sgi/flp/test_cb.py
new file mode 100755
index 0000000..d622332
--- /dev/null
+++ b/Demo/sgi/flp/test_cb.py
@@ -0,0 +1,62 @@
+#
+# Example 2 - Using fl in python with callbacks.
+#
+# The form is named 'main_form' and resides on file 'test_cb.fd'.
+# It has three objects named button1, button2 and exitbutton.
+# All buttons have callbacks with the same names as their corresponding
+# buttons but with CB appended.
+#
+import fl		# The forms library
+import FL		# Symbolic constants for the above
+import flp		# The module to parse .fd files
+import sys
+
+# The following struct is created to hold the instance variables
+# main_form, button1, button2 and exitbutton.
+
+class myform():
+	#
+	# The init function parses and creates the form, but doesn't
+	# display it (yet).
+	def init(self, number):
+		#
+		# First we parse the form
+		parsetree = flp.parse_form('test_cb', 'main_form')
+		#
+		# Next we create it
+		
+		flp.create_full_form(self, parsetree)
+
+		# And keep our number
+		self.number = number
+		return self
+
+	#
+	# The show function displays the form. It doesn't do any interaction,
+	# though.
+	def show(self):
+		self.main_form.show_form(FL.PLACE_SIZE, 1, '')
+
+	# The callback functions
+	def button1CB(self, obj, arg):
+		print 'Button 1 pressed on form', self.number
+
+	def button2CB(self, obj, arg):
+		print 'Button 2 pressed on form', self.number
+
+	def exitbuttonCB(self, obj, arg):
+		print 'Ok, bye bye'
+		sys.exit(0)
+
+#
+# The main program. Instantiate two variables of the forms class
+# and interact with them.
+
+form1 = myform().init(1)
+form2 = myform().init(2)
+
+form1.show()
+form2.show()
+
+obj = fl.do_forms()
+print 'do_forms() returned. This should not happen. obj=', obj
diff --git a/Demo/sgi/flp/test_nocb.fd b/Demo/sgi/flp/test_nocb.fd
new file mode 100755
index 0000000..4d3f7ef
--- /dev/null
+++ b/Demo/sgi/flp/test_nocb.fd
@@ -0,0 +1,75 @@
+Magic: 12321
+
+Internal Form Definition File
+    (do not change)
+
+Number of forms: 1
+
+=============== FORM ===============
+Name: main_form
+Width: 170.000000
+Height: 190.000000
+Number of Objects: 4
+
+--------------------
+class: 1
+type: 1
+box: 0.000000 0.000000 170.000000 190.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: 
+name: 
+callback: 
+argument: 
+
+--------------------
+class: 11
+type: 0
+box: 10.000000 140.000000 150.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Button 1
+name: button1
+callback: 
+argument: 
+
+--------------------
+class: 11
+type: 0
+box: 10.000000 100.000000 150.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: Button 2
+name: button2
+callback: 
+argument: 
+
+--------------------
+class: 11
+type: 6
+box: 10.000000 10.000000 150.000000 40.000000
+boxtype: 1
+colors: 47 47
+alignment: 4
+style: 0
+size: 11.000000
+lcol: 0
+label: EXIT
+name: exitbutton
+callback: 
+argument: 
+
+==============================
+create_the_forms
diff --git a/Demo/sgi/flp/test_nocb.py b/Demo/sgi/flp/test_nocb.py
new file mode 100755
index 0000000..48cee9d
--- /dev/null
+++ b/Demo/sgi/flp/test_nocb.py
@@ -0,0 +1,45 @@
+#
+# Example 1 - Using fl in python without callbacks.
+#
+# The form is named 'main_form' and resides on file 'test_nocb.fd'.
+# It has three objects named button1, button2 and exitbutton.
+#
+import fl		# The forms library
+import FL		# Symbolic constants for the above
+import flp		# The module to parse .fd files
+import sys
+
+# The following struct is created to hold the instance variables
+# main_form, button1, button2 and exitbutton.
+
+class struct(): pass
+container = struct()
+
+#
+# We now first parse the forms file
+
+parsetree = flp.parse_form('test_nocb', 'main_form')
+
+#
+# Next we create it
+
+flp.create_full_form(container, parsetree)
+
+#
+# And display it
+
+container.main_form.show_form(FL.PLACE_MOUSE, 1, '')
+
+#
+# And interact until the exit button is pressed
+while 1:
+	selected_obj = fl.do_forms()
+	if selected_obj == container.button1:
+		print 'Button 1 selected'
+	elif selected_obj == container.button2:
+		print 'Button 2 selected'
+	elif selected_obj == container.exitbutton:
+		print 'Ok, bye bye'
+		sys.exit(0)
+	else:
+		print 'do_forms() returned unknown object ', selected_obj