Initial revision
diff --git a/Demo/cwilib/cwilib.py b/Demo/cwilib/cwilib.py
new file mode 100755
index 0000000..25e0622
--- /dev/null
+++ b/Demo/cwilib/cwilib.py
@@ -0,0 +1,226 @@
+# Interface to the interactive CWI library catalog.
+
+import sys
+import stdwin
+from stdwinevents import *
+import select
+import telnetlib
+import vt100win
+from form import Form
+
+
+# Main program
+
+def main():
+	vt = vt100win.VT100win()
+	#
+	host = 'biefstuk.cwi.nl'
+	port = 0
+	timeout = 10.0
+	tn = telnetlib.Telnet(host, port)
+	tn.set_timeout(timeout)
+	#
+	try:
+		vt.send(tn.read_until('login: '))
+		tn.write('cwilib\r')
+		#
+		vt.send(tn.read_until('Hit <RETURN> to continue...'))
+		tn.write('\r')
+		#
+		vt.send(tn.read_until('QUIT'))
+	except EOFError:
+		sys.stderr.write('Connection closed prematurely\n')
+		sys.exit(1)
+	#
+	define_screens(vt)
+	matches = vt.which_screens()
+	if 'menu' not in matches:
+		sys.stderr.write('No main menu within %g seconds\n' % timeout)
+		sys.exit(1)
+	#
+	tn.set_timeout(0)
+	tn.write('\r\r')
+	vt.open('Progress -- CWI Library')
+	ui = UserInterface()
+	#
+	while 1:
+		event = stdwin.pollevent()
+		if not event:
+			rfd, wfd, xfd = select.select([stdwin, tn], [], [])
+			if stdwin in rfd:
+				event = stdwin.getevent()
+		if event:
+			type, window, detail = event
+			if window == None and type == WE_LOST_SEL:
+				window = ui.queryform.window
+				event = type, window, detail
+			if type == WE_CLOSE:
+				break
+			if window in ui.windows:
+				ui.dispatch(type, window, detail)
+			elif window == vt.window:
+				if type == WE_NULL:
+					pass
+				elif type == WE_COMMAND:
+					if detail == WC_RETURN:
+						tn.write('\r')
+					elif detail == WC_BACKSPACE:
+						tn.write('\b')
+					elif detail == WC_TAB:
+						tn.write('\t')
+					elif detail == WC_UP:
+						tn.write('\033[A')
+					elif detail == WC_DOWN:
+						tn.write('\033[B')
+					elif detail == WC_RIGHT:
+						tn.write('\033[C')
+					elif detail == WC_LEFT:
+						tn.write('\033[D')
+					else:
+						print '*** Command:', detail
+				elif type == WE_CHAR:
+					tn.write(detail)
+				elif type == WE_DRAW:
+					vt.draw(detail)
+				elif type in (WE_ACTIVATE, WE_DEACTIVATE):
+					pass
+				else:
+					print '*** VT100 event:', type, detail
+			else:
+				print '*** Alien event:', type, window, detail
+		elif tn in rfd:
+			vt.window.setwincursor('watch')
+			try:
+				data = tn.read_now()
+			except EOFError:
+				stdwin.message('Connection closed--goodbye')
+				break
+			print 'send...'
+			vt.send(data)
+			print 'send...done'
+			vt.window.setwincursor('arrow')
+			matches = vt.which_screens()
+			if 'timelimit' in matches:
+				stdwin.message('Time limit--goodbye')
+				break
+			print '*** Matches:', matches
+		else:
+			print '*** Weird return from select:', rfd, wfd, xfd
+
+
+# Subroutine to define our screen recognition patterns
+
+def define_screens(vt):
+	vt.define_screen('menu', {
+		  'title': ('search', 0, 0, 80,
+		            ' SEARCH FUNCTIONS  +OTHER FUNCTIONS '),
+		  })
+	vt.define_screen('search', {
+		  'title': ('search', 0, 0, 80, ' Search '),
+		  })
+	vt.define_screen('shortlist', {'title': ('search', 0, 0, 80,
+		  ' Short-list')})
+	vt.define_screen('showrecord', {
+		  'title': ('search', 0, 0, 80, ' Show record '),
+		  })
+	vt.define_screen('timelimit', {
+		  'limit': ('search', 12, 0, 80, ' TIME LIMIT '),
+		  })
+	vt.define_screen('attention', {
+		  'BASE': ('copy', 0, 0, 0, 'search'),
+		  'title': ('search', 10, 0, 80, ' ATTENTION ')})
+	vt.define_screen('syntaxerror', {
+		  'BASE': ('copy', 0, 0, 0, 'attention'),
+		  'message': ('search', 12, 0, 80, ' Syntax error'),
+		  })
+	vt.define_screen('emptyerror', {
+		  'BASE': ('copy', 0, 0, 0, 'attention'),
+		  'message': ('search', 12, 0, 80,
+		              ' Check your input. Search at least one term'),
+		  })
+	vt.define_screen('unsortedwarning', {
+		  'BASE': ('copy', 0, 0, 0, 'attention'),
+		  'message': ('search', 12, 0, 80,
+		              ' Number of records exceeds sort limit'),
+		  })
+	vt.define_screen('thereismore', {
+		  'BASE': ('copy', 0, 0, 0, 'showrecord'),
+		  'message': ('search', 15, 0, 80,
+		     'There is more within this record. Use the arrow keys'),
+		  })
+	vt.define_screen('nofurther', {
+		  'BASE': ('copy', 0, 0, 0, 'showrecord'),
+		  'message': ('search', 17, 0, 80, 'You cannot go further\.'),
+		  })
+	vt.define_screen('nofurtherback', {
+		  'BASE': ('copy', 0, 0, 0, 'showrecord'),
+		  'message': ('search', 17, 0, 80,
+		              'You cannot go further back'),
+		  })
+
+
+# Class to implement our user interface.
+
+class UserInterface:
+
+	def __init__(self):
+		stdwin.setfont('7x14')
+		self.queryform = QueryForm()
+		self.listform = ListForm()
+		self.recordform = RecordForm()
+		self.forms = [self.queryform, self.listform, self.recordform]
+		define_query_fields(self.queryform)
+		self.windows = []
+		for form in self.forms:
+			if form.formheight > 0:
+				form.open()
+				self.windows.append(form.window)
+
+	def __del__(self):
+		self.close()
+
+	def close(self):
+		for form in self.forms:
+			form.close()
+
+	def dispatch(self, type, window, detail):
+		for form in self.forms:
+			if window == form.window:
+				form.dispatch(type, detail)
+
+
+def define_query_fields(f):
+	f.define_field('name', 'Name auth./ed.', 1, 60)
+	f.define_field('title',  'Title', 4, 60)
+	f.define_field('shelfmark', 'Shelf mark', 1, 60)
+	f.define_field('class', 'Prim. classif.', 1, 60)
+	f.define_field('series', 'Series', 1, 60)
+	f.define_field('congress', 'Congr. pl./year', 1, 60)
+	f.define_field('type', 'Type', 1, 60)
+
+
+class QueryForm(Form):
+
+	def __init__(self):
+		Form.__init__(self, 'Query form -- CWI Library')
+
+	def dispatch(self, type, detail):
+		if type == WE_COMMAND and detail == WC_RETURN:
+			print '*** SUBMIT ***'
+		else:
+			Form.dispatch(self, type, detail)
+
+
+class ListForm(Form):
+
+	def __init__(self):
+		Form.__init__(self, 'Short list -- CWI Library')
+
+
+class RecordForm(Form):
+
+	def __init__(self):
+		Form.__init__(self, 'Record detail -- CWI Library')
+
+
+main()