summaryrefslogtreecommitdiffstats
path: root/Tools/demo
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2010-12-30 21:33:07 (GMT)
committerGeorg Brandl <georg@python.org>2010-12-30 21:33:07 (GMT)
commit7fafbc95c0c963438197c9a43fe893c4ea6fe759 (patch)
tree5c9ceda4bdc5260236a230554b9ed56b8c0cdbd3 /Tools/demo
parent6f17e2df29a865a29447531e89fb22be710e382d (diff)
downloadcpython-7fafbc95c0c963438197c9a43fe893c4ea6fe759.zip
cpython-7fafbc95c0c963438197c9a43fe893c4ea6fe759.tar.gz
cpython-7fafbc95c0c963438197c9a43fe893c4ea6fe759.tar.bz2
More cleanup: Move some demos into a dedicated Tools/demo dir, move 2to3 demo to Tools, and remove all the other Demo content.
Diffstat (limited to 'Tools/demo')
-rw-r--r--Tools/demo/Eiffel.py141
-rw-r--r--Tools/demo/Vec.py68
-rwxr-xr-xTools/demo/beer.py20
-rw-r--r--Tools/demo/hanoi.py154
-rwxr-xr-xTools/demo/life.py250
-rwxr-xr-xTools/demo/markov.py121
-rwxr-xr-xTools/demo/mcast.py80
-rwxr-xr-xTools/demo/queens.py85
-rwxr-xr-xTools/demo/rpython.py36
-rwxr-xr-xTools/demo/rpythond.py55
-rw-r--r--Tools/demo/sortvisu.py636
-rw-r--r--Tools/demo/ss1.py842
12 files changed, 2488 insertions, 0 deletions
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()