mirror of https://github.com/python/cpython
gh-97681: Remove Tools/demo/ directory (#97682)
Remove the Tools/demo/ directory which contained old demo scripts. A copy can be found in the old-demos project: https://github.com/gvanrossum/old-demos Remove the following old demo scripts: * beer.py * eiffel.py * hanoi.py * life.py * markov.py * mcast.py * queens.py * redemo.py * rpython.py * rpythond.py * sortvisu.py * spreadsheet.py * vector.py Changes: * Remove a reference to the redemo.py script in the regex howto documentation. * Remove a reference to the removed Tools/demo/ directory in the curses documentation. * Update PC/layout/ to remove the reference to Tools/demo/ directory.
This commit is contained in:
parent
e6f9ec5c03
commit
64fe343717
|
@ -378,11 +378,7 @@ containing information about the match: where it starts and ends, the substring
|
|||
it matched, and more.
|
||||
|
||||
You can learn about this by interactively experimenting with the :mod:`re`
|
||||
module. If you have :mod:`tkinter` available, you may also want to look at
|
||||
:source:`Tools/demo/redemo.py`, a demonstration program included with the
|
||||
Python distribution. It allows you to enter REs and strings, and displays
|
||||
whether the RE matches or fails. :file:`redemo.py` can be quite useful when
|
||||
trying to debug a complicated RE.
|
||||
module.
|
||||
|
||||
This HOWTO uses the standard Python interpreter for its examples. First, run the
|
||||
Python interpreter, import the :mod:`re` module, and compile a RE::
|
||||
|
|
|
@ -42,9 +42,6 @@ Linux and the BSD variants of Unix.
|
|||
Tutorial material on using curses with Python, by Andrew Kuchling and Eric
|
||||
Raymond.
|
||||
|
||||
The :source:`Tools/demo/` directory in the Python source distribution contains
|
||||
some example programs using the curses bindings provided by this module.
|
||||
|
||||
|
||||
.. _curses-functions:
|
||||
|
||||
|
|
|
@ -170,6 +170,15 @@ CPython bytecode changes
|
|||
(Contributed by Ken Jin in :gh:`93429`.)
|
||||
|
||||
|
||||
Demos and Tools
|
||||
===============
|
||||
|
||||
* Remove the ``Tools/demo/`` directory which contained old demo scripts. A copy
|
||||
can be found in the `old-demos project
|
||||
<https://github.com/gvanrossum/old-demos>`_.
|
||||
(Contributed by Victor Stinner in :gh:`97681`.)
|
||||
|
||||
|
||||
Deprecated
|
||||
==========
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
Remove the ``Tools/demo/`` directory which contained old demo scripts. A copy
|
||||
can be found in the `old-demos project
|
||||
<https://github.com/gvanrossum/old-demos>`_. Patch by Victor Stinner.
|
|
@ -58,7 +58,7 @@ CDF_FILES = FileSuffixSet(".cdf")
|
|||
|
||||
DATA_DIRS = FileNameSet("data")
|
||||
|
||||
TOOLS_DIRS = FileNameSet("scripts", "i18n", "demo", "parser")
|
||||
TOOLS_DIRS = FileNameSet("scripts", "i18n", "parser")
|
||||
TOOLS_FILES = FileSuffixSet(".py", ".pyw", ".txt")
|
||||
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ buildbot Batchfiles for running on Windows buildbot workers.
|
|||
|
||||
ccbench A Python threads-based concurrency benchmark. (*)
|
||||
|
||||
demo Several Python programming demos.
|
||||
|
||||
freeze Create a stand-alone executable from a Python program.
|
||||
|
||||
gdb Python code to be run inside gdb, to make it easier to
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
This directory contains a collection of demonstration scripts for
|
||||
various aspects of Python programming.
|
||||
|
||||
beer.py Well-known programming example: Bottles of beer.
|
||||
eiffel.py Python advanced magic: A metaclass for Eiffel post/preconditions.
|
||||
hanoi.py Well-known programming example: Towers of Hanoi.
|
||||
life.py Curses programming: Simple game-of-life.
|
||||
markov.py Algorithms: Markov chain simulation.
|
||||
mcast.py Network programming: Send and receive UDP multicast packets.
|
||||
queens.py Well-known programming example: N-Queens problem.
|
||||
redemo.py Regular Expressions: GUI script to test regexes.
|
||||
rpython.py Network programming: Small client for remote code execution.
|
||||
rpythond.py Network programming: Small server for remote code execution.
|
||||
sortvisu.py GUI programming: Visualization of different sort algorithms.
|
||||
spreadsheet.py GUI/Application programming: A simple spreadsheet application.
|
||||
vector.py Python basics: A vector class demonstrating special methods.
|
|
@ -1,25 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
A Python version of the classic "bottles of beer on the wall" programming
|
||||
example.
|
||||
|
||||
By Guido van Rossum, 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.")
|
|
@ -1,146 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Support Eiffel-style preconditions and postconditions for functions.
|
||||
|
||||
An example for Python metaclasses.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
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[m] = 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)
|
||||
rv = func(self, *args, **kwargs)
|
||||
if post:
|
||||
post(self, rv, *args, **kwargs)
|
||||
return rv
|
||||
|
||||
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:
|
||||
|
||||
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=None):
|
||||
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
|
||||
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
|
||||
def testEiffelMetaClass1(self):
|
||||
self._test(EiffelMetaClass1)
|
||||
|
||||
def testEiffelMetaClass2(self):
|
||||
self._test(EiffelMetaClass2)
|
||||
|
||||
def _test(self, 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()
|
||||
self.assertEqual(t.m(1), 2)
|
||||
self.assertEqual(t.m2(1), 2)
|
||||
self.assertRaises(AssertionError, t.m2, 0)
|
||||
|
||||
s = Sub()
|
||||
self.assertRaises(AssertionError, s.m2, 1)
|
||||
self.assertRaises(AssertionError, s.m2, 10)
|
||||
self.assertEqual(s.m2(5), 25)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,154 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Animated Towers of Hanoi using Tk with optional bitmap file in background.
|
||||
|
||||
Usage: hanoi.py [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.
|
||||
"""
|
||||
|
||||
from tkinter import Tk, Canvas
|
||||
|
||||
# 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 True:
|
||||
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 True:
|
||||
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 True:
|
||||
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 True:
|
||||
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)
|
||||
|
||||
|
||||
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()
|
|
@ -1,262 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
A curses-based version of Conway's Game of Life.
|
||||
|
||||
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
|
||||
|
||||
Contributed by Andrew Kuchling, Mouse support and color by Dafydd Crosby.
|
||||
"""
|
||||
|
||||
import curses
|
||||
import random
|
||||
|
||||
|
||||
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
|
||||
make_random() -- 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:
|
||||
# Survival
|
||||
d[i, j] = 1
|
||||
elif live:
|
||||
# Death
|
||||
self.scr.addch(j + 1, i + 1, ' ')
|
||||
self.boring = 0
|
||||
self.state = d
|
||||
self.scr.refresh()
|
||||
|
||||
def make_random(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 True:
|
||||
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 True:
|
||||
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.make_random()
|
||||
board.display(update_board=False)
|
||||
elif c in 'Ss':
|
||||
board.display()
|
||||
else:
|
||||
# Ignore incorrect keys
|
||||
pass
|
||||
elif c == curses.KEY_UP and ypos > 0:
|
||||
ypos -= 1
|
||||
elif c == curses.KEY_DOWN and ypos + 1 < board.Y:
|
||||
ypos += 1
|
||||
elif c == curses.KEY_LEFT and xpos > 0:
|
||||
xpos -= 1
|
||||
elif c == curses.KEY_RIGHT and xpos + 1 < board.X:
|
||||
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)
|
|
@ -1,125 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Markov chain simulation of words or characters.
|
||||
"""
|
||||
|
||||
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')
|
||||
with f:
|
||||
if debug: print('processing', filename, '...')
|
||||
text = f.read()
|
||||
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()
|
|
@ -1,82 +0,0 @@
|
|||
#!/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()
|
|
@ -1,85 +0,0 @@
|
|||
#!/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()
|
|
@ -1,171 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""Basic regular expression demonstration facility (Perl style syntax)."""
|
||||
|
||||
from tkinter import *
|
||||
import re
|
||||
|
||||
class ReDemo:
|
||||
|
||||
def __init__(self, master):
|
||||
self.master = master
|
||||
|
||||
self.promptdisplay = Label(self.master, anchor=W,
|
||||
text="Enter a Perl-style regular expression:")
|
||||
self.promptdisplay.pack(side=TOP, fill=X)
|
||||
|
||||
self.regexdisplay = Entry(self.master)
|
||||
self.regexdisplay.pack(fill=X)
|
||||
self.regexdisplay.focus_set()
|
||||
|
||||
self.addoptions()
|
||||
|
||||
self.statusdisplay = Label(self.master, text="", anchor=W)
|
||||
self.statusdisplay.pack(side=TOP, fill=X)
|
||||
|
||||
self.labeldisplay = Label(self.master, anchor=W,
|
||||
text="Enter a string to search:")
|
||||
self.labeldisplay.pack(fill=X)
|
||||
self.labeldisplay.pack(fill=X)
|
||||
|
||||
self.showframe = Frame(master)
|
||||
self.showframe.pack(fill=X, anchor=W)
|
||||
|
||||
self.showvar = StringVar(master)
|
||||
self.showvar.set("first")
|
||||
|
||||
self.showfirstradio = Radiobutton(self.showframe,
|
||||
text="Highlight first match",
|
||||
variable=self.showvar,
|
||||
value="first",
|
||||
command=self.recompile)
|
||||
self.showfirstradio.pack(side=LEFT)
|
||||
|
||||
self.showallradio = Radiobutton(self.showframe,
|
||||
text="Highlight all matches",
|
||||
variable=self.showvar,
|
||||
value="all",
|
||||
command=self.recompile)
|
||||
self.showallradio.pack(side=LEFT)
|
||||
|
||||
self.stringdisplay = Text(self.master, width=60, height=4)
|
||||
self.stringdisplay.pack(fill=BOTH, expand=1)
|
||||
self.stringdisplay.tag_configure("hit", background="yellow")
|
||||
|
||||
self.grouplabel = Label(self.master, text="Groups:", anchor=W)
|
||||
self.grouplabel.pack(fill=X)
|
||||
|
||||
self.grouplist = Listbox(self.master)
|
||||
self.grouplist.pack(expand=1, fill=BOTH)
|
||||
|
||||
self.regexdisplay.bind('<Key>', self.recompile)
|
||||
self.stringdisplay.bind('<Key>', self.reevaluate)
|
||||
|
||||
self.compiled = None
|
||||
self.recompile()
|
||||
|
||||
btags = self.regexdisplay.bindtags()
|
||||
self.regexdisplay.bindtags(btags[1:] + btags[:1])
|
||||
|
||||
btags = self.stringdisplay.bindtags()
|
||||
self.stringdisplay.bindtags(btags[1:] + btags[:1])
|
||||
|
||||
def addoptions(self):
|
||||
self.frames = []
|
||||
self.boxes = []
|
||||
self.vars = []
|
||||
for name in ('IGNORECASE',
|
||||
'MULTILINE',
|
||||
'DOTALL',
|
||||
'VERBOSE'):
|
||||
if len(self.boxes) % 3 == 0:
|
||||
frame = Frame(self.master)
|
||||
frame.pack(fill=X)
|
||||
self.frames.append(frame)
|
||||
val = getattr(re, name).value
|
||||
var = IntVar()
|
||||
box = Checkbutton(frame,
|
||||
variable=var, text=name,
|
||||
offvalue=0, onvalue=val,
|
||||
command=self.recompile)
|
||||
box.pack(side=LEFT)
|
||||
self.boxes.append(box)
|
||||
self.vars.append(var)
|
||||
|
||||
def getflags(self):
|
||||
flags = 0
|
||||
for var in self.vars:
|
||||
flags = flags | var.get()
|
||||
return flags
|
||||
|
||||
def recompile(self, event=None):
|
||||
try:
|
||||
self.compiled = re.compile(self.regexdisplay.get(),
|
||||
self.getflags())
|
||||
bg = self.promptdisplay['background']
|
||||
self.statusdisplay.config(text="", background=bg)
|
||||
except re.error as msg:
|
||||
self.compiled = None
|
||||
self.statusdisplay.config(
|
||||
text="re.error: %s" % str(msg),
|
||||
background="red")
|
||||
self.reevaluate()
|
||||
|
||||
def reevaluate(self, event=None):
|
||||
try:
|
||||
self.stringdisplay.tag_remove("hit", "1.0", END)
|
||||
except TclError:
|
||||
pass
|
||||
try:
|
||||
self.stringdisplay.tag_remove("hit0", "1.0", END)
|
||||
except TclError:
|
||||
pass
|
||||
self.grouplist.delete(0, END)
|
||||
if not self.compiled:
|
||||
return
|
||||
self.stringdisplay.tag_configure("hit", background="yellow")
|
||||
self.stringdisplay.tag_configure("hit0", background="orange")
|
||||
text = self.stringdisplay.get("1.0", END)
|
||||
last = 0
|
||||
nmatches = 0
|
||||
while last <= len(text):
|
||||
m = self.compiled.search(text, last)
|
||||
if m is None:
|
||||
break
|
||||
first, last = m.span()
|
||||
if last == first:
|
||||
last = first+1
|
||||
tag = "hit0"
|
||||
else:
|
||||
tag = "hit"
|
||||
pfirst = "1.0 + %d chars" % first
|
||||
plast = "1.0 + %d chars" % last
|
||||
self.stringdisplay.tag_add(tag, pfirst, plast)
|
||||
if nmatches == 0:
|
||||
self.stringdisplay.yview_pickplace(pfirst)
|
||||
groups = list(m.groups())
|
||||
groups.insert(0, m.group())
|
||||
for i in range(len(groups)):
|
||||
g = "%2d: %r" % (i, groups[i])
|
||||
self.grouplist.insert(END, g)
|
||||
nmatches = nmatches + 1
|
||||
if self.showvar.get() == "first":
|
||||
break
|
||||
|
||||
if nmatches == 0:
|
||||
self.statusdisplay.config(text="(no match)",
|
||||
background="yellow")
|
||||
else:
|
||||
self.statusdisplay.config(text="")
|
||||
|
||||
|
||||
# Main function, run when invoked as a stand-alone Python program.
|
||||
|
||||
def main():
|
||||
root = Tk()
|
||||
demo = ReDemo(root)
|
||||
root.protocol('WM_DELETE_WINDOW', root.quit)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,37 +0,0 @@
|
|||
#!/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(host[i+1:])
|
||||
host = host[:i]
|
||||
command = ' '.join(sys.argv[2:])
|
||||
with socket(AF_INET, SOCK_STREAM) as s:
|
||||
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=' ')
|
||||
|
||||
main()
|
|
@ -1,58 +0,0 @@
|
|||
#!/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()
|
||||
with conn:
|
||||
print('connection from', remotehost, remoteport)
|
||||
request = b''
|
||||
while True:
|
||||
data = conn.recv(BUFSIZE)
|
||||
if not data:
|
||||
break
|
||||
request += data
|
||||
reply = execute(request.decode())
|
||||
conn.send(reply.encode())
|
||||
|
||||
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
|
|
@ -1,635 +0,0 @@
|
|||
#!/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 True:
|
||||
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 True:
|
||||
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()
|
|
@ -1,829 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
SS1 -- a spreadsheet-like application.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from xml.parsers import expat
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
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.maxsize, y2)
|
||||
|
||||
def clearcolumns(self, x1, x2):
|
||||
self.clearcells(x1, 0, x2, sys.maxsize)
|
||||
|
||||
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.maxsize, sys.maxsize, 0, n)
|
||||
|
||||
def deleterows(self, y1, y2):
|
||||
if y1 > y2:
|
||||
y1, y2 = y2, y1
|
||||
self.clearrows(y1, y2)
|
||||
self.movecells(0, y2+1, sys.maxsize, sys.maxsize, 0, y1-y2-1)
|
||||
|
||||
def insertcolumns(self, x, n):
|
||||
assert n > 0
|
||||
self.movecells(x, 0, sys.maxsize, sys.maxsize, n, 0)
|
||||
|
||||
def deletecolumns(self, x1, x2):
|
||||
if x1 > x2:
|
||||
x1, x2 = x2, x1
|
||||
self.clearcells(x1, x2)
|
||||
self.movecells(x2+1, 0, sys.maxsize, sys.maxsize, 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>' % 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()
|
||||
with open(filename, "w", encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
if text and not text.endswith('\n'):
|
||||
f.write('\n')
|
||||
|
||||
def load(self, filename):
|
||||
with open(filename, 'rb') as f:
|
||||
SheetParser(self).parsefile(f)
|
||||
|
||||
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:
|
||||
method(attrs)
|
||||
self.texts = []
|
||||
|
||||
def data(self, text):
|
||||
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 (TypeError, ValueError):
|
||||
self.value = None
|
||||
|
||||
end_long = end_int
|
||||
|
||||
def end_double(self, text):
|
||||
try:
|
||||
self.value = float(text)
|
||||
except (TypeError, ValueError):
|
||||
self.value = None
|
||||
|
||||
def end_complex(self, text):
|
||||
try:
|
||||
self.value = complex(text)
|
||||
except (TypeError, ValueError):
|
||||
self.value = None
|
||||
|
||||
def end_string(self, text):
|
||||
self.value = text
|
||||
|
||||
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, 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 '<long>%s</long>' % self.value
|
||||
|
||||
def _xml_float(self):
|
||||
return '<double>%r</double>' % self.value
|
||||
|
||||
def _xml_complex(self):
|
||||
return '<complex>%r</complex>' % self.value
|
||||
|
||||
class StringCell(BaseCell):
|
||||
|
||||
def __init__(self, text, fmt="%s", alignment=LEFT):
|
||||
assert isinstance(text, 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,
|
||||
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:
|
||||
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,
|
||||
escape(self.formula))
|
||||
|
||||
def renumber(self, x1, y1, x2, y2, dx, dy):
|
||||
out = []
|
||||
for part in re.split(r'(\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 configure 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.maxsize, sys.maxsize)
|
||||
|
||||
def selectcolumn(self, event):
|
||||
x, y = self.whichxy(event)
|
||||
self.setcurrent(x, 1)
|
||||
self.setcorner(x, sys.maxsize)
|
||||
|
||||
def extendcolumn(self, event):
|
||||
x, y = self.whichxy(event)
|
||||
if x > 0:
|
||||
self.setcurrent(self.currentxy[0], 1)
|
||||
self.setcorner(x, sys.maxsize)
|
||||
|
||||
def selectrow(self, event):
|
||||
x, y = self.whichxy(event)
|
||||
self.setcurrent(1, y)
|
||||
self.setcorner(sys.maxsize, y)
|
||||
|
||||
def extendrow(self, event):
|
||||
x, y = self.whichxy(event)
|
||||
if y > 0:
|
||||
self.setcurrent(1, self.currentxy[1])
|
||||
self.setcorner(sys.maxsize, 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.maxsize:
|
||||
name = ":"
|
||||
elif (x1, x2) == (1, sys.maxsize):
|
||||
if y1 == y2:
|
||||
name = "%d" % y1
|
||||
else:
|
||||
name = "%d:%d" % (y1, y2)
|
||||
elif (y1, y2) == (1, sys.maxsize):
|
||||
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, float, complex:
|
||||
try:
|
||||
value = cls(text)
|
||||
except (TypeError, ValueError):
|
||||
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."
|
||||
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()
|
|
@ -1,94 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
A demonstration of classes and their special methods in Python.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
and dot product
|
||||
>>> a.dot(b)
|
||||
10
|
||||
|
||||
and printed in vector notation
|
||||
>>> print(a)
|
||||
<1 2 3>
|
||||
|
||||
"""
|
||||
|
||||
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 f'{type(self).__name__}({args})'
|
||||
|
||||
def __str__(self):
|
||||
components = ' '.join([str(x) for x in self.v])
|
||||
return f'<{components}>'
|
||||
|
||||
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 dot(self, other):
|
||||
"Vector dot product"
|
||||
if not isinstance(other, Vec):
|
||||
raise TypeError
|
||||
return sum(x_i * y_i for (x_i, y_i) in zip(self, other))
|
||||
|
||||
|
||||
def test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
test()
|
Loading…
Reference in New Issue