Initial revision
diff --git a/Demo/stdwin/miniedit.py b/Demo/stdwin/miniedit.py
new file mode 100755
index 0000000..3e8da2b
--- /dev/null
+++ b/Demo/stdwin/miniedit.py
@@ -0,0 +1,356 @@
+#! /usr/local/python
+
+# A miniature multi-window editor using STDWIN's text objects.
+#
+# Usage: miniedit [file] ...
+#
+# The user interface is similar to that of the miniedit demo application
+# in C that comes with STDWIN.
+#
+# XXX need to comment the functions
+# XXX Not yet implemented:
+#	disabling menu entries for inapplicable actions
+#	Find operations
+
+
+import sys
+import stdwin
+from stdwinevents import *
+
+
+# Constant: list of WE_COMMAND events that (may) change the text buffer
+# so we can decide whether to set the 'changed' flag.
+# Note that it is possible for such a command to fail (a backspace
+# at the beginning of the buffer) but we'll set the changed flag anyway
+# -- it's too complicated to check this condition right now.
+#
+changing = [WC_RETURN, WC_TAB, WC_BACKSPACE]
+
+
+# The list of currently open windows;
+# this is maintained so we can stop when there are no windows left
+#
+windows = []
+
+
+# A note on window data attributes (set by open_window):
+#
+# w.textobject	the window's text object
+# w.changed	true when the window's text is changed
+# w.filename	filename connected to the window; '' if none
+
+
+# Main program
+#
+def main():
+	#
+	# Set a reasonable default window size.
+	# If we are using a fixed-width font this will open a 80x24 window;
+	# for variable-width fonts we approximate this based on an average
+	#
+	stdwin.setdefwinsize(40*stdwin.textwidth('in'), 24*stdwin.lineheight())
+	#
+	# Create global menus (as local variables)
+	#
+	filemenu = make_file_menu(stdwin)
+	editmenu = make_edit_menu(stdwin)
+	findmenu = make_find_menu(stdwin)
+	#
+	# Get the list of files from the command line (maybe none)
+	#
+	files = sys.argv[1:]
+	#
+	# Open any files -- errors will be reported but do won't stop us
+	#
+	for filename in files:
+		open_file(filename)
+	#
+	# If there were no files, or none of them could be opened,
+	# put up a dialog asking for a filename
+	#
+	if not windows:
+		try:
+			open_dialog(None)
+		except KeyboardInterrupt:
+			pass		# User cancelled
+	#
+	# If the dialog was cancelled, create an empty new window
+	#
+	if not windows:
+		new_window(None)
+	#
+	# Main event loop -- stop when we have no open windows left
+	#
+	while windows:
+		#
+		# Get the next event -- ignore interrupts
+		#
+		try:
+			type, window, detail = event = stdwin.getevent()
+		except KeyboardInterrupt:
+			type, window, detail = event = WE_NONE, None, None
+		#
+		# Event decoding switch
+		#
+		if not window:
+			pass		# Ignore such events
+		elif type == WE_MENU:
+			#
+			# Execute menu operation
+			#
+			menu, item = detail
+			try:
+				menu.actions[item](window)
+			except KeyboardInterrupt:
+				pass	# User cancelled
+		elif type == WE_CLOSE:
+			#
+			# Close a window
+			#
+			try:
+				close_dialog(window)
+			except KeyboardInterrupt:
+				pass	# User cancelled
+		elif type == WE_SIZE:
+			#
+			# A window was resized --
+			# let the text object recompute the line breaks
+			# and change the document size accordingly,
+			# so scroll bars will work
+			#
+			fix_textsize(window)
+		elif window.textobject.event(event):
+			#
+			# The event was eaten by the text object --
+			# set the changed flag if not already set
+			#
+			if type == WE_CHAR or \
+			   type == WE_COMMAND and detail in changing:
+				window.changed = 1
+				fix_docsize(window)
+		#
+		# Delete all objects that may still reference the window
+		# in the event -- this is needed otherwise the window
+		# won't actually be closed and may receive further
+		# events, which will confuse the event decoder
+		#
+		del type, window, detail, event
+
+
+def make_file_menu(object):
+	menu = object.menucreate('File')
+	menu.actions = []
+	additem(menu, 'New',		'N', new_window)
+	additem(menu, 'Open..',		'O', open_dialog)
+	additem(menu, '',		'',  None)
+	additem(menu, 'Save',		'S', save_dialog)
+	additem(menu, 'Save As..',	'',  save_as_dialog)
+	additem(menu, 'Save a Copy..',	'',  save_copy_dialog)
+	additem(menu, 'Revert',		'R', revert_dialog)
+	additem(menu, 'Quit',		'Q', quit_dialog)
+	return menu
+
+
+def make_edit_menu(object):
+	menu = object.menucreate('Edit')
+	menu.actions = []
+	additem(menu, 'Cut',		'X', do_cut)
+	additem(menu, 'Copy',		'C', do_copy)
+	additem(menu, 'Paste',		'V', do_paste)
+	additem(menu, 'Clear',		'B', do_clear)
+	additem(menu, 'Select All',	'A', do_select_all)
+	return menu
+
+
+def make_find_menu(object):
+	menu = object.menucreate('Find')
+	menu.actions = []
+	# XXX
+	return menu
+
+
+def additem(menu, text, shortcut, function):
+	if shortcut:
+		menu.additem(text, shortcut)
+	else:
+		menu.additem(text)
+	menu.actions.append(function)
+
+
+def open_dialog(current_ignored):
+	filename = stdwin.askfile('Open file:', '', 0)
+	open_file(filename)
+
+
+def open_file(filename):
+	try:
+		fp = open(filename, 'r')
+	except RuntimeError:
+		stdwin.message(filename + ': cannot open')
+		return			# Error, forget it
+	try:
+		contents = fp.read()
+	except RuntimeError:
+		stdwin.message(filename + ': read error')
+		return			# Error, forget it
+	del fp				# Close the file
+	open_window(filename, filename, contents)
+
+
+def new_window(current_ignored):
+	open_window('', 'Untitled', '')
+
+
+def open_window(filename, title, contents):
+	try:
+		window = stdwin.open(title)
+	except RuntimeError:
+		stdwin.message('cannot open new window')
+		return			# Error, forget it
+	window.textobject = window.textcreate((0, 0), window.getwinsize())
+	window.textobject.settext(contents)
+	window.changed = 0
+	window.filename = filename
+	fix_textsize(window)
+	windows.append(window)
+
+
+def quit_dialog(window):
+	for window in windows[:]:
+		close_dialog(window)
+
+
+def close_dialog(window):
+	if window.changed:
+		prompt = 'Save changes to ' + window.gettitle() + ' ?'
+		if stdwin.askync(prompt, 1):
+			save_dialog(window)
+			if window.changed:
+				return	# Save failed (not) cancelled
+	windows.remove(window)
+	del window.textobject
+
+
+def save_dialog(window):
+	if not window.filename:
+		save_as_dialog(window)
+		return
+	if save_file(window, window.filename):
+		window.changed = 0
+
+
+def save_as_dialog(window):
+	prompt = 'Save ' + window.gettitle() + ' as:'
+	filename = stdwin.askfile(prompt, window.filename, 1)
+	if save_file(window, filename):
+		window.filename = filename
+		window.settitle(filename)
+		window.changed = 0
+
+
+def save_copy_dialog(window):
+	prompt = 'Save a copy of ' + window.gettitle() + ' as:'
+	filename = stdwin.askfile(prompt, window.filename, 1)
+	void = save_file(window, filename)
+
+
+def save_file(window, filename):
+	try:
+		fp = open(filename, 'w')
+	except RuntimeError:
+		stdwin.message(filename + ': cannot create')
+		return 0
+	contents = window.textobject.gettext()
+	try:
+		fp.write(contents)
+	except RuntimeError:
+		stdwin.message(filename + ': write error')
+		return 0
+	return 1
+
+
+def revert_dialog(window):
+	if not window.filename:
+		stdwin.message('This window has no file to revert from')
+		return
+	if window.changed:
+		prompt = 'Really read ' + window.filename + ' back from file?'
+		if not stdwin.askync(prompt, 1):
+			return
+	try:
+		fp = open(window.filename, 'r')
+	except RuntimeError:
+		stdwin.message(filename + ': cannot open')
+		return
+	contents = fp.read()
+	del fp				# Close the file
+	window.textobject.settext(contents)
+	window.changed = 0
+	fix_docsize(window)
+
+
+def fix_textsize(window):
+	corner = window.getwinsize()
+	area = (0, 0), (corner)
+	window.textobject.move(area)
+	fix_docsize(window)
+
+
+def fix_docsize(window):
+	area = window.textobject.getrect()
+	origin, corner = area
+	width, height = corner
+	window.setdocsize(0, height)
+
+
+def do_cut(window):
+	selection = window.textobject.getfocustext()
+	if not selection:
+		stdwin.fleep()		# Nothing to cut
+	elif not window.setselection(WS_PRIMARY, selection):
+		stdwin.fleep()		# Window manager glitch...
+	else:
+		stdwin.rotatecutbuffers(1)
+		stdwin.setcutbuffer(0, selection)
+		window.textobject.replace('')
+		window.changed = 1
+		fix_docsize(window)
+
+
+def do_copy(window):
+	selection = window.textobject.getfocustext()
+	if not selection:
+		stdwin.fleep()		# Nothing to cut
+	elif not window.setselection(WS_PRIMARY, selection):
+		stdwin.fleep()		# Window manager glitch...
+	else:
+		stdwin.rotatecutbuffers(1)
+		stdwin.setcutbuffer(0, selection)
+
+
+def do_paste(window):
+	selection = stdwin.getselection(WS_PRIMARY)
+	if not selection:
+		selection = stdwin.getcutbuffer(0)
+	if not selection:
+		stdwin.fleep()		# Nothing to paste
+	else:
+		window.textobject.replace(selection)
+		window.changed = 1
+		fix_docsize(window)
+
+def do_clear(window):
+	first, last = window.textobject.getfocus()
+	if first == last:
+		stdwin.fleep()		# Nothing to clear
+	else:
+		window.textobject.replace('')
+		window.changed = 1
+		fix_docsize(window)
+
+
+def do_select_all(window):
+	window.textobject.setfocus(0, 0x7fffffff) # XXX Smaller on the Mac!
+
+
+main()