Initial revision
diff --git a/Demo/tkinter/guido/MimeViewer.py b/Demo/tkinter/guido/MimeViewer.py
new file mode 100755
index 0000000..5bf194a
--- /dev/null
+++ b/Demo/tkinter/guido/MimeViewer.py
@@ -0,0 +1,143 @@
+#! /ufs/guido/bin/sgi/tkpython
+
+# View a single MIME multipart message.
+# Display each part as a box.
+
+import string
+from types import *
+from Tkinter import *
+from ScrolledText import ScrolledText
+
+class MimeViewer:
+	def __init__(self, parent, title, msg):
+		self.title = title
+		self.msg = msg
+		self.frame = Frame(parent, {'relief': 'raised', 'bd': 2})
+		self.frame.packing = {'expand': 0, 'fill': 'both'}
+		self.button = Checkbutton(self.frame,
+				     {'text': title,
+				      'command': self.toggle})
+		self.button.pack({'anchor': 'w'})
+		headertext = msg.getheadertext(
+			lambda x: x != 'received' and x[:5] != 'x400-')
+		height = countlines(headertext, 4)
+		if height:
+			self.htext = ScrolledText(self.frame,
+					  {'height': height,
+					   'width': 80,
+					   'wrap': 'none',
+					   'relief': 'raised',
+					   'bd': 2})
+			self.htext.packing = {'expand': 1, 'fill': 'both',
+					      'after': self.button}
+			self.htext.insert('end', headertext)
+		else:
+			self.htext = Frame(self.frame,
+					   {'relief': 'raised', 'bd': 2})
+			self.htext.packing = {'side': 'top',
+					      'ipady': 2,
+					      'fill': 'x',
+					      'after': self.button}
+		body = msg.getbody()
+		if type(body) == StringType:
+			self.pad = None
+			height = countlines(body, 10)
+			if height:
+				self.btext = ScrolledText(self.frame,
+						  {'height': height,
+						   'width': 80,
+						   'wrap': 'none',
+						   'relief': 'raised',
+						   'bd': 2})
+				self.btext.packing = {'expand': 1,
+						      'fill': 'both'}
+				self.btext.insert('end', body)
+			else:
+				self.btext = None
+			self.parts = None
+		else:
+			self.pad = Frame(self.frame,
+					 {'relief': 'flat', 'bd': 2})
+			self.pad.packing = {'side': 'left', 'ipadx': 10,
+					    'fill': 'y', 'after': self.htext}
+			self.parts = []
+			for i in range(len(body)):
+				p = MimeViewer(self.frame,
+					       '%s.%d' % (title, i+1),
+					       body[i])
+				self.parts.append(p)
+			self.btext = None
+		self.collapsed = 1
+	def pack(self):
+		self.frame.pack(self.frame.packing)
+	def destroy(self):
+		self.frame.destroy()
+	def show(self):
+		if self.collapsed:
+			self.button.invoke()
+	def toggle(self):
+		if self.collapsed:
+			self.explode()
+		else:
+			self.collapse()
+	def collapse(self):
+		self.collapsed = 1
+		for comp in self.htext, self.btext, self.pad:
+			if comp:
+				comp.forget()
+		if self.parts:
+			for part in self.parts:
+				part.frame.forget()
+		self.frame.pack({'expand': 0})
+	def explode(self):
+		self.collapsed = 0
+		for comp in self.htext, self.btext, self.pad:
+			if comp: comp.pack(comp.packing)
+		if self.parts:
+			for part in self.parts:
+				part.pack()
+		self.frame.pack({'expand': 1})
+
+def countlines(str, limit):
+	i = 0
+	n = 0
+	while  n < limit:
+		i = string.find(str, '\n', i)
+		if i < 0: break
+		n = n+1
+		i = i+1
+	return n
+
+def main():
+	import sys
+	import getopt
+	import mhlib
+	opts, args = getopt.getopt(sys.argv[1:], '')
+	for o, a in opts:
+		pass
+	message = None
+	folder = 'inbox'
+	for arg in args:
+		if arg[:1] == '+':
+			folder = arg[1:]
+		else:
+			message = string.atoi(arg)
+
+	mh = mhlib.MH()
+	f = mh.openfolder(folder)
+	if not message:
+		message = f.getcurrent()
+	m = f.openmessage(message)
+
+	root = Tk()
+	tk = root.tk
+
+	top = MimeViewer(root, '+%s/%d' % (folder, message), m)
+	top.pack()
+	top.show()
+
+	root.minsize(1, 1)
+
+	tk.mainloop()
+
+if __name__ == '__main__': main()
diff --git a/Demo/tkinter/guido/dialog.py b/Demo/tkinter/guido/dialog.py
new file mode 100755
index 0000000..31f5340
--- /dev/null
+++ b/Demo/tkinter/guido/dialog.py
@@ -0,0 +1,119 @@
+#! /ufs/guido/bin/sgi/tkpython
+
+# A Python function that generates dialog boxes with a text message,
+# optional bitmap, and any number of buttons.
+# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.2-3, pp. 269-270.
+
+from Tkinter import *
+
+def dialog(master, title, text, bitmap, default, *args):
+
+    # 1. Create the top-level window and divide it into top
+    # and bottom parts.
+
+    w = Toplevel(master, {'class': 'Dialog'})
+    w.tk.call('global', 'button')
+    w.title(title)
+    w.iconname('Dialog')
+
+    top = Frame(w, {'relief': 'raised', 'bd': 1,
+		    Pack: {'side': 'top', 'fill': 'both'}})
+    bot = Frame(w, {'relief': 'raised', 'bd': 1,
+		    Pack: {'side': 'bottom', 'fill': 'both'}})
+
+    # 2. Fill the top part with the bitmap and message.
+
+    msg = Message(top,
+		  {'width': '3i',
+		   'text': text,
+		   'font': '-Adobe-Times-Medium-R-Normal-*-180-*',
+		   Pack: {'side': 'right', 'expand': 1,
+			  'fill': 'both',
+			  'padx': '3m', 'pady': '3m'}})
+    if bitmap:
+	bm = Label(top, {'bitmap': bitmap,
+			 Pack: {'side': 'left',
+				'padx': '3m', 'pady': '3m'}})
+
+    # 3. Create a row of buttons at the bottom of the dialog.
+
+    buttons = []
+    i = 0
+    for but in args:
+	b = Button(bot, {'text': but,
+			 'command': ('set', 'button', i)})
+	buttons.append(b)
+	if i == default:
+	    bd = Frame(bot, {'relief': 'sunken', 'bd': 1,
+			     Pack: {'side': 'left', 'expand': 1,
+				    'padx': '3m', 'pady': '2m'}})
+	    w.tk.call('raise', b)
+	    b.pack ({'in': bd, 'side': 'left',
+		     'padx': '2m', 'pady': '2m',
+		     'ipadx': '2m', 'ipady': '1m'})
+	else:
+	    b.pack ({'side': 'left', 'expand': 1,
+		     'padx': '3m', 'pady': '3m',
+		     'ipady': '2m', 'ipady': '1m'})
+	i = i+1
+
+    # 4. Set up a binding for <Return>, if there's a default,
+    # set a grab, and claim the focus too.
+
+    if default >= 0:
+	w.bind('<Return>',
+	       lambda b=buttons[default], i=default:
+	       (b.cmd('flash'),
+		b.tk.call('set', 'button', i)))
+
+    oldFocus = w.tk.call('focus')
+    w.tk.call('grab', 'set', w)
+    w.tk.call('focus', w)
+
+    # 5. Wait for the user to respond, then restore the focus
+    # and return the index of the selected button.
+
+    w.tk.call('tkwait', 'variable', 'button')
+    w.tk.call('destroy', w)
+    w.tk.call('focus', oldFocus)
+    return w.tk.call('set', 'button')
+
+# The rest is the test program.
+
+def go():
+    i = dialog(mainWidget,
+	       'Not Responding',
+	       "The file server isn't responding right now; "
+	       "I'll keep trying.",
+	       '',
+	       -1,
+	       'OK')
+    print 'pressed button', i
+    i = dialog(mainWidget,
+	       'File Modified',
+	       'File "tcl.h" has been modified since '
+	       'the last time it was saved. '
+	       'Do you want to save it before exiting the application?',
+	       'warning',
+	       0,
+	       'Save File',
+	       'Discard Changes',
+	       'Return To Editor')
+    print 'pressed button', i
+
+def test():
+    import sys
+    global mainWidget
+    mainWidget = Frame()
+    Pack.config(mainWidget)
+    start = Button(mainWidget,
+		   {'text': 'Press Here To Start', 'command': go})
+    start.pack()
+    endit = Button(mainWidget,
+		   {'text': 'Exit',
+		    'command': 'exit',
+		    Pack: {'fill' : 'both'}})
+    mainWidget.tk.mainloop()
+
+if __name__ == '__main__':
+    test()
diff --git a/Demo/tkinter/guido/mbox.py b/Demo/tkinter/guido/mbox.py
new file mode 100755
index 0000000..a4e86da
--- /dev/null
+++ b/Demo/tkinter/guido/mbox.py
@@ -0,0 +1,288 @@
+#! /ufs/guido/bin/sgi/tkpython
+
+# Scan MH folder, display results in window
+
+import os
+import sys
+import regex
+import getopt
+import string
+import mhlib
+
+from Tkinter import *
+
+from dialog import dialog
+
+mailbox = os.environ['HOME'] + '/Mail'
+
+def main():
+	global root, tk, top, mid, bot
+	global folderbox, foldermenu, scanbox, scanmenu, viewer
+	global folder, seq
+	global mh, mhf
+
+	# Parse command line options
+
+	folder = 'inbox'
+	seq = 'all'
+	try:
+		opts, args = getopt.getopt(sys.argv[1:], '')
+	except getopt.error, msg:
+		print msg
+		sys.exit(2)
+	for arg in args:
+		if arg[:1] == '+':
+			folder = arg[1:]
+		else:
+			seq = arg
+
+	# Initialize MH
+
+	mh = mhlib.MH()
+	mhf = mh.openfolder(folder)
+
+	# Build widget hierarchy
+
+	root = Tk()
+	tk = root.tk
+
+	top = Frame(root)
+	top.pack({'expand': 1, 'fill': 'both'})
+
+	# Build right part: folder list
+
+	right = Frame(top)
+	right.pack({'fill': 'y', 'side': 'right'})
+
+	folderbar = Scrollbar(right, {'relief': 'sunken', 'bd': 2})
+	folderbar.pack({'fill': 'y', 'side': 'right'})
+
+	folderbox = Listbox(right)
+	folderbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
+
+	foldermenu = Menu(root)
+	foldermenu.add('command',
+		       {'label': 'Open Folder',
+			'command': open_folder})
+	foldermenu.add('separator')
+	foldermenu.add('command',
+		       {'label': 'Quit',
+			'command': 'exit'})
+	foldermenu.bind('<ButtonRelease-3>', folder_unpost)
+
+	folderbox['yscrollcommand'] = (folderbar, 'set')
+	folderbar['command'] = (folderbox, 'yview')
+	folderbox.bind('<Double-1>', open_folder, 1)
+	folderbox.bind('<3>', folder_post)
+
+	# Build left part: scan list
+
+	left = Frame(top)
+	left.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
+
+	scanbar = Scrollbar(left, {'relief': 'sunken', 'bd': 2})
+	scanbar.pack({'fill': 'y', 'side': 'right'})
+
+	scanbox = Listbox(left, {'font': 'fixed'})
+	scanbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
+
+	scanmenu = Menu(root)
+	scanmenu.add('command',
+		     {'label': 'Open Message',
+		      'command': open_message})
+	scanmenu.add('command',
+		     {'label': 'Remove Message',
+		      'command': remove_message})
+	scanmenu.add('command',
+		     {'label': 'Refile Message',
+		      'command': refile_message})
+	scanmenu.add('separator')
+	scanmenu.add('command',
+		     {'label': 'Quit',
+		      'command': 'exit'})
+	scanmenu.bind('<ButtonRelease-3>', scan_unpost)
+
+	scanbox['yscrollcommand'] = (scanbar, 'set')
+	scanbar['command'] = (scanbox, 'yview')
+	scanbox.bind('<Double-1>', open_message)
+	scanbox.bind('<3>', scan_post)
+
+	# Separator between middle and bottom part
+
+	rule2 = Frame(root, {'bg': 'black'})
+	rule2.pack({'fill': 'x'})
+
+	# Build bottom part: current message
+
+	bot = Frame(root)
+	bot.pack({'expand': 1, 'fill': 'both'})
+	#
+	viewer = None
+
+	# Window manager commands
+
+	root.minsize(800, 1) # Make window resizable
+
+	# Fill folderbox with text
+
+	setfolders()
+
+	# Fill scanbox with text
+
+	rescan()
+
+	# Enter mainloop
+
+	root.mainloop()
+
+def folder_post(e):
+	x, y = e.x_root, e.y_root
+	foldermenu.post(x - 10, y - 10)
+	foldermenu.grab_set()
+
+def folder_unpost(e):
+	tk.call('update', 'idletasks')
+	foldermenu.grab_release()
+	foldermenu.unpost()
+	foldermenu.invoke('active')
+
+def scan_post(e):
+	x, y = e.x_root, e.y_root
+	scanmenu.post(x - 10, y - 10)
+	scanmenu.grab_set()
+
+def scan_unpost(e):
+	tk.call('update', 'idletasks')
+	scanmenu.grab_release()
+	scanmenu.unpost()
+	scanmenu.invoke('active')
+
+scanparser = regex.compile('^ *\([0-9]+\)')
+
+def open_folder(*e):
+	global folder, mhf
+	sel = folderbox.curselection()
+	if len(sel) != 1:
+		if len(sel) > 1:
+			msg = "Please open one folder at a time"
+		else:
+			msg = "Please select a folder to open"
+		dialog(root, "Can't Open Folder", msg, "", 0, "OK")
+		return
+	i = sel[0]
+	folder = folderbox.get(i)
+	mhf = mh.openfolder(folder)
+	rescan()
+
+def open_message(*e):
+	global viewer
+	sel = scanbox.curselection()
+	if len(sel) != 1:
+		if len(sel) > 1:
+			msg = "Please open one message at a time"
+		else:
+			msg = "Please select a message to open"
+		dialog(root, "Can't Open Message", msg, "", 0, "OK")
+		return
+	cursor = scanbox['cursor']
+	scanbox['cursor'] = 'watch'
+	tk.call('update', 'idletasks')
+	i = sel[0]
+	line = scanbox.get(i)
+	if scanparser.match(line) >= 0:
+		num = string.atoi(scanparser.group(1))
+		m = mhf.openmessage(num)
+		if viewer: viewer.destroy()
+		from MimeViewer import MimeViewer
+		viewer = MimeViewer(bot, '+%s/%d' % (folder, num), m)
+		viewer.pack()
+		viewer.show()
+	scanbox['cursor'] = cursor
+
+def interestingheader(header):
+	return header != 'received'
+
+def remove_message():
+	itop = scanbox.nearest(0)
+	sel = scanbox.curselection()
+	if not sel:
+		dialog(root, "No Message To Remove",
+		       "Please select a message to remove", "", 0, "OK")
+		return
+	todo = []
+	for i in sel:
+		line = scanbox.get(i)
+		if scanparser.match(line) >= 0:
+			todo.append(string.atoi(scanparser.group(1)))
+	mhf.removemessages(todo)
+	rescan()
+	fixfocus(min(todo), itop)
+
+lastrefile = ''
+tofolder = None
+def refile_message():
+	global lastrefile, tofolder
+	itop = scanbox.nearest(0)
+	sel = scanbox.curselection()
+	if not sel:
+		dialog(root, "No Message To Refile",
+		       "Please select a message to refile", "", 0, "OK")
+		return
+	foldersel = folderbox.curselection()
+	if len(foldersel) != 1:
+		if not foldersel:
+			msg = "Please select a folder to refile to"
+		else:
+			msg = "Please select exactly one folder to refile to"
+		dialog(root, "No Folder To Refile", msg, "", 0, "OK")
+		return
+	refileto = folderbox.get(foldersel[0])
+	todo = []
+	for i in sel:
+		line = scanbox.get(i)
+		if scanparser.match(line) >= 0:
+			todo.append(string.atoi(scanparser.group(1)))
+	print 'refile', todo, tofolder
+	if lastrefile != refileto or not tofolder:
+		print 'new folder'
+		lastrefile = refileto
+		tofolder = None
+		tofolder = mh.openfolder(lastrefile)
+	mhf.refilemessages(todo, tofolder)
+	rescan()
+	fixfocus(min(todo), itop)
+
+def fixfocus(near, itop):
+	n = scanbox.size()
+	for i in range(n):
+		line = scanbox.get(`i`)
+		if scanparser.match(line) >= 0:
+			num = string.atoi(scanparser.group(1))
+			if num >= near:
+				break
+	else:
+		i = 'end'
+	scanbox.select_from(i)
+	print 'yview', `itop`
+	scanbox.yview(itop)
+
+def setfolders():
+	folderbox.delete(0, 'end')
+	for fn in mh.listallfolders():
+		folderbox.insert('end', fn)
+
+def rescan():
+	global viewer
+	if viewer:
+		viewer.destroy()
+		viewer = None
+	scanbox.delete(0, 'end')
+	for line in scanfolder(folder, seq):
+		scanbox.insert('end', line)
+
+def scanfolder(folder = 'inbox', sequence = 'all'):
+	return map(
+		lambda line: line[:-1],
+		os.popen('scan +%s %s' % (folder, sequence), 'r').readlines())
+
+main()
diff --git a/Demo/tkinter/guido/rmt.py b/Demo/tkinter/guido/rmt.py
new file mode 100755
index 0000000..2ac2408
--- /dev/null
+++ b/Demo/tkinter/guido/rmt.py
@@ -0,0 +1,152 @@
+#! /ufs/guido/bin/sgi/tkpython
+
+# A Python program implementing rmt, an application for remotely
+# controlling other Tk applications.
+# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276.
+
+# Note that because of forward references in the original, we
+# sometimes delay bindings until after the corresponding procedure is
+# defined.  We also introduce names for some unnamed code blocks in
+# the original because of restrictions on lambda forms in Python.
+
+from Tkinter import *
+
+# 1. Create basic application structure: menu bar on top of
+# text widget, scrollbar on right.
+
+root = Tk()
+tk = root.tk
+mBar = Frame(root, {'relief': 'raised', 'bd': 2,
+		    Pack: {'side': 'top', 'fill': 'x'}})
+f = Frame(root)
+f.pack({'expand': 1, 'fill': 'both'})
+s = Scrollbar(f, {'relief': 'flat',
+		  Pack: {'side': 'right', 'fill': 'y'}})
+t = Text(f, {'relief': 'raised', 'bd': 2, 'yscrollcommand': (s, 'set'),
+	     'setgrid': 1,
+	     Pack: {'side': 'left', 'fill': 'both', 'expand': 1}})
+
+t.tag_config('bold', {'font': '-Adobe-Courier-Bold-R-Normal-*-120-*'}) 
+s['command'] = (t, 'yview')
+root.title('Tk Remote Controller')
+root.iconname('Tk Remote')
+
+# 2. Create menu button and menus.
+
+file = Menubutton(mBar, {'text': 'File', 'underline': 0,
+			 Pack: {'side': 'left'}})
+file_m = Menu(file)
+file['menu'] = file_m
+file_m_apps = Menu(file_m)
+file_m.add('cascade', {'label': 'Select Application', 'underline': 0,
+		       'menu': file_m_apps})
+file_m.add('command', {'label': 'Quit', 'underline': 0, 'command': 'exit'})
+
+# 3. Create bindings for text widget to allow commands to be
+# entered and information to be selected.  New characters
+# can only be added at the end of the text (can't ever move
+# insertion point).
+
+def single1(e):
+	x = e.x
+	y = e.y
+	tk.setvar('tk_priv(selectMode)', 'char')
+	t.mark_set('anchor', At(x, y))
+	# Should focus W
+t.bind('<1>', single1)
+
+def double1(e):
+	x = e.x
+	y = e.y
+	tk.setvar('tk_priv(selectMode)', 'word')
+	tk.call('tk_textSelectTo', t, At(x, y))
+t.bind('<Double-1>', double1)
+
+def triple1(e):
+	x = e.x
+	y = e.y
+	tk.setvar('tk_priv(selectMode)', 'line')
+	tk.call('tk_textSelectTo', t, At(x, y))
+t.bind('<Triple-1>', triple1)
+
+def returnkey(e):
+	t.insert('insert', '\n')
+	invoke()
+t.bind('<Return>', returnkey)
+
+def controlv(e):
+	t.insert('insert', tk.call('selection', 'get'))
+	t.yview_pickplace('insert')
+	if t.index('insert')[-2:] == '.0':
+		invoke()
+t.bind('<Control-v>', controlv)
+
+# 4. Procedure to backspace over one character, as long as
+# the character isn't part of the prompt.
+
+def backspace(e):
+	if t.index('promptEnd') != t.index('insert - 1 char'):
+		t.delete('insert - 1 char', 'insert')
+		t.yview_pickplace('insert')
+t.bind('<BackSpace>', backspace)
+t.bind('<Control-h>', backspace)
+t.bind('<Delete>', backspace)
+
+
+# 5. Procedure that's invoked when return is typed:  if
+# there's not yet a complete command (e.g. braces are open)
+# then do nothing.  Otherwise, execute command (locally or
+# remotely), output the result or error message, and issue
+# a new prompt.
+
+def invoke():
+	cmd = t.get('promptEnd + 1 char', 'insert')
+	if tk.getboolean(tk.call('info', 'complete', cmd)):
+		if app == tk.call('winfo', 'name', '.'):
+			msg = tk.call('eval', cmd)
+		else:
+			msg = tk.call('send', app, cmd)
+		if msg:
+			t.insert('insert', msg + '\n')
+		prompt()
+	t.yview_pickplace('insert')
+
+def prompt():
+	t.insert('insert', app + ': ')
+	t.mark_set('promptEnd', 'insert - 1 char')
+	t.tag_add('bold', 'insert linestart', 'promptEnd')
+
+# 6. Procedure to select a new application.  Also changes
+# the prompt on the current command line to reflect the new
+# name.
+
+def newApp(appName):
+	global app
+	app = appName
+	t.delete('promptEnd linestart', 'promptEnd')
+	t.insert('promptEnd', appName + ':')
+	t.tag_add('bold', 'promptEnd linestart', 'promptEnd')
+
+newApp_tcl = `id(newApp)`
+tk.createcommand(newApp_tcl, newApp)
+
+def fillAppsMenu():
+	file_m_apps.add('command')
+	file_m_apps.delete(0, 'last')
+	names = tk.splitlist(tk.call('winfo', 'interps'))
+	names = map(None, names) # convert tuple to list
+	names.sort()
+	for name in names:
+		file_m_apps.add('command', {'label': name,
+					    'command': (newApp_tcl, name)})
+
+file_m_apps['postcommand'] = fillAppsMenu
+mBar.tk_menuBar(file)
+
+# 7. Miscellaneous initialization.
+
+app = tk.call('winfo', 'name', '.')
+prompt()
+tk.call('focus', t)
+
+root.mainloop()
diff --git a/Demo/tkinter/guido/tst.py b/Demo/tkinter/guido/tst.py
new file mode 100755
index 0000000..ea573d2
--- /dev/null
+++ b/Demo/tkinter/guido/tst.py
@@ -0,0 +1,81 @@
+# tst.py
+from Tkinter import *
+import sys
+
+def do_hello():
+	print 'Hello world!'
+
+class Quit(Button):
+	def action(self):
+		self.quit()
+	def __init__(self, master=None, cnf={}):
+		Button.__init__(self, master, 
+				{'text': 'Quit', 
+				 'command': self.action})
+		Button.config(self, cnf)
+
+class Stuff(Canvas):
+	def enter(self, e):
+		print 'Enter'
+		self.itemconfig('current', {'fill': 'red'})
+	def leave(self, e):
+		print 'Leave'
+		self.itemconfig('current', {'fill': 'blue'})
+	def __init__(self, master=None, cnf={}):
+		Canvas.__init__(self, master, 
+				{'width': 100, 'height': 100})
+		Canvas.config(self, cnf)
+		self.create_rectangle(30, 30, 70, 70, 
+				      {'fill': 'red', 'tags': 'box'})
+		Canvas.bind(self, 'box', '<Enter>', self.enter)
+		Canvas.bind(self, 'box', '<Leave>', self.leave)
+
+class Test(Frame):
+	text = 'Testing'
+	num = 1
+	def do_xy(self, e):
+		print (e.x, e.y)
+	def do_test(self):
+		if not self.num % 10:
+			self.text = 'Testing 1 ...'
+		self.text = self.text + ' ' + `self.num`
+		self.num = self.num + 1
+		self.testing['text'] = self.text
+	def do_err(self):
+		1/0
+	def do_after(self):
+		self.testing.invoke()
+		self.after(10000, self.do_after)
+	def __init__(self, master=None):
+		Frame.__init__(self, master)
+		self['bd'] = 30
+		Pack.config(self)
+		self.bind('<Motion>', self.do_xy)
+		self.hello = Button(self, {'name': 'hello', 
+					   'text': 'Hello', 
+					   'command': do_hello,
+					   Pack: {'fill': 'both'}})
+		self.testing = Button(self)
+		self.testing['text'] = self.text
+		self.testing['command'] = self.do_test
+		Pack.config(self.testing, {'fill': 'both'})
+		self.err = Button(self, {'text': 'Error', 
+					 'command': self.do_err,
+					 Pack: {'fill': 'both'}})
+		self.quit = Quit(self, {Pack: {'fill': 'both'}})
+		self.exit = Button(self, 
+				   {'text': 'Exit', 
+				    'command': lambda: sys.exit(0),
+				    Pack: {'fill': 'both'}})
+		self.stuff = Stuff(self, {Pack: {'padx': 2, 'pady': 2}})
+		self.do_after()
+
+test = Test()
+test.master.title('Tkinter Test')
+test.master.iconname('Test')
+test.master.maxsize(500, 500)
+test.testing.invoke()
+
+# Use the -i option and type ^C to get a prompt
+test.mainloop()
+
diff --git a/Demo/tkinter/guido/wish.py b/Demo/tkinter/guido/wish.py
new file mode 100755
index 0000000..16cacde
--- /dev/null
+++ b/Demo/tkinter/guido/wish.py
@@ -0,0 +1,26 @@
+# This is about all it requires to write a wish shell in Python!
+
+import tkinter
+
+tk = tkinter.create(':0', 'wish', 'Tk', 1)
+tk.call('update')
+
+cmd = ''
+
+while 1:
+	if cmd: prompt = ''
+	else: prompt = '% '
+	try:
+		line = raw_input(prompt)
+	except EOFError:
+		break
+	cmd = cmd + (line + '\n')
+	tk.record(line)
+	if tk.getboolean(tk.call('info', 'complete', cmd)):
+		try:
+			result = tk.call('eval', cmd)
+		except tkinter.TclError, msg:
+			print 'TclError:', msg
+		else:
+			if result: print result
+		cmd = ''