More cleanup: Move some demos into a dedicated Tools/demo dir, move 2to3 demo to Tools, and remove all the other Demo content.
diff --git a/Tools/demo/Eiffel.py b/Tools/demo/Eiffel.py
new file mode 100644
index 0000000..15fa58a
--- /dev/null
+++ b/Tools/demo/Eiffel.py
@@ -0,0 +1,141 @@
+"""Support Eiffel-style preconditions and postconditions."""
+
+from types import FunctionType as function
+
+class EiffelBaseMetaClass(type):
+
+    def __new__(meta, name, bases, dict):
+        meta.convert_methods(dict)
+        return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
+                                                        dict)
+
+    @classmethod
+    def convert_methods(cls, dict):
+        """Replace functions in dict with EiffelMethod wrappers.
+
+        The dict is modified in place.
+
+        If a method ends in _pre or _post, it is removed from the dict
+        regardless of whether there is a corresponding method.
+        """
+        # find methods with pre or post conditions
+        methods = []
+        for k, v in dict.items():
+            if k.endswith('_pre') or k.endswith('_post'):
+                assert isinstance(v, function)
+            elif isinstance(v, function):
+                methods.append(k)
+        for m in methods:
+            pre = dict.get("%s_pre" % m)
+            post = dict.get("%s_post" % m)
+            if pre or post:
+                dict[k] = cls.make_eiffel_method(dict[m], pre, post)
+
+class EiffelMetaClass1(EiffelBaseMetaClass):
+    # an implementation of the "eiffel" meta class that uses nested functions
+
+    @staticmethod
+    def make_eiffel_method(func, pre, post):
+        def method(self, *args, **kwargs):
+            if pre:
+                pre(self, *args, **kwargs)
+            x = func(self, *args, **kwargs)
+            if post:
+                post(self, x, *args, **kwargs)
+            return x
+
+        if func.__doc__:
+            method.__doc__ = func.__doc__
+
+        return method
+
+class EiffelMethodWrapper:
+
+    def __init__(self, inst, descr):
+        self._inst = inst
+        self._descr = descr
+
+    def __call__(self, *args, **kwargs):
+        return self._descr.callmethod(self._inst, args, kwargs)
+
+class EiffelDescriptor(object):
+
+    def __init__(self, func, pre, post):
+        self._func = func
+        self._pre = pre
+        self._post = post
+
+        self.__name__ = func.__name__
+        self.__doc__ = func.__doc__
+
+    def __get__(self, obj, cls):
+        return EiffelMethodWrapper(obj, self)
+
+    def callmethod(self, inst, args, kwargs):
+        if self._pre:
+            self._pre(inst, *args, **kwargs)
+        x = self._func(inst, *args, **kwargs)
+        if self._post:
+            self._post(inst, x, *args, **kwargs)
+        return x
+
+class EiffelMetaClass2(EiffelBaseMetaClass):
+    # an implementation of the "eiffel" meta class that uses descriptors
+
+    make_eiffel_method = EiffelDescriptor
+
+def _test(metaclass):
+    class Eiffel(metaclass=metaclass):
+        pass
+
+    class Test(Eiffel):
+
+        def m(self, arg):
+            """Make it a little larger"""
+            return arg + 1
+
+        def m2(self, arg):
+            """Make it a little larger"""
+            return arg + 1
+
+        def m2_pre(self, arg):
+            assert arg > 0
+
+        def m2_post(self, result, arg):
+            assert result > arg
+
+    class Sub(Test):
+        def m2(self, arg):
+            return arg**2
+        def m2_post(self, Result, arg):
+            super(Sub, self).m2_post(Result, arg)
+            assert Result < 100
+
+    t = Test()
+    t.m(1)
+    t.m2(1)
+    try:
+        t.m2(0)
+    except AssertionError:
+        pass
+    else:
+        assert False
+
+    s = Sub()
+    try:
+        s.m2(1)
+    except AssertionError:
+        pass # result == arg
+    else:
+        assert False
+    try:
+        s.m2(10)
+    except AssertionError:
+        pass # result ==  100
+    else:
+        assert False
+    s.m2(5)
+
+if __name__ == "__main__":
+    _test(EiffelMetaClass1)
+    _test(EiffelMetaClass2)
diff --git a/Tools/demo/Vec.py b/Tools/demo/Vec.py
new file mode 100644
index 0000000..787af69
--- /dev/null
+++ b/Tools/demo/Vec.py
@@ -0,0 +1,68 @@
+class Vec:
+    """ A simple vector class
+
+    Instances of the Vec class  can be constructed from numbers
+
+    >>> a = Vec(1, 2, 3)
+    >>> b = Vec(3, 2, 1)
+
+    added
+    >>> a + b
+    Vec(4, 4, 4)
+
+    subtracted
+    >>> a - b
+    Vec(-2, 0, 2)
+
+    and multiplied by a scalar on the left
+    >>> 3.0 * a
+    Vec(3.0, 6.0, 9.0)
+
+    or on the right
+    >>> a * 3.0
+    Vec(3.0, 6.0, 9.0)
+    """
+    def __init__(self, *v):
+        self.v = list(v)
+
+    @classmethod
+    def fromlist(cls, v):
+        if not isinstance(v, list):
+            raise TypeError
+        inst = cls()
+        inst.v = v
+        return inst
+
+    def __repr__(self):
+        args = ', '.join(repr(x) for x in self.v)
+        return 'Vec({})'.format(args)
+
+    def __len__(self):
+        return len(self.v)
+
+    def __getitem__(self, i):
+        return self.v[i]
+
+    def __add__(self, other):
+        # Element-wise addition
+        v = [x + y for x, y in zip(self.v, other.v)]
+        return Vec.fromlist(v)
+
+    def __sub__(self, other):
+        # Element-wise subtraction
+        v = [x - y for x, y in zip(self.v, other.v)]
+        return Vec.fromlist(v)
+
+    def __mul__(self, scalar):
+        # Multiply by scalar
+        v = [x * scalar for x in self.v]
+        return Vec.fromlist(v)
+
+    __rmul__ = __mul__
+
+
+def test():
+    import doctest
+    doctest.testmod()
+
+test()
diff --git a/Tools/demo/beer.py b/Tools/demo/beer.py
new file mode 100755
index 0000000..56eec7b
--- /dev/null
+++ b/Tools/demo/beer.py
@@ -0,0 +1,20 @@
+#! /usr/bin/env python3
+
+# By GvR, demystified after a version by Fredrik Lundh.
+
+import sys
+
+n = 100
+if sys.argv[1:]:
+    n = int(sys.argv[1])
+
+def bottle(n):
+    if n == 0: return "no more bottles of beer"
+    if n == 1: return "one bottle of beer"
+    return str(n) + " bottles of beer"
+
+for i in range(n, 0, -1):
+    print(bottle(i), "on the wall,")
+    print(bottle(i) + ".")
+    print("Take one down, pass it around,")
+    print(bottle(i-1), "on the wall.")
diff --git a/Tools/demo/hanoi.py b/Tools/demo/hanoi.py
new file mode 100644
index 0000000..34a0bba
--- /dev/null
+++ b/Tools/demo/hanoi.py
@@ -0,0 +1,154 @@
+# Animated Towers of Hanoi using Tk with optional bitmap file in
+# background.
+#
+# Usage: tkhanoi [n [bitmapfile]]
+#
+# n is the number of pieces to animate; default is 4, maximum 15.
+#
+# The bitmap file can be any X11 bitmap file (look in
+# /usr/include/X11/bitmaps for samples); it is displayed as the
+# background of the animation.  Default is no bitmap.
+
+# This uses Steen Lumholt's Tk interface
+from tkinter import *
+
+
+# Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c
+# as temporary.  For each move, call report()
+def hanoi(n, a, b, c, report):
+    if n <= 0: return
+    hanoi(n-1, a, c, b, report)
+    report(n, a, b)
+    hanoi(n-1, c, b, a, report)
+
+
+# The graphical interface
+class Tkhanoi:
+
+    # Create our objects
+    def __init__(self, n, bitmap = None):
+        self.n = n
+        self.tk = tk = Tk()
+        self.canvas = c = Canvas(tk)
+        c.pack()
+        width, height = tk.getint(c['width']), tk.getint(c['height'])
+
+        # Add background bitmap
+        if bitmap:
+            self.bitmap = c.create_bitmap(width//2, height//2,
+                                          bitmap=bitmap,
+                                          foreground='blue')
+
+        # Generate pegs
+        pegwidth = 10
+        pegheight = height//2
+        pegdist = width//3
+        x1, y1 = (pegdist-pegwidth)//2, height*1//3
+        x2, y2 = x1+pegwidth, y1+pegheight
+        self.pegs = []
+        p = c.create_rectangle(x1, y1, x2, y2, fill='black')
+        self.pegs.append(p)
+        x1, x2 = x1+pegdist, x2+pegdist
+        p = c.create_rectangle(x1, y1, x2, y2, fill='black')
+        self.pegs.append(p)
+        x1, x2 = x1+pegdist, x2+pegdist
+        p = c.create_rectangle(x1, y1, x2, y2, fill='black')
+        self.pegs.append(p)
+        self.tk.update()
+
+        # Generate pieces
+        pieceheight = pegheight//16
+        maxpiecewidth = pegdist*2//3
+        minpiecewidth = 2*pegwidth
+        self.pegstate = [[], [], []]
+        self.pieces = {}
+        x1, y1 = (pegdist-maxpiecewidth)//2, y2-pieceheight-2
+        x2, y2 = x1+maxpiecewidth, y1+pieceheight
+        dx = (maxpiecewidth-minpiecewidth) // (2*max(1, n-1))
+        for i in range(n, 0, -1):
+            p = c.create_rectangle(x1, y1, x2, y2, fill='red')
+            self.pieces[i] = p
+            self.pegstate[0].append(i)
+            x1, x2 = x1 + dx, x2-dx
+            y1, y2 = y1 - pieceheight-2, y2-pieceheight-2
+            self.tk.update()
+            self.tk.after(25)
+
+    # Run -- never returns
+    def run(self):
+        while 1:
+            hanoi(self.n, 0, 1, 2, self.report)
+            hanoi(self.n, 1, 2, 0, self.report)
+            hanoi(self.n, 2, 0, 1, self.report)
+            hanoi(self.n, 0, 2, 1, self.report)
+            hanoi(self.n, 2, 1, 0, self.report)
+            hanoi(self.n, 1, 0, 2, self.report)
+
+    # Reporting callback for the actual hanoi function
+    def report(self, i, a, b):
+        if self.pegstate[a][-1] != i: raise RuntimeError # Assertion
+        del self.pegstate[a][-1]
+        p = self.pieces[i]
+        c = self.canvas
+
+        # Lift the piece above peg a
+        ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a])
+        while 1:
+            x1, y1, x2, y2 = c.bbox(p)
+            if y2 < ay1: break
+            c.move(p, 0, -1)
+            self.tk.update()
+
+        # Move it towards peg b
+        bx1, by1, bx2, by2 = c.bbox(self.pegs[b])
+        newcenter = (bx1+bx2)//2
+        while 1:
+            x1, y1, x2, y2 = c.bbox(p)
+            center = (x1+x2)//2
+            if center == newcenter: break
+            if center > newcenter: c.move(p, -1, 0)
+            else: c.move(p, 1, 0)
+            self.tk.update()
+
+        # Move it down on top of the previous piece
+        pieceheight = y2-y1
+        newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2
+        while 1:
+            x1, y1, x2, y2 = c.bbox(p)
+            if y2 >= newbottom: break
+            c.move(p, 0, 1)
+            self.tk.update()
+
+        # Update peg state
+        self.pegstate[b].append(i)
+
+
+# Main program
+def main():
+    import sys
+
+    # First argument is number of pegs, default 4
+    if sys.argv[1:]:
+        n = int(sys.argv[1])
+    else:
+        n = 4
+
+    # Second argument is bitmap file, default none
+    if sys.argv[2:]:
+        bitmap = sys.argv[2]
+        # Reverse meaning of leading '@' compared to Tk
+        if bitmap[0] == '@': bitmap = bitmap[1:]
+        else: bitmap = '@' + bitmap
+    else:
+        bitmap = None
+
+    # Create the graphical objects...
+    h = Tkhanoi(n, bitmap)
+
+    # ...and run!
+    h.run()
+
+
+# Call main when run as script
+if __name__ == '__main__':
+    main()
diff --git a/Tools/demo/life.py b/Tools/demo/life.py
new file mode 100755
index 0000000..3cbc053
--- /dev/null
+++ b/Tools/demo/life.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3
+# life.py -- A curses-based version of Conway's Game of Life.
+# Contributed by AMK
+# Mouse support and color by Dafydd Crosby
+#
+# An empty board will be displayed, and the following commands are available:
+#  E : Erase the board
+#  R : Fill the board randomly
+#  S : Step for a single generation
+#  C : Update continuously until a key is struck
+#  Q : Quit
+#  Cursor keys :  Move the cursor around the board
+#  Space or Enter : Toggle the contents of the cursor's position
+#
+# TODO :
+#   Make board updates faster
+#
+
+import random, string, traceback
+import curses
+
+class LifeBoard:
+    """Encapsulates a Life board
+
+    Attributes:
+    X,Y : horizontal and vertical size of the board
+    state : dictionary mapping (x,y) to 0 or 1
+
+    Methods:
+    display(update_board) -- If update_board is true, compute the
+                             next generation.  Then display the state
+                             of the board and refresh the screen.
+    erase() -- clear the entire board
+    makeRandom() -- fill the board randomly
+    set(y,x) -- set the given cell to Live; doesn't refresh the screen
+    toggle(y,x) -- change the given cell from live to dead, or vice
+                   versa, and refresh the screen display
+
+    """
+    def __init__(self, scr, char=ord('*')):
+        """Create a new LifeBoard instance.
+
+        scr -- curses screen object to use for display
+        char -- character used to render live cells (default: '*')
+        """
+        self.state = {}
+        self.scr = scr
+        Y, X = self.scr.getmaxyx()
+        self.X, self.Y = X-2, Y-2-1
+        self.char = char
+        self.scr.clear()
+
+        # Draw a border around the board
+        border_line = '+'+(self.X*'-')+'+'
+        self.scr.addstr(0, 0, border_line)
+        self.scr.addstr(self.Y+1,0, border_line)
+        for y in range(0, self.Y):
+            self.scr.addstr(1+y, 0, '|')
+            self.scr.addstr(1+y, self.X+1, '|')
+        self.scr.refresh()
+
+    def set(self, y, x):
+        """Set a cell to the live state"""
+        if x<0 or self.X<=x or y<0 or self.Y<=y:
+            raise ValueError("Coordinates out of range %i,%i"% (y,x))
+        self.state[x,y] = 1
+
+    def toggle(self, y, x):
+        """Toggle a cell's state between live and dead"""
+        if x<0 or self.X<=x or y<0 or self.Y<=y:
+            raise ValueError("Coordinates out of range %i,%i"% (y,x))
+        if (x,y) in self.state:
+            del self.state[x,y]
+            self.scr.addch(y+1, x+1, ' ')
+        else:
+            self.state[x,y] = 1
+            if curses.has_colors():
+                #Let's pick a random color!
+                self.scr.attrset(curses.color_pair(random.randrange(1,7)))
+            self.scr.addch(y+1, x+1, self.char)
+            self.scr.attrset(0)
+        self.scr.refresh()
+
+    def erase(self):
+        """Clear the entire board and update the board display"""
+        self.state = {}
+        self.display(update_board=False)
+
+    def display(self, update_board=True):
+        """Display the whole board, optionally computing one generation"""
+        M,N = self.X, self.Y
+        if not update_board:
+            for i in range(0, M):
+                for j in range(0, N):
+                    if (i,j) in self.state:
+                        self.scr.addch(j+1, i+1, self.char)
+                    else:
+                        self.scr.addch(j+1, i+1, ' ')
+            self.scr.refresh()
+            return
+
+        d = {}
+        self.boring = 1
+        for i in range(0, M):
+            L = range( max(0, i-1), min(M, i+2) )
+            for j in range(0, N):
+                s = 0
+                live = (i,j) in self.state
+                for k in range( max(0, j-1), min(N, j+2) ):
+                    for l in L:
+                        if (l,k) in self.state:
+                            s += 1
+                s -= live
+                if s == 3:
+                    # Birth
+                    d[i,j] = 1
+                    if curses.has_colors():
+                        #Let's pick a random color!
+                        self.scr.attrset(curses.color_pair(random.randrange(1,7)))
+                    self.scr.addch(j+1, i+1, self.char)
+                    self.scr.attrset(0)
+                    if not live: self.boring = 0
+                elif s == 2 and live: d[i,j] = 1       # Survival
+                elif live:
+                    # Death
+                    self.scr.addch(j+1, i+1, ' ')
+                    self.boring = 0
+        self.state = d
+        self.scr.refresh()
+
+    def makeRandom(self):
+        "Fill the board with a random pattern"
+        self.state = {}
+        for i in range(0, self.X):
+            for j in range(0, self.Y):
+                if random.random() > 0.5:
+                    self.set(j,i)
+
+
+def erase_menu(stdscr, menu_y):
+    "Clear the space where the menu resides"
+    stdscr.move(menu_y, 0)
+    stdscr.clrtoeol()
+    stdscr.move(menu_y+1, 0)
+    stdscr.clrtoeol()
+
+def display_menu(stdscr, menu_y):
+    "Display the menu of possible keystroke commands"
+    erase_menu(stdscr, menu_y)
+
+    # If color, then light the menu up :-)
+    if curses.has_colors():
+        stdscr.attrset(curses.color_pair(1))
+    stdscr.addstr(menu_y, 4,
+                  'Use the cursor keys to move, and space or Enter to toggle a cell.')
+    stdscr.addstr(menu_y+1, 4,
+                  'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit')
+    stdscr.attrset(0)
+
+def keyloop(stdscr):
+    # Clear the screen and display the menu of keys
+    stdscr.clear()
+    stdscr_y, stdscr_x = stdscr.getmaxyx()
+    menu_y = (stdscr_y-3)-1
+    display_menu(stdscr, menu_y)
+
+    # If color, then initialize the color pairs
+    if curses.has_colors():
+        curses.init_pair(1, curses.COLOR_BLUE, 0)
+        curses.init_pair(2, curses.COLOR_CYAN, 0)
+        curses.init_pair(3, curses.COLOR_GREEN, 0)
+        curses.init_pair(4, curses.COLOR_MAGENTA, 0)
+        curses.init_pair(5, curses.COLOR_RED, 0)
+        curses.init_pair(6, curses.COLOR_YELLOW, 0)
+        curses.init_pair(7, curses.COLOR_WHITE, 0)
+
+    # Set up the mask to listen for mouse events
+    curses.mousemask(curses.BUTTON1_CLICKED)
+
+    # Allocate a subwindow for the Life board and create the board object
+    subwin = stdscr.subwin(stdscr_y-3, stdscr_x, 0, 0)
+    board = LifeBoard(subwin, char=ord('*'))
+    board.display(update_board=False)
+
+    # xpos, ypos are the cursor's position
+    xpos, ypos = board.X//2, board.Y//2
+
+    # Main loop:
+    while (1):
+        stdscr.move(1+ypos, 1+xpos)     # Move the cursor
+        c = stdscr.getch()                # Get a keystroke
+        if 0<c<256:
+            c = chr(c)
+            if c in ' \n':
+                board.toggle(ypos, xpos)
+            elif c in 'Cc':
+                erase_menu(stdscr, menu_y)
+                stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously '
+                              'updating the screen.')
+                stdscr.refresh()
+                # Activate nodelay mode; getch() will return -1
+                # if no keystroke is available, instead of waiting.
+                stdscr.nodelay(1)
+                while (1):
+                    c = stdscr.getch()
+                    if c != -1:
+                        break
+                    stdscr.addstr(0,0, '/')
+                    stdscr.refresh()
+                    board.display()
+                    stdscr.addstr(0,0, '+')
+                    stdscr.refresh()
+
+                stdscr.nodelay(0)       # Disable nodelay mode
+                display_menu(stdscr, menu_y)
+
+            elif c in 'Ee':
+                board.erase()
+            elif c in 'Qq':
+                break
+            elif c in 'Rr':
+                board.makeRandom()
+                board.display(update_board=False)
+            elif c in 'Ss':
+                board.display()
+            else: pass                  # Ignore incorrect keys
+        elif c == curses.KEY_UP and ypos>0:            ypos -= 1
+        elif c == curses.KEY_DOWN and ypos<board.Y-1:  ypos += 1
+        elif c == curses.KEY_LEFT and xpos>0:          xpos -= 1
+        elif c == curses.KEY_RIGHT and xpos<board.X-1: xpos += 1
+        elif c == curses.KEY_MOUSE:
+            (mouse_id, mouse_x, mouse_y, mouse_z, button_state) = curses.getmouse()
+            if (mouse_x>0 and mouse_x<board.X+1) and (mouse_y>0 and mouse_y<board.Y+1):
+                xpos = mouse_x - 1
+                ypos = mouse_y - 1
+                board.toggle(ypos, xpos)
+            else:
+                # They've clicked outside the board
+                curses.flash()
+        else:
+            # Ignore incorrect keys
+            pass
+
+
+def main(stdscr):
+    keyloop(stdscr)                    # Enter the main loop
+
+
+if __name__ == '__main__':
+    curses.wrapper(main)
diff --git a/Tools/demo/markov.py b/Tools/demo/markov.py
new file mode 100755
index 0000000..7c08bdb
--- /dev/null
+++ b/Tools/demo/markov.py
@@ -0,0 +1,121 @@
+#! /usr/bin/env python3
+
+class Markov:
+    def __init__(self, histsize, choice):
+        self.histsize = histsize
+        self.choice = choice
+        self.trans = {}
+
+    def add(self, state, next):
+        self.trans.setdefault(state, []).append(next)
+
+    def put(self, seq):
+        n = self.histsize
+        add = self.add
+        add(None, seq[:0])
+        for i in range(len(seq)):
+            add(seq[max(0, i-n):i], seq[i:i+1])
+        add(seq[len(seq)-n:], None)
+
+    def get(self):
+        choice = self.choice
+        trans = self.trans
+        n = self.histsize
+        seq = choice(trans[None])
+        while True:
+            subseq = seq[max(0, len(seq)-n):]
+            options = trans[subseq]
+            next = choice(options)
+            if not next:
+                break
+            seq += next
+        return seq
+
+
+def test():
+    import sys, random, getopt
+    args = sys.argv[1:]
+    try:
+        opts, args = getopt.getopt(args, '0123456789cdwq')
+    except getopt.error:
+        print('Usage: %s [-#] [-cddqw] [file] ...' % sys.argv[0])
+        print('Options:')
+        print('-#: 1-digit history size (default 2)')
+        print('-c: characters (default)')
+        print('-w: words')
+        print('-d: more debugging output')
+        print('-q: no debugging output')
+        print('Input files (default stdin) are split in paragraphs')
+        print('separated blank lines and each paragraph is split')
+        print('in words by whitespace, then reconcatenated with')
+        print('exactly one space separating words.')
+        print('Output consists of paragraphs separated by blank')
+        print('lines, where lines are no longer than 72 characters.')
+        sys.exit(2)
+    histsize = 2
+    do_words = False
+    debug = 1
+    for o, a in opts:
+        if '-0' <= o <= '-9': histsize = int(o[1:])
+        if o == '-c': do_words = False
+        if o == '-d': debug += 1
+        if o == '-q': debug = 0
+        if o == '-w': do_words = True
+    if not args:
+        args = ['-']
+
+    m = Markov(histsize, random.choice)
+    try:
+        for filename in args:
+            if filename == '-':
+                f = sys.stdin
+                if f.isatty():
+                    print('Sorry, need stdin from file')
+                    continue
+            else:
+                f = open(filename, 'r')
+            if debug: print('processing', filename, '...')
+            text = f.read()
+            f.close()
+            paralist = text.split('\n\n')
+            for para in paralist:
+                if debug > 1: print('feeding ...')
+                words = para.split()
+                if words:
+                    if do_words:
+                        data = tuple(words)
+                    else:
+                        data = ' '.join(words)
+                    m.put(data)
+    except KeyboardInterrupt:
+        print('Interrupted -- continue with data read so far')
+    if not m.trans:
+        print('No valid input files')
+        return
+    if debug: print('done.')
+
+    if debug > 1:
+        for key in m.trans.keys():
+            if key is None or len(key) < histsize:
+                print(repr(key), m.trans[key])
+        if histsize == 0: print(repr(''), m.trans[''])
+        print()
+    while True:
+        data = m.get()
+        if do_words:
+            words = data
+        else:
+            words = data.split()
+        n = 0
+        limit = 72
+        for w in words:
+            if n + len(w) > limit:
+                print()
+                n = 0
+            print(w, end=' ')
+            n += len(w) + 1
+        print()
+        print()
+
+if __name__ == "__main__":
+    test()
diff --git a/Tools/demo/mcast.py b/Tools/demo/mcast.py
new file mode 100755
index 0000000..6ce7c6d
--- /dev/null
+++ b/Tools/demo/mcast.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+#
+# Send/receive UDP multicast packets.
+# Requires that your OS kernel supports IP multicast.
+#
+# Usage:
+#   mcast -s (sender, IPv4)
+#   mcast -s -6 (sender, IPv6)
+#   mcast    (receivers, IPv4)
+#   mcast  -6  (receivers, IPv6)
+
+MYPORT = 8123
+MYGROUP_4 = '225.0.0.250'
+MYGROUP_6 = 'ff15:7079:7468:6f6e:6465:6d6f:6d63:6173'
+MYTTL = 1 # Increase to reach other networks
+
+import time
+import struct
+import socket
+import sys
+
+def main():
+    group = MYGROUP_6 if "-6" in sys.argv[1:] else MYGROUP_4
+
+    if "-s" in sys.argv[1:]:
+        sender(group)
+    else:
+        receiver(group)
+
+
+def sender(group):
+    addrinfo = socket.getaddrinfo(group, None)[0]
+
+    s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
+
+    # Set Time-to-live (optional)
+    ttl_bin = struct.pack('@i', MYTTL)
+    if addrinfo[0] == socket.AF_INET: # IPv4
+        s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl_bin)
+    else:
+        s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl_bin)
+
+    while True:
+        data = repr(time.time()).encode('utf-8') + b'\0'
+        s.sendto(data, (addrinfo[4][0], MYPORT))
+        time.sleep(1)
+
+
+def receiver(group):
+    # Look up multicast group address in name server and find out IP version
+    addrinfo = socket.getaddrinfo(group, None)[0]
+
+    # Create a socket
+    s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
+
+    # Allow multiple copies of this program on one machine
+    # (not strictly needed)
+    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+    # Bind it to the port
+    s.bind(('', MYPORT))
+
+    group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0])
+    # Join group
+    if addrinfo[0] == socket.AF_INET: # IPv4
+        mreq = group_bin + struct.pack('=I', socket.INADDR_ANY)
+        s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+    else:
+        mreq = group_bin + struct.pack('@I', 0)
+        s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
+
+    # Loop, printing any data we receive
+    while True:
+        data, sender = s.recvfrom(1500)
+        while data[-1:] == '\0': data = data[:-1] # Strip trailing \0's
+        print(str(sender) + '  ' + repr(data))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/Tools/demo/queens.py b/Tools/demo/queens.py
new file mode 100755
index 0000000..ffd4bea
--- /dev/null
+++ b/Tools/demo/queens.py
@@ -0,0 +1,85 @@
+#! /usr/bin/env python3
+
+"""N queens problem.
+
+The (well-known) problem is due to Niklaus Wirth.
+
+This solution is inspired by Dijkstra (Structured Programming).  It is
+a classic recursive backtracking approach.
+
+"""
+
+N = 8                                   # Default; command line overrides
+
+class Queens:
+
+    def __init__(self, n=N):
+        self.n = n
+        self.reset()
+
+    def reset(self):
+        n = self.n
+        self.y = [None] * n             # Where is the queen in column x
+        self.row = [0] * n              # Is row[y] safe?
+        self.up = [0] * (2*n-1)         # Is upward diagonal[x-y] safe?
+        self.down = [0] * (2*n-1)       # Is downward diagonal[x+y] safe?
+        self.nfound = 0                 # Instrumentation
+
+    def solve(self, x=0):               # Recursive solver
+        for y in range(self.n):
+            if self.safe(x, y):
+                self.place(x, y)
+                if x+1 == self.n:
+                    self.display()
+                else:
+                    self.solve(x+1)
+                self.remove(x, y)
+
+    def safe(self, x, y):
+        return not self.row[y] and not self.up[x-y] and not self.down[x+y]
+
+    def place(self, x, y):
+        self.y[x] = y
+        self.row[y] = 1
+        self.up[x-y] = 1
+        self.down[x+y] = 1
+
+    def remove(self, x, y):
+        self.y[x] = None
+        self.row[y] = 0
+        self.up[x-y] = 0
+        self.down[x+y] = 0
+
+    silent = 0                          # If true, count solutions only
+
+    def display(self):
+        self.nfound = self.nfound + 1
+        if self.silent:
+            return
+        print('+-' + '--'*self.n + '+')
+        for y in range(self.n-1, -1, -1):
+            print('|', end=' ')
+            for x in range(self.n):
+                if self.y[x] == y:
+                    print("Q", end=' ')
+                else:
+                    print(".", end=' ')
+            print('|')
+        print('+-' + '--'*self.n + '+')
+
+def main():
+    import sys
+    silent = 0
+    n = N
+    if sys.argv[1:2] == ['-n']:
+        silent = 1
+        del sys.argv[1]
+    if sys.argv[1:]:
+        n = int(sys.argv[1])
+    q = Queens(n)
+    q.silent = silent
+    q.solve()
+    print("Found", q.nfound, "solutions.")
+
+if __name__ == "__main__":
+    main()
diff --git a/Tools/demo/rpython.py b/Tools/demo/rpython.py
new file mode 100755
index 0000000..e965720
--- /dev/null
+++ b/Tools/demo/rpython.py
@@ -0,0 +1,36 @@
+#! /usr/bin/env python3
+
+# Remote python client.
+# Execute Python commands remotely and send output back.
+
+import sys
+from socket import socket, AF_INET, SOCK_STREAM, SHUT_WR
+
+PORT = 4127
+BUFSIZE = 1024
+
+def main():
+    if len(sys.argv) < 3:
+        print("usage: rpython host command")
+        sys.exit(2)
+    host = sys.argv[1]
+    port = PORT
+    i = host.find(':')
+    if i >= 0:
+        port = int(port[i+1:])
+        host = host[:i]
+    command = ' '.join(sys.argv[2:])
+    s = socket(AF_INET, SOCK_STREAM)
+    s.connect((host, port))
+    s.send(command.encode())
+    s.shutdown(SHUT_WR)
+    reply = b''
+    while True:
+        data = s.recv(BUFSIZE)
+        if not data:
+            break
+        reply += data
+    print(reply.decode(), end=' ')
+    s.close()
+
+main()
diff --git a/Tools/demo/rpythond.py b/Tools/demo/rpythond.py
new file mode 100755
index 0000000..fe24b3d
--- /dev/null
+++ b/Tools/demo/rpythond.py
@@ -0,0 +1,55 @@
+#! /usr/bin/env python3
+
+# Remote python server.
+# Execute Python commands remotely and send output back.
+# WARNING: This version has a gaping security hole -- it accepts requests
+# from any host on the Internet!
+
+import sys
+from socket import socket, AF_INET, SOCK_STREAM
+import io
+import traceback
+
+PORT = 4127
+BUFSIZE = 1024
+
+def main():
+    if len(sys.argv) > 1:
+        port = int(sys.argv[1])
+    else:
+        port = PORT
+    s = socket(AF_INET, SOCK_STREAM)
+    s.bind(('', port))
+    s.listen(1)
+    while True:
+        conn, (remotehost, remoteport) = s.accept()
+        print('connection from', remotehost, remoteport)
+        request = b''
+        while 1:
+            data = conn.recv(BUFSIZE)
+            if not data:
+                break
+            request += data
+        reply = execute(request.decode())
+        conn.send(reply.encode())
+        conn.close()
+
+def execute(request):
+    stdout = sys.stdout
+    stderr = sys.stderr
+    sys.stdout = sys.stderr = fakefile = io.StringIO()
+    try:
+        try:
+            exec(request, {}, {})
+        except:
+            print()
+            traceback.print_exc(100)
+    finally:
+        sys.stderr = stderr
+        sys.stdout = stdout
+    return fakefile.getvalue()
+
+try:
+    main()
+except KeyboardInterrupt:
+    pass
diff --git a/Tools/demo/sortvisu.py b/Tools/demo/sortvisu.py
new file mode 100644
index 0000000..4173121
--- /dev/null
+++ b/Tools/demo/sortvisu.py
@@ -0,0 +1,636 @@
+#! /usr/bin/env python3
+
+"""Sorting algorithms visualizer using Tkinter.
+
+This module is comprised of three ``components'':
+
+- an array visualizer with methods that implement basic sorting
+operations (compare, swap) as well as methods for ``annotating'' the
+sorting algorithm (e.g. to show the pivot element);
+
+- a number of sorting algorithms (currently quicksort, insertion sort,
+selection sort and bubble sort, as well as a randomization function),
+all using the array visualizer for its basic operations and with calls
+to its annotation methods;
+
+- and a ``driver'' class which can be used as a Grail applet or as a
+stand-alone application.
+
+"""
+
+from tkinter import *
+import random
+
+
+XGRID = 10
+YGRID = 10
+WIDTH = 6
+
+
+class Array:
+
+    class Cancelled(BaseException):
+        pass
+
+    def __init__(self, master, data=None):
+        self.master = master
+        self.frame = Frame(self.master)
+        self.frame.pack(fill=X)
+        self.label = Label(self.frame)
+        self.label.pack()
+        self.canvas = Canvas(self.frame)
+        self.canvas.pack()
+        self.report = Label(self.frame)
+        self.report.pack()
+        self.left = self.canvas.create_line(0, 0, 0, 0)
+        self.right = self.canvas.create_line(0, 0, 0, 0)
+        self.pivot = self.canvas.create_line(0, 0, 0, 0)
+        self.items = []
+        self.size = self.maxvalue = 0
+        if data:
+            self.setdata(data)
+
+    def setdata(self, data):
+        olditems = self.items
+        self.items = []
+        for item in olditems:
+            item.delete()
+        self.size = len(data)
+        self.maxvalue = max(data)
+        self.canvas.config(width=(self.size+1)*XGRID,
+                           height=(self.maxvalue+1)*YGRID)
+        for i in range(self.size):
+            self.items.append(ArrayItem(self, i, data[i]))
+        self.reset("Sort demo, size %d" % self.size)
+
+    speed = "normal"
+
+    def setspeed(self, speed):
+        self.speed = speed
+
+    def destroy(self):
+        self.frame.destroy()
+
+    in_mainloop = 0
+    stop_mainloop = 0
+
+    def cancel(self):
+        self.stop_mainloop = 1
+        if self.in_mainloop:
+            self.master.quit()
+
+    def step(self):
+        if self.in_mainloop:
+            self.master.quit()
+
+    def wait(self, msecs):
+        if self.speed == "fastest":
+            msecs = 0
+        elif self.speed == "fast":
+            msecs = msecs//10
+        elif self.speed == "single-step":
+            msecs = 1000000000
+        if not self.stop_mainloop:
+            self.master.update()
+            id = self.master.after(msecs, self.master.quit)
+            self.in_mainloop = 1
+            self.master.mainloop()
+            self.master.after_cancel(id)
+            self.in_mainloop = 0
+        if self.stop_mainloop:
+            self.stop_mainloop = 0
+            self.message("Cancelled")
+            raise Array.Cancelled
+
+    def getsize(self):
+        return self.size
+
+    def show_partition(self, first, last):
+        for i in range(self.size):
+            item = self.items[i]
+            if first <= i < last:
+                self.canvas.itemconfig(item, fill='red')
+            else:
+                self.canvas.itemconfig(item, fill='orange')
+        self.hide_left_right_pivot()
+
+    def hide_partition(self):
+        for i in range(self.size):
+            item = self.items[i]
+            self.canvas.itemconfig(item, fill='red')
+        self.hide_left_right_pivot()
+
+    def show_left(self, left):
+        if not 0 <= left < self.size:
+            self.hide_left()
+            return
+        x1, y1, x2, y2 = self.items[left].position()
+##      top, bot = HIRO
+        self.canvas.coords(self.left, (x1 - 2, 0, x1 - 2, 9999))
+        self.master.update()
+
+    def show_right(self, right):
+        if not 0 <= right < self.size:
+            self.hide_right()
+            return
+        x1, y1, x2, y2 = self.items[right].position()
+        self.canvas.coords(self.right, (x2 + 2, 0, x2 + 2, 9999))
+        self.master.update()
+
+    def hide_left_right_pivot(self):
+        self.hide_left()
+        self.hide_right()
+        self.hide_pivot()
+
+    def hide_left(self):
+        self.canvas.coords(self.left, (0, 0, 0, 0))
+
+    def hide_right(self):
+        self.canvas.coords(self.right, (0, 0, 0, 0))
+
+    def show_pivot(self, pivot):
+        x1, y1, x2, y2 = self.items[pivot].position()
+        self.canvas.coords(self.pivot, (0, y1 - 2, 9999, y1 - 2))
+
+    def hide_pivot(self):
+        self.canvas.coords(self.pivot, (0, 0, 0, 0))
+
+    def swap(self, i, j):
+        if i == j: return
+        self.countswap()
+        item = self.items[i]
+        other = self.items[j]
+        self.items[i], self.items[j] = other, item
+        item.swapwith(other)
+
+    def compare(self, i, j):
+        self.countcompare()
+        item = self.items[i]
+        other = self.items[j]
+        return item.compareto(other)
+
+    def reset(self, msg):
+        self.ncompares = 0
+        self.nswaps = 0
+        self.message(msg)
+        self.updatereport()
+        self.hide_partition()
+
+    def message(self, msg):
+        self.label.config(text=msg)
+
+    def countswap(self):
+        self.nswaps = self.nswaps + 1
+        self.updatereport()
+
+    def countcompare(self):
+        self.ncompares = self.ncompares + 1
+        self.updatereport()
+
+    def updatereport(self):
+        text = "%d cmps, %d swaps" % (self.ncompares, self.nswaps)
+        self.report.config(text=text)
+
+
+class ArrayItem:
+
+    def __init__(self, array, index, value):
+        self.array = array
+        self.index = index
+        self.value = value
+        self.canvas = array.canvas
+        x1, y1, x2, y2 = self.position()
+        self.item_id = array.canvas.create_rectangle(x1, y1, x2, y2,
+            fill='red', outline='black', width=1)
+        self.canvas.tag_bind(self.item_id, '<Button-1>', self.mouse_down)
+        self.canvas.tag_bind(self.item_id, '<Button1-Motion>', self.mouse_move)
+        self.canvas.tag_bind(self.item_id, '<ButtonRelease-1>', self.mouse_up)
+
+    def delete(self):
+        item_id = self.item_id
+        self.array = None
+        self.item_id = None
+        self.canvas.delete(item_id)
+
+    def mouse_down(self, event):
+        self.lastx = event.x
+        self.lasty = event.y
+        self.origx = event.x
+        self.origy = event.y
+        self.canvas.tag_raise(self.item_id)
+
+    def mouse_move(self, event):
+        self.canvas.move(self.item_id,
+                         event.x - self.lastx, event.y - self.lasty)
+        self.lastx = event.x
+        self.lasty = event.y
+
+    def mouse_up(self, event):
+        i = self.nearestindex(event.x)
+        if i >= self.array.getsize():
+            i = self.array.getsize() - 1
+        if i < 0:
+            i = 0
+        other = self.array.items[i]
+        here = self.index
+        self.array.items[here], self.array.items[i] = other, self
+        self.index = i
+        x1, y1, x2, y2 = self.position()
+        self.canvas.coords(self.item_id, (x1, y1, x2, y2))
+        other.setindex(here)
+
+    def setindex(self, index):
+        nsteps = steps(self.index, index)
+        if not nsteps: return
+        if self.array.speed == "fastest":
+            nsteps = 0
+        oldpts = self.position()
+        self.index = index
+        newpts = self.position()
+        trajectory = interpolate(oldpts, newpts, nsteps)
+        self.canvas.tag_raise(self.item_id)
+        for pts in trajectory:
+            self.canvas.coords(self.item_id, pts)
+            self.array.wait(50)
+
+    def swapwith(self, other):
+        nsteps = steps(self.index, other.index)
+        if not nsteps: return
+        if self.array.speed == "fastest":
+            nsteps = 0
+        myoldpts = self.position()
+        otheroldpts = other.position()
+        self.index, other.index = other.index, self.index
+        mynewpts = self.position()
+        othernewpts = other.position()
+        myfill = self.canvas.itemcget(self.item_id, 'fill')
+        otherfill = self.canvas.itemcget(other.item_id, 'fill')
+        self.canvas.itemconfig(self.item_id, fill='green')
+        self.canvas.itemconfig(other.item_id, fill='yellow')
+        self.array.master.update()
+        if self.array.speed == "single-step":
+            self.canvas.coords(self.item_id, mynewpts)
+            self.canvas.coords(other.item_id, othernewpts)
+            self.array.master.update()
+            self.canvas.itemconfig(self.item_id, fill=myfill)
+            self.canvas.itemconfig(other.item_id, fill=otherfill)
+            self.array.wait(0)
+            return
+        mytrajectory = interpolate(myoldpts, mynewpts, nsteps)
+        othertrajectory = interpolate(otheroldpts, othernewpts, nsteps)
+        if self.value > other.value:
+            self.canvas.tag_raise(self.item_id)
+            self.canvas.tag_raise(other.item_id)
+        else:
+            self.canvas.tag_raise(other.item_id)
+            self.canvas.tag_raise(self.item_id)
+        try:
+            for i in range(len(mytrajectory)):
+                mypts = mytrajectory[i]
+                otherpts = othertrajectory[i]
+                self.canvas.coords(self.item_id, mypts)
+                self.canvas.coords(other.item_id, otherpts)
+                self.array.wait(50)
+        finally:
+            mypts = mytrajectory[-1]
+            otherpts = othertrajectory[-1]
+            self.canvas.coords(self.item_id, mypts)
+            self.canvas.coords(other.item_id, otherpts)
+            self.canvas.itemconfig(self.item_id, fill=myfill)
+            self.canvas.itemconfig(other.item_id, fill=otherfill)
+
+    def compareto(self, other):
+        myfill = self.canvas.itemcget(self.item_id, 'fill')
+        otherfill = self.canvas.itemcget(other.item_id, 'fill')
+        if self.value < other.value:
+            myflash = 'white'
+            otherflash = 'black'
+            outcome = -1
+        elif self.value > other.value:
+            myflash = 'black'
+            otherflash = 'white'
+            outcome = 1
+        else:
+            myflash = otherflash = 'grey'
+            outcome = 0
+        try:
+            self.canvas.itemconfig(self.item_id, fill=myflash)
+            self.canvas.itemconfig(other.item_id, fill=otherflash)
+            self.array.wait(500)
+        finally:
+            self.canvas.itemconfig(self.item_id, fill=myfill)
+            self.canvas.itemconfig(other.item_id, fill=otherfill)
+        return outcome
+
+    def position(self):
+        x1 = (self.index+1)*XGRID - WIDTH//2
+        x2 = x1+WIDTH
+        y2 = (self.array.maxvalue+1)*YGRID
+        y1 = y2 - (self.value)*YGRID
+        return x1, y1, x2, y2
+
+    def nearestindex(self, x):
+        return int(round(float(x)/XGRID)) - 1
+
+
+# Subroutines that don't need an object
+
+def steps(here, there):
+    nsteps = abs(here - there)
+    if nsteps <= 3:
+        nsteps = nsteps * 3
+    elif nsteps <= 5:
+        nsteps = nsteps * 2
+    elif nsteps > 10:
+        nsteps = 10
+    return nsteps
+
+def interpolate(oldpts, newpts, n):
+    if len(oldpts) != len(newpts):
+        raise ValueError("can't interpolate arrays of different length")
+    pts = [0]*len(oldpts)
+    res = [tuple(oldpts)]
+    for i in range(1, n):
+        for k in range(len(pts)):
+            pts[k] = oldpts[k] + (newpts[k] - oldpts[k])*i//n
+        res.append(tuple(pts))
+    res.append(tuple(newpts))
+    return res
+
+
+# Various (un)sorting algorithms
+
+def uniform(array):
+    size = array.getsize()
+    array.setdata([(size+1)//2] * size)
+    array.reset("Uniform data, size %d" % size)
+
+def distinct(array):
+    size = array.getsize()
+    array.setdata(range(1, size+1))
+    array.reset("Distinct data, size %d" % size)
+
+def randomize(array):
+    array.reset("Randomizing")
+    n = array.getsize()
+    for i in range(n):
+        j = random.randint(0, n-1)
+        array.swap(i, j)
+    array.message("Randomized")
+
+def insertionsort(array):
+    size = array.getsize()
+    array.reset("Insertion sort")
+    for i in range(1, size):
+        j = i-1
+        while j >= 0:
+            if array.compare(j, j+1) <= 0:
+                break
+            array.swap(j, j+1)
+            j = j-1
+    array.message("Sorted")
+
+def selectionsort(array):
+    size = array.getsize()
+    array.reset("Selection sort")
+    try:
+        for i in range(size):
+            array.show_partition(i, size)
+            for j in range(i+1, size):
+                if array.compare(i, j) > 0:
+                    array.swap(i, j)
+        array.message("Sorted")
+    finally:
+        array.hide_partition()
+
+def bubblesort(array):
+    size = array.getsize()
+    array.reset("Bubble sort")
+    for i in range(size):
+        for j in range(1, size):
+            if array.compare(j-1, j) > 0:
+                array.swap(j-1, j)
+    array.message("Sorted")
+
+def quicksort(array):
+    size = array.getsize()
+    array.reset("Quicksort")
+    try:
+        stack = [(0, size)]
+        while stack:
+            first, last = stack[-1]
+            del stack[-1]
+            array.show_partition(first, last)
+            if last-first < 5:
+                array.message("Insertion sort")
+                for i in range(first+1, last):
+                    j = i-1
+                    while j >= first:
+                        if array.compare(j, j+1) <= 0:
+                            break
+                        array.swap(j, j+1)
+                        j = j-1
+                continue
+            array.message("Choosing pivot")
+            j, i, k = first, (first+last) // 2, last-1
+            if array.compare(k, i) < 0:
+                array.swap(k, i)
+            if array.compare(k, j) < 0:
+                array.swap(k, j)
+            if array.compare(j, i) < 0:
+                array.swap(j, i)
+            pivot = j
+            array.show_pivot(pivot)
+            array.message("Pivot at left of partition")
+            array.wait(1000)
+            left = first
+            right = last
+            while 1:
+                array.message("Sweep right pointer")
+                right = right-1
+                array.show_right(right)
+                while right > first and array.compare(right, pivot) >= 0:
+                    right = right-1
+                    array.show_right(right)
+                array.message("Sweep left pointer")
+                left = left+1
+                array.show_left(left)
+                while left < last and array.compare(left, pivot) <= 0:
+                    left = left+1
+                    array.show_left(left)
+                if left > right:
+                    array.message("End of partition")
+                    break
+                array.message("Swap items")
+                array.swap(left, right)
+            array.message("Swap pivot back")
+            array.swap(pivot, right)
+            n1 = right-first
+            n2 = last-left
+            if n1 > 1: stack.append((first, right))
+            if n2 > 1: stack.append((left, last))
+        array.message("Sorted")
+    finally:
+        array.hide_partition()
+
+def demosort(array):
+    while 1:
+        for alg in [quicksort, insertionsort, selectionsort, bubblesort]:
+            randomize(array)
+            alg(array)
+
+
+# Sort demo class -- usable as a Grail applet
+
+class SortDemo:
+
+    def __init__(self, master, size=15):
+        self.master = master
+        self.size = size
+        self.busy = 0
+        self.array = Array(self.master)
+
+        self.botframe = Frame(master)
+        self.botframe.pack(side=BOTTOM)
+        self.botleftframe = Frame(self.botframe)
+        self.botleftframe.pack(side=LEFT, fill=Y)
+        self.botrightframe = Frame(self.botframe)
+        self.botrightframe.pack(side=RIGHT, fill=Y)
+
+        self.b_qsort = Button(self.botleftframe,
+                              text="Quicksort", command=self.c_qsort)
+        self.b_qsort.pack(fill=X)
+        self.b_isort = Button(self.botleftframe,
+                              text="Insertion sort", command=self.c_isort)
+        self.b_isort.pack(fill=X)
+        self.b_ssort = Button(self.botleftframe,
+                              text="Selection sort", command=self.c_ssort)
+        self.b_ssort.pack(fill=X)
+        self.b_bsort = Button(self.botleftframe,
+                              text="Bubble sort", command=self.c_bsort)
+        self.b_bsort.pack(fill=X)
+
+        # Terrible hack to overcome limitation of OptionMenu...
+        class MyIntVar(IntVar):
+            def __init__(self, master, demo):
+                self.demo = demo
+                IntVar.__init__(self, master)
+            def set(self, value):
+                IntVar.set(self, value)
+                if str(value) != '0':
+                    self.demo.resize(value)
+
+        self.v_size = MyIntVar(self.master, self)
+        self.v_size.set(size)
+        sizes = [1, 2, 3, 4] + list(range(5, 55, 5))
+        if self.size not in sizes:
+            sizes.append(self.size)
+            sizes.sort()
+        self.m_size = OptionMenu(self.botleftframe, self.v_size, *sizes)
+        self.m_size.pack(fill=X)
+
+        self.v_speed = StringVar(self.master)
+        self.v_speed.set("normal")
+        self.m_speed = OptionMenu(self.botleftframe, self.v_speed,
+                                  "single-step", "normal", "fast", "fastest")
+        self.m_speed.pack(fill=X)
+
+        self.b_step = Button(self.botleftframe,
+                             text="Step", command=self.c_step)
+        self.b_step.pack(fill=X)
+
+        self.b_randomize = Button(self.botrightframe,
+                                  text="Randomize", command=self.c_randomize)
+        self.b_randomize.pack(fill=X)
+        self.b_uniform = Button(self.botrightframe,
+                                  text="Uniform", command=self.c_uniform)
+        self.b_uniform.pack(fill=X)
+        self.b_distinct = Button(self.botrightframe,
+                                  text="Distinct", command=self.c_distinct)
+        self.b_distinct.pack(fill=X)
+        self.b_demo = Button(self.botrightframe,
+                             text="Demo", command=self.c_demo)
+        self.b_demo.pack(fill=X)
+        self.b_cancel = Button(self.botrightframe,
+                               text="Cancel", command=self.c_cancel)
+        self.b_cancel.pack(fill=X)
+        self.b_cancel.config(state=DISABLED)
+        self.b_quit = Button(self.botrightframe,
+                             text="Quit", command=self.c_quit)
+        self.b_quit.pack(fill=X)
+
+    def resize(self, newsize):
+        if self.busy:
+            self.master.bell()
+            return
+        self.size = newsize
+        self.array.setdata(range(1, self.size+1))
+
+    def c_qsort(self):
+        self.run(quicksort)
+
+    def c_isort(self):
+        self.run(insertionsort)
+
+    def c_ssort(self):
+        self.run(selectionsort)
+
+    def c_bsort(self):
+        self.run(bubblesort)
+
+    def c_demo(self):
+        self.run(demosort)
+
+    def c_randomize(self):
+        self.run(randomize)
+
+    def c_uniform(self):
+        self.run(uniform)
+
+    def c_distinct(self):
+        self.run(distinct)
+
+    def run(self, func):
+        if self.busy:
+            self.master.bell()
+            return
+        self.busy = 1
+        self.array.setspeed(self.v_speed.get())
+        self.b_cancel.config(state=NORMAL)
+        try:
+            func(self.array)
+        except Array.Cancelled:
+            pass
+        self.b_cancel.config(state=DISABLED)
+        self.busy = 0
+
+    def c_cancel(self):
+        if not self.busy:
+            self.master.bell()
+            return
+        self.array.cancel()
+
+    def c_step(self):
+        if not self.busy:
+            self.master.bell()
+            return
+        self.v_speed.set("single-step")
+        self.array.setspeed("single-step")
+        self.array.step()
+
+    def c_quit(self):
+        if self.busy:
+            self.array.cancel()
+        self.master.after_idle(self.master.quit)
+
+
+# Main program -- for stand-alone operation outside Grail
+
+def main():
+    root = Tk()
+    demo = SortDemo(root)
+    root.protocol('WM_DELETE_WINDOW', demo.c_quit)
+    root.mainloop()
+
+if __name__ == '__main__':
+    main()
diff --git a/Tools/demo/ss1.py b/Tools/demo/ss1.py
new file mode 100644
index 0000000..8b07489
--- /dev/null
+++ b/Tools/demo/ss1.py
@@ -0,0 +1,842 @@
+"""SS1 -- a spreadsheet."""
+
+import os
+import re
+import sys
+import html
+from xml.parsers import expat
+
+LEFT, CENTER, RIGHT = "LEFT", "CENTER", "RIGHT"
+
+def ljust(x, n):
+    return x.ljust(n)
+def center(x, n):
+    return x.center(n)
+def rjust(x, n):
+    return x.rjust(n)
+align2action = {LEFT: ljust, CENTER: center, RIGHT: rjust}
+
+align2xml = {LEFT: "left", CENTER: "center", RIGHT: "right"}
+xml2align = {"left": LEFT, "center": CENTER, "right": RIGHT}
+
+align2anchor = {LEFT: "w", CENTER: "center", RIGHT: "e"}
+
+def sum(seq):
+    total = 0
+    for x in seq:
+        if x is not None:
+            total += x
+    return total
+
+class Sheet:
+
+    def __init__(self):
+        self.cells = {} # {(x, y): cell, ...}
+        self.ns = dict(
+            cell = self.cellvalue,
+            cells = self.multicellvalue,
+            sum = sum,
+        )
+
+    def cellvalue(self, x, y):
+        cell = self.getcell(x, y)
+        if hasattr(cell, 'recalc'):
+            return cell.recalc(self.ns)
+        else:
+            return cell
+
+    def multicellvalue(self, x1, y1, x2, y2):
+        if x1 > x2:
+            x1, x2 = x2, x1
+        if y1 > y2:
+            y1, y2 = y2, y1
+        seq = []
+        for y in range(y1, y2+1):
+            for x in range(x1, x2+1):
+                seq.append(self.cellvalue(x, y))
+        return seq
+
+    def getcell(self, x, y):
+        return self.cells.get((x, y))
+
+    def setcell(self, x, y, cell):
+        assert x > 0 and y > 0
+        assert isinstance(cell, BaseCell)
+        self.cells[x, y] = cell
+
+    def clearcell(self, x, y):
+        try:
+            del self.cells[x, y]
+        except KeyError:
+            pass
+
+    def clearcells(self, x1, y1, x2, y2):
+        for xy in self.selectcells(x1, y1, x2, y2):
+            del self.cells[xy]
+
+    def clearrows(self, y1, y2):
+        self.clearcells(0, y1, sys.maxint, y2)
+
+    def clearcolumns(self, x1, x2):
+        self.clearcells(x1, 0, x2, sys.maxint)
+
+    def selectcells(self, x1, y1, x2, y2):
+        if x1 > x2:
+            x1, x2 = x2, x1
+        if y1 > y2:
+            y1, y2 = y2, y1
+        return [(x, y) for x, y in self.cells
+                if x1 <= x <= x2 and y1 <= y <= y2]
+
+    def movecells(self, x1, y1, x2, y2, dx, dy):
+        if dx == 0 and dy == 0:
+            return
+        if x1 > x2:
+            x1, x2 = x2, x1
+        if y1 > y2:
+            y1, y2 = y2, y1
+        assert x1+dx > 0 and y1+dy > 0
+        new = {}
+        for x, y in self.cells:
+            cell = self.cells[x, y]
+            if hasattr(cell, 'renumber'):
+                cell = cell.renumber(x1, y1, x2, y2, dx, dy)
+            if x1 <= x <= x2 and y1 <= y <= y2:
+                x += dx
+                y += dy
+            new[x, y] = cell
+        self.cells = new
+
+    def insertrows(self, y, n):
+        assert n > 0
+        self.movecells(0, y, sys.maxint, sys.maxint, 0, n)
+
+    def deleterows(self, y1, y2):
+        if y1 > y2:
+            y1, y2 = y2, y1
+        self.clearrows(y1, y2)
+        self.movecells(0, y2+1, sys.maxint, sys.maxint, 0, y1-y2-1)
+
+    def insertcolumns(self, x, n):
+        assert n > 0
+        self.movecells(x, 0, sys.maxint, sys.maxint, n, 0)
+
+    def deletecolumns(self, x1, x2):
+        if x1 > x2:
+            x1, x2 = x2, x1
+        self.clearcells(x1, x2)
+        self.movecells(x2+1, 0, sys.maxint, sys.maxint, x1-x2-1, 0)
+
+    def getsize(self):
+        maxx = maxy = 0
+        for x, y in self.cells:
+            maxx = max(maxx, x)
+            maxy = max(maxy, y)
+        return maxx, maxy
+
+    def reset(self):
+        for cell in self.cells.values():
+            if hasattr(cell, 'reset'):
+                cell.reset()
+
+    def recalc(self):
+        self.reset()
+        for cell in self.cells.values():
+            if hasattr(cell, 'recalc'):
+                cell.recalc(self.ns)
+
+    def display(self):
+        maxx, maxy = self.getsize()
+        width, height = maxx+1, maxy+1
+        colwidth = [1] * width
+        full = {}
+        # Add column heading labels in row 0
+        for x in range(1, width):
+            full[x, 0] = text, alignment = colnum2name(x), RIGHT
+            colwidth[x] = max(colwidth[x], len(text))
+        # Add row labels in column 0
+        for y in range(1, height):
+            full[0, y] = text, alignment = str(y), RIGHT
+            colwidth[0] = max(colwidth[0], len(text))
+        # Add sheet cells in columns with x>0 and y>0
+        for (x, y), cell in self.cells.items():
+            if x <= 0 or y <= 0:
+                continue
+            if hasattr(cell, 'recalc'):
+                cell.recalc(self.ns)
+            if hasattr(cell, 'format'):
+                text, alignment = cell.format()
+                assert isinstance(text, str)
+                assert alignment in (LEFT, CENTER, RIGHT)
+            else:
+                text = str(cell)
+                if isinstance(cell, str):
+                    alignment = LEFT
+                else:
+                    alignment = RIGHT
+            full[x, y] = (text, alignment)
+            colwidth[x] = max(colwidth[x], len(text))
+        # Calculate the horizontal separator line (dashes and dots)
+        sep = ""
+        for x in range(width):
+            if sep:
+                sep += "+"
+            sep += "-"*colwidth[x]
+        # Now print The full grid
+        for y in range(height):
+            line = ""
+            for x in range(width):
+                text, alignment = full.get((x, y)) or ("", LEFT)
+                text = align2action[alignment](text, colwidth[x])
+                if line:
+                    line += '|'
+                line += text
+            print(line)
+            if y == 0:
+                print(sep)
+
+    def xml(self):
+        out = ['<spreadsheet>']
+        for (x, y), cell in self.cells.items():
+            if hasattr(cell, 'xml'):
+                cellxml = cell.xml()
+            else:
+                cellxml = '<value>%s</value>' % html.escape(cell)
+            out.append('<cell row="%s" col="%s">\n  %s\n</cell>' %
+                       (y, x, cellxml))
+        out.append('</spreadsheet>')
+        return '\n'.join(out)
+
+    def save(self, filename):
+        text = self.xml()
+        f = open(filename, "w")
+        f.write(text)
+        if text and not text.endswith('\n'):
+            f.write('\n')
+        f.close()
+
+    def load(self, filename):
+        f = open(filename, 'rb')
+        SheetParser(self).parsefile(f)
+        f.close()
+
+class SheetParser:
+
+    def __init__(self, sheet):
+        self.sheet = sheet
+
+    def parsefile(self, f):
+        parser = expat.ParserCreate()
+        parser.StartElementHandler = self.startelement
+        parser.EndElementHandler = self.endelement
+        parser.CharacterDataHandler = self.data
+        parser.ParseFile(f)
+
+    def startelement(self, tag, attrs):
+        method = getattr(self, 'start_'+tag, None)
+        if method:
+            for key, value in attrs.items():
+                attrs[key] = str(value) # XXX Convert Unicode to 8-bit
+            method(attrs)
+        self.texts = []
+
+    def data(self, text):
+        text = str(text) # XXX Convert Unicode to 8-bit
+        self.texts.append(text)
+
+    def endelement(self, tag):
+        method = getattr(self, 'end_'+tag, None)
+        if method:
+            method("".join(self.texts))
+
+    def start_cell(self, attrs):
+        self.y = int(attrs.get("row"))
+        self.x = int(attrs.get("col"))
+
+    def start_value(self, attrs):
+        self.fmt = attrs.get('format')
+        self.alignment = xml2align.get(attrs.get('align'))
+
+    start_formula = start_value
+
+    def end_int(self, text):
+        try:
+            self.value = int(text)
+        except:
+            self.value = None
+
+    def end_long(self, text):
+        try:
+            self.value = int(text)
+        except:
+            self.value = None
+
+    def end_double(self, text):
+        try:
+            self.value = float(text)
+        except:
+            self.value = None
+
+    def end_complex(self, text):
+        try:
+            self.value = complex(text)
+        except:
+            self.value = None
+
+    def end_string(self, text):
+        try:
+            self.value = text
+        except:
+            self.value = None
+
+    def end_value(self, text):
+        if isinstance(self.value, BaseCell):
+            self.cell = self.value
+        elif isinstance(self.value, str):
+            self.cell = StringCell(self.value,
+                                   self.fmt or "%s",
+                                   self.alignment or LEFT)
+        else:
+            self.cell = NumericCell(self.value,
+                                    self.fmt or "%s",
+                                    self.alignment or RIGHT)
+
+    def end_formula(self, text):
+        self.cell = FormulaCell(text,
+                                self.fmt or "%s",
+                                self.alignment or RIGHT)
+
+    def end_cell(self, text):
+        self.sheet.setcell(self.x, self.y, self.cell)
+
+class BaseCell:
+    __init__ = None # Must provide
+    """Abstract base class for sheet cells.
+
+    Subclasses may but needn't provide the following APIs:
+
+    cell.reset() -- prepare for recalculation
+    cell.recalc(ns) -> value -- recalculate formula
+    cell.format() -> (value, alignment) -- return formatted value
+    cell.xml() -> string -- return XML
+    """
+
+class NumericCell(BaseCell):
+
+    def __init__(self, value, fmt="%s", alignment=RIGHT):
+        assert isinstance(value, (int, int, float, complex))
+        assert alignment in (LEFT, CENTER, RIGHT)
+        self.value = value
+        self.fmt = fmt
+        self.alignment = alignment
+
+    def recalc(self, ns):
+        return self.value
+
+    def format(self):
+        try:
+            text = self.fmt % self.value
+        except:
+            text = str(self.value)
+        return text, self.alignment
+
+    def xml(self):
+        method = getattr(self, '_xml_' + type(self.value).__name__)
+        return '<value align="%s" format="%s">%s</value>' % (
+                align2xml[self.alignment],
+                self.fmt,
+                method())
+
+    def _xml_int(self):
+        if -2**31 <= self.value < 2**31:
+            return '<int>%s</int>' % self.value
+        else:
+            return self._xml_long()
+
+    def _xml_long(self):
+        return '<long>%s</long>' % self.value
+
+    def _xml_float(self):
+        return '<double>%s</double>' % repr(self.value)
+
+    def _xml_complex(self):
+        return '<complex>%s</double>' % repr(self.value)
+
+class StringCell(BaseCell):
+
+    def __init__(self, text, fmt="%s", alignment=LEFT):
+        assert isinstance(text, (str, str))
+        assert alignment in (LEFT, CENTER, RIGHT)
+        self.text = text
+        self.fmt = fmt
+        self.alignment = alignment
+
+    def recalc(self, ns):
+        return self.text
+
+    def format(self):
+        return self.text, self.alignment
+
+    def xml(self):
+        s = '<value align="%s" format="%s"><string>%s</string></value>'
+        return s % (
+            align2xml[self.alignment],
+            self.fmt,
+            html.escape(self.text))
+
+class FormulaCell(BaseCell):
+
+    def __init__(self, formula, fmt="%s", alignment=RIGHT):
+        assert alignment in (LEFT, CENTER, RIGHT)
+        self.formula = formula
+        self.translated = translate(self.formula)
+        self.fmt = fmt
+        self.alignment = alignment
+        self.reset()
+
+    def reset(self):
+        self.value = None
+
+    def recalc(self, ns):
+        if self.value is None:
+            try:
+                # A hack to evaluate expressions using true division
+                self.value = eval(self.translated, ns)
+            except:
+                exc = sys.exc_info()[0]
+                if hasattr(exc, "__name__"):
+                    self.value = exc.__name__
+                else:
+                    self.value = str(exc)
+        return self.value
+
+    def format(self):
+        try:
+            text = self.fmt % self.value
+        except:
+            text = str(self.value)
+        return text, self.alignment
+
+    def xml(self):
+        return '<formula align="%s" format="%s">%s</formula>' % (
+            align2xml[self.alignment],
+            self.fmt,
+            self.formula)
+
+    def renumber(self, x1, y1, x2, y2, dx, dy):
+        out = []
+        for part in re.split('(\w+)', self.formula):
+            m = re.match('^([A-Z]+)([1-9][0-9]*)$', part)
+            if m is not None:
+                sx, sy = m.groups()
+                x = colname2num(sx)
+                y = int(sy)
+                if x1 <= x <= x2 and y1 <= y <= y2:
+                    part = cellname(x+dx, y+dy)
+            out.append(part)
+        return FormulaCell("".join(out), self.fmt, self.alignment)
+
+def translate(formula):
+    """Translate a formula containing fancy cell names to valid Python code.
+
+    Examples:
+        B4 -> cell(2, 4)
+        B4:Z100 -> cells(2, 4, 26, 100)
+    """
+    out = []
+    for part in re.split(r"(\w+(?::\w+)?)", formula):
+        m = re.match(r"^([A-Z]+)([1-9][0-9]*)(?::([A-Z]+)([1-9][0-9]*))?$", part)
+        if m is None:
+            out.append(part)
+        else:
+            x1, y1, x2, y2 = m.groups()
+            x1 = colname2num(x1)
+            if x2 is None:
+                s = "cell(%s, %s)" % (x1, y1)
+            else:
+                x2 = colname2num(x2)
+                s = "cells(%s, %s, %s, %s)" % (x1, y1, x2, y2)
+            out.append(s)
+    return "".join(out)
+
+def cellname(x, y):
+    "Translate a cell coordinate to a fancy cell name (e.g. (1, 1)->'A1')."
+    assert x > 0 # Column 0 has an empty name, so can't use that
+    return colnum2name(x) + str(y)
+
+def colname2num(s):
+    "Translate a column name to number (e.g. 'A'->1, 'Z'->26, 'AA'->27)."
+    s = s.upper()
+    n = 0
+    for c in s:
+        assert 'A' <= c <= 'Z'
+        n = n*26 + ord(c) - ord('A') + 1
+    return n
+
+def colnum2name(n):
+    "Translate a column number to name (e.g. 1->'A', etc.)."
+    assert n > 0
+    s = ""
+    while n:
+        n, m = divmod(n-1, 26)
+        s = chr(m+ord('A')) + s
+    return s
+
+import tkinter as Tk
+
+class SheetGUI:
+
+    """Beginnings of a GUI for a spreadsheet.
+
+    TO DO:
+    - clear multiple cells
+    - Insert, clear, remove rows or columns
+    - Show new contents while typing
+    - Scroll bars
+    - Grow grid when window is grown
+    - Proper menus
+    - Undo, redo
+    - Cut, copy and paste
+    - Formatting and alignment
+    """
+
+    def __init__(self, filename="sheet1.xml", rows=10, columns=5):
+        """Constructor.
+
+        Load the sheet from the filename argument.
+        Set up the Tk widget tree.
+        """
+        # Create and load the sheet
+        self.filename = filename
+        self.sheet = Sheet()
+        if os.path.isfile(filename):
+            self.sheet.load(filename)
+        # Calculate the needed grid size
+        maxx, maxy = self.sheet.getsize()
+        rows = max(rows, maxy)
+        columns = max(columns, maxx)
+        # Create the widgets
+        self.root = Tk.Tk()
+        self.root.wm_title("Spreadsheet: %s" % self.filename)
+        self.beacon = Tk.Label(self.root, text="A1",
+                               font=('helvetica', 16, 'bold'))
+        self.entry = Tk.Entry(self.root)
+        self.savebutton = Tk.Button(self.root, text="Save",
+                                    command=self.save)
+        self.cellgrid = Tk.Frame(self.root)
+        # Configure the widget lay-out
+        self.cellgrid.pack(side="bottom", expand=1, fill="both")
+        self.beacon.pack(side="left")
+        self.savebutton.pack(side="right")
+        self.entry.pack(side="left", expand=1, fill="x")
+        # Bind some events
+        self.entry.bind("<Return>", self.return_event)
+        self.entry.bind("<Shift-Return>", self.shift_return_event)
+        self.entry.bind("<Tab>", self.tab_event)
+        self.entry.bind("<Shift-Tab>", self.shift_tab_event)
+        self.entry.bind("<Delete>", self.delete_event)
+        self.entry.bind("<Escape>", self.escape_event)
+        # Now create the cell grid
+        self.makegrid(rows, columns)
+        # Select the top-left cell
+        self.currentxy = None
+        self.cornerxy = None
+        self.setcurrent(1, 1)
+        # Copy the sheet cells to the GUI cells
+        self.sync()
+
+    def delete_event(self, event):
+        if self.cornerxy != self.currentxy and self.cornerxy is not None:
+            self.sheet.clearcells(*(self.currentxy + self.cornerxy))
+        else:
+            self.sheet.clearcell(*self.currentxy)
+        self.sync()
+        self.entry.delete(0, 'end')
+        return "break"
+
+    def escape_event(self, event):
+        x, y = self.currentxy
+        self.load_entry(x, y)
+
+    def load_entry(self, x, y):
+        cell = self.sheet.getcell(x, y)
+        if cell is None:
+            text = ""
+        elif isinstance(cell, FormulaCell):
+            text = '=' + cell.formula
+        else:
+            text, alignment = cell.format()
+        self.entry.delete(0, 'end')
+        self.entry.insert(0, text)
+        self.entry.selection_range(0, 'end')
+
+    def makegrid(self, rows, columns):
+        """Helper to create the grid of GUI cells.
+
+        The edge (x==0 or y==0) is filled with labels; the rest is real cells.
+        """
+        self.rows = rows
+        self.columns = columns
+        self.gridcells = {}
+        # Create the top left corner cell (which selects all)
+        cell = Tk.Label(self.cellgrid, relief='raised')
+        cell.grid_configure(column=0, row=0, sticky='NSWE')
+        cell.bind("<ButtonPress-1>", self.selectall)
+        # Create the top row of labels, and confiure the grid columns
+        for x in range(1, columns+1):
+            self.cellgrid.grid_columnconfigure(x, minsize=64)
+            cell = Tk.Label(self.cellgrid, text=colnum2name(x), relief='raised')
+            cell.grid_configure(column=x, row=0, sticky='WE')
+            self.gridcells[x, 0] = cell
+            cell.__x = x
+            cell.__y = 0
+            cell.bind("<ButtonPress-1>", self.selectcolumn)
+            cell.bind("<B1-Motion>", self.extendcolumn)
+            cell.bind("<ButtonRelease-1>", self.extendcolumn)
+            cell.bind("<Shift-Button-1>", self.extendcolumn)
+        # Create the leftmost column of labels
+        for y in range(1, rows+1):
+            cell = Tk.Label(self.cellgrid, text=str(y), relief='raised')
+            cell.grid_configure(column=0, row=y, sticky='WE')
+            self.gridcells[0, y] = cell
+            cell.__x = 0
+            cell.__y = y
+            cell.bind("<ButtonPress-1>", self.selectrow)
+            cell.bind("<B1-Motion>", self.extendrow)
+            cell.bind("<ButtonRelease-1>", self.extendrow)
+            cell.bind("<Shift-Button-1>", self.extendrow)
+        # Create the real cells
+        for x in range(1, columns+1):
+            for y in range(1, rows+1):
+                cell = Tk.Label(self.cellgrid, relief='sunken',
+                                bg='white', fg='black')
+                cell.grid_configure(column=x, row=y, sticky='NSWE')
+                self.gridcells[x, y] = cell
+                cell.__x = x
+                cell.__y = y
+                # Bind mouse events
+                cell.bind("<ButtonPress-1>", self.press)
+                cell.bind("<B1-Motion>", self.motion)
+                cell.bind("<ButtonRelease-1>", self.release)
+                cell.bind("<Shift-Button-1>", self.release)
+
+    def selectall(self, event):
+        self.setcurrent(1, 1)
+        self.setcorner(sys.maxint, sys.maxint)
+
+    def selectcolumn(self, event):
+        x, y = self.whichxy(event)
+        self.setcurrent(x, 1)
+        self.setcorner(x, sys.maxint)
+
+    def extendcolumn(self, event):
+        x, y = self.whichxy(event)
+        if x > 0:
+            self.setcurrent(self.currentxy[0], 1)
+            self.setcorner(x, sys.maxint)
+
+    def selectrow(self, event):
+        x, y = self.whichxy(event)
+        self.setcurrent(1, y)
+        self.setcorner(sys.maxint, y)
+
+    def extendrow(self, event):
+        x, y = self.whichxy(event)
+        if y > 0:
+            self.setcurrent(1, self.currentxy[1])
+            self.setcorner(sys.maxint, y)
+
+    def press(self, event):
+        x, y = self.whichxy(event)
+        if x > 0 and y > 0:
+            self.setcurrent(x, y)
+
+    def motion(self, event):
+        x, y = self.whichxy(event)
+        if x > 0 and y > 0:
+            self.setcorner(x, y)
+
+    release = motion
+
+    def whichxy(self, event):
+        w = self.cellgrid.winfo_containing(event.x_root, event.y_root)
+        if w is not None and isinstance(w, Tk.Label):
+            try:
+                return w.__x, w.__y
+            except AttributeError:
+                pass
+        return 0, 0
+
+    def save(self):
+        self.sheet.save(self.filename)
+
+    def setcurrent(self, x, y):
+        "Make (x, y) the current cell."
+        if self.currentxy is not None:
+            self.change_cell()
+        self.clearfocus()
+        self.beacon['text'] = cellname(x, y)
+        self.load_entry(x, y)
+        self.entry.focus_set()
+        self.currentxy = x, y
+        self.cornerxy = None
+        gridcell = self.gridcells.get(self.currentxy)
+        if gridcell is not None:
+            gridcell['bg'] = 'yellow'
+
+    def setcorner(self, x, y):
+        if self.currentxy is None or self.currentxy == (x, y):
+            self.setcurrent(x, y)
+            return
+        self.clearfocus()
+        self.cornerxy = x, y
+        x1, y1 = self.currentxy
+        x2, y2 = self.cornerxy or self.currentxy
+        if x1 > x2:
+            x1, x2 = x2, x1
+        if y1 > y2:
+            y1, y2 = y2, y1
+        for (x, y), cell in self.gridcells.items():
+            if x1 <= x <= x2 and y1 <= y <= y2:
+                cell['bg'] = 'lightBlue'
+        gridcell = self.gridcells.get(self.currentxy)
+        if gridcell is not None:
+            gridcell['bg'] = 'yellow'
+        self.setbeacon(x1, y1, x2, y2)
+
+    def setbeacon(self, x1, y1, x2, y2):
+        if x1 == y1 == 1 and x2 == y2 == sys.maxint:
+            name = ":"
+        elif (x1, x2) == (1, sys.maxint):
+            if y1 == y2:
+                name = "%d" % y1
+            else:
+                name = "%d:%d" % (y1, y2)
+        elif (y1, y2) == (1, sys.maxint):
+            if x1 == x2:
+                name = "%s" % colnum2name(x1)
+            else:
+                name = "%s:%s" % (colnum2name(x1), colnum2name(x2))
+        else:
+            name1 = cellname(*self.currentxy)
+            name2 = cellname(*self.cornerxy)
+            name = "%s:%s" % (name1, name2)
+        self.beacon['text'] = name
+
+
+    def clearfocus(self):
+        if self.currentxy is not None:
+            x1, y1 = self.currentxy
+            x2, y2 = self.cornerxy or self.currentxy
+            if x1 > x2:
+                x1, x2 = x2, x1
+            if y1 > y2:
+                y1, y2 = y2, y1
+            for (x, y), cell in self.gridcells.items():
+                if x1 <= x <= x2 and y1 <= y <= y2:
+                    cell['bg'] = 'white'
+
+    def return_event(self, event):
+        "Callback for the Return key."
+        self.change_cell()
+        x, y = self.currentxy
+        self.setcurrent(x, y+1)
+        return "break"
+
+    def shift_return_event(self, event):
+        "Callback for the Return key with Shift modifier."
+        self.change_cell()
+        x, y = self.currentxy
+        self.setcurrent(x, max(1, y-1))
+        return "break"
+
+    def tab_event(self, event):
+        "Callback for the Tab key."
+        self.change_cell()
+        x, y = self.currentxy
+        self.setcurrent(x+1, y)
+        return "break"
+
+    def shift_tab_event(self, event):
+        "Callback for the Tab key with Shift modifier."
+        self.change_cell()
+        x, y = self.currentxy
+        self.setcurrent(max(1, x-1), y)
+        return "break"
+
+    def change_cell(self):
+        "Set the current cell from the entry widget."
+        x, y = self.currentxy
+        text = self.entry.get()
+        cell = None
+        if text.startswith('='):
+            cell = FormulaCell(text[1:])
+        else:
+            for cls in int, int, float, complex:
+                try:
+                    value = cls(text)
+                except:
+                    continue
+                else:
+                    cell = NumericCell(value)
+                    break
+        if cell is None and text:
+            cell = StringCell(text)
+        if cell is None:
+            self.sheet.clearcell(x, y)
+        else:
+            self.sheet.setcell(x, y, cell)
+        self.sync()
+
+    def sync(self):
+        "Fill the GUI cells from the sheet cells."
+        self.sheet.recalc()
+        for (x, y), gridcell in self.gridcells.items():
+            if x == 0 or y == 0:
+                continue
+            cell = self.sheet.getcell(x, y)
+            if cell is None:
+                gridcell['text'] = ""
+            else:
+                if hasattr(cell, 'format'):
+                    text, alignment = cell.format()
+                else:
+                    text, alignment = str(cell), LEFT
+                gridcell['text'] = text
+                gridcell['anchor'] = align2anchor[alignment]
+
+
+def test_basic():
+    "Basic non-gui self-test."
+    import os
+    a = Sheet()
+    for x in range(1, 11):
+        for y in range(1, 11):
+            if x == 1:
+                cell = NumericCell(y)
+            elif y == 1:
+                cell = NumericCell(x)
+            else:
+                c1 = cellname(x, 1)
+                c2 = cellname(1, y)
+                formula = "%s*%s" % (c1, c2)
+                cell = FormulaCell(formula)
+            a.setcell(x, y, cell)
+##    if os.path.isfile("sheet1.xml"):
+##        print "Loading from sheet1.xml"
+##        a.load("sheet1.xml")
+    a.display()
+    a.save("sheet1.xml")
+
+def test_gui():
+    "GUI test."
+    if sys.argv[1:]:
+        filename = sys.argv[1]
+    else:
+        filename = "sheet1.xml"
+    g = SheetGUI(filename)
+    g.root.mainloop()
+
+if __name__ == '__main__':
+    #test_basic()
+    test_gui()
diff --git a/Tools/test2to3/README b/Tools/test2to3/README
new file mode 100644
index 0000000..9365593
--- /dev/null
+++ b/Tools/test2to3/README
@@ -0,0 +1,3 @@
+This project demonstrates how a distutils package
+can support Python 2.x and Python 3.x from a single
+source, using lib2to3.
\ No newline at end of file
diff --git a/Tools/test2to3/maintest.py b/Tools/test2to3/maintest.py
new file mode 100644
index 0000000..036dd4f
--- /dev/null
+++ b/Tools/test2to3/maintest.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+# The above line should get replaced with the path to the Python
+# interpreter; the block below should get 2to3-converted.
+
+try:
+    from test2to3.hello import hello
+except ImportError, e:
+    print "Import failed", e
+hello()
diff --git a/Tools/test2to3/setup.py b/Tools/test2to3/setup.py
new file mode 100644
index 0000000..a0f9024
--- /dev/null
+++ b/Tools/test2to3/setup.py
@@ -0,0 +1,26 @@
+# -*- coding: iso-8859-1 -*-
+from distutils.core import setup
+
+try:
+    from distutils.command.build_py import build_py_2to3 as build_py
+except ImportError:
+    from distutils.command.build_py import build_py
+
+try:
+    from distutils.command.build_scripts import build_scripts_2to3 as build_scripts
+except ImportError:
+    from distutils.command.build_scripts import build_scripts
+
+setup(
+    name = "test2to3",
+    version = "1.0",
+    description = "2to3 distutils test package",
+    author = "Martin v. Löwis",
+    author_email = "python-dev@python.org",
+    license = "PSF license",
+    packages = ["test2to3"],
+    scripts = ["maintest.py"],
+    cmdclass = {'build_py': build_py,
+                'build_scripts': build_scripts,
+                }
+)
diff --git a/Tools/test2to3/test/runtests.py b/Tools/test2to3/test/runtests.py
new file mode 100644
index 0000000..1730f0d
--- /dev/null
+++ b/Tools/test2to3/test/runtests.py
@@ -0,0 +1,19 @@
+# Fictitious test runner for the project
+
+import sys, os
+
+if sys.version_info > (3,):
+    # copy test suite over to "build/lib" and convert it
+    from distutils.util import copydir_run_2to3
+    testroot = os.path.dirname(__file__)
+    newroot = os.path.join(testroot, '..', 'build/lib/test')
+    copydir_run_2to3(testroot, newroot)
+    # in the following imports, pick up the converted modules
+    sys.path[0] = newroot
+
+# run the tests here...
+
+from test_foo import FooTest
+
+import unittest
+unittest.main()
diff --git a/Tools/test2to3/test/test_foo.py b/Tools/test2to3/test/test_foo.py
new file mode 100644
index 0000000..ec8f26a
--- /dev/null
+++ b/Tools/test2to3/test/test_foo.py
@@ -0,0 +1,8 @@
+import sys
+import unittest
+
+class FooTest(unittest.TestCase):
+    def test_foo(self):
+        # use 2.6 syntax to demonstrate conversion
+        print 'In test_foo, using Python %s...' % (sys.version_info,)
+        self.assertTrue(False)
diff --git a/Tools/test2to3/test2to3/__init__.py b/Tools/test2to3/test2to3/__init__.py
new file mode 100644
index 0000000..1bb8bf6
--- /dev/null
+++ b/Tools/test2to3/test2to3/__init__.py
@@ -0,0 +1 @@
+# empty
diff --git a/Tools/test2to3/test2to3/hello.py b/Tools/test2to3/test2to3/hello.py
new file mode 100644
index 0000000..f52926b
--- /dev/null
+++ b/Tools/test2to3/test2to3/hello.py
@@ -0,0 +1,5 @@
+def hello():
+    try:
+        print "Hello, world"
+    except IOError, e:
+        print e.errno