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()
diff --git a/Demo/cwilib/form.py b/Demo/cwilib/form.py
new file mode 100755
index 0000000..8dd6ef9
--- /dev/null
+++ b/Demo/cwilib/form.py
@@ -0,0 +1,170 @@
+# Fill-out form window
+
+import stdwin
+from stdwinevents import *
+
+
+class Form:
+
+	def __init__(self, title):
+		self.title = title
+		self.window = None
+		self.fields = {}
+		self.fieldnames = []
+		self.formwidth = self.formheight = 0
+		self.focusname = None
+		self.tefocus = None
+
+	def define_field(self, name, label, lines, chars):
+		self.fieldnames.append(name)
+		lh = stdwin.lineheight()
+		cw = stdwin.textwidth('m')
+		left = 20*cw
+		top = self.formheight + 4
+		right = left + chars*cw
+		bottom = top + lines*lh
+		te = None
+		self.fields[name] = (label, left, top, right, bottom, te)
+		self.formheight = bottom + 2
+		self.formwidth = max(self.formwidth, right + 4)
+
+	def open(self):
+		if self.window: return
+		self.formwidth = max(100, self.formwidth)
+		self.formheight = max(50, self.formheight)
+		stdwin.setdefwinsize(self.formwidth, self.formheight)
+		stdwin.setdefscrollbars(0, 0)
+		self.window = stdwin.open(self.title)
+		self.window.setdocsize(self.formwidth, self.formheight)
+		for name in self.fieldnames:
+			label, left, top, right, bottom, te = \
+				  self.fields[name]
+			rect = (left, top), (right, bottom)
+			te = self.window.textcreate(rect)
+			te.setactive(0)
+			te.setview(rect)
+			self.fields[name] = \
+				  label, left, top, right, bottom, te
+		if self.fieldnames:
+			self.setfocus(self.fieldnames[0])
+
+	def setfocus(self, name):
+		if name <> self.focusname and self.tefocus:
+			self.tefocus.setactive(0)
+		self.focusname = name
+		if self.focusname:
+			self.tefocus = self.fields[self.focusname][-1]
+			self.tefocus.setactive(1)
+		else:
+			self.tefocus = None
+
+	def dispatch(self, type, detail):
+		event = type, self.window, detail
+		if type == WE_NULL:
+			pass
+		elif type == WE_DRAW:
+			self.draw(detail)
+		elif type == WE_MOUSE_DOWN:
+			x, y = detail[0]
+			for name in self.fieldnames:
+				label, left, top, right, bottom, te = \
+					  self.fields[name]
+				if left <= x < right and \
+					  top <= y < bottom:
+					self.setfocus(name)
+					break
+			else:
+				stdwin.fleep()
+				return
+			if self.tefocus:
+				(left, top), (right, bottom) = \
+					  self.tefocus.getrect()
+				if x < left: x = left
+				if x >= right: x = right-1
+				if y < top: y = top
+				if y >= bottom:
+					y = bottom-1
+					x = right-1
+				event = type, self.window, ((x,y),)+detail[1:]
+				if not self.tefocus.event(event):
+					stdwin.fleep()
+		elif type in (WE_MOUSE_MOVE, WE_MOUSE_UP, WE_CHAR):
+			if not self.tefocus or not self.tefocus.event(event):
+				stdwin.fleep()
+			elif type == WE_MOUSE_UP:
+				button = detail[2]
+				if button == 2:
+					self.paste_selection()
+				else:
+					self.make_selection()
+		elif type == WE_COMMAND:
+			if detail in (WC_BACKSPACE, WC_UP, WC_DOWN,
+				      WC_LEFT, WC_RIGHT):
+				if not self.tefocus or \
+					  not self.tefocus.event(event):
+					stdwin.fleep()
+			elif detail == WC_RETURN:
+				print '*** Submit query'
+			elif detail == WC_TAB:
+				if not self.fields:
+					stdwin.fleep()
+					return
+				if not self.focusname:
+					i = 0
+				else:
+					i = self.fieldnames.index(
+						  self.focusname)
+					i = (i+1) % len(self.fieldnames)
+				self.setfocus(self.fieldnames[i])
+				self.tefocus.setfocus(0, 0x7fff)
+				self.make_selection()
+		elif type in (WE_ACTIVATE, WE_DEACTIVATE):
+			pass
+		elif type == WE_LOST_SEL:
+			if self.tefocus:
+				a, b = self.tefocus.getfocus()
+				self.tefocus.setfocus(a, a)
+		else:
+			print 'Form.dispatch(%d, %s)' % (type, `detail`)
+
+	def draw(self, detail):
+		d = self.window.begindrawing()
+		d.cliprect(detail)
+		d.erase(detail)
+		self.drawform(d, detail)
+		d.noclip()
+		d.close()
+		# Stupid textedit objects can't draw with open draw object...
+		self.drawtextedit(detail)
+
+	def drawform(self, d, detail):
+		for name in self.fieldnames:
+			label, left, top, right, bottom, te = self.fields[name]
+			d.text((0, top), label)
+			d.box((left-3, top-2), (right+4, bottom+2))
+
+	def drawtextedit(self, detail):
+		for name in self.fieldnames:
+			label, left, top, right, bottom, te = self.fields[name]
+			te.draw(detail)
+
+	def make_selection(self):
+		s = self.tefocus.getfocustext()
+		if not s:
+			return
+		stdwin.rotatecutbuffers(1)
+		stdwin.setcutbuffer(0, s)
+		if not self.window.setselection(WS_PRIMARY, s):
+			stdwin.fleep()
+
+	def paste_selection(self):
+		if not self.tefocus:
+			stdwin.fleep()
+			return
+		s = stdwin.getselection(WS_PRIMARY)
+		if not s:
+			s = stdwin.getcutbuffer(0)
+			if not s:
+				stdwin.fleep()
+				return
+		self.tefocus.replace(s)
diff --git a/Demo/cwilib/telnetlib.py b/Demo/cwilib/telnetlib.py
new file mode 100755
index 0000000..5c862e7
--- /dev/null
+++ b/Demo/cwilib/telnetlib.py
@@ -0,0 +1,181 @@
+# Telnet client library
+
+import socket
+import select
+import string
+import regsub
+
+# Tunable parameters
+TIMEOUT = 30.0
+DEBUGLEVEL = 1
+
+# Telnet protocol defaults
+TELNET_PORT = 23
+
+# Telnet protocol characters (don't change)
+IAC  = chr(255)	# "Interpret As Command"
+DONT = chr(254)
+DO   = chr(253)
+WONT = chr(252)
+WILL = chr(251)
+
+
+# Telnet interface class
+
+class Telnet:
+
+	# Constructor
+	def __init__(self, host, port):
+		self.debuglevel = DEBUGLEVEL
+		self.host = host
+		if not port: port = TELNET_PORT
+		self.port = port
+		self.timeout = TIMEOUT
+		self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+		self.sock.connect((self.host, self.port))
+		self.rawq = ''
+		self.irawq = 0
+		self.cookedq = ''
+
+	# Destructor
+	def __del__(self):
+		self.close()
+
+	# Print debug message
+	def msg(self, msg, *args):
+		if self.debuglevel > 0:
+			print 'TELNET:', msg%args
+
+	# Set debug level
+	def set_debuglevel(self, debuglevel):
+		self.debuglevel = debuglevel
+
+	# Set time-out on certain reads
+	def set_timeout(self, timeout):
+		self.timeout = float(timeout)
+
+	# Explicit close
+	def close(self):
+		if self.sock:
+			self.sock.close()
+		self.sock = None
+
+	# Return socket (e.g. for select)
+	def get_socket(self):
+		return self.sock
+
+	# Return socket's fileno (e.g. for select)
+	def fileno(self):
+		return self.sock.fileno()
+
+	# Write a string to the socket, doubling any IAC characters
+	def write(self, buffer):
+		if IAC in buffer:
+			buffer = regsub.gsub(IAC, IAC+IAC, buffer)
+		self.sock.send(buffer)
+
+	# Read until a given string is encountered or until timeout
+	def read_until(self, match):
+##		self.msg('read_until(%s)' % `match`)
+		n = len(match)
+		self.process_rawq()
+		i = string.find(self.cookedq, match)
+		if i < 0:
+			i = max(0, len(self.cookedq)-n)
+			self.fill_cookedq()
+			i = string.find(self.cookedq, match, i)
+		if i >= 0:
+			i = i+n
+			buf = self.cookedq[:i]
+			self.cookedq = self.cookedq[i:]
+##			self.msg('read_until(%s) -> %s' % (`match`, `buf`))
+			return buf
+		while select.select([self], [], [], self.timeout) == \
+			  ([self], [], []):
+			i = max(0, len(self.cookedq)-n)
+			self.fill_rawq()
+			self.process_rawq()
+			i = string.find(self.cookedq, match, i)
+			if i >= 0:
+				i = i+n
+				buf = self.cookedq[:i]
+				self.cookedq = self.cookedq[i:]
+##				self.msg('read_until(%s) -> %s' %
+##					  (`match`, `buf`))
+				return buf
+		buf = self.cookedq
+		self.cookedq = ''
+##		self.msg('read_until(%s) -> %s' % (`match`, `buf`))
+		return buf
+
+	# Read everything that's possible without really blocking
+	def read_now(self):
+		self.fill_cookedq()
+		buf = self.cookedq
+		self.cookedq = ''
+##		self.msg('read_now() --> %s' % `buf`)
+		return buf
+
+	# Fill cooked queue without blocking
+	def fill_cookedq(self):
+		self.process_rawq()
+		while select.select([self], [], [], 0) == ([self], [], []):
+			self.fill_rawq()
+			if not self.rawq:
+				raise EOFError
+			self.process_rawq()
+
+	# Transfer from raw queue to cooked queue
+	def process_rawq(self):
+		# There is some silliness going on here in an attempt
+		# to avoid quadratic behavior with large inputs...
+		buf = ''
+		while self.rawq:
+			c = self.rawq_getchar()
+			if c != IAC:
+				buf = buf + c
+				if len(buf) >= 44:
+##					self.msg('transfer: %s' % `buf`)
+					self.cookedq = self.cookedq + buf
+					buf = ''
+				continue
+			c = self.rawq_getchar()
+			if c == IAC:
+				buf = buf + c
+			elif c in (DO, DONT):
+				opt = self.rawq_getchar()
+				self.msg('IAC %s %d',
+					  c == DO and 'DO' or 'DONT',
+					  ord(c))
+				self.sock.send(IAC + WONT + opt)
+			elif c in (WILL, WONT):
+				opt = self.rawq_getchar()
+				self.msg('IAC %s %d',
+					  c == WILL and 'WILL' or 'WONT',
+					  ord(c))
+			else:
+				self.msg('IAC %s not recognized' % `c`)
+##		self.msg('transfer: %s' % `buf`)
+		self.cookedq = self.cookedq + buf
+
+	# Get next char from raw queue, blocking if necessary
+	def rawq_getchar(self):
+		if not self.rawq:
+			self.fill_rawq()
+		if self.irawq >= len(self.rawq):
+			raise EOFError
+		c = self.rawq[self.irawq]
+		self.irawq = self.irawq + 1
+		if self.irawq >= len(self.rawq):
+			self.rawq = ''
+			self.irawq = 0
+		return c
+
+	# Fill raw queue
+	def fill_rawq(self):
+		if self.irawq >= len(self.rawq):
+			self.rawq = ''
+			self.irawq = 0
+		buf = self.sock.recv(50)
+##		self.msg('fill_rawq(): %s' % `buf`)
+		self.rawq = self.rawq + buf
diff --git a/Demo/cwilib/vt100.py b/Demo/cwilib/vt100.py
new file mode 100755
index 0000000..e802389
--- /dev/null
+++ b/Demo/cwilib/vt100.py
@@ -0,0 +1,326 @@
+# VT100 terminal emulator.
+# This is incomplete and slow, but will do for now...
+# It shouldn't be difficult to extend it to be a more-or-less complete
+# VT100 emulator.  And little bit of profiling could go a long way...
+
+from array import array
+import regex
+import string
+
+# Tunable parameters
+DEBUGLEVEL = 1
+
+# Symbolic constants
+ESC = '\033'
+
+
+# VT100 emulation class
+
+class VT100:
+
+	def __init__(self):
+		self.debuglevel = DEBUGLEVEL
+		# Unchangeable parameters (for now)
+		self.width = 80
+		self.height = 24
+		self.blankline = array('c', ' '*self.width)
+		self.blankattr = array('b', '\0'*self.width)
+		# Set mutable display state
+		self.reset()
+		# Set parser state
+		self.unfinished = ''
+		# Set screen recognition state
+		self.reset_recognizer()
+
+	def msg(self, msg, *args):
+		if self.debuglevel > 0:
+			print 'VT100:', msg%args
+
+	def set_debuglevel(self, debuglevel):
+		self.debuglevel = debuglevel
+
+	def reset(self):
+		self.lines = []
+		self.attrs = []
+		self.fill_bottom()
+		self.x = 0
+		self.y = 0
+		self.curattrs = []
+
+	def show(self):
+		lineno = 0
+		for line in self.lines:
+			lineno = lineno + 1
+			i = len(line)
+			while i > 0 and line[i-1] == ' ': i = i-1
+			print line[:i]
+			print 'CURSOR:', self.x, self.y
+
+	def fill_bottom(self):
+		while len(self.lines) < self.height:
+			self.lines.append(self.blankline[:])
+			self.attrs.append(self.blankattr[:])
+
+	def fill_top(self):
+		while len(self.lines) < self.height:
+			self.lines.insert(0, self.blankline[:])
+			self.attrs.insert(0, self.blankattr[:])
+
+	def clear_all(self):
+		self.lines = []
+		self.attrs = []
+		self.fill_bottom()
+
+	def clear_below(self):
+		del self.lines[self.y:]
+		del self.attrs[self.y:]
+		self.fill_bottom()
+
+	def clear_above(self):
+		del self.lines[:self.y]
+		del self.attrs[:self.y]
+		self.fill_top()
+
+	def send(self, buffer):
+		self.unfinished = self.unfinished + buffer
+		i = 0
+		n = len(self.unfinished)
+		while i < n:
+			c = self.unfinished[i]
+			i = i+1
+			if c != ESC:
+				self.add_char(c)
+				continue
+			if i >= n:
+				i = i-1
+				break
+			c = self.unfinished[i]
+			i = i+1
+			if c == 'c':
+				self.reset()
+				continue
+			if c <> '[':
+				self.msg('unrecognized: ESC %s', `c`)
+				continue
+			argstr = ''
+			while i < n:
+				c = self.unfinished[i]
+				i = i+1
+				if c not in '0123456789;':
+					break
+				argstr = argstr + c
+			else:
+				i = i - len(argstr)
+				break
+##			self.msg('found ESC [ %s %s' % (`argstr`, `c`))
+			args = string.splitfields(argstr, ';')
+			for j in range(len(args)):
+				s = args[j]
+				while s[:1] == '0': s = s[1:]
+				if s: args[j] = eval(s)
+				else: args[j] = 0
+			p1 = p2 = 0
+			if args: p1 = args[0]
+			if args[1:]: p2 = args[1]
+			if c in '@ABCDH':
+				if not p1: p1 = 1
+			if c in 'H':
+				if not p2: p2 = 1
+			if c == '@':
+				for j in range(p1):
+					self.add_char(' ')
+			elif c == 'A':
+				self.move_by(0, -p1)
+			elif c == 'B':
+				self.move_by(0, p1)
+			elif c == 'C':
+				self.move_by(p1, 0)
+			elif c == 'D':
+				self.move_by(-p1, 0)
+			elif c == 'H':
+				self.move_to(p2-1, p1-1)
+			elif c == 'J':
+				if p1 == 0: self.clear_above()
+				elif p1 == 1: self.clear_below()
+				elif p1 == 2: self.clear_all()
+				else: self.msg('weird ESC [ %d J', p1)
+			elif c == 'K':
+				if p1 == 0: self.erase_right()
+				elif p1 == 1: self.erase_left()
+				elif p1 == 2: self.erase_line()
+				else: self.msg('weird ESC [ %d K', p1)
+			elif c == 'm':
+				if p1 == 0:
+					self.curattrs = []
+				else:
+					if p1 not in self.curattrs:
+						self.curattrs.append(p1)
+						self.curattrs.sort()
+			else:
+				self.msg('unrecognized: ESC [ %s', `argstr+c`)
+		self.unfinished = self.unfinished[i:]
+
+	def add_char(self, c):
+		if c == '\r':
+			self.move_to(0, self.y)
+			return
+		if c in '\n\f\v':
+			self.move_to(self.x, self.y + 1)
+			if self.y >= self.height:
+				self.scroll_up(1)
+				self.move_to(self.x, self.height - 1)
+			return
+		if c == '\b':
+			self.move_by(-1, 0)
+			return
+		if c == '\a':
+			self.msg('BELL')
+			return
+		if c == '\t':
+			self.move_to((self.x+8)/8*8, self.y)
+			return
+		if c == '\0':
+			return
+		if c < ' ' or c > '~':
+			self.msg('ignored control char: %s', `c`)
+			return
+		if self.x >= self.width:
+			self.move_to(0, self.y + 1)
+		if self.y >= self.height:
+			self.scroll_up(1)
+			self.move_to(self.x, self.height - 1)
+		self.lines[self.y][self.x] = c
+		if self.curattrs:
+			self.attrs[self.y][self.x] = max(self.curattrs)
+		else:
+			self.attrs[self.y][self.x] = 0
+		self.move_by(1, 0)
+
+	def move_to(self, x, y):
+		self.x = min(max(0, x), self.width)
+		self.y = min(max(0, y), self.height)
+
+	def move_by(self, dx, dy):
+		self.move_to(self.x + dx, self.y + dy)
+
+	def scroll_up(self, nlines):
+		del self.lines[:max(0, nlines)]
+		del self.attrs[:max(0, nlines)]
+		self.fill_bottom()
+
+	def scroll_down(self, nlines):
+		del self.lines[-max(0, nlines):]
+		del self.attrs[-max(0, nlines):]
+		self.fill_top()
+
+	def erase_left(self):
+		x = min(self.width-1, x)
+		y = min(self.height-1, y)
+		self.lines[y][:x] = self.blankline[:x]
+		self.attrs[y][:x] = self.blankattr[:x]
+
+	def erase_right(self):
+		x = min(self.width-1, x)
+		y = min(self.height-1, y)
+		self.lines[y][x:] = self.blankline[x:]
+		self.attrs[y][x:] = self.blankattr[x:]
+
+	def erase_line(self):
+		self.lines[y][:] = self.blankline
+		self.attrs[y][:] = self.blankattr
+
+	# The following routines help automating the recognition of
+	# standard screens.  A standard screen is characterized by
+	# a number of fields.  A field is part of a line,
+	# characterized by a (lineno, begin, end) tuple;
+	# e.g. the first 10 characters of the second line are
+	# specified by the tuple (1, 0, 10).  Fields can be:
+	# - regex: desired contents given by a regular expression,
+	# - extract: can be extracted,
+	# - cursor: screen is only valid if cursor in field,
+	# - copy: identical to another screen (position is ignored).
+	# A screen is defined as a dictionary full of fields.  Screens
+	# also have names and are placed in a dictionary.
+
+	def reset_recognizer(self):
+		self.screens = {}
+
+	def define_screen(self, screenname, fields):
+		fieldscopy = {}
+		# Check if the fields make sense
+		for fieldname in fields.keys():
+			field = fields[fieldname]
+			ftype, lineno, begin, end, extra = field
+			if ftype in ('match', 'search'):
+				extra = regex.compile(extra)
+			elif ftype == 'extract':
+				extra = None
+			elif ftype == 'cursor':
+				extra = None
+			elif ftype == 'copy':
+				if not self.screens.has_key(extra):
+					raise ValueError, 'bad copy ref'
+			else:
+				raise ValueError, 'bad ftype: %s' % `ftype`
+			fieldscopy[fieldname] = (
+				  ftype, lineno, begin, end, extra)
+		self.screens[screenname] = fieldscopy
+
+	def which_screens(self):
+		self.busy = []
+		self.okay = []
+		self.fail = []
+		for name in self.screens.keys():
+			ok = self.match_screen(name)
+		return self.okay[:]
+
+	def match_screen(self, name):
+		if name in self.busy: raise RuntimeError, 'recursive match'
+		if name in self.okay: return 1
+		if name in self.fail: return 0
+		self.busy.append(name)
+		fields = self.screens[name]
+		ok = 0
+		for key in fields.keys():
+			field = fields[key]
+			ftype, lineno, begin, end, extra = field
+			if ftype == 'copy':
+				if not self.match_screen(extra): break
+			elif ftype == 'search':
+				text = self.lines[lineno][begin:end].tostring()
+				if extra.search(text) < 0:
+					break
+			elif ftype == 'match':
+				text = self.lines[lineno][begin:end].tostring()
+				if extra.match(text) < 0:
+					break
+			elif ftype == 'cursor':
+				if self.x != lineno or not \
+					  begin <= self.y < end:
+					break
+		else:
+			ok = 1
+		if ok:
+			self.okay.append(name)
+		else:
+			self.fail.append(name)
+		self.busy.remove(name)
+		return ok
+
+	def extract_field(self, screenname, fieldname):
+		ftype, lineno, begin, end, extra = \
+			  self.screens[screenname][fieldname]
+		return stripright(self.lines[lineno][begin:end].tostring())
+
+	def extract_rect(self, left, top, right, bottom):
+		lines = []
+		for i in range(top, bottom):
+			lines.append(stripright(self.lines[i][left:right])
+				  .tostring())
+		return lines
+
+
+def stripright(line):
+	i = len(line)
+	while i > 0 and line[i-1] in string.whitespace: i = i-1
+	return line[:i]
diff --git a/Demo/cwilib/vt100win.py b/Demo/cwilib/vt100win.py
new file mode 100755
index 0000000..dcaf17b
--- /dev/null
+++ b/Demo/cwilib/vt100win.py
@@ -0,0 +1,68 @@
+# VT100 terminal emulator in a STDWIN window.
+
+import stdwin
+from stdwinevents import *
+from vt100 import VT100
+
+class VT100win(VT100):
+
+	def __init__(self):
+		VT100.__init__(self)
+		self.window = None
+##		self.last_x = -1
+##		self.last_y = -1
+
+	def __del__(self):
+		self.close()
+
+	def open(self, title):
+		stdwin.setfont('7x14')
+		self.docwidth = self.width * stdwin.textwidth('m')
+		self.docheight = self.height * stdwin.lineheight()
+		stdwin.setdefwinsize(self.docwidth + 2, self.docheight + 2)
+		stdwin.setdefscrollbars(0, 0)
+		self.window = stdwin.open(title)
+		self.window.setdocsize(self.docwidth + 2, self.docheight + 2)
+
+	def close(self):
+		if self.window:
+			self.window.close()
+		self.window = None
+
+	def show(self):
+		if not self.window: return
+		self.draw(((-10, -10), (self.docwidth+10, self.docheight+10)))
+
+	def draw(self, detail):
+		d = self.window.begindrawing()
+		fg = stdwin.getfgcolor()
+		red = stdwin.fetchcolor('red')
+		d.cliprect(detail)
+		d.erase(detail)
+		lh = d.lineheight()
+		cw = d.textwidth('m')
+		for y in range(self.height):
+			d.text((0, y*lh), self.lines[y].tostring())
+			if self.attrs[y] <> self.blankattr:
+				for x in range(len(self.attrs[y])):
+					if self.attrs[y][x] == 7:
+						p1 = x*cw, y*lh
+						p2 = (x+1)*cw, (y+1)*lh
+						d.invert((p1, p2))
+		x = self.x * cw
+		y = self.y * lh
+		d.setfgcolor(red)
+		d.invert((x, y), (x+cw, y+lh))
+		d.setfgcolor(fg)
+		d.close()
+
+##	def move_to(self, x, y):
+##		VT100.move_to(self, x, y)
+##		if self.y != self.last_y:
+##			self.show()
+##		self.last_x = self.x
+##		self.last_y = y
+
+	def send(self, str):
+		VT100.send(self, str)
+		self.show()