Initial revision
diff --git a/Lib/lib-stdwin/anywin.py b/Lib/lib-stdwin/anywin.py
new file mode 100644
index 0000000..bb7e865
--- /dev/null
+++ b/Lib/lib-stdwin/anywin.py
@@ -0,0 +1,14 @@
+# Module 'anywin'
+# Open a file or directory in a window
+
+import dirwin
+import filewin
+import path
+
+def open(name):
+	print 'opening', name, '...'
+	if path.isdir(name):
+		w = dirwin.open(name)
+	else:
+		w = filewin.open(name)
+	return w
diff --git a/Lib/lib-stdwin/dirwin.py b/Lib/lib-stdwin/dirwin.py
new file mode 100644
index 0000000..5df85e7
--- /dev/null
+++ b/Lib/lib-stdwin/dirwin.py
@@ -0,0 +1,28 @@
+# Module 'dirwin'
+
+# Directory windows, a subclass of listwin
+
+import gwin
+import listwin
+import anywin
+import path
+import dircache
+
+def action(w, string, i, detail):
+	(h, v), clicks, button, mask = detail
+	if clicks = 2:
+		name = path.cat(w.name, string)
+		try:
+			w = anywin.open(name)
+		except posix.error, why:
+			stdwin.message('Can\'t open ' + name + ': ' + why[1])
+
+def open(name):
+	name = path.cat(name, '')
+	list = dircache.opendir(name)[:]
+	list.sort()
+	dircache.annotate(name, list)
+	w = listwin.open(name, list)
+	w.name = name
+	w.action = action
+	return w
diff --git a/Lib/lib-stdwin/filewin.py b/Lib/lib-stdwin/filewin.py
new file mode 100644
index 0000000..1beb0b6
--- /dev/null
+++ b/Lib/lib-stdwin/filewin.py
@@ -0,0 +1,31 @@
+# Module 'filewin'
+# File windows, a subclass of textwin (which is a subclass of gwin)
+
+import stdwin
+import textwin
+import path
+
+builtin_open = open
+
+def readfile(fn): # Return a string containing the file's contents
+	fp = builtin_open(fn, 'r')
+	a = ''
+	n = 8096
+	while 1:
+		b = fp.read(n)
+		if not b: break
+		a = a + b
+	return a
+
+
+# FILE WINDOW
+
+def open_readonly(fn): # Open a file window
+	w = textwin.open_readonly(fn, readfile(fn))
+	w.fn = fn
+	return w
+
+def open(fn): # Open a file window
+	w = textwin.open(fn, readfile(fn))
+	w.fn = fn
+	return w
diff --git a/Lib/lib-stdwin/gwin.py b/Lib/lib-stdwin/gwin.py
new file mode 100644
index 0000000..15aa432
--- /dev/null
+++ b/Lib/lib-stdwin/gwin.py
@@ -0,0 +1,118 @@
+# Module 'gwin'
+# Generic stdwin windows
+
+# This is used as a base class from which to derive other window types.
+# The mainloop() function here is an event dispatcher for all window types.
+
+import stdwin
+import stdwinsupport
+
+S = stdwinsupport			# Shorthand
+
+windows = []				# List of open windows
+
+
+# Open a window
+
+def open(title):			# Open a generic window
+	w = stdwin.open(title)
+	stdwin.setdefwinsize(0, 0)
+	# Set default event handlers
+	w.draw = nop
+	w.char = nop
+	w.mdown = nop
+	w.mmove = nop
+	w.mup = nop
+	w.m2down = m2down
+	w.m2up = m2up
+	w.size = nop
+	w.move = nop
+	w.activate = w.deactivate = nop
+	w.timer = nop
+	# default command handlers
+	w.close = close
+	w.tab = tab
+	w.enter = enter
+	w.backspace = backspace
+	w.arrow = arrow
+	w.kleft = w.kup = w.kright = w.kdown = nop
+	windows.append(w)
+	return w
+
+
+# Generic event dispatching
+
+def mainloop():				# Handle events until no windows left
+	while windows:
+		treatevent(stdwin.getevent())
+
+def treatevent(e):			# Handle a stdwin event
+	type, w, detail = e
+	if type = S.we_draw:
+		w.draw(w, detail)
+	elif type = S.we_menu:
+		m, item = detail
+		m.action[item](w, m, item)
+	elif type = S.we_command:
+		treatcommand(w, detail)
+	elif type = S.we_char:
+		w.char(w, detail)
+	elif type = S.we_mouse_down:
+		if detail[1] > 1: w.m2down(w, detail)
+		else: w.mdown(w, detail)
+	elif type = S.we_mouse_move:
+		w.mmove(w, detail)
+	elif type = S.we_mouse_up:
+		if detail[1] > 1: w.m2up(w, detail)
+		else: w.mup(w, detail)
+	elif type = S.we_size:
+		w.size(w, w.getwinsize())
+	elif type = S.we_activate:
+		w.activate(w)
+	elif type = S.we_deactivate:
+		w.deactivate(w)
+	elif type = S.we_move:
+		w.move(w)
+	elif type = S.we_timer:
+		w.timer(w)
+
+def treatcommand(w, type):		# Handle a we_command event
+	if type = S.wc_close:
+		w.close(w)
+	elif type = S.wc_return:
+		w.enter(w)
+	elif type = S.wc_tab:
+		w.tab(w)
+	elif type = S.wc_backspace:
+		w.backspace(w)
+	elif type in (S.wc_left, S.wc_up, S.wc_right, S.wc_down):
+		w.arrow(w, type)
+
+
+# Methods
+
+def close(w):				# Close method
+	for i in range(len(windows)):
+		if windows[i] is w:
+			del windows[i]
+			break
+
+def arrow(w, detail):			# Arrow key method
+	if detail = S.wc_left:
+		w.kleft(w)
+	elif detail = S.wc_up:
+		w.kup(w)
+	elif detail = S.wc_right:
+		w.kright(w)
+	elif detail = S.wc_down:
+		w.kdown(w)
+
+
+# Trivial methods
+
+def tab(w): w.char(w, '\t')
+def enter(w): w.char(w, '\n')		# 'return' is a Python reserved word
+def backspace(w): w.char(w, '\b')
+def m2down(w, detail): w.mdown(w, detail)
+def m2up(w, detail): w.mup(w, detail)
+def nop(args): pass
diff --git a/Lib/lib-stdwin/listwin.py b/Lib/lib-stdwin/listwin.py
new file mode 100644
index 0000000..9480a81
--- /dev/null
+++ b/Lib/lib-stdwin/listwin.py
@@ -0,0 +1,47 @@
+# Module 'listwin'
+# List windows, a subclass of gwin
+
+import gwin
+import stdwin
+
+def maxlinewidth(a): # Compute maximum textwidth of lines in a sequence
+	max = 0
+	for line in a:
+		width = stdwin.textwidth(line)
+		if width > max: max = width
+	return max
+
+def action(w, string, i, detail): # Default item selection method
+	pass
+
+def mup(w, detail): # Mouse up method
+	(h, v), clicks, button, mask = detail
+	i = divmod(v, w.lineheight)[0]
+	if 0 <= i < len(w.data):
+		w.action(w, w.data[i], i, detail)
+
+def draw(w, ((left, top), (right, bottom))): # Text window draw method
+	data = w.data
+	d = w.begindrawing()
+	lh = w.lineheight
+	itop = top/lh
+	ibot = (bottom-1)/lh + 1
+	if itop < 0: itop = 0
+	if ibot > len(data): ibot = len(data)
+	for i in range(itop, ibot): d.text((0, i*lh), data[i])
+
+def open(title, data): # Display a list of texts in a window
+	lineheight = stdwin.lineheight()
+	h, v = maxlinewidth(data), len(data)*lineheight
+	h0, v0 = h + stdwin.textwidth(' '), v + lineheight
+	if h0 > stdwin.textwidth(' ')*80: h0 = 0
+	if v0 > stdwin.lineheight()*24: v0 = 0
+	stdwin.setdefwinsize(h0, v0)
+	w = gwin.open(title)
+	w.setdocsize(h, v)
+	w.lineheight = lineheight
+	w.data = data
+	w.draw = draw
+	w.action = action
+	w.mup = mup
+	return w
diff --git a/Lib/lib-stdwin/rect.py b/Lib/lib-stdwin/rect.py
new file mode 100644
index 0000000..c044b9f
--- /dev/null
+++ b/Lib/lib-stdwin/rect.py
@@ -0,0 +1,87 @@
+# Module 'rect'.
+#
+# Operations on rectangles.
+# There is some normalization: all results return the object 'empty'
+# if their result would contain no points.
+
+
+# Exception.
+#
+error = 'rect.error'
+
+
+# The empty rectangle.
+#
+empty = (0, 0), (0, 0)
+
+
+# Check if a rectangle is empty.
+#
+def is_empty((left, top), (right, bottom)):
+	return left >= right or top >= bottom
+
+
+# Compute the intersection or two or more rectangles.
+# This works with a list or tuple argument.
+#
+def intersect(list):
+	if not list: raise error, 'intersect called with empty list'
+	if is_empty(list[0]): return empty
+	(left, top), (right, bottom) = list[0]
+	for rect in list[1:]:
+		if not is_empty(rect):
+			(l, t), (r, b) = rect
+			if left < l: left = l
+			if top < t: top = t
+			if right > r: right = r
+			if bottom > b: bottom = b
+			if is_empty((left, top), (right, bottom)):
+				return empty
+	return (left, top), (right, bottom)
+
+
+# Compute the smallest rectangle containing all given rectangles.
+# This works with a list or tuple argument.
+#
+def union(list):
+	(left, top), (right, bottom) = empty
+	for (l, t), (r, b) in list[1:]:
+		if not is_empty((l, t), (r, b)):
+			if l < left: left = l
+			if t < top: top = t
+			if r > right: right = r
+			if b > bottom: bottom = b
+	res = (left, top), (right, bottom)
+	if is_empty(res):
+		return empty
+	return res
+
+
+# Check if a point is in a rectangle.
+#
+def pointinrect((h, v), ((left, top), (right, bottom))):
+	return left <= h < right and top <= v < bottom
+
+
+# Return a rectangle that is dh, dv inside another
+#
+def inset(((left, top), (right, bottom)), (dh, dv)):
+	left = left + dh
+	top = top + dv
+	right = right - dh
+	bottom = bottom - dv
+	r = (left, top), (right, bottom)
+	if is_empty(r):
+		return empty
+	else:
+		return r
+
+
+# Conversions between rectangles and 'geometry tuples',
+# given as origin (h, v) and dimensions (width, height).
+#
+def rect2geom((left, top), (right, bottom)):
+	return (left, top), (right-left, bottom-top)
+
+def geom2rect((h, v), (width, height)):
+	return (h, v), (h+width, v+height)
diff --git a/Lib/lib-stdwin/stdwinevents.py b/Lib/lib-stdwin/stdwinevents.py
new file mode 100644
index 0000000..889dd95
--- /dev/null
+++ b/Lib/lib-stdwin/stdwinevents.py
@@ -0,0 +1,36 @@
+# Module 'stdwinevents' -- Constants for stdwin event types
+#
+# Suggested usage:
+#	from stdwinevents import *
+
+# The function stdwin.getevent() returns a tuple containing:
+#	(type, window, detail)
+# where detail may be <no value> or a value depending on type, see below:
+
+# Values for type:
+
+WE_NULL       =  0	# not reported -- means 'no event' internally
+WE_ACTIVATE   =  1	# detail is <no object>
+WE_CHAR       =  2	# detail is the character
+WE_COMMAND    =  3	# detail is one of the WC_* constants below
+WE_MOUSE_DOWN =  4	# detail is ((h, v), clicks, button, mask)
+WE_MOUSE_MOVE =  5	# ditto
+WE_MOUSE_UP   =  6	# ditto
+WE_MENU       =  7	# detail is (menu, item)
+WE_SIZE       =  8	# detail is (width, height) [???]
+WE_MOVE       =  9	# not reported -- reserved for future use
+WE_DRAW       = 10	# detail is ((left, top), (right, bottom))
+WE_TIMER      = 11	# detail is <no object>
+WE_DEACTIVATE = 12	# detail is <no object>
+
+# Values for detail when type is WE_COMMAND:
+
+WC_CLOSE      =  1	# user hit close box
+WC_LEFT       =  2	# left arrow key
+WC_RIGHT      =  3	# right arrow key
+WC_UP         =  4	# up arrow key
+WC_DOWN       =  5	# down arrow key
+WC_CANCEL     =  6	# not reported -- turned into KeyboardInterrupt
+WC_BACKSPACE  =  7	# backspace key
+WC_TAB        =  8	# tab key
+WC_RETURN     =  9	# return or enter key
diff --git a/Lib/lib-stdwin/tablewin.py b/Lib/lib-stdwin/tablewin.py
new file mode 100644
index 0000000..05a954e
--- /dev/null
+++ b/Lib/lib-stdwin/tablewin.py
@@ -0,0 +1,237 @@
+# Module 'tablewin'
+
+# Display a table, with per-item actions:
+
+#	   A1   |   A2   |   A3   |  ....  |   AN
+#	   B1   |   B2   |   B3   |  ....  |   BN
+#	   C1   |   C2   |   C3   |  ....  |   CN
+#	   ..   |   ..   |   ..   |  ....  |   ..
+#	   Z1   |   Z2   |   Z3   |  ....  |   ZN
+
+# Not all columns need to have the same length.
+# The data structure is a list of columns;
+# each column is a list of items.
+# Each item is a pair of a string and an action procedure.
+# The first item may be a column title.
+
+import stdwin
+import gwin
+
+def open(title, data): # Public function to open a table window
+	#
+	# Set geometry parameters (one day, these may be changeable)
+	#
+	margin = stdwin.textwidth('  ')
+	lineheight = stdwin.lineheight()
+	#
+	# Geometry calculations
+	#
+	colstarts = [0]
+	totwidth = 0
+	maxrows = 0
+	for coldata in data:
+		# Height calculations
+		rows = len(coldata)
+		if rows > maxrows: maxrows = rows
+		# Width calculations
+		width = colwidth(coldata) + margin
+		totwidth = totwidth + width
+		colstarts.append(totwidth)
+	#
+	# Calculate document and window height
+	#
+	docwidth, docheight = totwidth, maxrows*lineheight
+	winwidth, winheight = docwidth, docheight
+	if winwidth > stdwin.textwidth('n')*100: winwidth = 0
+	if winheight > stdwin.lineheight()*30: winheight = 0
+	#
+	# Create the window
+	#
+	stdwin.setdefwinsize(winwidth, winheight)
+	w = gwin.open(title)
+	#
+	# Set properties and override methods
+	#
+	w.data = data
+	w.margin = margin
+	w.lineheight = lineheight
+	w.colstarts = colstarts
+	w.totwidth = totwidth
+	w.maxrows = maxrows
+	w.selection = (-1, -1)
+	w.lastselection = (-1, -1)
+	w.selshown = 0
+	w.setdocsize(docwidth, docheight)
+	w.draw = draw
+	w.mup = mup
+	w.arrow = arrow
+	#
+	# Return
+	#
+	return w
+
+def update(w, data): # Change the data
+	#
+	# Hide selection
+	#
+	hidesel(w, w.begindrawing())
+	#
+	# Get old geometry parameters
+	#
+	margin = w.margin
+	lineheight = w.lineheight
+	#
+	# Geometry calculations
+	#
+	colstarts = [0]
+	totwidth = 0
+	maxrows = 0
+	for coldata in data:
+		# Height calculations
+		rows = len(coldata)
+		if rows > maxrows: maxrows = rows
+		# Width calculations
+		width = colwidth(coldata) + margin
+		totwidth = totwidth + width
+		colstarts.append(totwidth)
+	#
+	# Calculate document and window height
+	#
+	docwidth, docheight = totwidth, maxrows*lineheight
+	#
+	# Set changed properties and change window size
+	#
+	w.data = data
+	w.colstarts = colstarts
+	w.totwidth = totwidth
+	w.maxrows = maxrows
+	w.change((0, 0), (10000, 10000))
+	w.setdocsize(docwidth, docheight)
+	w.change((0, 0), (docwidth, docheight))
+	#
+	# Show selection, or forget it if out of range
+	#
+	showsel(w, w.begindrawing())
+	if not w.selshown: w.selection = (-1, -1)
+
+def colwidth(coldata): # Subroutine to calculate column width
+	maxwidth = 0
+	for string, action in coldata:
+		width = stdwin.textwidth(string)
+		if width > maxwidth: maxwidth = width
+	return maxwidth
+
+def draw(w, ((left, top), (right, bottom))): # Draw method
+	ileft = whichcol(w, left)
+	iright = whichcol(w, right-1) + 1
+	if iright > len(w.data): iright = len(w.data)
+	itop = divmod(top, w.lineheight)[0]
+	if itop < 0: itop = 0
+	ibottom, remainder = divmod(bottom, w.lineheight)
+	if remainder: ibottom = ibottom + 1
+	d = w.begindrawing()
+	if ileft <= w.selection[0] < iright:
+		if itop <= w.selection[1] < ibottom:
+			hidesel(w, d)
+	d.erase((left, top), (right, bottom))
+	for i in range(ileft, iright):
+		col = w.data[i]
+		jbottom = len(col)
+		if ibottom < jbottom: jbottom = ibottom
+		h = w.colstarts[i]
+		v = itop * w.lineheight
+		for j in range(itop, jbottom):
+			string, action = col[j]
+			d.text((h, v), string)
+			v = v + w.lineheight
+	showsel(w, d)
+
+def mup(w, detail): # Mouse up method
+	(h, v), nclicks, button, mask = detail
+	icol = whichcol(w, h)
+	if 0 <= icol < len(w.data):
+		irow = divmod(v, w.lineheight)[0]
+		col = w.data[icol]
+		if 0 <= irow < len(col):
+			string, action = col[irow]
+			action(w, string, (icol, irow), detail)
+
+def whichcol(w, h): # Return column number (may be >= len(w.data))
+	for icol in range(0, len(w.data)):
+		if h < w.colstarts[icol+1]:
+			return icol
+	return len(w.data)
+
+def arrow(w, type):
+	import stdwinsupport
+	S = stdwinsupport
+	if type = S.wc_left:
+		incr = -1, 0
+	elif type = S.wc_up:
+		incr = 0, -1
+	elif type = S.wc_right:
+		incr = 1, 0
+	elif type = S.wc_down:
+		incr = 0, 1
+	else:
+		return
+	icol, irow = w.lastselection
+	icol = icol + incr[0]
+	if icol < 0: icol = len(w.data)-1
+	if icol >= len(w.data): icol = 0
+	if 0 <= icol < len(w.data):
+		irow = irow + incr[1]
+		if irow < 0: irow = len(w.data[icol]) - 1
+		if irow >= len(w.data[icol]): irow = 0
+	else:
+		irow = 0
+	if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
+		w.lastselection = icol, irow
+		string, action = w.data[icol][irow]
+		detail = (0, 0), 1, 1, 1
+		action(w, string, (icol, irow), detail)
+
+
+# Selection management
+# TO DO: allow multiple selected entries
+
+def select(w, selection): # Public function to set the item selection
+	d = w.begindrawing()
+	hidesel(w, d)
+	w.selection = selection
+	showsel(w, d)
+	if w.selshown: lastselection = selection
+
+def hidesel(w, d): # Hide the selection, if shown
+	if w.selshown: invertsel(w, d)
+
+def showsel(w, d): # Show the selection, if hidden
+	if not w.selshown: invertsel(w, d)
+
+def invertsel(w, d): # Invert the selection, if valid
+	icol, irow = w.selection
+	if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
+		left = w.colstarts[icol]
+		right = w.colstarts[icol+1]
+		top = irow * w.lineheight
+		bottom = (irow+1) * w.lineheight
+		d.invert((left, top), (right, bottom))
+		w.selshown = (not w.selshown)
+
+
+# Demonstration
+
+def demo_action(w, string, (icol, irow), detail): # Action function for demo
+	select(w, (irow, icol))
+
+def demo(): # Demonstration
+	da = demo_action # shorthand
+	col0 = [('a1', da), ('bbb1', da), ('c1', da)]
+	col1 = [('a2', da), ('bbb2', da)]
+	col2 = [('a3', da), ('b3', da), ('c3', da), ('d4', da), ('d5', da)]
+	col3 = []
+	for i in range(1, 31): col3.append('xxx' + `i`, da)
+	data = [col0, col1, col2, col3]
+	w = open('tablewin.demo', data)
+	gwin.mainloop()
+	return w
diff --git a/Lib/lib-stdwin/textwin.py b/Lib/lib-stdwin/textwin.py
new file mode 100644
index 0000000..2631ca4
--- /dev/null
+++ b/Lib/lib-stdwin/textwin.py
@@ -0,0 +1,119 @@
+# Module 'textwin'
+
+# Text windows, a subclass of gwin
+
+import stdwin
+import stdwinsupport
+import gwin
+
+S = stdwinsupport			# Shorthand
+
+
+def fixsize(w):
+	docwidth, docheight = w.text.getrect()[1]
+	winheight = w.getwinsize()[1]
+	if winheight > docheight: docheight = winheight
+	w.setdocsize(0, docheight)
+	fixeditmenu(w)
+
+def cut(w, m, id):
+	s = w.text.getfocustext()
+	if s:
+		stdwin.setcutbuffer(s)
+		w.text.replace('')
+		fixsize(w)
+
+def copy(w, m, id):
+	s = w.text.getfocustext()
+	if s:
+		stdwin.setcutbuffer(s)
+		fixeditmenu(w)
+
+def paste(w, m, id):
+	w.text.replace(stdwin.getcutbuffer())
+	fixsize(w)
+
+def addeditmenu(w):
+	m = w.editmenu = w.menucreate('Edit')
+	m.action = []
+	m.additem('Cut', 'X')
+	m.action.append(cut)
+	m.additem('Copy', 'C')
+	m.action.append(copy)
+	m.additem('Paste', 'V')
+	m.action.append(paste)
+
+def fixeditmenu(w):
+	m = w.editmenu
+	f = w.text.getfocus()
+	can_copy = (f[0] < f[1])
+	m.enable(1, can_copy)
+	if not w.readonly:
+		m.enable(0, can_copy)
+		m.enable(2, (stdwin.getcutbuffer() <> ''))
+
+def draw(w, area):			# Draw method
+	w.text.draw(area)
+
+def size(w, newsize):			# Size method
+	w.text.move((0, 0), newsize)
+	fixsize(w)
+
+def close(w):				# Close method
+	del w.text  # Break circular ref
+	gwin.close(w)
+
+def char(w, c):				# Char method
+	w.text.replace(c)
+	fixsize(w)
+
+def backspace(w):			# Backspace method
+	void = w.text.event(S.we_command, w, S.wc_backspace)
+	fixsize(w)
+
+def arrow(w, detail):			# Arrow method
+	w.text.arrow(detail)
+	fixeditmenu(w)
+
+def mdown(w, detail):			# Mouse down method
+	void = w.text.event(S.we_mouse_down, w, detail)
+	fixeditmenu(w)
+
+def mmove(w, detail):			# Mouse move method
+	void = w.text.event(S.we_mouse_move, w, detail)
+
+def mup(w, detail):			# Mouse up method
+	void = w.text.event(S.we_mouse_up, w, detail)
+	fixeditmenu(w)
+
+def activate(w):			# Activate method
+	fixeditmenu(w)
+
+def open(title, str):			# Display a string in a window
+	w = gwin.open(title)
+	w.readonly = 0
+	w.text = w.textcreate((0, 0), w.getwinsize())
+	w.text.replace(str)
+	w.text.setfocus(0, 0)
+	addeditmenu(w)
+	fixsize(w)
+	w.draw = draw
+	w.size = size
+	w.close = close
+	w.mdown = mdown
+	w.mmove = mmove
+	w.mup = mup
+	w.char = char
+	w.backspace = backspace
+	w.arrow = arrow
+	w.activate = activate
+	return w
+
+def open_readonly(title, str):		# Same with char input disabled
+	w = open(title, str)
+	w.readonly = 1
+	w.char = w.backspace = gwin.nop
+	# Disable Cut and Paste menu item; leave Copy alone
+	w.editmenu.enable(0, 0)
+	w.editmenu.enable(2, 0)
+	return w