Initial revision
diff --git a/Demo/tkinter/guido/ManPage.py b/Demo/tkinter/guido/ManPage.py
new file mode 100755
index 0000000..993a3db
--- /dev/null
+++ b/Demo/tkinter/guido/ManPage.py
@@ -0,0 +1,149 @@
+# Widget to display a man page
+
+import regex
+from Tkinter import *
+from ScrolledText import ScrolledText
+
+# XXX These fonts may have to be changed to match your system
+BOLDFONT = '*-Courier-Bold-R-Normal-*-120-*'
+ITALICFONT = '*-Courier-Medium-O-Normal-*-120-*'
+
+# XXX Recognizing footers is system dependent
+# (This one works for IRIX 5.2 and Solaris 2.2)
+footerprog = regex.compile(
+	'^     Page [1-9][0-9]*[ \t]+\|^.*Last change:.*[1-9][0-9]*\n')
+emptyprog = regex.compile('^[ \t]*\n')
+ulprog = regex.compile('^[ \t]*[Xv!_][Xv!_ \t]*\n')
+
+# Basic Man Page class -- does not disable editing
+class EditableManPage(ScrolledText):
+
+	def __init__(self, master=None, cnf={}):
+		# Initialize base class
+		ScrolledText.__init__(self, master, cnf)
+
+		# Define tags for formatting styles
+		self.text.tag_config('bold', {'font': BOLDFONT})
+		self.text.tag_config('italic', {'font': ITALICFONT})
+		self.text.tag_config('underline', {'underline': 1})
+
+		# Create mapping from characters to tags
+		self.tagmap = {
+			'X': 'bold',
+			'_': 'underline',
+			'!': 'italic',
+			}
+
+	# Parse nroff output piped through ul -i and append it to the
+	# text widget
+	def parsefile(self, fp):
+		save_cursor = self.text['cursor']
+		self.text['cursor'] = 'watch'
+		self.text.update()
+		ok = 0
+		empty = 0
+		nextline = None
+		while 1:
+			if nextline:
+				line = nextline
+				nextline = None
+			else:
+				line = fp.readline()
+				if not line:
+					break
+			if emptyprog.match(line) >= 0:
+				empty = 1
+				continue
+			nextline = fp.readline()
+			if nextline and ulprog.match(nextline) >= 0:
+				propline = nextline
+				nextline = None
+			else:
+				propline = ''
+			if not ok:
+				ok = 1
+				empty = 0
+				continue
+			if footerprog.match(line) >= 0:
+				ok = 0
+				empty = 0
+				continue
+			if empty:
+				self.insert_prop('\n')
+				empty = 0
+			p = ''
+			j = 0
+			for i in range(min(len(propline), len(line))):
+				if propline[i] != p:
+					if j < i:
+						self.insert_prop(line[j:i], p)
+						j = i
+					p = propline[i]
+			self.insert_prop(line[j:])
+		self.text['cursor'] = save_cursor
+
+	def insert_prop(self, str, prop = ' '):
+		here = self.text.index(AtInsert())
+		self.text.insert(AtInsert(), str)
+		for tag in self.tagmap.values():
+			self.text.tag_remove(tag, here, AtInsert())
+		if self.tagmap.has_key(prop):
+			self.text.tag_add(self.tagmap[prop], here, AtInsert())
+
+# Readonly Man Page class -- disables editing, otherwise the same
+class ReadonlyManPage(EditableManPage):
+
+	def __init__(self, master=None, cnf={}):
+		# Initialize base class
+		EditableManPage.__init__(self, master, cnf)
+
+		# Make the text readonly
+		self.text.bind('<Any-KeyPress>', self.modify_cb)
+		self.text.bind('<Return>', self.modify_cb)
+		self.text.bind('<BackSpace>', self.modify_cb)
+		self.text.bind('<Delete>', self.modify_cb)
+		self.text.bind('<Control-h>', self.modify_cb)
+		self.text.bind('<Control-d>', self.modify_cb)
+		self.text.bind('<Control-v>', self.modify_cb)
+
+	def modify_cb(self, e):
+		pass
+
+# Alias
+ManPage = ReadonlyManPage
+
+# Test program.
+# usage: ManPage [manpage]; or ManPage [-f] file
+# -f means that the file is nroff -man output run through ul -i
+def test():
+	import os
+	import sys
+	# XXX This directory may be different on your system
+	MANDIR = '/usr/local/man/mann'
+	DEFAULTPAGE = 'Tcl'
+	formatted = 0
+	if sys.argv[1:] and sys.argv[1] == '-f':
+		formatted = 1
+		del sys.argv[1]
+	if sys.argv[1:]:
+		name = sys.argv[1]
+	else:
+		name = DEFAULTPAGE
+	if not formatted:
+		if name[-2:-1] != '.':
+			name = name + '.n'
+		name = os.path.join(MANDIR, name)
+	root = Tk()
+	root.minsize(1, 1)
+	manpage = ManPage(root, {'relief': 'sunken', 'bd': 2,
+				 Pack: {'expand': 1, 'fill': 'both'}})
+	if formatted:
+		fp = open(name, 'r')
+	else:
+		fp = os.popen('nroff -man %s | ul -i' % name, 'r')
+	manpage.parsefile(fp)
+	root.mainloop()
+
+# Run the test program when called as a script
+if __name__ == '__main__':
+	test()
diff --git a/Demo/tkinter/guido/tkman.py b/Demo/tkinter/guido/tkman.py
new file mode 100755
index 0000000..c6610f8
--- /dev/null
+++ b/Demo/tkinter/guido/tkman.py
@@ -0,0 +1,183 @@
+#! /ufs/guido/bin/sgi/tkpython
+
+# Tk man page browser -- currently only shows the Tcl/Tk man pages
+
+import sys
+import os
+import string
+import regex
+from Tkinter import *
+from ManPage import ManPage
+
+MANDIR = '/usr/local/man/mann'
+
+def listmanpages(mandir = MANDIR):
+	files = os.listdir(mandir)
+	names = []
+	for file in files:
+		if file[-2:] == '.n':
+			names.append(file[:-2])
+	names.sort()
+	return names
+
+class SelectionBox:
+
+	def __init__(self, master=None):
+		self.choices = []
+
+		self.frame = Frame(master, {
+			Pack: {'expand': 1, 'fill': 'both'}})
+		self.master = self.frame.master
+		self.subframe = Frame(self.frame, {
+			Pack: {'expand': 0, 'fill': 'both'}})
+		self.listbox = Listbox(self.subframe,
+				       {'relief': 'sunken', 'bd': 2,
+					'geometry': '20x6',
+					Pack: {'side': 'right',
+					       'expand': 1, 'fill': 'both'}})
+		self.subsubframe = Frame(self.subframe, {
+			Pack: {'side': 'left', 'expand': 1, 'fill': 'both'}})
+		self.l1 = Label(self.subsubframe,
+				{'text': 'Display manual page named:',
+				 Pack: {'side': 'top'}})
+		self.entry = Entry(self.subsubframe,
+				   {'relief': 'sunken', 'bd': 2,
+				    'width': 20,
+				    Pack: {'side': 'top',
+					   'expand': 0, 'fill': 'x'}})
+		self.l2 = Label(self.subsubframe,
+				{'text': 'Search (regexp, case insensitive):',
+				 Pack: {'side': 'top'}})
+		self.search = Entry(self.subsubframe,
+				   {'relief': 'sunken', 'bd': 2,
+				    'width': 20,
+				    Pack: {'side': 'top',
+					   'expand': 0, 'fill': 'x'}})
+		self.title = Label(self.subsubframe,
+				   {'text': '(none)',
+				    Pack: {'side': 'bottom'}})
+		self.text = ManPage(self.frame,
+					 {'relief': 'sunken', 'bd': 2,
+					  'wrap': 'none', 'width': 72,
+					  Pack: {'expand': 1, 'fill': 'both'}})
+
+		self.entry.bind('<Return>', self.entry_cb)
+		self.search.bind('<Return>', self.search_cb)
+		self.listbox.bind('<Double-1>', self.listbox_cb)
+
+		self.entry.focus_set()
+
+		self.showing = None
+
+	def addchoice(self, choice):
+		if choice not in self.choices:
+			self.choices.append(choice)
+			self.choices.sort()
+		self.update()
+
+	def addlist(self, list):
+		self.choices[len(self.choices):] = list
+		self.choices.sort()
+		self.update()
+
+	def updatelist(self):
+		key = self.entry.get()
+		ok = filter(lambda name, key=key, n=len(key): name[:n]==key,
+			 self.choices)
+		self.listbox.delete(0, AtEnd())
+		exactmatch = 0
+		for item in ok:
+			if item == key: exactmatch = 1
+			self.listbox.insert(AtEnd(), item)
+		if exactmatch:
+			return key
+		elif self.listbox.size() == 1:
+			return self.listbox.get(0)
+
+	def entry_cb(self, e):
+		self.update()
+
+	def update(self):
+		self.show_page(self.updatelist())
+
+	def show_page(self, name):
+		if not name:
+			return
+		if name == self.showing:
+			print 'show_page: already showing'
+			return
+		name = '%s/%s.n' % (MANDIR, name)
+		fp = os.popen('nroff -man %s | ul -i' % name, 'r')
+		self.text.delete('1.0', AtEnd())
+		frame_cursor = self.frame['cursor']
+		entry_cursor = self.entry['cursor']
+		self.entry['cursor'] = 'watch'
+		self.search['cursor'] = 'watch'
+		self.frame['cursor'] = 'watch'
+		self.text.parsefile(fp)
+		self.search['cursor'] = entry_cursor
+		self.entry['cursor'] = entry_cursor
+		self.frame['cursor'] = frame_cursor
+		self.entry.delete(0, AtEnd())
+		self.updatelist()
+
+	def listbox_cb(self, e):
+		selection = self.listbox.curselection()
+		if selection and len(selection) == 1:
+			which = self.listbox.get(selection[0])
+			self.show_page(which)
+
+	def search_cb(self, e):
+		self.search_string(self.search.get())
+
+	def search_string(self, search):
+		if not search:
+			print 'Empty search string'
+			return
+		try:
+			prog = regex.compile(search, regex.casefold)
+		except regex.error, msg:
+			print 'Regex error:', msg
+			return
+		here = self.text.index(AtInsert())
+		lineno = string.atoi(here[:string.find(here, '.')])
+		end = self.text.index(AtEnd())
+		endlineno = string.atoi(end[:string.find(end, '.')])
+		wraplineno = lineno
+		while 1:
+			lineno = lineno + 1
+			if lineno > endlineno:
+				if wraplineno <= 0:
+					break
+				endlineno = wraplineno
+				lineno = 0
+				wraplineno = 0
+			line = self.text.get('%d.0 linestart' % lineno,
+					     '%d.0 lineend' % lineno)
+			i = prog.search(line)
+			if i >= 0:
+				n = max(1, len(prog.group(0)))
+				try:
+					self.text.tag_remove('sel',
+							     AtSelFirst(),
+							     AtSelLast())
+				except TclError:
+					pass
+				self.text.tag_add('sel',
+						  '%d.%d' % (lineno, i),
+						  '%d.%d' % (lineno, i+n))
+				self.text.mark_set(AtInsert(),
+						   '%d.%d' % (lineno, i))
+				self.text.yview_pickplace(AtInsert())
+				break
+
+def main():
+	root = Tk()
+	sb = SelectionBox(root)
+	sb.addlist(listmanpages())
+	if sys.argv[1:]:
+		sb.show_page(sys.argv[1])
+	root.minsize(1, 1)
+	root.mainloop()
+
+main()