Initial revision
diff --git a/Demo/stdwin/python.py b/Demo/stdwin/python.py
new file mode 100755
index 0000000..bb85316
--- /dev/null
+++ b/Demo/stdwin/python.py
@@ -0,0 +1,514 @@
+#! /usr/local/python
+
+XXX This file needs some work for Python 0.9.6!!!
+
+# A STDWIN-based front end for the Python interpreter.
+#
+# This is useful if you want to avoid console I/O and instead
+# use text windows to issue commands to the interpreter.
+#
+# It supports multiple interpreter windows, each with its own context.
+#
+# BUGS AND CAVEATS:
+#
+# Although this supports multiple windows, the whole application
+# is deaf and dumb when a command is running in one window.
+#
+# Everything written to stdout or stderr is saved on a file which
+# is inserted in the window at the next input request.
+#
+# On UNIX (using X11), interrupts typed in the window will not be
+# seen until the next input request.  (On the Mac, interrupts work.)
+#
+# Direct input from stdin should not be attempted.
+
+
+import sys
+import builtin
+import stdwin
+from stdwinevents import *
+import rand
+import mainloop
+
+from util import readfile # 0.9.1
+
+try:
+	import mac
+	os = mac
+except NameError:
+	import posix
+	os = posix
+
+
+# Filename used to capture output from commands; change to suit your taste
+#
+OUTFILE = '@python.stdout.tmp'
+
+
+# Stack of windows waiting for [raw_]input().
+# Element [0] is the top.
+# If there are multiple windows waiting for input, only the
+# one on top of the stack can accept input, because the way
+# raw_input() is implemented (using recursive mainloop() calls).
+#
+inputwindows = []
+
+
+# Exception raised when input is available.
+#
+InputAvailable = 'input available for raw_input (not an error)'
+
+
+# Main program.  Create the window and call the mainloop.
+#
+def main():
+	# Hack so 'import python' won't load another copy
+	# of this if we were loaded though 'python python.py'.
+	# (Should really look at sys.argv[0]...)
+	if 'inputwindows' in dir(sys.modules['__main__']) and \
+			sys.modules['__main__'].inputwindows is inputwindows:
+		sys.modules['python'] = sys.modules['__main__']
+	#
+	win = makewindow()
+	mainloop.mainloop()
+
+
+# Create a new window.
+#
+def makewindow():
+	# stdwin.setdefscrollbars(0, 1) # Not in Python 0.9.1
+	# stdwin.setfont('monaco') # Not on UNIX! and not Python 0.9.1
+	# stdwin.setdefwinsize(stdwin.textwidth('in')*40, stdwin.lineheight() * 24)
+	win = stdwin.open('Python interpreter ready')
+	win.editor = win.textcreate((0,0), win.getwinsize())
+	win.outfile = OUTFILE + `rand.rand()`
+	win.globals = {}	# Dictionary for user's global variables
+	win.command = ''	# Partially read command
+	win.busy = 0		# Ready to accept a command
+	win.auto = 1		# [CR] executes command
+	win.insertOutput = 1		# Insert output at focus.
+	win.insertError = 1			# Insert error output at focus.
+	win.setwincursor('ibeam')
+	win.filename = ''			# Empty if no file associated with this window
+	makefilemenu(win)
+	makeeditmenu(win)
+	win.dispatch = pdispatch	# Event dispatch function
+	mainloop.register(win)
+	return win
+
+
+# Make a 'File' menu
+#
+def makefilemenu(win):
+	win.filemenu = mp = win.menucreate('File')
+	mp.callback = []
+	additem(mp, 'New',		'N', do_new)
+	additem(mp, 'Open...',	'O', do_open)
+	additem(mp, '',		'', None)
+	additem(mp, 'Close',	'W', do_close)
+	additem(mp, 'Save',		'S', do_save)
+	additem(mp, 'Save as...',	'', do_saveas)
+	additem(mp, '',		'', None)
+	additem(mp, 'Quit',		'Q', do_quit)
+
+
+# Make an 'Edit' menu
+#
+def makeeditmenu(win):
+	win.editmenu = mp = win.menucreate('Edit')
+	mp.callback = []
+	additem(mp, 'Cut',	'X', do_cut)
+	additem(mp, 'Copy',	'C', do_copy)
+	additem(mp, 'Paste',	'V', do_paste)
+	additem(mp, 'Clear',	'',  do_clear)
+	additem(mp, '',		'', None)
+	win.iauto = len(mp.callback)
+	additem(mp, 'Autoexecute',	'', do_auto)
+	mp.check(win.iauto, win.auto)
+	win.insertOutputNum = len(mp.callback)
+	additem(mp, 'Insert Output',	'', do_insertOutputOption)
+	win.insertErrorNum = len(mp.callback)
+	additem(mp, 'Insert Error',	'', do_insertErrorOption)
+	additem(mp, 'Exec',	'\r', do_exec)
+
+
+# Helper to add a menu item and callback function
+#
+def additem(mp, text, shortcut, handler):
+	if shortcut:
+		mp.additem(text, shortcut)
+	else:
+		mp.additem(text)
+	mp.callback.append(handler)
+
+
+# Dispatch a single event to the interpreter.
+# Resize events cause a resize of the editor.
+# Other events are directly sent to the editor.
+#
+# Exception: WE_COMMAND/WC_RETURN causes the current selection
+# (if not empty) or current line (if empty) to be sent to the
+# interpreter.  (In the future, there should be a way to insert
+# newlines in the text; or perhaps Enter or Meta-RETURN should be
+# used to trigger execution, like in MPW, though personally I prefer
+# using a plain Return to trigger execution, as this is what I want
+# in the majority of cases.)
+#
+# Also, WE_COMMAND/WC_CANCEL cancels any command in progress.
+#
+def pdispatch(event):
+	type, win, detail = event
+	if type == WE_CLOSE:
+		do_close(win)
+	elif type == WE_SIZE:
+		win.editor.move((0, 0), win.getwinsize())
+	elif type == WE_COMMAND and detail == WC_RETURN:
+		if win.auto:
+			do_exec(win)
+		else:
+			void = win.editor.event(event)
+	elif type == WE_COMMAND and detail == WC_CANCEL:
+		if win.busy:
+			raise InputAvailable, (EOFError, None)
+		else:
+			win.command = ''
+			settitle(win)
+	elif type == WE_MENU:
+		mp, item = detail
+		mp.callback[item](win)
+	else:
+		void = win.editor.event(event)
+	if win.editor:
+		# May have been deleted by close...
+		win.setdocsize(0, win.editor.getrect()[1][1])
+		if type in (WE_CHAR, WE_COMMAND):
+			win.editor.setfocus(win.editor.getfocus())
+
+
+# Helper to set the title of the window. 
+#
+def settitle(win):
+	if win.filename == '':
+		win.settitle('Python interpreter ready')
+	else:
+		win.settitle(win.filename)
+
+
+# Helper to replace the text of the focus.
+#
+def replace(win, text):
+	win.editor.replace(text)
+	# Resize the window to display the text
+	win.setdocsize(0, win.editor.getrect()[1][1])	# update the size before..
+	win.editor.setfocus(win.editor.getfocus())		# move focus to the change - dml
+
+
+# File menu handlers
+#
+def do_new(win):
+	win = makewindow()
+#
+def do_open(win):
+	try:
+		filename = stdwin.askfile('Open file', '', 0)
+		win = makewindow()
+		win.filename = filename
+		win.editor.replace(readfile(filename)) # 0.9.1
+		# win.editor.replace(open(filename, 'r').read()) # 0.9.2
+		win.editor.setfocus(0, 0)
+		win.settitle(win.filename)
+		#
+	except KeyboardInterrupt:
+		pass # Don't give an error on cancel.
+#
+def do_save(win):
+	try:
+		if win.filename == '':
+			win.filename = stdwin.askfile('Open file', '', 1)
+		f = open(win.filename, 'w')
+		f.write(win.editor.gettext())
+		#
+	except KeyboardInterrupt:
+		pass # Don't give an error on cancel.
+	
+def do_saveas(win):
+	currentFilename = win.filename
+	win.filename = ''
+	do_save(win)				# Use do_save with empty filename
+	if win.filename == '':		# Restore the name if do_save did not set it.
+		win.filename = currentFilename
+#
+def do_close(win):
+	if win.busy:
+		stdwin.message('Can\'t close busy window')
+		return		# need to fail if quitting??
+	win.editor = None # Break circular reference
+	#del win.editmenu	# What about the filemenu??
+	try:
+		os.unlink(win.outfile)
+	except os.error:
+		pass
+	mainloop.unregister(win)
+#
+def do_quit(win):
+	# Call win.dispatch instead of do_close because there
+	# may be 'alien' windows in the list.
+	for win in mainloop.windows:
+		mainloop.dispatch(WE_CLOSE, win, None)	# need to catch failed close
+
+
+# Edit menu handlers
+#
+def do_cut(win):
+	text = win.editor.getfocustext()
+	if not text:
+		stdwin.fleep()
+		return
+	stdwin.setcutbuffer(0, text)
+	replace(win, '')
+#
+def do_copy(win):
+	text = win.editor.getfocustext()
+	if not text:
+		stdwin.fleep()
+		return
+	stdwin.setcutbuffer(0, text)
+#
+def do_paste(win):
+	text = stdwin.getcutbuffer(0)
+	if not text:
+		stdwin.fleep()
+		return
+	replace(win, text)
+#
+def do_clear(win):
+	replace(win, '')
+
+#
+# These would be better in a preferences dialog:
+def do_auto(win):
+	win.auto = (not win.auto)
+	win.editmenu.check(win.iauto, win.auto)
+#
+def do_insertOutputOption(win):
+	win.insertOutput = (not win.insertOutput)
+	title = ['Append Output', 'Insert Output'][win.insertOutput]
+	win.editmenu.setitem(win.insertOutputNum, title)
+#
+def do_insertErrorOption(win):
+	win.insertError = (not win.insertError)
+	title = ['Error Dialog', 'Insert Error'][win.insertError]
+	win.editmenu.setitem(win.insertErrorNum, title)
+
+
+# Extract a command from the editor and execute it, or pass input to
+# an interpreter waiting for it.
+# Incomplete commands are merely placed in the window's command buffer.
+# All exceptions occurring during the execution are caught and reported.
+# (Tracebacks are currently not possible, as the interpreter does not
+# save the traceback pointer until it reaches its outermost level.)
+#
+def do_exec(win):
+	if win.busy:
+		if win not in inputwindows:
+			stdwin.message('Can\'t run recursive commands')
+			return
+		if win <> inputwindows[0]:
+			stdwin.message( \
+				'Please complete recursive input first')
+			return
+	#
+	# Set text to the string to execute.
+	a, b = win.editor.getfocus()
+	alltext = win.editor.gettext()
+	n = len(alltext)
+	if a == b:
+		# There is no selected text, just an insert point;
+		# so execute the current line.
+		while 0 < a and alltext[a-1] <> '\n': a = a-1	# Find beginning of line.
+		while b < n and alltext[b] <> '\n':		# Find end of line after b.
+			b = b+1
+		text = alltext[a:b] + '\n'
+	else:
+		# Execute exactly the selected text.
+		text = win.editor.getfocustext()
+		if text[-1:] <> '\n':					# Make sure text ends with newline.
+			text = text + '\n'
+		while b < n and alltext[b] <> '\n':		# Find end of line after b.
+			b = b+1
+	#
+	# Set the focus to expect the output, since there is always something.
+	# Output will be inserted at end of line after current focus,
+	# or appended to the end of the text.
+	b = [n, b][win.insertOutput]
+	win.editor.setfocus(b, b)
+	#
+	# Make sure there is a preceeding newline.
+	if alltext[b-1:b] <> '\n':
+		win.editor.replace('\n')
+	#
+	#
+	if win.busy:
+		# Send it to raw_input() below
+		raise InputAvailable, (None, text)
+	#
+	# Like the real Python interpreter, we want to execute
+	# single-line commands immediately, but save multi-line
+	# commands until they are terminated by a blank line.
+	# Unlike the real Python interpreter, we don't do any syntax
+	# checking while saving up parts of a multi-line command.
+	#
+	# The current heuristic to determine whether a command is
+	# the first line of a multi-line command simply checks whether
+	# the command ends in a colon (followed by a newline).
+	# This is not very robust (comments and continuations will
+	# confuse it), but it is usable, and simple to implement.
+	# (It even has the advantage that single-line loops etc.
+	# don't need te be terminated by a blank line.)
+	#
+	if win.command:
+		# Already continuing
+		win.command = win.command + text
+		if win.command[-2:] <> '\n\n':
+			win.settitle('Unfinished command...')
+			return # Need more...
+	else:
+		# New command
+		win.command = text
+		if text[-2:] == ':\n':
+			win.settitle('Unfinished command...')
+			return
+	command = win.command
+	win.command = ''
+	win.settitle('Executing command...')
+	#
+	# Some hacks: sys.stdout is temporarily redirected to a file,
+	# so we can intercept the command's output and insert it
+	# in the editor window; the built-in function raw_input
+	# and input() are replaced by out versions;
+	# and a second, undocumented argument
+	# to exec() is used to specify the directory holding the
+	# user's global variables.  (If this wasn't done, the
+	# exec would be executed in the current local environment,
+	# and the user's assignments to globals would be lost...)
+	#
+	save_input = builtin.input
+	save_raw_input = builtin.raw_input
+	save_stdout = sys.stdout
+	save_stderr = sys.stderr
+	iwin = Input().init(win)
+	try:
+		builtin.input = iwin.input
+		builtin.raw_input = iwin.raw_input
+		sys.stdout = sys.stderr = open(win.outfile, 'w')
+		win.busy = 1
+		try:
+			exec(command, win.globals)
+		except KeyboardInterrupt:
+			pass # Don't give an error.
+		except:
+			msg = sys.exc_type
+			if sys.exc_value <> None:
+				msg = msg + ': ' + `sys.exc_value`
+			if win.insertError:
+				stdwin.fleep()
+				replace(win, msg + '\n')
+			else:
+				win.settitle('Unhandled exception')
+				stdwin.message(msg)
+	finally:
+		# Restore redirected I/O in *all* cases
+		win.busy = 0
+		sys.stderr = save_stderr
+		sys.stdout = save_stdout
+		builtin.raw_input = save_raw_input
+		builtin.input = save_input
+		settitle(win)
+	getoutput(win)
+
+
+# Read any output the command may have produced back from the file
+# and show it.  Optionally insert it after the focus, like MPW does, 
+# or always append at the end.
+#
+def getoutput(win):
+	filename = win.outfile
+	try:
+		fp = open(filename, 'r')
+	except:
+		stdwin.message('Can\'t read output from ' + filename)
+		return
+	#out = fp.read() # Not in Python 0.9.1
+	out = fp.read(10000) # For Python 0.9.1
+	del fp # Close it
+	if out or win.insertOutput:
+		replace(win, out)
+
+
+# Implementation of input() and raw_input().
+# This uses a class only because we must support calls
+# with and without arguments; this can't be done normally in Python,
+# but the extra, implicit argument for instance methods does the trick.
+#
+class Input:
+	#
+	def init(self, win):
+		self.win = win
+		return self
+	#
+	def input(args):
+		# Hack around call with or without argument:
+		if type(args) == type(()):
+			self, prompt = args
+		else:
+			self, prompt = args, ''
+		#
+		return eval(self.raw_input(prompt), self.win.globals)
+	#
+	def raw_input(args):
+		# Hack around call with or without argument:
+		if type(args) == type(()):
+			self, prompt = args
+		else:
+			self, prompt = args, ''
+		#
+		print prompt		# Need to terminate with newline.
+		sys.stdout.close()
+		sys.stdout = sys.stderr = None
+		getoutput(self.win)
+		sys.stdout = sys.stderr = open(self.win.outfile, 'w')
+		save_title = self.win.gettitle()
+		n = len(inputwindows)
+		title = n*'(' + 'Requesting input...' + ')'*n
+		self.win.settitle(title)
+		inputwindows.insert(0, self.win)
+		try:
+			mainloop.mainloop()
+		except InputAvailable, (exc, val):		# See do_exec above.
+			if exc:
+				raise exc, val
+			if val[-1:] == '\n':
+				val = val[:-1]
+			return val
+		finally:
+			del inputwindows[0]
+			self.win.settitle(save_title)
+		# If we don't catch InputAvailable, something's wrong...
+		raise EOFError
+	#
+
+
+# Currently unused function to test a command's syntax without executing it
+#
+def testsyntax(s):
+	import string
+	lines = string.splitfields(s, '\n')
+	for i in range(len(lines)): lines[i] = '\t' + lines[i]
+	lines.insert(0, 'if 0:')
+	lines.append('')
+	exec(string.joinfields(lines, '\n'))
+
+
+# Call the main program.
+#
+main()