aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorSkip Montanaro <[email protected]>2021-02-16 14:40:46 -0600
committerSkip Montanaro <[email protected]>2021-02-16 14:40:46 -0600
commita19a216bc60160c162e616145ef091dd18ce4e61 (patch)
treefa4bdff21f9b04a125c84a2bfab8a1c738359e15 /lib
downloadpython-0.9.1-patched-QoL-a19a216bc60160c162e616145ef091dd18ce4e61.tar.xz
python-0.9.1-patched-QoL-a19a216bc60160c162e616145ef091dd18ce4e61.zip
Python 0.9.1 as posted in alt.sources
Diffstat (limited to 'lib')
-rw-r--r--lib/Abstract.py61
-rw-r--r--lib/Buttons.py399
-rw-r--r--lib/CSplit.py70
-rw-r--r--lib/DEVICE.py423
-rw-r--r--lib/GL.py365
-rw-r--r--lib/HVSplit.py56
-rw-r--r--lib/Histogram.py36
-rw-r--r--lib/Sliders.py175
-rw-r--r--lib/Soundogram.py36
-rw-r--r--lib/Split.py116
-rw-r--r--lib/StripChart.py68
-rw-r--r--lib/Tcl.py421
-rw-r--r--lib/TclShell.py255
-rw-r--r--lib/TclUtil.py377
-rw-r--r--lib/TestCSplit.py26
-rw-r--r--lib/TransParent.py96
-rw-r--r--lib/VUMeter.py47
-rw-r--r--lib/WindowParent.py101
-rw-r--r--lib/adv.py366
-rw-r--r--lib/anywin.py14
-rw-r--r--lib/auds.py106
-rw-r--r--lib/calendar.py213
-rw-r--r--lib/clock.py202
-rw-r--r--lib/cmp.py61
-rw-r--r--lib/cmpcache.py68
-rw-r--r--lib/commands.py77
-rw-r--r--lib/dircache.py36
-rw-r--r--lib/dircmp.py205
-rw-r--r--lib/dirwin.py29
-rw-r--r--lib/dis.py176
-rw-r--r--lib/dump.py63
-rw-r--r--lib/fact.py37
-rw-r--r--lib/filewin.py18
-rw-r--r--lib/fnmatch.py35
-rw-r--r--lib/getopt.py47
-rw-r--r--lib/glob.py44
-rw-r--r--lib/grep.py32
-rw-r--r--lib/gwin.py122
-rw-r--r--lib/lambda.py151
-rw-r--r--lib/listwin.py47
-rw-r--r--lib/localtime.py53
-rw-r--r--lib/maccache.py61
-rw-r--r--lib/macglob.py46
-rw-r--r--lib/macpath.py108
-rw-r--r--lib/macshell.py399
-rw-r--r--lib/minmax.py6
-rw-r--r--lib/packmail.py48
-rw-r--r--lib/panel.py281
-rw-r--r--lib/panelparser.py128
-rw-r--r--lib/path.py125
-rw-r--r--lib/poly.py55
-rw-r--r--lib/rand.py12
-rw-r--r--lib/rect.py88
-rw-r--r--lib/selection.py72
-rw-r--r--lib/shutil.py70
-rw-r--r--lib/stat.py57
-rw-r--r--lib/statcache.py86
-rw-r--r--lib/stdwinevents.py46
-rw-r--r--lib/stdwinsupport.py34
-rw-r--r--lib/string.py129
-rw-r--r--lib/sunaudio.py54
-rw-r--r--lib/tablewin.py237
-rw-r--r--lib/tb.py220
-rw-r--r--lib/testall.py416
-rw-r--r--lib/textwin.py119
-rw-r--r--lib/util.py30
-rw-r--r--lib/whrandom.py74
-rw-r--r--lib/zmod.py94
68 files changed, 8425 insertions, 0 deletions
diff --git a/lib/Abstract.py b/lib/Abstract.py
new file mode 100644
index 0000000..385e6e5
--- /dev/null
+++ b/lib/Abstract.py
@@ -0,0 +1,61 @@
+# Abstract classes for parents and children.
+#
+# Do not use as base class -- this is for documentation only.
+#
+# Note that the tree must be built top down (create the parent,
+# then add the children).
+#
+# Also note that the creation methods are not standardized --
+# these have extra parameters dependent on the widget type.
+# For historical reasons, button creation methods are called
+# define() while split creation methods are called create().
+
+class AbstractParent():
+ #
+ # Upcalls from child to parent
+ #
+ def addchild(self, child): unimpl()
+ def delchild(self, child): unimpl()
+ #
+ def need_mouse(self, child): unimpl()
+ def no_mouse(self, child): unimpl()
+ #
+ def need_timer(self, child): unimpl()
+ def no_timer(self, child): unimpl()
+ #
+ # XXX need_kbd, no_kbd; focus???
+ #
+ def begindrawing(self): return unimpl()
+ def beginmeasuring(self): return unimpl()
+ #
+ def change(self, area): unimpl()
+ def scroll(self, (area, (dh, dv))): unimpl()
+ def settimer(self, itimer): unimpl()
+
+class AbstractChild():
+ #
+ # Downcalls from parent to child
+ #
+ def destroy(self): unimpl()
+ #
+ def minsize(self, m): return unimpl()
+ def getbounds(self): return unimpl()
+ def setbounds(self, bounds): unimpl()
+ def draw(self, (d, area)): unimpl()
+ #
+ # Downcalls only made after certain upcalls
+ #
+ def mouse_down(self, detail): unimpl()
+ def mouse_move(self, detail): unimpl()
+ def mouse_up(self, detail): unimpl()
+ #
+ def timer(self): unimpl()
+
+# A "Split" is a child that manages one or more children.
+# (This terminology is due to DEC SRC, except for CSplits.)
+# A child of a split may be another split, a button, a slider, etc.
+# Certain upcalls and downcalls can be handled transparently, but
+# for others (e.g., all geometry related calls) this is not possible.
+
+class AbstractSplit() = AbstractChild(), AbstractParent():
+ pass
diff --git a/lib/Buttons.py b/lib/Buttons.py
new file mode 100644
index 0000000..7c36021
--- /dev/null
+++ b/lib/Buttons.py
@@ -0,0 +1,399 @@
+# Module 'Buttons'
+
+
+# Import module 'rect' renamed as '_rect' to avoid exporting it on
+# 'from Buttons import *'
+#
+import rect
+_rect = rect
+del rect
+
+
+# Field indices in mouse event detail
+#
+_HV = 0
+_CLICKS = 1
+_BUTTON = 2
+_MASK = 3
+
+
+# LabelAppearance provides defaults for all appearance methods.
+# selected state not visible
+# disabled --> crossed out
+# hilited --> inverted
+#
+class LabelAppearance():
+ #
+ # Initialization
+ #
+ def init_appearance(self):
+ self.bounds = _rect.empty
+ self.enabled = 1
+ self.hilited = 0
+ self.selected = 0
+ self.text = ''
+ #
+ # Size enquiry
+ #
+ def minsize(self, m):
+ try:
+ self.text = self.text
+ except NameError:
+ self.text = ''
+ return m.textwidth(self.text) + 6, m.lineheight() + 6
+ #
+ def getbounds(self):
+ return self.bounds
+ #
+ # Changing the parameters
+ #
+ def settext(self, text):
+ self.text = text
+ if self.bounds <> _rect.empty:
+ self.recalctextpos()
+ self.redraw()
+ #
+ def setbounds(self, bounds):
+ if self.bounds <> _rect.empty:
+ self.parent.change(self.bounds)
+ self.bounds = bounds
+ if self.bounds <> _rect.empty:
+ self.recalc()
+ self.parent.change(bounds)
+ #
+ # Changing the state bits
+ #
+ def enable(self, flag):
+ if flag <> self.enabled:
+ self.enabled = flag
+ if self.bounds <> _rect.empty:
+ self.flipenable(self.parent.begindrawing())
+ #
+ def hilite(self, flag):
+ if flag <> self.hilited:
+ self.hilited = flag
+ if self.bounds <> _rect.empty:
+ self.fliphilite(self.parent.begindrawing())
+ #
+ def select(self, flag):
+ if flag <> self.selected:
+ self.selected = flag
+ if self.bounds <> _rect.empty:
+ self.redraw()
+ #
+ # Recalculate the box bounds and text position.
+ # This can be overridden by buttons that draw different boxes
+ # or want their text in a different position.
+ #
+ def recalc(self):
+ if self.bounds <> _rect.empty:
+ self.recalcbounds()
+ self.recalctextpos()
+ #
+ def recalcbounds(self):
+ self.hilitebounds = _rect.inset(self.bounds, (3, 3))
+ self.crossbounds = self.bounds
+ #
+ def recalctextpos(self):
+ (left, top), (right, bottom) = self.bounds
+ m = self.parent.beginmeasuring()
+ h = (left + right - m.textwidth(self.text)) / 2
+ v = (top + bottom - m.lineheight()) / 2
+ self.textpos = h, v
+ #
+ # Generic drawing interface.
+ # Do not override redraw() or draw() methods; override drawit() c.s.
+ #
+ def redraw(self):
+ if self.bounds <> _rect.empty:
+ self.draw(self.parent.begindrawing(), self.bounds)
+ #
+ def draw(self, (d, area)):
+ area = _rect.intersect(area, self.bounds)
+ if area = _rect.empty:
+ return
+ d.cliprect(area)
+ d.erase(self.bounds)
+ self.drawit(d)
+ d.noclip()
+ #
+ # The drawit() method is fairly generic but may be overridden.
+ #
+ def drawit(self, d):
+ self.drawpict(d)
+ if self.text:
+ d.text(self.textpos, self.text)
+ if not self.enabled:
+ self.flipenable(d)
+ if self.hilited:
+ self.fliphilite(d)
+ #
+ # Default drawing detail functions.
+ # Overriding these is normally sufficient to get different
+ # appearances.
+ #
+ def drawpict(self, d):
+ pass
+ #
+ def flipenable(self, d):
+ _xorcross(d, self.crossbounds)
+ #
+ def fliphilite(self, d):
+ d.invert(self.hilitebounds)
+
+
+# ButtonAppearance displays a centered string in a box.
+# selected --> bold border
+# disabled --> crossed out
+# hilited --> inverted
+#
+class ButtonAppearance() = LabelAppearance():
+ #
+ def drawpict(self, d):
+ d.box(_rect.inset(self.bounds, (1, 1)))
+ if self.selected:
+ # Make a thicker box
+ d.box(self.bounds)
+ d.box(_rect.inset(self.bounds, (2, 2)))
+ d.box(_rect.inset(self.bounds, (3, 3)))
+ #
+
+
+# CheckAppearance displays a small square box and a left-justified string.
+# selected --> a cross appears in the box
+# disabled --> whole button crossed out
+# hilited --> box is inverted
+#
+class CheckAppearance() = LabelAppearance():
+ #
+ def minsize(self, m):
+ width, height = m.textwidth(self.text) + 6, m.lineheight() + 6
+ return width + height + m.textwidth(' '), height
+ #
+ def drawpict(self, d):
+ d.box(self.boxbounds)
+ if self.selected: _xorcross(d, self.boxbounds)
+ #
+ def recalcbounds(self):
+ LabelAppearance.recalcbounds(self)
+ (left, top), (right, bottom) = self.bounds
+ self.size = bottom - top - 4
+ self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2)
+ self.hilitebounds = self.boxbounds
+ #
+ def recalctextpos(self):
+ m = self.parent.beginmeasuring()
+ (left, top), (right, bottom) = self.boxbounds
+ h = right + m.textwidth(' ')
+ v = top + (self.size - m.lineheight()) / 2
+ self.textpos = h, v
+ #
+
+
+# RadioAppearance displays a round indicator and a left-justified string.
+# selected --> a dot appears in the indicator
+# disabled --> whole button crossed out
+# hilited --> indicator is inverted
+#
+class RadioAppearance() = CheckAppearance():
+ #
+ def drawpict(self, d):
+ (left, top), (right, bottom) = self.boxbounds
+ radius = self.size / 2
+ h, v = left + radius, top + radius
+ d.circle((h, v), radius)
+ if self.selected:
+ some = radius/3
+ d.paint((h-some, v-some), (h+some, v+some))
+ #
+
+
+# NoReactivity ignores mouse events.
+#
+class NoReactivity():
+ def init_reactivity(self): pass
+
+
+# BaseReactivity defines hooks and asks for mouse events,
+# but provides only dummy mouse event handlers.
+# The trigger methods call the corresponding hooks set by the user.
+# Hooks (and triggers) mean the following:
+# down_hook called on some mouse-down events
+# move_hook called on some mouse-move events
+# up_hook called on mouse-up events
+# on_hook called for buttons with on/off state, when it goes on
+# hook called when a button 'fires' or a radiobutton goes on
+# There are usually extra conditions, e.g., hooks are only called
+# when the button is enabled, or active, or selected (on).
+#
+class BaseReactivity():
+ #
+ def init_reactivity(self):
+ self.down_hook = self.move_hook = self.up_hook = \
+ self.on_hook = self.off_hook = \
+ self.hook = self.active = 0
+ self.parent.need_mouse(self)
+ #
+ def mousetest(self, hv):
+ return _rect.pointinrect(hv, self.bounds)
+ #
+ def mouse_down(self, detail):
+ pass
+ #
+ def mouse_move(self, detail):
+ pass
+ #
+ def mouse_up(self, detail):
+ pass
+ #
+ def down_trigger(self):
+ if self.down_hook: self.down_hook(self)
+ #
+ def move_trigger(self):
+ if self.move_hook: self.move_hook(self)
+ #
+ def up_trigger(self):
+ if self.up_hook: self.up_hook(self)
+ #
+ def on_trigger(self):
+ if self.on_hook: self.on_hook(self)
+ #
+ def off_trigger(self):
+ if self.off_hook: self.off_hook(self)
+ #
+ def trigger(self):
+ if self.hook: self.hook(self)
+
+
+# ToggleReactivity acts like a simple pushbutton.
+# It toggles its hilite state on mouse down events.
+#
+class ToggleReactivity() = BaseReactivity():
+ #
+ def mouse_down(self, detail):
+ if self.enabled and self.mousetest(detail[_HV]):
+ self.active = 1
+ self.hilite(not self.hilited)
+ self.down_trigger()
+ #
+ def mouse_move(self, detail):
+ if self.active:
+ self.move_trigger()
+ #
+ def mouse_up(self, detail):
+ if self.active:
+ self.up_trigger()
+ self.active = 0
+ #
+ def down_trigger(self):
+ if self.hilited:
+ self.on_trigger()
+ else:
+ self.off_trigger()
+ self.trigger()
+ #
+
+
+# TriggerReactivity acts like a fancy pushbutton.
+# It hilites itself while the mouse is down within its bounds.
+#
+class TriggerReactivity() = BaseReactivity():
+ #
+ def mouse_down(self, detail):
+ if self.enabled and self.mousetest(detail[_HV]):
+ self.active = 1
+ self.hilite(1)
+ self.down_trigger()
+ #
+ def mouse_move(self, detail):
+ if self.active:
+ self.hilite(self.mousetest(detail[_HV]))
+ if self.hilited:
+ self.move_trigger()
+ #
+ def mouse_up(self, detail):
+ if self.active:
+ self.hilite(self.mousetest(detail[_HV]))
+ if self.hilited:
+ self.up_trigger()
+ self.trigger()
+ self.active = 0
+ self.hilite(0)
+ #
+
+
+# CheckReactivity handles mouse events like TriggerReactivity,
+# It overrides the up_trigger method to flip its selected state.
+#
+class CheckReactivity() = TriggerReactivity():
+ #
+ def up_trigger(self):
+ self.select(not self.selected)
+ if self.selected:
+ self.on_trigger()
+ else:
+ self.off_trigger()
+ self.trigger()
+
+
+# RadioReactivity turns itself on and the other buttons in its group
+# off when its up_trigger method is called.
+#
+class RadioReactivity() = TriggerReactivity():
+ #
+ def init_reactivity(self):
+ TriggerReactivity.init_reactivity(self)
+ self.group = []
+ #
+ def up_trigger(self):
+ for b in self.group:
+ if b <> self:
+ if b.selected:
+ b.select(0)
+ b.off_trigger()
+ self.select(1)
+ self.on_trigger()
+ self.trigger()
+
+
+# Auxiliary class for 'define' method.
+# Call the initializers in the right order.
+#
+class Define():
+ #
+ def define(self, parent):
+ self.parent = parent
+ parent.addchild(self)
+ self.init_appearance()
+ self.init_reactivity()
+ return self
+ #
+ def destroy(self):
+ self.parent = 0
+ #
+ def definetext(self, (parent, text)):
+ self = self.define(parent)
+ self.settext(text)
+ return self
+
+
+# Subroutine to cross out a rectangle.
+#
+def _xorcross(d, bounds):
+ ((left, top), (right, bottom)) = bounds
+ # This is s bit funny to make it look better
+ left = left + 2
+ right = right - 2
+ top = top + 2
+ bottom = bottom - 3
+ d.xorline(((left, top), (right, bottom)))
+ d.xorline((left, bottom), (right, top))
+
+
+# Ready-made button classes.
+#
+class Label() = NoReactivity(), LabelAppearance(), Define(): pass
+class PushButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass
+class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass
+class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass
+class ToggleButton() = ToggleReactivity(), ButtonAppearance(), Define(): pass
diff --git a/lib/CSplit.py b/lib/CSplit.py
new file mode 100644
index 0000000..a28b1c8
--- /dev/null
+++ b/lib/CSplit.py
@@ -0,0 +1,70 @@
+# A CSplit is a Clock-shaped split: the children are grouped in a circle.
+# The numbering is a little different from a real clock: the 12 o'clock
+# position is called 0, not 12. This is a little easier since Python
+# usually counts from zero. (BTW, there needn't be exactly 12 children.)
+
+
+from math import pi, sin, cos
+from Split import Split
+
+class CSplit() = Split():
+ #
+ def minsize(self, m):
+ # Since things look best if the children are spaced evenly
+ # along the circle (and often all children have the same
+ # size anyway) we compute the max child size and assume
+ # this is each child's size.
+ width, height = 0, 0
+ for child in self.children:
+ wi, he = child.minsize(m)
+ width = max(width, wi)
+ height = max(height, he)
+ # In approximation, the diameter of the circle we need is
+ # (diameter of box) * (#children) / pi.
+ # We approximate pi by 3 (so we slightly overestimate
+ # our minimal size requirements -- not so bad).
+ # Because the boxes stick out of the circle we add the
+ # box size to each dimension.
+ # Because we really deal with ellipses, do everything
+ # separate in each dimension.
+ n = len(self.children)
+ return width + (width*n + 2)/3, height + (height*n + 2)/3
+ #
+ def getbounds(self):
+ return self.bounds
+ #
+ def setbounds(self, bounds):
+ self.bounds = bounds
+ # Place the children. This involves some math.
+ # Compute center positions for children as if they were
+ # ellipses with a diameter about 1/N times the
+ # circumference of the big ellipse.
+ # (There is some rounding involved to make it look
+ # reasonable for small and large N alike.)
+ # XXX One day Python will have automatic conversions...
+ n = len(self.children)
+ fn = float(n)
+ if n = 0: return
+ (left, top), (right, bottom) = bounds
+ width, height = right-left, bottom-top
+ child_width, child_height = width*3/(n+4), height*3/(n+4)
+ half_width, half_height = \
+ float(width-child_width)/2.0, \
+ float(height-child_height)/2.0
+ center_h, center_v = center = (left+right)/2, (top+bottom)/2
+ fch, fcv = float(center_h), float(center_v)
+ alpha = 2.0 * pi / fn
+ for i in range(n):
+ child = self.children[i]
+ fi = float(i)
+ fh, fv = \
+ fch + half_width*sin(fi*alpha), \
+ fcv - half_height*cos(fi*alpha)
+ left, top = \
+ int(fh) - child_width/2, \
+ int(fv) - child_height/2
+ right, bottom = \
+ left + child_width, \
+ top + child_height
+ child.setbounds((left, top), (right, bottom))
+ #
diff --git a/lib/DEVICE.py b/lib/DEVICE.py
new file mode 100644
index 0000000..4f2fadc
--- /dev/null
+++ b/lib/DEVICE.py
@@ -0,0 +1,423 @@
+#/**************************************************************************
+# * *
+# * Copyright (C) 1984, Silicon Graphics, Inc. *
+# * *
+# * These coded instructions, statements, and computer programs contain *
+# * unpublished proprietary information of Silicon Graphics, Inc., and *
+# * are protected by Federal copyright law. They may not be disclosed *
+# * to third parties or copied or duplicated in any form, in whole or *
+# * in part, without the prior written consent of Silicon Graphics, Inc. *
+# * *
+# **************************************************************************/
+#/* file with device definitions (see /usr/include/device.h) */
+
+NULLDEV = 0
+BUTOFFSET = 1
+VALOFFSET = 256
+TIMOFFSET = 515
+XKBDOFFSET = 143
+INOFFSET = 1024
+OUTOFFSET = 1033
+BUTCOUNT = 190
+VALCOUNT = 27
+TIMCOUNT = 4
+XKBDCOUNT = 28
+INCOUNT = 8
+OUTCOUNT = 8
+#
+#
+#
+#
+BUT0 = 1
+BUT1 = 2
+BUT2 = 3
+BUT3 = 4
+BUT4 = 5
+BUT5 = 6
+BUT6 = 7
+BUT7 = 8
+BUT8 = 9
+BUT9 = 10
+BUT10 = 11
+BUT11 = 12
+BUT12 = 13
+BUT13 = 14
+BUT14 = 15
+BUT15 = 16
+BUT16 = 17
+BUT17 = 18
+BUT18 = 19
+BUT19 = 20
+BUT20 = 21
+BUT21 = 22
+BUT22 = 23
+BUT23 = 24
+BUT24 = 25
+BUT25 = 26
+BUT26 = 27
+BUT27 = 28
+BUT28 = 29
+BUT29 = 30
+BUT30 = 31
+BUT31 = 32
+BUT32 = 33
+BUT33 = 34
+BUT34 = 35
+BUT35 = 36
+BUT36 = 37
+BUT37 = 38
+BUT38 = 39
+BUT39 = 40
+BUT40 = 41
+BUT41 = 42
+BUT42 = 43
+BUT43 = 44
+BUT44 = 45
+BUT45 = 46
+BUT46 = 47
+BUT47 = 48
+BUT48 = 49
+BUT49 = 50
+BUT50 = 51
+BUT51 = 52
+BUT52 = 53
+BUT53 = 54
+BUT54 = 55
+BUT55 = 56
+BUT56 = 57
+BUT57 = 58
+BUT58 = 59
+BUT59 = 60
+BUT60 = 61
+BUT61 = 62
+BUT62 = 63
+BUT63 = 64
+BUT64 = 65
+BUT65 = 66
+BUT66 = 67
+BUT67 = 68
+BUT68 = 69
+BUT69 = 70
+BUT70 = 71
+BUT71 = 72
+BUT72 = 73
+BUT73 = 74
+BUT74 = 75
+BUT75 = 76
+BUT76 = 77
+BUT77 = 78
+BUT78 = 79
+BUT79 = 80
+BUT80 = 81
+BUT81 = 82
+BUT82 = 83
+MAXKBDBUT = 83
+BUT100 = 101
+BUT101 = 102
+BUT102 = 103
+BUT110 = 111
+BUT111 = 112
+BUT112 = 113
+BUT113 = 114
+BUT114 = 115
+BUT115 = 116
+BUT116 = 117
+BUT117 = 118
+BUT118 = 119
+BUT119 = 120
+BUT120 = 121
+BUT121 = 122
+BUT122 = 123
+BUT123 = 124
+BUT124 = 125
+BUT125 = 126
+BUT126 = 127
+BUT127 = 128
+BUT128 = 129
+BUT129 = 130
+BUT130 = 131
+BUT131 = 132
+BUT132 = 133
+BUT133 = 134
+BUT134 = 135
+BUT135 = 136
+BUT136 = 137
+BUT137 = 138
+BUT138 = 139
+BUT139 = 140
+BUT140 = 141
+BUT141 = 142
+BUT142 = 143
+BUT143 = 144
+BUT144 = 145
+BUT145 = 146
+BUT146 = 147
+BUT147 = 148
+BUT148 = 149
+BUT149 = 150
+BUT150 = 151
+BUT151 = 152
+BUT152 = 153
+BUT153 = 154
+BUT154 = 155
+BUT155 = 156
+BUT156 = 157
+BUT157 = 158
+BUT158 = 159
+BUT159 = 160
+BUT160 = 161
+BUT161 = 162
+BUT162 = 163
+BUT163 = 164
+BUT164 = 165
+BUT165 = 166
+BUT166 = 167
+BUT167 = 168
+BUT168 = 169
+BUT181 = 182
+BUT182 = 183
+BUT183 = 184
+BUT184 = 185
+BUT185 = 186
+BUT186 = 187
+BUT187 = 188
+BUT188 = 189
+BUT189 = 190
+MOUSE1 = 101
+MOUSE2 = 102
+MOUSE3 = 103
+LEFTMOUSE = 103
+MIDDLEMOUSE = 102
+RIGHTMOUSE = 101
+LPENBUT = 104
+BPAD0 = 105
+BPAD1 = 106
+BPAD2 = 107
+BPAD3 = 108
+LPENVALID = 109
+SWBASE = 111
+SW0 = 111
+SW1 = 112
+SW2 = 113
+SW3 = 114
+SW4 = 115
+SW5 = 116
+SW6 = 117
+SW7 = 118
+SW8 = 119
+SW9 = 120
+SW10 = 121
+SW11 = 122
+SW12 = 123
+SW13 = 124
+SW14 = 125
+SW15 = 126
+SW16 = 127
+SW17 = 128
+SW18 = 129
+SW19 = 130
+SW20 = 131
+SW21 = 132
+SW22 = 133
+SW23 = 134
+SW24 = 135
+SW25 = 136
+SW26 = 137
+SW27 = 138
+SW28 = 139
+SW29 = 140
+SW30 = 141
+SW31 = 142
+SBBASE = 182
+SBPICK = 182
+SBBUT1 = 183
+SBBUT2 = 184
+SBBUT3 = 185
+SBBUT4 = 186
+SBBUT5 = 187
+SBBUT6 = 188
+SBBUT7 = 189
+SBBUT8 = 190
+AKEY = 11
+BKEY = 36
+CKEY = 28
+DKEY = 18
+EKEY = 17
+FKEY = 19
+GKEY = 26
+HKEY = 27
+IKEY = 40
+JKEY = 34
+KKEY = 35
+LKEY = 42
+MKEY = 44
+NKEY = 37
+OKEY = 41
+PKEY = 48
+QKEY = 10
+RKEY = 24
+SKEY = 12
+TKEY = 25
+UKEY = 33
+VKEY = 29
+WKEY = 16
+XKEY = 21
+YKEY = 32
+ZKEY = 20
+ZEROKEY = 46
+ONEKEY = 8
+TWOKEY = 14
+THREEKEY = 15
+FOURKEY = 22
+FIVEKEY = 23
+SIXKEY = 30
+SEVENKEY = 31
+EIGHTKEY = 38
+NINEKEY = 39
+BREAKKEY = 1
+SETUPKEY = 2
+CTRLKEY = 3
+LEFTCTRLKEY = CTRLKEY
+CAPSLOCKKEY = 4
+RIGHTSHIFTKEY = 5
+LEFTSHIFTKEY = 6
+NOSCRLKEY = 13
+ESCKEY = 7
+TABKEY = 9
+RETKEY = 51
+SPACEKEY = 83
+LINEFEEDKEY = 60
+BACKSPACEKEY = 61
+DELKEY = 62
+SEMICOLONKEY = 43
+PERIODKEY = 52
+COMMAKEY = 45
+QUOTEKEY = 50
+ACCENTGRAVEKEY = 55
+MINUSKEY = 47
+VIRGULEKEY = 53
+BACKSLASHKEY = 57
+EQUALKEY = 54
+LEFTBRACKETKEY = 49
+RIGHTBRACKETKEY = 56
+LEFTARROWKEY = 73
+DOWNARROWKEY = 74
+RIGHTARROWKEY = 80
+UPARROWKEY = 81
+PAD0 = 59
+PAD1 = 58
+PAD2 = 64
+PAD3 = 65
+PAD4 = 63
+PAD5 = 69
+PAD6 = 70
+PAD7 = 67
+PAD8 = 68
+PAD9 = 75
+PADPF1 = 72
+PADPF2 = 71
+PADPF3 = 79
+PADPF4 = 78
+PADPERIOD = 66
+PADMINUS = 76
+PADCOMMA = 77
+PADENTER = 82
+LEFTALTKEY = 143
+RIGHTALTKEY = 144
+RIGHTCTRLKEY = 145
+F1KEY = 146
+F2KEY = 147
+F3KEY = 148
+F4KEY = 149
+F5KEY = 150
+F6KEY = 151
+F7KEY = 152
+F8KEY = 153
+F9KEY = 154
+F10KEY = 155
+F11KEY = 156
+F12KEY = 157
+PRINTSCREENKEY = 158
+SCROLLLOCKKEY = 159
+PAUSEKEY = 160
+INSERTKEY = 161
+HOMEKEY = 162
+PAGEUPKEY = 163
+ENDKEY = 164
+PAGEDOWNKEY = 165
+NUMLOCKKEY = 166
+PADVIRGULEKEY = 167
+PADASTERKEY = 168
+PADPLUSKEY = 169
+SGIRESERVED = 256
+DIAL0 = 257
+DIAL1 = 258
+DIAL2 = 259
+DIAL3 = 260
+DIAL4 = 261
+DIAL5 = 262
+DIAL6 = 263
+DIAL7 = 264
+DIAL8 = 265
+MOUSEX = 266
+MOUSEY = 267
+LPENX = 268
+LPENY = 269
+BPADX = 270
+BPADY = 271
+CURSORX = 272
+CURSORY = 273
+GHOSTX = 274
+GHOSTY = 275
+SBTX = 276
+SBTY = 277
+SBTZ = 278
+SBRX = 279
+SBRY = 280
+SBRZ = 281
+SBPERIOD = 282
+TIMER0 = 515
+TIMER1 = 516
+TIMER2 = 517
+TIMER3 = 518
+KEYBD = 513
+RAWKEYBD = 514
+VALMARK = 523
+GERROR = 524
+REDRAW = 528
+WMSEND = 529
+WMREPLY = 530
+WMGFCLOSE = 531
+WMTXCLOSE = 532
+MODECHANGE = 533
+INPUTCHANGE = 534
+QFULL = 535
+PIECECHANGE = 536
+WINCLOSE = 537
+QREADERROR = 538
+WINFREEZE = 539
+WINTHAW = 540
+REDRAWICONIC = 541
+WINQUIT = 542
+DEPTHCHANGE = 543
+KEYBDFNAMES = 544
+KEYBDFSTRINGS = 545
+WINSHUT = 546
+INPUT0 = 1024
+INPUT1 = 1025
+INPUT2 = 1026
+INPUT3 = 1027
+INPUT4 = 1028
+INPUT5 = 1029
+INPUT6 = 1030
+INPUT7 = 1032
+OUTPUT0 = 1033
+OUTPUT1 = 1034
+OUTPUT2 = 1035
+OUTPUT3 = 1036
+OUTPUT4 = 1037
+OUTPUT5 = 1038
+OUTPUT6 = 1039
+OUTPUT7 = 1040
+MAXSGIDEVICE = 20000
+MENUBUTTON = RIGHTMOUSE
diff --git a/lib/GL.py b/lib/GL.py
new file mode 100644
index 0000000..b733e51
--- /dev/null
+++ b/lib/GL.py
@@ -0,0 +1,365 @@
+# Constants defined in <gl.h>
+
+#**************************************************************************
+#* *
+#* Copyright (C) 1984, Silicon Graphics, Inc. *
+#* *
+#* These coded instructions, statements, and computer programs contain *
+#* unpublished proprietary information of Silicon Graphics, Inc., and *
+#* are protected by Federal copyright law. They may not be disclosed *
+#* to third parties or copied or duplicated in any form, in whole or *
+#* in part, without the prior written consent of Silicon Graphics, Inc. *
+#* *
+#**************************************************************************
+
+# Graphics Libary constants
+
+# Booleans
+TRUE = 1
+FALSE = 0
+
+# maximum X and Y screen coordinates
+XMAXSCREEN = 1279
+YMAXSCREEN = 1023
+XMAXMEDIUM = 1023 # max for medium res monitor
+YMAXMEDIUM = 767
+XMAX170 = 645 # max for RS-170
+YMAX170 = 484
+XMAXPAL = 779 # max for PAL
+YMAXPAL = 574
+
+# various hardware/software limits
+ATTRIBSTACKDEPTH = 10
+VPSTACKDEPTH = 8
+MATRIXSTACKDEPTH = 32
+NAMESTACKDEPTH = 1025
+STARTTAG = -2
+ENDTAG = -3
+CPOSX_INVALID = -(2*XMAXSCREEN)
+
+# names for colors in color map loaded by greset
+BLACK = 0
+RED = 1
+GREEN = 2
+YELLOW = 3
+BLUE = 4
+MAGENTA = 5
+CYAN = 6
+WHITE = 7
+
+# popup colors
+PUP_CLEAR = 0
+PUP_COLOR = 1
+PUP_BLACK = 2
+PUP_WHITE = 3
+
+# defines for drawmode
+NORMALDRAW = 0
+PUPDRAW = 1
+OVERDRAW = 2
+UNDERDRAW = 3
+CURSORDRAW = 4
+
+# defines for defpattern
+PATTERN_16 = 16
+PATTERN_32 = 32
+PATTERN_64 = 64
+
+PATTERN_16_SIZE = 16
+PATTERN_32_SIZE = 64
+PATTERN_64_SIZE = 256
+
+# defines for readsource
+SRC_AUTO = 0
+SRC_FRONT = 1
+SRC_BACK = 2
+SRC_ZBUFFER = 3
+SRC_PUP = 4
+SRC_OVER = 5
+SRC_UNDER = 6
+SRC_FRAMEGRABBER = 7
+
+# defines for blendfunction
+BF_ZERO = 0
+BF_ONE = 1
+BF_DC = 2
+BF_SC = 2
+BF_MDC = 3
+BF_MSC = 3
+BF_SA = 4
+BF_MSA = 5
+BF_DA = 6
+BF_MDA = 7
+
+# defines for zfunction
+ZF_NEVER = 0
+ZF_LESS = 1
+ZF_EQUAL = 2
+ZF_LEQUAL = 3
+ZF_GREATER = 4
+ZF_NOTEQUAL = 5
+ZF_GEQUAL = 6
+ZF_ALWAYS = 7
+
+# defines for zsource
+ZSRC_DEPTH = 0
+ZSRC_COLOR = 1
+
+# defines for pntsmooth
+SMP_OFF = 0
+SMP_ON = 1
+
+# defines for linesmooth
+SML_OFF = 0
+SML_ON = 1
+
+# defines for setpup
+PUP_NONE = 0
+PUP_GREY = 1
+
+# defines for glcompat
+GLC_OLDPOLYGON = 0
+GLC_ZRANGEMAP = 1
+
+# defines for curstype
+C16X1 = 0
+C16X2 = 1
+C32X1 = 2
+C32X2 = 3
+CCROSS = 4
+
+# defines for shademodel
+FLAT = 0
+GOURAUD = 1
+
+# defines for logicop
+### LO_ZERO = 0x0
+### LO_AND = 0x1
+### LO_ANDR = 0x2
+### LO_SRC = 0x3
+### LO_ANDI = 0x4
+### LO_DST = 0x5
+### LO_XOR = 0x6
+### LO_OR = 0x7
+### LO_NOR = 0x8
+### LO_XNOR = 0x9
+### LO_NDST = 0xa
+### LO_ORR = 0xb
+### LO_NSRC = 0xc
+### LO_ORI = 0xd
+### LO_NAND = 0xe
+### LO_ONE = 0xf
+
+
+#
+# START defines for getgdesc
+#
+
+GD_XPMAX = 0
+GD_YPMAX = 1
+GD_XMMAX = 2
+GD_YMMAX = 3
+GD_ZMIN = 4
+GD_ZMAX = 5
+GD_BITS_NORM_SNG_RED = 6
+GD_BITS_NORM_SNG_GREEN = 7
+GD_BITS_NORM_SNG_BLUE = 8
+GD_BITS_NORM_DBL_RED = 9
+GD_BITS_NORM_DBL_GREEN = 10
+GD_BITS_NORM_DBL_BLUE = 11
+GD_BITS_NORM_SNG_CMODE = 12
+GD_BITS_NORM_DBL_CMODE = 13
+GD_BITS_NORM_SNG_MMAP = 14
+GD_BITS_NORM_DBL_MMAP = 15
+GD_BITS_NORM_ZBUFFER = 16
+GD_BITS_OVER_SNG_CMODE = 17
+GD_BITS_UNDR_SNG_CMODE = 18
+GD_BITS_PUP_SNG_CMODE = 19
+GD_BITS_NORM_SNG_ALPHA = 21
+GD_BITS_NORM_DBL_ALPHA = 22
+GD_BITS_CURSOR = 23
+GD_OVERUNDER_SHARED = 24
+GD_BLEND = 25
+GD_CIFRACT = 26
+GD_CROSSHAIR_CINDEX = 27
+GD_DITHER = 28
+GD_LINESMOOTH_CMODE = 30
+GD_LINESMOOTH_RGB = 31
+GD_LOGICOP = 33
+GD_NSCRNS = 35
+GD_NURBS_ORDER = 36
+GD_NBLINKS = 37
+GD_NVERTEX_POLY = 39
+GD_PATSIZE_64 = 40
+GD_PNTSMOOTH_CMODE = 41
+GD_PNTSMOOTH_RGB = 42
+GD_PUP_TO_OVERUNDER = 43
+GD_READSOURCE = 44
+GD_READSOURCE_ZBUFFER = 48
+GD_STEREO = 50
+GD_SUBPIXEL_LINE = 51
+GD_SUBPIXEL_PNT = 52
+GD_SUBPIXEL_POLY = 53
+GD_TRIMCURVE_ORDER = 54
+GD_WSYS = 55
+GD_ZDRAW_GEOM = 57
+GD_ZDRAW_PIXELS = 58
+GD_SCRNTYPE = 61
+GD_TEXTPORT = 62
+GD_NMMAPS = 63
+GD_FRAMEGRABBER = 64
+GD_TIMERHZ = 66
+GD_DBBOX = 67
+GD_AFUNCTION = 68
+GD_ALPHA_OVERUNDER = 69
+GD_BITS_ACBUF = 70
+GD_BITS_ACBUF_HW = 71
+GD_BITS_STENCIL = 72
+GD_CLIPPLANES = 73
+GD_FOGVERTEX = 74
+GD_LIGHTING_TWOSIDE = 76
+GD_POLYMODE = 77
+GD_POLYSMOOTH = 78
+GD_SCRBOX = 79
+GD_TEXTURE = 80
+
+# return value for inquiries when there is no limit
+GD_NOLIMIT = 2
+
+# return values for GD_WSYS
+GD_WSYS_NONE = 0
+GD_WSYS_4S = 1
+
+# return values for GD_SCRNTYPE
+GD_SCRNTYPE_WM = 0
+GD_SCRNTYPE_NOWM = 1
+
+#
+# END defines for getgdesc
+#
+
+
+#
+# START NURBS interface definitions
+#
+
+# NURBS Rendering Properties
+N_PIXEL_TOLERANCE = 1
+N_CULLING = 2
+N_DISPLAY = 3
+N_ERRORCHECKING = 4
+N_SUBDIVISIONS = 5
+N_S_STEPS = 6
+N_T_STEPS = 7
+N_TILES = 8
+
+N_SHADED = 1.0
+
+# ---------------------------------------------------------------------------
+# FLAGS FOR NURBS SURFACES AND CURVES
+#
+# Bit: 9876 5432 1 0
+# |tttt|nnnn|f|r| : r - 1 bit = 1 if rational coordinate exists
+# : f - 1 bit = 1 if rational coordinate is before rest
+# : = 0 if rational coordinate is after rest
+# : nnnn - 4 bits for number of coordinates
+# : tttt - 4 bits for type of data (color, position, etc.)
+#
+# NURBS data type
+# N_T_ST 0 parametric space data
+# N_T_XYZ 1 model space data
+#
+# rational or non-rational data and position in memory
+# N_NONRATIONAL 0 non-rational data
+# N_RATAFTER 1 rational data with rat coord after rest
+# N_RATBEFORE 3 rational data with rat coord before rest
+#
+# N_MKFLAG(a,b,c) ((a<<6) | (b<<2) | c)
+#
+# ---------------------------------------------------------------------------
+#
+N_ST = 0x8 # N_MKFLAG( N_T_ST, 2, N_NONRATIONAL )
+N_STW = 0xd # N_MKFLAG( N_T_ST, 3, N_RATAFTER )
+N_WST = 0xf # N_MKFLAG( N_T_ST, 3, N_RATBEFORE )
+N_XYZ = 0x4c # N_MKFLAG( N_T_XYZ, 3, N_NONRATIONAL )
+N_XYZW = 0x51 # N_MKFLAG( N_T_XYZ, 4, N_RATAFTER )
+N_WXYZ = 0x53 # N_MKFLAG( N_T_XYZ, 4, N_RATBEFORE )
+
+#
+# END NURBS interface definitions
+#
+
+
+#
+# START lighting model defines
+#
+
+LMNULL = 0.0
+
+# MATRIX modes
+MSINGLE = 0
+MPROJECTION = 1
+MVIEWING = 2
+
+# LIGHT constants
+MAXLIGHTS = 8
+MAXRESTRICTIONS = 4
+
+# MATERIAL properties
+DEFMATERIAL = 0
+EMISSION = 1
+AMBIENT = 2
+DIFFUSE = 3
+SPECULAR = 4
+SHININESS = 5
+COLORINDEXES = 6
+ALPHA = 7
+
+# LIGHT properties
+DEFLIGHT = 100
+LCOLOR = 101
+POSITION = 102
+
+# LIGHTINGMODEL properties
+DEFLMODEL = 200
+LOCALVIEWER = 201
+ATTENUATION = 202
+
+# TARGET constants
+MATERIAL = 1000
+LIGHT0 = 1100
+LIGHT1 = 1101
+LIGHT2 = 1102
+LIGHT3 = 1103
+LIGHT4 = 1104
+LIGHT5 = 1105
+LIGHT6 = 1106
+LIGHT7 = 1107
+LMODEL = 1200
+
+# lmcolor modes
+LMC_COLOR = 0
+LMC_EMISSION = 1
+LMC_AMBIENT = 2
+LMC_DIFFUSE = 3
+LMC_SPECULAR = 4
+LMC_AD = 5
+LMC_NULL = 6
+
+#
+# END lighting model defines
+#
+
+
+#
+# START distributed graphics library defines
+#
+
+DGLSINK = 0 # sink connection
+DGLLOCAL = 1 # local connection
+DGLTSOCKET = 2 # tcp socket connection
+DGL4DDN = 3 # 4DDN (DECnet)
+
+#
+# END distributed graphics library defines
+#
diff --git a/lib/HVSplit.py b/lib/HVSplit.py
new file mode 100644
index 0000000..d52af8e
--- /dev/null
+++ b/lib/HVSplit.py
@@ -0,0 +1,56 @@
+# HVSplit contains generic code for HSplit and VSplit.
+# HSplit and VSplit are specializations to either dimension.
+
+# XXX This does not yet stretch/shrink children if there is too much
+# XXX or too little space in the split dimension.
+# XXX (NB There is no interface to ask children for stretch preferences.)
+
+from Split import Split
+
+class HVSplit() = Split():
+ #
+ def create(self, (parent, hv)):
+ # hv is 0 or 1 for HSplit or VSplit
+ self = Split.create(self, parent)
+ self.hv = hv
+ return self
+ #
+ def minsize(self, m):
+ hv, vh = self.hv, 1 - self.hv
+ size = [0, 0]
+ for c in self.children:
+ csize = c.minsize(m)
+ if csize[vh] > size[vh]: size[vh] = csize[vh]
+ size[hv] = size[hv] + csize[hv]
+ return size[0], size[1]
+ #
+ def getbounds(self):
+ return self.bounds
+ #
+ def setbounds(self, bounds):
+ self.bounds = bounds
+ hv, vh = self.hv, 1 - self.hv
+ mf = self.parent.beginmeasuring
+ size = self.minsize(mf())
+ # XXX not yet used! Later for stretching
+ maxsize_hv = bounds[1][hv] - bounds[0][hv]
+ origin = [self.bounds[0][0], self.bounds[0][1]]
+ for c in self.children:
+ size = c.minsize(mf())
+ corner = [0, 0]
+ corner[vh] = bounds[1][vh]
+ corner[hv] = origin[hv] + size[hv]
+ c.setbounds((origin[0], origin[1]), \
+ (corner[0], corner[1]))
+ origin[hv] = corner[hv]
+ # XXX stretch
+ # XXX too-small
+ #
+
+class HSplit() = HVSplit():
+ def create(self, parent):
+ return HVSplit.create(self, (parent, 0))
+
+class VSplit() = HVSplit():
+ def create(self, parent):
+ return HVSplit.create(self, (parent, 1))
diff --git a/lib/Histogram.py b/lib/Histogram.py
new file mode 100644
index 0000000..3f6da76
--- /dev/null
+++ b/lib/Histogram.py
@@ -0,0 +1,36 @@
+# Module 'Histogram'
+
+from Buttons import *
+
+# A Histogram displays a histogram of numeric data.
+#
+class HistogramAppearance() = LabelAppearance(), Define():
+ #
+ def define(self, parent):
+ Define.define(self, (parent, ''))
+ self.ydata = []
+ self.scale = (0, 100)
+ return self
+ #
+ def setdata(self, (ydata, scale)):
+ self.ydata = ydata
+ self.scale = scale # (min, max)
+ self.parent.change(self.bounds)
+ #
+ def drawpict(self, d):
+ (left, top), (right, bottom) = self.bounds
+ min, max = self.scale
+ size = max-min
+ width, height = right-left, bottom-top
+ ydata = self.ydata
+ npoints = len(ydata)
+ v1 = top + height # constant
+ h1 = left # changed in loop
+ for i in range(npoints):
+ h0 = h1
+ v0 = top + height - (ydata[i]-min)*height/size
+ h1 = left + (i+1) * width/npoints
+ d.paint((h0, v0), (h1, v1))
+ #
+
+class Histogram() = NoReactivity(), HistogramAppearance(): pass
diff --git a/lib/Sliders.py b/lib/Sliders.py
new file mode 100644
index 0000000..e44b466
--- /dev/null
+++ b/lib/Sliders.py
@@ -0,0 +1,175 @@
+# Module 'Sliders'
+
+
+import stdwin
+from stdwinevents import *
+import rect
+from Buttons import *
+from HVSplit import HSplit
+
+
+# Field indices in event detail
+#
+_HV = 0
+_CLICKS = 1
+_BUTTON = 2
+_MASK = 3
+
+
+# DragSlider is the simplest possible slider.
+# It looks like a button but dragging the mouse left or right
+# changes the controlled value.
+# It does not support any of the triggers or hooks defined by Buttons,
+# but defines its own setval_trigger and setval_hook.
+#
+class DragSliderReactivity() = BaseReactivity():
+ #
+ def mouse_down(self, detail):
+ h, v = hv = detail[_HV]
+ if self.enabled and self.mousetest(hv):
+ self.anchor = h
+ self.oldval = self.val
+ self.active = 1
+ #
+ def mouse_move(self, detail):
+ if self.active:
+ h, v = detail[_HV]
+ self.setval(self.oldval + (h - self.anchor))
+ #
+ def mouse_up(self, detail):
+ if self.active:
+ h, v = detail[_HV]
+ self.setval(self.oldval + (h - self.anchor))
+ self.active = 0
+ #
+
+class DragSliderAppearance() = ButtonAppearance():
+ #
+ # INVARIANTS maintained by the setval method:
+ #
+ # self.min <= self.val <= self.max
+ # self.text = self.pretext + `self.val` + self.postext
+ #
+ # (Notice that unlike Python ranges, the end point belongs
+ # to the range.)
+ #
+ def init_appearance(self):
+ ButtonAppearance.init_appearance(self)
+ self.min = 0
+ self.val = 0
+ self.max = 100
+ self.hook = 0
+ self.pretext = self.postext = ''
+ self.recalctext()
+ #
+ # The 'get*' and 'set*' methods belong to the generic slider interface
+ #
+ def getval(self): return self.val
+ #
+ def sethook(self, hook):
+ self.hook = hook
+ #
+ def setminvalmax(self, (min, val, max)):
+ self.min = min
+ self.max = max
+ self.setval(val)
+ #
+ def settexts(self, (pretext, postext)):
+ self.pretext = pretext
+ self.postext = postext
+ self.recalctext()
+ #
+ def setval(self, val):
+ val = min(self.max, max(self.min, val))
+ if val <> self.val:
+ self.val = val
+ self.recalctext()
+ self.trigger()
+ #
+ def trigger(self):
+ if self.hook:
+ self.hook(self)
+ #
+ def recalctext(self):
+ self.settext(self.pretext + `self.val` + self.postext)
+ #
+
+class DragSlider() = DragSliderReactivity(), DragSliderAppearance(), Define():
+ def definetext(self, (parent, text)):
+ raise RuntimeError, 'DragSlider.definetext() not supported'
+
+
+# Auxiliary class for PushButton incorporated in ComplexSlider
+#
+class _StepButton() = PushButton():
+ def define(self, parent):
+ self = PushButton.define(self, parent)
+ self.step = 0
+ return self
+ def setstep(self, step):
+ self.step = step
+ def definetextstep(self, (parent, text, step)):
+ self = self.definetext(parent, text)
+ self.setstep(step)
+ return self
+ def init_reactivity(self):
+ PushButton.init_reactivity(self)
+ self.parent.need_timer(self)
+ def step_trigger(self):
+ self.parent.setval(self.parent.getval() + self.step)
+ def down_trigger(self):
+ self.step_trigger()
+ self.parent.settimer(5)
+ def timer(self):
+ if self.hilited:
+ self.step_trigger()
+ if self.active:
+ self.parent.settimer(1)
+
+
+# A complex slider is an HSplit initialized to three buttons:
+# one to step down, a dragslider, and one to step up.
+#
+class ComplexSlider() = HSplit():
+ #
+ # Override Slider define() method
+ #
+ def define(self, parent):
+ self = self.create(parent) # HSplit
+ #
+ self.downbutton = _StepButton().definetextstep(self, '-', -1)
+ self.dragbutton = DragSlider().define(self)
+ self.upbutton = _StepButton().definetextstep(self, '+', 1)
+ #
+ return self
+ #
+ # Override HSplit methods
+ #
+ def minsize(self, m):
+ w1, h1 = self.downbutton.minsize(m)
+ w2, h2 = self.dragbutton.minsize(m)
+ w3, h3 = self.upbutton.minsize(m)
+ height = max(h1, h2, h3)
+ w1 = max(w1, height)
+ w3 = max(w3, height)
+ return w1+w2+w3, height
+ #
+ def setbounds(self, bounds):
+ (left, top), (right, bottom) = self.bounds = bounds
+ size = bottom - top
+ self.downbutton.setbounds((left, top), (left+size, bottom))
+ self.dragbutton.setbounds((left+size, top), \
+ (right-size, bottom))
+ self.upbutton.setbounds((right-size, top), (right, bottom))
+ #
+ # Pass other Slider methods on to dragbutton
+ #
+ def getval(self): return self.dragbutton.getval()
+ def sethook(self, hook): self.dragbutton.sethook(hook)
+ def setminvalmax(self, args): self.dragbutton.setminvalmax(args)
+ def settexts(self, args): self.dragbutton.settexts(args)
+ def setval(self, val): self.dragbutton.setval(val)
+ def enable(self, flag):
+ self.downbutton.enable(flag)
+ self.dragbutton.enable(flag)
+ self.upbutton.enable(flag)
diff --git a/lib/Soundogram.py b/lib/Soundogram.py
new file mode 100644
index 0000000..38dd5ef
--- /dev/null
+++ b/lib/Soundogram.py
@@ -0,0 +1,36 @@
+# Module 'Soundogram'
+
+import audio
+from Histogram import Histogram
+
+class Soundogram() = Histogram():
+ #
+ def define(self, (win, chunk)):
+ width, height = corner = win.getwinsize()
+ bounds = (0, 0), corner
+ self.chunk = chunk
+ self.step = (len(chunk)-1)/(width/2+1) + 1
+ ydata = _make_ydata(chunk, self.step)
+ return Histogram.define(self, (win, bounds, ydata, (0, 128)))
+ #
+ def setchunk(self, chunk):
+ self.chunk = chunk
+ self.recompute()
+ #
+ def recompute(self):
+ (left, top), (right, bottom) = self.bounds
+ width = right - left
+ self.step = (len(chunk)-1)/width + 1
+ ydata = _make_ydata(chunk, self.step)
+ self.setdata(ydata, (0, 128))
+ #
+
+
+def _make_ydata(chunk, step):
+ ydata = []
+ for i in range(0, len(chunk), step):
+ piece = audio.chr2num(chunk[i:i+step])
+ mi, ma = min(piece), max(piece)
+ y = max(abs(mi), abs(ma))
+ ydata.append(y)
+ return ydata
diff --git a/lib/Split.py b/lib/Split.py
new file mode 100644
index 0000000..8c0a8dc
--- /dev/null
+++ b/lib/Split.py
@@ -0,0 +1,116 @@
+# Generic Split implementation.
+# Use as a base class for other splits.
+# Derived classes should at least implement the methods that call
+# unimpl() below: minsize(), getbounds() and setbounds().
+
+Error = 'Split.Error' # Exception
+
+import rect
+from util import remove
+
+class Split():
+ #
+ # Calls from creator
+ # NB derived classes may add parameters to create()
+ #
+ def create(self, parent):
+ parent.addchild(self)
+ self.parent = parent
+ self.children = []
+ self.mouse_interest = []
+ self.timer_interest = []
+ self.mouse_focus = 0
+ return self
+ #
+ # Downcalls from parent to child
+ #
+ def destroy(self):
+ self.parent = None
+ for child in self.children:
+ child.destroy()
+ del self.children[:]
+ del self.mouse_interest[:]
+ del self.timer_interest[:]
+ self.mouse_focus = None
+ #
+ def minsize(self, m): return unimpl()
+ def getbounds(self): return unimpl()
+ def setbounds(self, bounds): unimpl()
+ #
+ def draw(self, d_detail):
+ # (Could avoid calls to children outside the area)
+ for child in self.children:
+ child.draw(d_detail)
+ #
+ # Downcalls only made after certain upcalls
+ #
+ def mouse_down(self, detail):
+ if self.mouse_focus:
+ self.mouse_focus.mouse_down(detail)
+ p = detail[0]
+ for child in self.mouse_interest:
+ if rect.pointinrect(p, child.getbounds()):
+ self.mouse_focus = child
+ child.mouse_down(detail)
+ def mouse_move(self, detail):
+ if self.mouse_focus:
+ self.mouse_focus.mouse_move(detail)
+ def mouse_up(self, detail):
+ if self.mouse_focus:
+ self.mouse_focus.mouse_up(detail)
+ self.mouse_focus = 0
+ #
+ def timer(self):
+ for child in self.timer_interest:
+ child.timer()
+ #
+ # Upcalls from child to parent
+ #
+ def addchild(self, child):
+ if child in self.children:
+ raise Error, 'addchild: child already inlist'
+ self.children.append(child)
+ def delchild(self, child):
+ if child not in self.children:
+ raise Error, 'delchild: child not in list'
+ remove(child, self.children)
+ if child in self.mouse_interest:
+ remove(child, self.mouse_interest)
+ if child in self.timer_interest:
+ remove(child, self.timer_interest)
+ if child = self.mouse_focus:
+ self.mouse_focus = 0
+ #
+ def need_mouse(self, child):
+ if child not in self.mouse_interest:
+ self.mouse_interest.append(child)
+ self.parent.need_mouse(self)
+ def no_mouse(self, child):
+ if child in self.mouse_interest:
+ remove(child, self.mouse_interest)
+ if not self.mouse_interest:
+ self.parent.no_mouse(self)
+ #
+ def need_timer(self, child):
+ if child not in self.timer_interest:
+ self.timer_interest.append(child)
+ self.parent.need_timer(self)
+ def no_timer(self, child):
+ if child in self.timer_interest:
+ remove(child, self.timer_interest)
+ if not self.timer_interest:
+ self.parent.no_timer(self)
+ #
+ # The rest are transparent:
+ #
+ def begindrawing(self):
+ return self.parent.begindrawing()
+ def beginmeasuring(self):
+ return self.parent.beginmeasuring()
+ #
+ def change(self, area):
+ self.parent.change(area)
+ def scroll(self, area_vector):
+ self.parent.scroll(area_vector)
+ def settimer(self, itimer):
+ self.parent.settimer(itimer)
diff --git a/lib/StripChart.py b/lib/StripChart.py
new file mode 100644
index 0000000..fc707cb
--- /dev/null
+++ b/lib/StripChart.py
@@ -0,0 +1,68 @@
+# Module 'StripChart'
+
+import rect
+from Buttons import LabelAppearance, NoReactivity
+
+# A StripChart doesn't really look like a label but it needs a base class.
+# LabelAppearance allows it to be disabled and hilited.
+
+class StripChart() = LabelAppearance(), NoReactivity():
+ #
+ def define(self, (parent, scale)):
+ self.parent = parent
+ parent.addchild(self)
+ self.init_appearance()
+ self.init_reactivity()
+ self.ydata = []
+ self.scale = scale
+ self.resetbounds()
+ return self
+ #
+ def setbounds(self, bounds):
+ LabelAppearance.setbounds(self, bounds)
+ self.resetbounds()
+ #
+ def resetbounds(self):
+ (left, top), (right, bottom) = self.bounds
+ self.width = right-left
+ self.height = bottom-top
+ excess = len(self.ydata) - self.width
+ if excess > 0:
+ del self.ydata[:excess]
+ elif excess < 0:
+ while len(self.ydata) < self.width:
+ self.ydata.insert(0, 0)
+ #
+ def append(self, y):
+ self.ydata.append(y)
+ excess = len(self.ydata) - self.width
+ if excess > 0:
+ del self.ydata[:excess]
+ if self.bounds <> rect.empty:
+ self.parent.scroll(self.bounds, (-excess, 0))
+ if self.bounds <> rect.empty:
+ (left, top), (right, bottom) = self.bounds
+ i = len(self.ydata)
+ area = (left+i-1, top), (left+i, bottom)
+ self.draw(self.parent.begindrawing(), area)
+ #
+ def draw(self, (d, area)):
+ area = rect.intersect(area, self.bounds)
+ if area = rect.empty:
+ print 'mt'
+ return
+ d.cliprect(area)
+ d.erase(self.bounds)
+ (a_left, a_top), (a_right, a_bottom) = area
+ (left, top), (right, bottom) = self.bounds
+ height = bottom - top
+ i1 = a_left - left
+ i2 = a_right - left
+ for i in range(max(0, i1), min(len(self.ydata), i2)):
+ split = bottom-self.ydata[i]*height/self.scale
+ d.paint((left+i, split), (left+i+1, bottom))
+ if not self.enabled:
+ self.flipenable(d)
+ if self.hilited:
+ self.fliphilite(d)
+ d.noclip()
diff --git a/lib/Tcl.py b/lib/Tcl.py
new file mode 100644
index 0000000..9d45e37
--- /dev/null
+++ b/lib/Tcl.py
@@ -0,0 +1,421 @@
+# An emulator for John Ousterhout's 'Tcl' language in Python (wow!).
+# Currently only the most basic commands are implemented.
+#
+# Design choices:
+#
+# - Names used for functions are not exactly those used by C Tcl.
+# In Python, names without 'Tcl_' prefix are acceptable because
+# names are less global than in C (and often they are prefixed
+# with a module name anyway). Parameter conventions also differ.
+#
+# - The Tcl Interpreter type is implemented using a Python class.
+# Almost all functions with an Interpreter as first parameter are
+# methods of this class.
+# Applications can create derived classes to add additional commands
+# or to override specific internal functions.
+#
+# - Tcl errors are mapped to Python exceptions.
+# (I bet Ousterhout would have done the same in a language with
+# a proper exception mechanism).
+#
+# - Tcl expressions are evaluated by Python's built-in function eval().
+# This makes Python Tcl scripts incompatible with C Tcl scripts,
+# but is the only sensible solution for a quick-and-dirty version.
+# It also makes an escape to Python possible.
+#
+# - The Backslash function interprets \<newline>, since it
+# can return a string instead of a character.
+
+
+from TclUtil import *
+
+
+# Exceptions used to signify 'break' and 'continue'
+
+TclBreak = 'TclBreak'
+TclContinue = 'TclContinue'
+TclReturn = 'TclReturn'
+
+
+class CmdBuf():
+ #
+ def Create(buffer):
+ buffer.string = ''
+ return buffer
+ #
+ def Assemble(buffer, str):
+ buffer.string = buffer.string + str
+ if buffer.string[-1:] = '\n':
+ i, end = 0, len(buffer.string)
+ try:
+ while i < end:
+ list, i = FindNextCommand( \
+ buffer.string, i, end, 0)
+ except TclMatchingError:
+ return ''
+ except TclSyntaxError:
+ pass # Let Eval() return the error
+ ret = buffer.string
+ buffer.string = ''
+ return ret
+ else:
+ return ''
+
+
+class _Frame():
+ def Create(frame):
+ frame.locals = {}
+ return frame
+
+class _Proc():
+ #
+ def Create(proc, (interp, args, body)):
+ proc.interp = interp
+ proc.args = SplitList(args) # Do this once here
+ proc.body = body
+ return proc
+ #
+ def Call(proc, argv):
+ if len(argv) <> len(proc.args)+1:
+ raise TclRuntimeError, \
+ 'wrong # args to proc "' + \
+ argv[0] + '"'
+ # XXX No defaults or variable length 'args' yet
+ frame = _Frame().Create()
+ for i in range(len(proc.args)):
+ frame.locals[proc.args[i]] = argv[i+1]
+ proc.interp.stack.append(frame)
+ try:
+ value = proc.interp.Eval(proc.body)
+ except TclReturn, value:
+ pass
+ del proc.interp.stack[-1:]
+ return value
+
+
+import regexp
+_expand_prog = regexp.compile('([^[$\\]+|\n)*')
+del regexp
+
+class Interpreter():
+ #
+ def Create(interp):
+ interp.globals = {}
+ interp.commands = {}
+ interp.stack = []
+ interp.commands['break'] = interp.BreakCmd
+ interp.commands['concat'] = interp.ConcatCmd
+ interp.commands['continue'] = interp.ContinueCmd
+ interp.commands['echo'] = interp.EchoCmd
+ interp.commands['eval'] = interp.EvalCmd
+ interp.commands['expr'] = interp.ExprCmd
+ interp.commands['for'] = interp.ForCmd
+ interp.commands['glob'] = interp.GlobCmd
+ interp.commands['global'] = interp.GlobalCmd
+ interp.commands['if'] = interp.IfCmd
+ interp.commands['index'] = interp.IndexCmd
+ interp.commands['list'] = interp.ListCmd
+ interp.commands['proc'] = interp.ProcCmd
+ interp.commands['rename'] = interp.RenameCmd
+ interp.commands['return'] = interp.ReturnCmd
+ interp.commands['set'] = interp.SetCmd
+ return interp
+ #
+ def Delete(interp):
+ #
+ # Only break circular references here;
+ # most things will be garbage-collected.
+ #
+ for name in interp.commands.keys():
+ del interp.commands[name]
+ #
+ def CreateCommand(interp, (name, proc)):
+ interp.commands[name] = proc
+ #
+ def DeleteCommand(interp, (name)):
+ del interp.commands[name]
+ #
+ # Local variables are maintained on the stack.
+ # A local variable with value "None" is a dummy
+ # meaning that the corresponding global variable
+ # should be used.
+ #
+ def GetVar(interp, varName):
+ dict = interp.globals
+ if interp.stack:
+ d = interp.stack[-1:][0].locals
+ if d.has_key(varName) and d[varName] = None:
+ pass
+ else:
+ dict = d
+ if not dict.has_key(varName):
+ raise TclRuntimeError, \
+ 'Variable "' + varName + '" not found'
+ return dict[varName]
+ #
+ def SetVar(interp, (varName, newValue)):
+ dict = interp.globals
+ if interp.stack:
+ d = interp.stack[-1:][0].locals
+ if d.has_key(varName) and d[varName] = None:
+ pass
+ else:
+ dict = d
+ dict[varName] = newValue
+ #
+ def Expand(interp, (str, i, end)):
+ if end <= i: return ''
+ if str[i] = '{' and str[end-1] = '}':
+ return str[i+1:end-1]
+ if str[i] = '"' and str[end-1] = '"':
+ i, end = i+1, end-1
+ result = ''
+ while i < end:
+ c = str[i]
+ if c = '\\':
+ x, i = Backslash(str, i, end)
+ result = result + x
+ elif c = '[':
+ j = BalanceBrackets(str, i, end)
+ x = interp.EvalBasic(str, i+1, j-1, 1)
+ result = result + x
+ i = j
+ elif c = '$':
+ i = i+1
+ j = FindVarName(str, i, end)
+ name = str[i:j]
+ i = j
+ if not name:
+ result = result + '$'
+ else:
+ if name[:1] = '{' and name[-1:] = '}':
+ name = name[1:-1]
+ result = result + interp.GetVar(name)
+ else:
+ j = _expand_prog.exec(str, i)
+ j = min(j, end)
+ result = result + str[i:j]
+ i = j
+ return result
+ #
+ def EvalBasic(interp, (str, i, end, bracketed)):
+ result = ''
+ while i < end:
+ indexargv, i = FindNextCommand( \
+ str, i, end, bracketed)
+ if indexargv:
+ argv = []
+ for x, y in indexargv:
+ arg = interp.Expand(str, x, y)
+ argv.append(arg)
+ name = argv[0]
+ if not interp.commands.has_key(name):
+ raise TclRuntimeError, \
+ 'Command "' + name + \
+ '" not found'
+ result = interp.commands[name](argv)
+ return result
+ #
+ def Eval(interp, str):
+ return interp.EvalBasic(str, 0, len(str), 0)
+ #
+ def ExprBasic(interp, (str, begin, end)):
+ expr = interp.Expand(str, begin, end)
+ i = SkipSpaces(expr, 0, len(expr))
+ expr = expr[i:]
+ try:
+ return eval(expr, {})
+ except (NameError, TypeError, RuntimeError, EOFError), msg:
+ import sys
+ raise TclRuntimeError, sys.exc_type + ': ' + msg
+ #
+ def Expr(interp, str):
+ return interp.ExprBasic(str, 0, len(str))
+ #
+ # The rest are command implementations
+ #
+ def BreakCmd(interp, argv):
+ if len(argv) <> 1:
+ raise TclRuntimeError, 'usage: break'
+ raise TclBreak
+ #
+ def ConcatCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: concat arg ...'
+ return Concat(argv[1:])
+ #
+ def ContinueCmd(interp, argv):
+ if len(argv) <> 1:
+ raise TclRuntimeError, 'usage: continue'
+ raise TclContinue
+ #
+ def EchoCmd(interp, argv):
+ for arg in argv[1:]: print arg,
+ print
+ return ''
+ #
+ def EvalCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: eval arg [arg ...]'
+ str = Concat(argv[1:])
+ return interp.Eval(str)
+ #
+ def ExprCmd(interp, argv):
+ if len(argv) <> 2:
+ raise TclRuntimeError, 'usage: expr expression'
+ expr = argv[1]
+ result = interp.Expr(expr)
+ if type(result) <> type(''): result = `result`
+ return result
+ #
+ def ForCmd(interp, argv):
+ if len(argv) <> 5:
+ raise TclRuntimeError, \
+ 'usage: for start test next body'
+ x = interp.Eval(argv[1])
+ while interp.Expr(argv[2]):
+ try:
+ x = interp.Eval(argv[4])
+ except TclBreak:
+ break
+ except TclContinue:
+ pass
+ x = interp.Eval(argv[3])
+ return ''
+ #
+ def GlobCmd(interp, argv):
+ import macglob
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: glob pattern ...'
+ list = []
+ for pat in argv[1:]:
+ list = list + macglob.glob(pat)
+ if not list:
+ raise TclRuntimeError, 'no match for glob pattern(s)'
+ return BuildList(list)
+ #
+ def GlobalCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: global varname ...'
+ if not interp.stack:
+ raise TclRuntimeError, 'global used outside proc'
+ dict = interp.stack[-1:][0].locals
+ for name in argv[1:]:
+ dict[name] = None
+ return ''
+ #
+ def IfCmd(interp, argv):
+ argv = argv[:]
+ if len(argv) > 2 and argv[2] = 'then': del argv[2]
+ if len(argv) > 3 and argv[3] = 'else': del argv[3]
+ if not 3 <= len(argv) <= 4:
+ raise TclRuntimeError, \
+ 'usage: if test [then] trueBody [else] falseBody'
+ if interp.Expr(argv[1]):
+ return interp.Eval(argv[2])
+ if len(argv) > 3:
+ return interp.Eval(argv[3])
+ return ''
+ #
+ def IndexCmd(interp, argv):
+ if len(argv) <> 3:
+ raise TclRuntimeError, 'usage: index value index'
+ import string
+ try:
+ index = string.atoi(argv[2])
+ if index < 0: raise string.atoi_error
+ except string.atoi_error:
+ raise TclRuntimeError, 'bad index: ' + argv[2]
+ list = SplitList(argv[1])
+ if index >= len(list): return ''
+ return list[index]
+ #
+ def ListCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: list arg ...'
+ return BuildList(argv[1:])
+ #
+ def ProcCmd(interp, argv):
+ if len(argv) <> 4:
+ raise TclRuntimeError, 'usage: proc name args body'
+ x = _Proc().Create(interp, argv[2], argv[3])
+ interp.CreateCommand(argv[1], x.Call)
+ return ''
+ #
+ def RenameCmd(interp, argv):
+ if len(argv) <> 3:
+ raise TclRuntimeError, 'usage: rename oldName newName'
+ oldName, newName = argv[1], argv[2]
+ if not interp.commands.has_key(oldName):
+ raise TclRuntimeError, \
+ 'command "' + oldName + '" not found'
+ if newName: interp.commands[newName] = interp.commands[oldName]
+ del interp.commands[oldName]
+ return ''
+ #
+ def ReturnCmd(interp, argv):
+ if not 1 <= len(argv) <= 2:
+ raise TclRuntimeError, 'usage: return [arg]'
+ if len(argv) = 1: raise TclReturn, ''
+ raise TclReturn, argv[1]
+ #
+ def SetCmd(interp, argv):
+ n = len(argv)
+ if not 2 <= n <= 3:
+ raise TclRuntimeError, 'usage: set varname [newvalue]'
+ if n = 2: return interp.GetVar(argv[1])
+ interp.SetVar(argv[1], argv[2])
+ return ''
+
+
+# The rest are just demos:
+
+def MainLoop(interp):
+ buffer = CmdBuf().Create()
+ if not interp.globals.has_key('ps1'): interp.globals['ps1'] = '% '
+ if not interp.globals.has_key('ps2'): interp.globals['ps2'] = ''
+ psname = 'ps1'
+ while 1:
+ try:
+ line = raw_input(interp.globals[psname])
+ except (EOFError, KeyboardInterrupt):
+ print
+ break
+ line = buffer.Assemble(line + '\n')
+ if not line:
+ psname = 'ps2'
+ else:
+ psname = 'ps1'
+ try:
+ x = interp.Eval(line)
+ if x <> '': print 'Result:', `x`
+ except (TclRuntimeError, TclSyntaxError, \
+ TclMatchingError), msg:
+ print 'Error:', msg
+ except (TclBreak, TclContinue):
+ print 'Error: break or continue outside loop'
+ except TclReturn, value:
+ # Return outside proc returns to main loop
+ if value: print value
+
+
+the_interpreter = Interpreter().Create()
+
+def main():
+ MainLoop(the_interpreter)
+
+
+# XXX To do:
+# for proc: "args" and default arguments
+# More commands:
+# case
+# uplevel
+# info
+# string
+# list operations
+# error, catch
+# print
+# scan, format
+# source
+# history?
+# others?
diff --git a/lib/TclShell.py b/lib/TclShell.py
new file mode 100644
index 0000000..78d8dc4
--- /dev/null
+++ b/lib/TclShell.py
@@ -0,0 +1,255 @@
+# Tcl-based shell (for the Macintosh)
+
+import TclUtil
+import Tcl
+from Tcl import Interpreter, TclRuntimeError
+import mac
+import macpath
+from macpath import isfile, isdir, exists
+
+UsageError = TclRuntimeError
+
+class ShellInterpreter() = Interpreter():
+ #
+ def ResetVariables(interp):
+ interp.globals['ps1'] = '$ '
+ interp.globals['ps2'] = '> '
+ interp.globals['home'] = mac.getcwd()
+ #
+ def DefineCommands(interp):
+ interp.commands['cd'] = interp.CdCmd
+ interp.commands['grep'] = interp.GrepCmd
+ interp.commands['ls'] = interp.LsCmd
+ interp.commands['mkdir'] = interp.MkdirCmd
+ interp.commands['mv'] = interp.MvCmd
+ interp.commands['pg'] = interp.PgCmd
+ interp.commands['pwd'] = interp.PwdCmd
+ interp.commands['rm'] = interp.RmCmd
+ interp.commands['rmdir'] = interp.RmdirCmd
+ interp.commands['sync'] = interp.SyncCmd
+ #
+ def Reset(interp):
+ interp.ResetVariables()
+ interp.DefineCommands()
+ #
+ def Create(interp):
+ interp = Interpreter.Create(interp) # initialize base class
+ interp.Reset()
+ return interp
+ #
+ # Command-implementing functions
+ #
+ def CdCmd(interp, argv):
+ if len(argv) > 2:
+ raise UsageError, 'usage: cd [dirname]'
+ if len(argv) = 2:
+ chdirto(argv[1])
+ else:
+ chdirto(interp.globals['home'])
+ return ''
+ #
+ def GrepCmd(interp, argv):
+ if len(argv) < 3:
+ raise UsageError, 'usage: grep regexp file ...'
+ import regexp
+ try:
+ prog = regexp.compile(argv[1])
+ except regexp.error, msg:
+ raise TclRuntimeError, \
+ ('grep', argv[1], ': bad regexp :', msg)
+ for file in argv[2:]:
+ grepfile(prog, file)
+ return ''
+ #
+ def LsCmd(interp, argv):
+ if len(argv) < 2:
+ lsdir(':')
+ else:
+ for dirname in argv[1:]:
+ lsdir(dirname)
+ return ''
+ #
+ def MkdirCmd(interp, argv):
+ if len(argv) < 2:
+ raise UsageError, 'usage: mkdir name ...'
+ for name in argv[1:]:
+ makedir(name)
+ return ''
+ #
+ def MvCmd(interp, argv):
+ if len(argv) <> 3:
+ raise UsageError, 'usage: mv src dst'
+ src, dst = argv[1], argv[2]
+ if not exists(src):
+ raise TclRuntimeError, \
+ ('mv', src, dst, ': source does not exist')
+ if exists(dst):
+ raise TclRuntimeError, \
+ ('mv', src, dst, ': destination already exists')
+ try:
+ mac.rename(src, dst)
+ except mac.error, msg:
+ raise TclRuntimeError, \
+ (src, dst, ': rename failed :', msg)
+ return ''
+ #
+ def PgCmd(interp, argv):
+ if len(argv) < 2:
+ raise UsageError, 'usage: page file ...'
+ for name in argv[1:]:
+ pagefile(name)
+ return ''
+ #
+ def PwdCmd(interp, argv):
+ if len(argv) > 1:
+ raise UsageError, 'usage: pwd'
+ else:
+ return mac.getcwd()
+ #
+ def RmCmd(interp, argv):
+ if len(argv) < 2:
+ raise UsageError, 'usage: rm file ...'
+ for name in argv[1:]:
+ remove(name)
+ return ''
+ #
+ def RmdirCmd(interp, argv):
+ if len(argv) < 2:
+ raise UsageError, 'usage: rmdir dir ...'
+ for name in argv[1:]:
+ rmdir(name)
+ return ''
+ #
+ def SyncCmd(interp, argv):
+ if len(argv) > 1:
+ raise UsageError, 'usage: sync'
+ try:
+ mac.sync()
+ except mac.error, msg:
+ raise TclRuntimeError, ('sync failed :', msg)
+ #
+
+def chdirto(dirname):
+ try:
+ mac.chdir(dirname)
+ except mac.error, msg:
+ raise TclRuntimeError, (dirname, ': chdir failed :', msg)
+
+def grepfile(prog, file):
+ try:
+ fp = open(file, 'r')
+ except RuntimeError, msg:
+ raise TclRuntimeError, (file, ': open failed :', msg)
+ lineno = 0
+ while 1:
+ line = fp.readline()
+ if not line: break
+ lineno = lineno+1
+ if prog.exec(line):
+ print file+'('+`lineno`+'):', line,
+
+def lsdir(dirname):
+ if not isdir(dirname):
+ print dirname, ': no such directory'
+ return
+ names = mac.listdir(dirname)
+ lsfiles(names, dirname)
+
+def lsfiles(names, dirname):
+ names = names[:] # Make a copy so we can modify it
+ for i in range(len(names)):
+ name = names[i]
+ if isdir(macpath.cat(dirname, name)):
+ names[i] = ':' + name + ':'
+ columnize(names)
+
+def makedir(name):
+ if exists(name):
+ print name, ': already exists'
+ return
+ try:
+ mac.mkdir(name, 0777)
+ except mac.error, msg:
+ raise TclRuntimeError, (name, ': mkdir failed :', msg)
+
+def pagefile(name):
+ import string
+ if not isfile(name):
+ print name, ': no such file'
+ return
+ LINES = 24 - 1
+ # For THINK C 3.0, make the path absolute:
+ # if not macpath.isabs(name):
+ # name = macpath.cat(mac.getcwd(), name)
+ try:
+ fp = open(name, 'r')
+ except RuntimeError, msg:
+ raise TclRuntimeError, (name, ': open failed :', msg)
+ line = fp.readline()
+ while line:
+ for i in range(LINES):
+ print line,
+ line = fp.readline()
+ if not line: break
+ if line:
+ try:
+ more = raw_input('[more]')
+ except (EOFError, KeyboardInterrupt):
+ print
+ break
+ if string.strip(more)[:1] in ('q', 'Q'):
+ break
+
+def remove(name):
+ if not isfile(name):
+ print name, ': no such file'
+ return
+ try:
+ mac.unlink(name)
+ except mac.error, msg:
+ raise TclRuntimeError, (name, ': unlink failed :', msg)
+
+def rmdir(name):
+ if not isdir(name):
+ raise TclRuntimeError, (name, ': no such directory')
+ try:
+ mac.rmdir(name)
+ except mac.error, msg:
+ raise TclRuntimeError, (name, ': rmdir failed :', msg)
+
+def printlist(list):
+ for word in list:
+ print word,
+
+def columnize(list):
+ import string
+ COLUMNS = 80-1
+ n = len(list)
+ colwidth = maxwidth(list)
+ ncols = (COLUMNS + 1) / (colwidth + 1)
+ if ncols < 1: ncols = 1
+ nrows = (n + ncols - 1) / ncols
+ for irow in range(nrows):
+ line = ''
+ for icol in range(ncols):
+ i = irow + nrows*icol
+ if 0 <= i < n:
+ word = list[i]
+ if i+nrows < n:
+ word = string.ljust(word, colwidth)
+ if icol > 0:
+ word = ' ' + word
+ line = line + word
+ print line
+
+def maxwidth(list):
+ width = 0
+ for word in list:
+ if len(word) > width:
+ width = len(word)
+ return width
+
+the_interpreter = ShellInterpreter().Create()
+
+def main():
+ Tcl.MainLoop(the_interpreter)
diff --git a/lib/TclUtil.py b/lib/TclUtil.py
new file mode 100644
index 0000000..0990530
--- /dev/null
+++ b/lib/TclUtil.py
@@ -0,0 +1,377 @@
+# Utilities used by 'Tcl' emulator.
+
+
+# Many functions in this file parse specific constructs from strings.
+# In order to limit the number of slice operations (the strings can
+# be very large), they always receive indices into the string that
+# indicate the slice of the string that should be considered.
+# The return value is in general another index, pointing to the first
+# character in the string beyond the recognized construct.
+# Errors are reported as exceptions (TclSyntaxError, TclMatchingError).
+# A few functions have multiple return values.
+
+
+# For efficiency, the Tcl "tokenizing" routines used pre-compiled
+# regular expressions. This is less readable but should be much faster
+# than scanning the string a character at a time.
+#
+# The global variables
+# containing the compiled regexp's are named _foo_prog where foo is
+# an indication of the function that uses them.
+#
+# The patterns always
+# have the form <something>* so they always match at the start of the
+# search buffer---maybe with the empty string. This makes it possible
+# to use the expression "_foo_prog.exec(str, i)[0][1]" to find the first
+# character beyond the matched string. Note that this may be beyond the
+# end variable -- where this matters, "min(i, end)" is used.
+
+# Constructs that cannot
+# be recognized by a finite automaton (like matching braces) are scanned
+# by a hybrid technique where the regular expression excludes the
+# braces.
+#
+# Many regular expressions contain an expression that matches
+# a Tcl backslash sequence as a subpart:
+# \\\\C?M?(.|\n)
+#
+# This is a bit hard to
+# read because the backslash contained in it must be doubled twice:
+# once to get past Python's backslash mechanism, once to get past that
+# of regular expressions. It uses (.|\n) to match absolutely
+# *every character*, becase the MULTILINE regular expression package does
+# not accept '\n' as a match for '.'.
+#
+# There is also a simplification in the pattern for backslashes:
+# *any* single character following a backslash is escaped,
+# so hex and octal
+# excapes are not scanned fully. The forms \Cx, \Mx and \CMx are
+# scanned correctly, as these may hide a special character.
+# (This does not invalidate the recognition of strings, although the
+# match is effectuated in a different way than by the Backslash function.)
+
+import regexp
+
+
+# Exceptions raised for various error conditions.
+
+TclAssertError = 'Tcl assert error'
+TclSyntaxError = 'Tcl syntax error'
+TclRuntimeError = 'Tcl runtime error'
+TclMatchingError = 'Tcl matching error'
+
+
+# Find a variable name.
+# A variable name is either a (possiblly empty) sequence of letters,
+# digits and underscores, or anything enclosed in matching braces.
+# Return the index past the end of the name.
+
+_varname_prog = regexp.compile('[a-zA-Z0-9_]*')
+
+def FindVarName(str, i, end):
+ if i < end and str[i] = '{': return BalanceBraces(str, i, end)
+ i = _varname_prog.exec(str, i)[0][1]
+ return min(i, end)
+
+
+# Split a list into its elements.
+# Return a list of elements (strings).
+
+def SplitList(str):
+ i, end = 0, len(str)
+ list = []
+ while 1:
+ i = SkipSpaces(str, i, end)
+ if i >= end: break
+ j = i
+ i = FindNextElement(str, i, end)
+ if str[j] = '{' and str[i-1] = '}':
+ element = str[j+1:i-1]
+ else:
+ element = Collapse(str[j:i])
+ list.append(element)
+ return list
+
+
+# Find the next element from a list.
+
+_element_prog = regexp.compile('([^ \t\n\\]+|\\\\C?M?(.|\n))*')
+
+def FindNextElement(str, i, end):
+ if i < end and str[i] = '{':
+ i = BalanceBraces(str, i, end)
+ if i < end and str[i] not in ' \t\n':
+ raise TclSyntaxError, 'Garbage after } in list'
+ return i
+ i = _element_prog.exec(str, i)[0][1]
+ return min(i, end)
+
+
+# Copy a string, expanding all backslash sequences.
+
+_collapse_prog = regexp.compile('(\n|[^\\]+)*')
+
+def Collapse(str):
+ if '\\' not in str: return str
+ i, end = 0, len(str)
+ result = ''
+ while i < end:
+ j = _collapse_prog.exec(str, i)[0][1]
+ j = min(j, end)
+ result = result + str[i:j]
+ if j >= end: break
+ c = str[j]
+ if c <> '\\': raise TclAssertError, 'collapse error'
+ x, i = Backslash(str, j, end)
+ result = result + x
+ return result
+
+
+# Find the next full command.
+# Return a list of begin, end indices of words in the string,
+# and an index pointing just after the terminating newline or
+# semicolon.
+# Initial spaces are skipped.
+# If the command begins with '#', it is considered empty and
+# characters until '\n' are skipped.
+
+_eol_prog = regexp.compile('[^\n]*')
+
+def FindNextCommand(str, i, end, bracketed):
+ i = SkipSpaces(str, i, end)
+ if i >= end: return [], end
+ if str[i] = '#':
+ i = _eol_prog.exec(str, i)
+ i = min(i, end)
+ if i < end and str[i] = '\n': i = i+1
+ return [], i
+ if bracketed: terminators = [';']
+ else: terminators = [';', '\n']
+ list = []
+ while i < end:
+ j = FindNextWord(str, i, end)
+ word = str[i:j]
+ if word in terminators:
+ i = j
+ break
+ if word <> '\n': list.append(i, j)
+ i = SkipSpaces(str, j, end)
+ return list, i
+
+
+# Find the next word of a command.
+# Semicolon and newline terminate words but also count as a word
+# themselves.
+# The start index must point to the start of the word.
+
+_word_prog = regexp.compile('([^ \t\n;[\\]+|\\\\C?M?(.|\n))*')
+
+def FindNextWord(str, i, end):
+ if i >= end: return end
+ if str[i] in '{"':
+ if str[i] = '{': i = BalanceBraces(str, i, end)
+ else: i = BalanceQuotes(str, i, end)
+ if i >= end or str[i] in ' \t\n;': return min(i, end)
+ raise TclSyntaxError, 'Garbage after } or "'
+ begin = i
+ while i < end:
+ i = _word_prog.exec(str, i)[0][1]
+ if i >= end:
+ i = end
+ break
+ c = str[i]
+ if c in ' \t': break
+ if c in ';\n':
+ if i = begin: i = i+1
+ break
+ if c = '[': i = BalanceBrackets(str, i, end)
+ else: raise TclAssertError, 'word error'
+ return i
+
+
+# Parse balanced brackets from str[i:end].
+# str[i] must be '['.
+# Returns end such that str[i:end] ends with ']'
+# and contains balanced braces and brackets.
+
+_brackets_prog = regexp.compile('([^][{\\]+|\n|\\\\C?M?(.|\n))*')
+
+def BalanceBrackets(str, i, end):
+ if i >= end or str[i] <> '[':
+ raise TclAssertError, 'BalanceBrackets'
+ nesting = 0
+ while i < end:
+ i = _brackets_prog.exec(str, i)[0][1]
+ if i >= end: break
+ c = str[i]
+ if c = '{': i = BalanceBraces(str, i, end)
+ else:
+ i = i+1
+ if c = '[': nesting = nesting + 1
+ elif c = ']':
+ nesting = nesting - 1
+ if nesting = 0: return i
+ else: raise TclAssertError, 'brackets error'
+ raise TclMatchingError, 'Unmatched bracket ([)'
+
+
+# Parse balanced braces from str[i:end].
+# str[i] must be '{'.
+# Returns end such that str[i:end] ends with '}'
+# and contains balanced braces.
+
+_braces_prog = regexp.compile('([^{}\\]+|\n|\\\\C?M?(.|\n))*')
+
+def BalanceBraces(str, i, end):
+ if i >= end or str[i] <> '{':
+ raise TclAssertError, 'BalanceBraces'
+ nesting = 0
+ while i < end:
+ i = _braces_prog.exec(str, i)[0][1]
+ if i >= end: break
+ c = str[i]
+ i = i+1
+ if c = '{': nesting = nesting + 1
+ elif c = '}':
+ nesting = nesting - 1
+ if nesting = 0: return i
+ else: raise TclAssertError, 'braces error'
+ raise TclMatchingError, 'Unmatched brace ({)'
+
+
+# Parse double quotes from str[i:end].
+# str[i] must be '"'.
+# Returns end such that str[i:end] ends with an unescaped '"'.
+
+_quotes_prog = regexp.compile('([^"\\]+|\n|\\\\C?M?(.|\n))*')
+
+def BalanceQuotes(str, i, end):
+ if i >= end or str[i] <> '"':
+ raise TclAssertError, 'BalanceQuotes'
+ i = _quotes_prog.exec(str, i+1)[0][1]
+ if i < end and str[i] = '"': return i+1
+ raise TclMatchingError, 'Unmatched quote (")'
+
+
+# Static data used by Backslash()
+
+_bstab = {}
+_bstab['n'] = '\n'
+_bstab['r'] = '\r'
+_bstab['t'] = '\t'
+_bstab['b'] = '\b'
+_bstab['e'] = '\033'
+_bstab['\n'] = ''
+for c in ' {}[]$";\\': _bstab[c] = c
+del c
+
+# Backslash interpretation.
+# First character must be a backslash.
+# Return a pair (<replacement string>, <end of sequence>).
+# Unrecognized or incomplete backslash sequences are not errors;
+# this takes only the backslash itself off the string.
+
+def Backslash(str, i, end):
+ if i >= end or str[i] <> '\\':
+ raise TclAssertError, 'Backslash'
+ i = i+1
+ if i = end: return '\\', i
+ c = str[i]
+ i = i+1
+ if _bstab.has_key(c): return _bstab[c], i
+ if c = 'C':
+ if i = end: return '\\', i-1
+ c = str[i]
+ i = i+1
+ if c = 'M':
+ if i = end: return '\\', i-2
+ c = str[i]
+ i = i+1
+ x = ord(c) % 040 + 0200
+ else:
+ x = ord(c) % 040
+ return chr(x), i
+ elif c = 'M':
+ if i = end: return '\\', i-1
+ c = str[i]
+ i = i+1
+ x = ord(c)
+ if x < 0200: x = x + 0200
+ return chr(x), i
+ elif c and c in '0123456789':
+ x = ord(c) - ord('0')
+ end = min(end, i+2)
+ while i < end:
+ c = str[i]
+ if c not in '0123456789': break
+ i = i+1
+ x = x*8 + ord(c) - ord('0')
+ return ord(x), i
+ else:
+ # Not something that we recognize
+ return '\\', i-1
+
+
+# Skip over spaces and tabs (but not newlines).
+
+_spaces_prog = regexp.compile('[ \t]*')
+
+def SkipSpaces(str, i, end):
+ i = _spaces_prog.exec(str, i)[0][1]
+ return min(i, end)
+
+
+# Concatenate the elements of a list with intervening spaces.
+
+def Concat(argv):
+ result = ''
+ sep = ''
+ for arg in argv:
+ result = result + (sep + arg)
+ sep = ' '
+ return result
+
+
+# Concatenate list elements, adding braces etc. to make them parseable
+# again with SplitList.
+
+def BuildList(argv):
+ result = ''
+ sep = ''
+ for arg in argv:
+ arg = AddBraces(arg)
+ result = result + (sep + arg)
+ sep = ' '
+ return result
+
+
+# Add braces around a string if necessary to make it parseable by SplitList.
+
+def AddBraces(str):
+ # Special case for empty string
+ if str = '': return '{}'
+ # See if it contains balanced braces
+ res = '{' + str + '}'
+ if TryNextElement(res):
+ # See if it would survive unquoted
+ # XXX should escape [] and $ as well???
+ if TryNextElement(str) and Collapse(str) = str: return str
+ # No -- return with added braces
+ return res
+ # Unbalanced braces. Add backslashes before suspect characters
+ res = ''
+ for c in str:
+ if c in '$\\[]{} ;': c = '\\' + c
+ elif c = '\n': c = '\\n'
+ elif c = '\t': c = '\\t'
+ res = res + c
+ return res
+
+
+def TryNextElement(str):
+ end = len(str)
+ try:
+ i = FindNextElement(str, 0, end)
+ return i = end
+ except (TclSyntaxError, TclMatchingError):
+ return 0
diff --git a/lib/TestCSplit.py b/lib/TestCSplit.py
new file mode 100644
index 0000000..b638f81
--- /dev/null
+++ b/lib/TestCSplit.py
@@ -0,0 +1,26 @@
+# TestCSplit
+
+import stdwin
+from stdwinevents import WE_CLOSE
+from WindowParent import WindowParent
+from Buttons import PushButton
+
+def main(n):
+ from CSplit import CSplit
+
+ the_window = WindowParent().create('TestCSplit', (0, 0))
+ the_csplit = CSplit().create(the_window)
+
+ for i in range(n):
+ the_child = PushButton().define(the_csplit)
+ the_child.settext(`(i+n-1)%n+1`)
+
+ the_window.realize()
+
+ while 1:
+ the_event = stdwin.getevent()
+ if the_event[0] = WE_CLOSE: break
+ the_window.dispatch(the_event)
+ the_window.destroy()
+
+main(12)
diff --git a/lib/TransParent.py b/lib/TransParent.py
new file mode 100644
index 0000000..a07b4c4
--- /dev/null
+++ b/lib/TransParent.py
@@ -0,0 +1,96 @@
+# A class that sits transparently between a parent and one child.
+# First create the parent, then this thing, then the child.
+# Use this as a base class for objects that are almost transparent.
+# Don't use as a base class for parents with multiple children.
+
+Error = 'TransParent.Error' # Exception
+
+class ManageOneChild():
+ #
+ # Upcalls shared with other single-child parents
+ #
+ def addchild(self, child):
+ if self.child:
+ raise Error, 'addchild: one child only'
+ if not child:
+ raise Error, 'addchild: bad child'
+ self.child = child
+ #
+ def delchild(self, child):
+ if not self.child:
+ raise Error, 'delchild: no child'
+ if child <> self.child:
+ raise Error, 'delchild: not my child'
+ self.child = 0
+
+class TransParent() = ManageOneChild():
+ #
+ # Calls from creator
+ # NB derived classes may add parameters to create()
+ #
+ def create(self, parent):
+ parent.addchild(self)
+ self.parent = parent
+ self.child = 0 # No child yet
+ #
+ # Downcalls from parent to child
+ #
+ def destroy(self):
+ del self.parent
+ if self.child: self.child.destroy()
+ del self.child
+ #
+ def minsize(self, m):
+ if not self.child:
+ return 0, 0
+ else:
+ return self.child.minsize(m)
+ def getbounds(self, bounds):
+ if not self.child:
+ raise Error, 'getbounds w/o child'
+ else:
+ return self.child.getbounds()
+ def setbounds(self, bounds):
+ if not self.child:
+ raise Error, 'setbounds w/o child'
+ else:
+ self.child.setbounds(bounds)
+ def draw(self, args):
+ if self.child:
+ self.child.draw(args)
+ #
+ # Downcalls only made after certain upcalls
+ #
+ def mouse_down(self, detail):
+ if self.child: self.child.mouse_down(detail)
+ def mouse_move(self, detail):
+ if self.child: self.child.mouse_move(detail)
+ def mouse_up(self, detail):
+ if self.child: self.child.mouse_up(detail)
+ #
+ def timer(self):
+ if self.child: self.child.timer()
+ #
+ # Upcalls from child to parent
+ #
+ def need_mouse(self, child):
+ self.parent.need_mouse(self)
+ def no_mouse(self, child):
+ self.parent.no_mouse(self)
+ #
+ def need_timer(self, child):
+ self.parent.need_timer(self)
+ def no_timer(self, child):
+ self.parent.no_timer(self)
+ #
+ def begindrawing(self):
+ return self.parent.begindrawing()
+ def beginmeasuring(self):
+ return self.parent.beginmeasuring()
+ #
+ def change(self, area):
+ self.parent.change(area)
+ def scroll(self, args):
+ self.parent.scroll(args)
+ def settimer(self, itimer):
+ self.parent.settimer(itimer)
diff --git a/lib/VUMeter.py b/lib/VUMeter.py
new file mode 100644
index 0000000..c862452
--- /dev/null
+++ b/lib/VUMeter.py
@@ -0,0 +1,47 @@
+# Module 'VUMeter'
+
+import audio
+from StripChart import StripChart
+
+K = 1024
+Rates = [0, 32*K, 16*K, 8*K]
+
+class VUMeter() = StripChart():
+ #
+ # Override define() and timer() methods
+ #
+ def define(self, parent):
+ self = StripChart.define(self, (parent, 128))
+ self.parent.need_timer(self)
+ self.sampling = 0
+ self.rate = 3
+ self.enable(0)
+ return self
+ #
+ def timer(self):
+ if self.sampling:
+ chunk = audio.wait_recording()
+ self.sampling = 0
+ nums = audio.chr2num(chunk)
+ ampl = max(abs(min(nums)), abs(max(nums)))
+ self.append(ampl)
+ if self.enabled and not self.sampling:
+ audio.setrate(self.rate)
+ size = Rates[self.rate]/10
+ size = size/48*48
+ audio.start_recording(size)
+ self.sampling = 1
+ if self.sampling:
+ self.parent.settimer(1)
+ #
+ # New methods: start() and stop()
+ #
+ def stop(self):
+ if self.sampling:
+ chunk = audio.stop_recording()
+ self.sampling = 0
+ self.enable(0)
+ #
+ def start(self):
+ self.enable(1)
+ self.timer()
diff --git a/lib/WindowParent.py b/lib/WindowParent.py
new file mode 100644
index 0000000..1e18930
--- /dev/null
+++ b/lib/WindowParent.py
@@ -0,0 +1,101 @@
+# A 'WindowParent' is the only module that uses real stdwin functionality.
+# It is the root of the tree.
+# It should have exactly one child when realized.
+
+import stdwin
+from stdwinevents import *
+
+from TransParent import ManageOneChild
+
+Error = 'WindowParent.Error' # Exception
+
+class WindowParent() = ManageOneChild():
+ #
+ def create(self, (title, size)):
+ self.title = title
+ self.size = size # (width, height)
+ self._reset()
+ return self
+ #
+ def _reset(self):
+ self.child = 0
+ self.win = 0
+ self.itimer = 0
+ self.do_mouse = 0
+ self.do_timer = 0
+ #
+ def destroy(self):
+ if self.child: self.child.destroy()
+ self._reset()
+ #
+ def need_mouse(self, child): self.do_mouse = 1
+ def no_mouse(self, child): self.do_mouse = 0
+ #
+ def need_timer(self, child): self.do_timer = 1
+ def no_timer(self, child): self.do_timer = 0
+ #
+ def realize(self):
+ if self.win:
+ raise Error, 'realize(): called twice'
+ if not self.child:
+ raise Error, 'realize(): no child'
+ size = self.child.minsize(self.beginmeasuring())
+ self.size = max(self.size[0], size[0]), \
+ max(self.size[1], size[1])
+ #stdwin.setdefwinsize(self.size)
+ # XXX Compensate stdwin bug:
+ stdwin.setdefwinsize(self.size[0]+4, self.size[1]+2)
+ self.win = stdwin.open(self.title)
+ if self.itimer:
+ self.win.settimer(self.itimer)
+ bounds = (0, 0), self.win.getwinsize()
+ self.child.setbounds(bounds)
+ #
+ def beginmeasuring(self):
+ # Return something with which a child can measure text
+ if self.win:
+ return self.win.begindrawing()
+ else:
+ return stdwin
+ #
+ def begindrawing(self):
+ if self.win:
+ return self.win.begindrawing()
+ else:
+ raise Error, 'begindrawing(): not realized yet'
+ #
+ def change(self, area):
+ if self.win:
+ self.win.change(area)
+ #
+ def scroll(self, args):
+ if self.win:
+ self.win.scroll(args)
+ #
+ def settimer(self, itimer):
+ if self.win:
+ self.win.settimer(itimer)
+ else:
+ self.itimer = itimer
+ #
+ # Only call dispatch if we have a child
+ #
+ def dispatch(self, (type, win, detail)):
+ if win <> self.win:
+ return
+ elif type = WE_DRAW:
+ d = self.win.begindrawing()
+ self.child.draw(d, detail)
+ elif type = WE_MOUSE_DOWN:
+ if self.do_mouse: self.child.mouse_down(detail)
+ elif type = WE_MOUSE_MOVE:
+ if self.do_mouse: self.child.mouse_move(detail)
+ elif type = WE_MOUSE_UP:
+ if self.do_mouse: self.child.mouse_up(detail)
+ elif type = WE_TIMER:
+ if self.do_timer: self.child.timer()
+ elif type = WE_SIZE:
+ self.win.change((0, 0), (10000, 10000)) # XXX
+ bounds = (0, 0), self.win.getwinsize()
+ self.child.setbounds(bounds)
+ #
diff --git a/lib/adv.py b/lib/adv.py
new file mode 100644
index 0000000..f32f91b
--- /dev/null
+++ b/lib/adv.py
@@ -0,0 +1,366 @@
+#! /ufs/guido/bin/sgi/python
+
+# Module 'adv' -- text-oriented adventure game.
+
+
+# Name a constant that may once appear in the language...
+
+def return_nil(): return
+nil = return_nil()
+
+
+# Copy of string.split() (to avoid loading all of string.py)
+
+whitespace = ' \t\n'
+def split(s):
+ res = []
+ i, n = 0, len(s)
+ while i < n:
+ while i < n and s[i] in whitespace: i = i+1
+ if i = n: break
+ j = i
+ while j < n and s[j] not in whitespace: j = j+1
+ res.append(s[i:j])
+ i = j
+ return res
+
+
+# Constants to name directions
+
+N = 'north'
+S = 'south'
+W = 'west'
+E = 'east'
+U = 'up'
+D = 'down'
+NW = 'nw'
+NE = 'ne'
+SW = 'sw'
+SE = 'se'
+
+
+# Constants to name other commands
+
+INVENT = 'invent'
+LOOK = 'look'
+BACK = 'back'
+HELP = 'help'
+GET = 'get'
+PUT = 'put'
+
+
+# Aliases recognized by the parser
+
+alias = {}
+alias['n'] = N
+alias['s'] = S
+alias['e'] = E
+alias['w'] = W
+alias['u'] = U
+alias['d'] = D
+alias['i'] = INVENT
+alias['l'] = LOOK
+alias['b'] = BACK
+alias['take'] = GET
+alias['drop'] = PUT
+
+
+# Normalize a command, in place: truncate words to 6 chars, and expand aliases.
+
+def normalize(cmd):
+ for i in range(len(cmd)):
+ word = cmd[i][:6]
+ if alias.has_key(word):
+ word = alias[word]
+ cmd[i] = word
+
+
+# The Object class describes objects that the player can carry around.
+
+class Object():
+ def init(this, name):
+ this.name = name
+ return this
+ def describe(this):
+ print 'A', this.name + '.'
+ def get(this, (player, room)):
+ del room.objects[this.name]
+ player.objects[this.name] = this
+ def put(this, (player, room)):
+ del player.objects[this.name]
+ room.objects[this.name] = this
+
+
+# The Player class embodies first person control.
+
+class Player():
+ # Set initial state, except current room.
+ def init(self, initial_room):
+ self.blind = 0
+ self.here = initial_room
+ self.prev = nil
+ self.objects = {}
+ return self
+ # Read and execute commands forever.
+ def play(self):
+ self.here.casualdescribe()
+ while 1:
+ self.move()
+ # Read and execute one command.
+ def move(self):
+ next = self.here.parser()
+ if next and next <> self.here:
+ self.prev = self.here
+ self.here = next
+ if not self.blind:
+ self.here.casualdescribe()
+ # Print inventory.
+ def inventory(self):
+ if not self.objects:
+ print 'You aren\'t carrying anything.'
+ return
+ print 'You are carrying:'
+ for key in self.objects.keys():
+ self.objects[key].describe()
+ # Go back to previous room.
+ def back(self):
+ if self.prev: return self.prev
+ print 'You can\'t go back now.'
+ # Get an object from the room.
+ def get(self, name):
+ if self.here.objects.has_key(name):
+ self.here.objects[name].get(self, self.here)
+ else:
+ print 'I see no', name, 'here.'
+ # Put an object in the room.
+ def put(self, name):
+ if self.objects.has_key(name):
+ self.objects[name].put(self, self.here)
+ else:
+ print 'You have no', name, 'with you.'
+
+
+# The Room class describes a generic room.
+# Rooms with special properties are defined by derived classes
+# that override certain operations.
+
+class Room():
+ # Initialize a featureless room.
+ def init(here, name):
+ here.seen = 0
+ here.name = name
+ here.exits = {}
+ here.objects = {}
+ here.description = []
+ return here
+ # Add an object to the room. Used during initialization.
+ def add(here, obj):
+ here.objects[obj.name] = obj
+ # Print a casual description.
+ def casualdescribe(here):
+ if here.seen:
+ print here.name + '.'
+ here.listobjects()
+ return
+ here.seen = 1
+ here.describe()
+ # Print a full description, including all exits and objects seen.
+ def describe(here):
+ if not here.description:
+ print here.name + '.'
+ here.listexits()
+ else:
+ for line in here.description: print line
+ here.listobjects()
+ # List exits.
+ def listexits(here):
+ there = here.exits.keys()
+ if there:
+ if len(there) = 1:
+ print 'There is an exit leading',
+ else:
+ print 'There are exits leading',
+ for name in there[:-2]:
+ print name + ',',
+ print there[len(there)-2], 'and',
+ print there[len(there)-1] + '.'
+ # List objects
+ def listobjects(here):
+ if here.objects:
+ print 'I see:'
+ for key in here.objects.keys():
+ here.objects[key].describe()
+ # Default parser. Returns next room (possibly the same) or nil.
+ def parser(here):
+ cmd = here.getcmd(here.prompt())
+ return here.decide(cmd)
+ # Return default prompt string.
+ def prompt(here): return '> '
+ # Default input routine. Returns a non-empty list of words.
+ def getcmd(here, prompt):
+ # Loop until non-empty command gotten
+ # EOFError and KeyboardInterrupt may be caught elsewhere
+ while 1:
+ line = raw_input(prompt)
+ cmd = split(line)
+ if cmd:
+ normalize(cmd)
+ return cmd
+ # Default decision routine. Override for room-specific commands.
+ def decide(here, cmd):
+ key, args = cmd[0], cmd[1:]
+ if not args:
+ if key = N: return here.north()
+ if key = S: return here.south()
+ if key = E: return here.east()
+ if key = W: return here.west()
+ if key = U: return here.up()
+ if key = D: return here.down()
+ if key = NW: return here.nw()
+ if key = NE: return here.ne()
+ if key = SW: return here.sw()
+ if key = SE: return here.se()
+ if key = LOOK: return here.look()
+ if key = INVENT: return here.inventory()
+ if key = BACK: return here.back()
+ if here.objects.has_key(key):
+ print 'What do you want to do with the', key+'?'
+ else:
+ print 'Huh?'
+ return
+ if key = GET:
+ for arg in args:
+ player.get(arg)
+ return
+ if key = PUT:
+ for arg in args:
+ player.put(arg)
+ # Standard commands.
+ def look(here):
+ here.describe()
+ def inventory(here):
+ player.inventory()
+ def back(here):
+ return player.back()
+ # Standard exits.
+ def north(here): return here.take_exit(N)
+ def south(here): return here.take_exit(S)
+ def west(here): return here.take_exit(W)
+ def east(here): return here.take_exit(E)
+ def up(here): return here.take_exit(U)
+ def down(here): return here.take_exit(D)
+ def nw(here): return here.take_exit(NW)
+ def ne(here): return here.take_exit(NE)
+ def sw(here): return here.take_exit(SW)
+ def se(here): return here.take_exit(SE)
+ # Subroutine for standard exits.
+ def take_exit(here, key):
+ if here.exits.has_key(key):
+ return here.exits[key]
+ print 'You cannot go in that direction.'
+ return here
+
+
+# Create the objects we know about.
+# Object names begin with 'o_'.
+
+o_lamp = Object().init('lamp')
+o_python = Object().init('python')
+
+
+# Subroutine to connect two rooms.
+
+def connect(rm1, rm2, dir1, dir2):
+ if dir1:
+ rm1.exits[dir1] = rm2
+ if dir2:
+ rm2.exits[dir2] = rm1
+
+
+# Create the rooms and connect them together.
+# Room names begin with 'r_'.
+
+r_front = Room().init('Front of building')
+r_initial = r_front
+r_front.description = [ \
+ 'You are standing in front of a large, desolate building.', \
+ 'Huge neon letters spell "CWI". The "I" is blinking.', \
+ 'There are entrances north and west from where you are standing.', \
+ ]
+
+r_entrance = Room().init('Entrance')
+r_entrance.description = [ \
+ 'You are standing in a small entrance room.', \
+ 'On the east side is a window to a reception room.', \
+ 'South is a door leading outside the building.', \
+ 'North is a large hall.' \
+ ]
+
+r_hall_s = Room().init('South of hall')
+r_hall_s.description = [ \
+ 'You are standing at the south side of a very large hall.', \
+ 'There are doors leading west, southwest, south and southeast,', \
+ 'and a corridor leads east.', \
+ 'The hall continues to the north.' \
+ ]
+
+r_hall_n = Room().init('North of hall')
+r_hall_n.description = [ \
+ 'You are stanting at the north side of a very large hall.', \
+ 'There are corridors leading west, northwest, northeast,', \
+ 'an elevator door north, and a door leading outside east.', \
+ 'There are stairs leading up, and the hall continues to the south.' \
+ ]
+
+r_reception = Room().init('Reception')
+
+r_mail = Room().init('Mail room')
+
+connect(r_front, r_entrance, N, S)
+connect(r_entrance, r_hall_s, N, SW)
+connect(r_hall_s, r_hall_n, N, S)
+connect(r_hall_s, r_reception, S, N)
+connect(r_hall_s, r_mail, SE, N)
+connect(r_reception, r_mail, E, W)
+
+r_aud_front = Room().init('Front of Auditorium')
+r_aud_back = Room().init('Back of Auditorium')
+r_aud_tech = Room().init('Technician\'s room in Auditorium')
+r_aud_proj = Room().init('Projection room in Auditorium')
+
+connect(r_aud_front, r_hall_s, E, W)
+connect(r_aud_front, r_aud_back, S, N)
+connect(r_aud_back, r_aud_proj, SE, N)
+connect(r_aud_front, r_aud_tech, W, E)
+
+r_floor1 = Room().init('First floor')
+r_floor2 = Room().init('Second floor')
+r_floor3 = Room().init('Third floor')
+
+connect(r_hall_n, r_floor1, U, D)
+connect(r_floor1, r_floor2, U, D)
+connect(r_floor2, r_floor3, U, D)
+
+
+# Drop objects here and there
+
+r_aud_proj.add(o_python)
+r_reception.add(o_lamp)
+
+# Create an uninitialized player object.
+# It is initialized by main(), but must be created here (as global)
+# since some Room methods reference it. (Though maybe they shouldn't?)
+
+player = Player()
+
+
+# Play the game from the beginning.
+
+def main():
+ x = player.init(r_initial)
+ try:
+ player.play()
+ except (EOFError, KeyboardInterrupt):
+ pass
+
+main()
diff --git a/lib/anywin.py b/lib/anywin.py
new file mode 100644
index 0000000..6de3605
--- /dev/null
+++ b/lib/anywin.py
@@ -0,0 +1,14 @@
+# Module 'anywin'
+# Open a file or directory in a window
+
+import dirwin
+import filewin
+import path
+
+def open(name):
+ print 'opening', name, '...'
+ if path.isdir(name):
+ w = dirwin.open(name)
+ else:
+ w = filewin.open(name)
+ return w
diff --git a/lib/auds.py b/lib/auds.py
new file mode 100644
index 0000000..53db6aa
--- /dev/null
+++ b/lib/auds.py
@@ -0,0 +1,106 @@
+import audio
+
+RATE = 8192
+
+# Initialize the audio stuff
+audio.setrate(3)
+audio.setoutgain(100) # for speaker
+
+play = audio.write
+
+def samp(n):
+ savegain = audio.getoutgain()
+ try:
+ audio.setoutgain(0)
+ x = raw_input('Hit Enter to sample ' + `n` + ' seconds: ')
+ return audio.read(n*RATE)
+ finally:
+ audio.setoutgain(savegain)
+
+def echo(s, delay, gain):
+ return s[:delay] + audio.add(s[delay:], audio.amplify(s, gain, gain))
+
+def save(s, file):
+ f = open(file, 'w')
+ f.write(s)
+
+def load(file):
+ return loadfp(open(file, 'r'))
+
+def loadfp(fp):
+ s = ''
+ while 1:
+ buf = fp.read(16*1024)
+ if not buf: break
+ s = s + buf
+ return s
+
+def unbias(s):
+ if not s: return s
+ a = audio.chr2num(s)
+ sum = 0
+ for i in a: sum = sum + i
+ bias = (sum + len(a)/2) / len(a)
+ print 'Bias value:', bias
+ if bias:
+ for i in range(len(a)):
+ a[i] = a[i] - bias
+ s = audio.num2chr(a)
+ return s
+
+# Stretch by a/b.
+# Think of this as converting the sampling rate from a samples/sec
+# to b samples/sec. Or, if the input is a bytes long, the output
+# will be b bytes long.
+#
+def stretch(s, a, b):
+ y = audio.chr2num(s)
+ m = len(y)
+ out = []
+ n = m * b / a
+ # i, j will walk through y and out (step 1)
+ # ib, ja are i*b, j*a and are kept as close together as possible
+ i, ib = 0, 0
+ j, ja = 0, 0
+ for j in range(n):
+ ja = ja+a
+ while ib < ja:
+ i = i+1
+ ib = ib+b
+ if i >= m:
+ break
+ if ib = ja:
+ out.append(y[i])
+ else:
+ out.append((y[i]*(ja-(ib-b)) + y[i-1]*(ib-ja)) / b)
+ return audio.num2chr(out)
+
+def sinus(freq): # return a 1-second sine wave
+ from math import sin, pi
+ factor = 2.0*pi*float(freq)/float(RATE)
+ list = range(RATE)
+ for i in list:
+ list[i] = int(sin(float(i) * factor) * 127.0)
+ return audio.num2chr(list)
+
+def softclip(s):
+ if '\177' not in s and '\200' not in s:
+ return s
+ num = audio.chr2num(s)
+ extremes = (-128, 127)
+ for i in range(1, len(num)-1):
+ if num[i] in extremes:
+ num[i] = (num[i-1] + num[i+1]) / 2
+ return audio.num2chr(num)
+
+def demo():
+ gday = load('gday')[1000:6000]
+ save(gday, 'gday0')
+ gg = [gday]
+ for i in range(1, 10):
+ for g in gg: play(g)
+ g = stretch(gday, 10, 10-i)
+ save(g, 'gday' + `i`)
+ gg.append(g)
+ while 1:
+ for g in gg: play(g)
diff --git a/lib/calendar.py b/lib/calendar.py
new file mode 100644
index 0000000..97ca86f
--- /dev/null
+++ b/lib/calendar.py
@@ -0,0 +1,213 @@
+# module calendar
+
+##############################
+# Calendar support functions #
+##############################
+
+# This is based on UNIX ctime() et al. (also Standard C and POSIX)
+# Subtle but crucial differences:
+# - the order of the elements of a 'struct tm' differs, to ease sorting
+# - months numbers are 1-12, not 0-11; month arrays have a dummy element 0
+# - Monday is the first day of the week (numbered 0)
+
+# These are really parameters of the 'time' module:
+epoch = 1970 # Time began on January 1 of this year (00:00:00 UCT)
+day_0 = 3 # The epoch begins on a Thursday (Monday = 0)
+
+# Return 1 for leap years, 0 for non-leap years
+def isleap(year):
+ return year % 4 = 0 and (year % 100 <> 0 or year % 400 = 0)
+
+# Constants for months referenced later
+January = 1
+February = 2
+
+# Number of days per month (except for February in leap years)
+mdays = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+
+# Exception raised for bad input (with string parameter for details)
+error = 'calendar error'
+
+# Turn seconds since epoch into calendar time
+def gmtime(secs):
+ if secs < 0: raise error, 'negative input to gmtime()'
+ mins, secs = divmod(secs, 60)
+ hours, mins = divmod(mins, 60)
+ days, hours = divmod(hours, 24)
+ wday = (days + day_0) % 7
+ year = epoch
+ # XXX Most of the following loop can be replaced by one division
+ while 1:
+ yd = 365 + isleap(year)
+ if days < yd: break
+ days = days - yd
+ year = year + 1
+ yday = days
+ month = January
+ while 1:
+ md = mdays[month] + (month = February and isleap(year))
+ if days < md: break
+ days = days - md
+ month = month + 1
+ return year, month, days + 1, hours, mins, secs, yday, wday
+ # XXX Week number also?
+
+# Return number of leap years in range [y1, y2)
+# Assume y1 <= y2 and no funny (non-leap century) years
+def leapdays(y1, y2):
+ return (y2+3)/4 - (y1+3)/4
+
+# Inverse of gmtime():
+# Turn UCT calendar time (less yday, wday) into seconds since epoch
+def mktime(year, month, day, hours, mins, secs):
+ days = day - 1
+ for m in range(January, month): days = days + mdays[m]
+ if isleap(year) and month > February: days = days+1
+ days = days + (year-epoch)*365 + leapdays(epoch, year)
+ return ((days*24 + hours)*60 + mins)*60 + secs
+
+# Full and abbreviated names of weekdays
+day_name = ('Monday', 'Tuesday', 'Wednesday', 'Thursday')
+day_name = day_name + ('Friday', 'Saturday', 'Sunday')
+day_abbr = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
+
+# Full and abbreviated of months (1-based arrays!!!)
+month_name = ('', 'January', 'February', 'March', 'April')
+month_name = month_name + ('May', 'June', 'July', 'August')
+month_name = month_name + ('September', 'October', 'November', 'December')
+month_abbr = (' ', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun')
+month_abbr = month_abbr + ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
+
+# Zero-fill string to two positions (helper for asctime())
+def dd(s):
+ while len(s) < 2: s = '0' + s
+ return s
+
+# Blank-fill string to two positions (helper for asctime())
+def zd(s):
+ while len(s) < 2: s = ' ' + s
+ return s
+
+# Turn calendar time as returned by gmtime() into a string
+# (the yday parameter is for compatibility with gmtime())
+def asctime(year, month, day, hours, mins, secs, yday, wday):
+ s = day_abbr[wday] + ' ' + month_abbr[month] + ' ' + zd(`day`)
+ s = s + ' ' + dd(`hours`) + ':' + dd(`mins`) + ':' + dd(`secs`)
+ return s + ' ' + `year`
+
+# Localization: Minutes West from Greenwich
+# timezone = -2*60 # Middle-European time with DST on
+timezone = 5*60 # EST (sigh -- THINK time() doesn't return UCT)
+
+# Local time ignores DST issues for now -- adjust 'timezone' to fake it
+def localtime(secs):
+ return gmtime(secs - timezone*60)
+
+# UNIX-style ctime (except it doesn't append '\n'!)
+def ctime(secs):
+ return asctime(localtime(secs))
+
+######################
+# Non-UNIX additions #
+######################
+
+# Calendar printing etc.
+
+# Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12), day (1-31)
+def weekday(year, month, day):
+ secs = mktime(year, month, day, 0, 0, 0)
+ days = secs / (24*60*60)
+ return (days + day_0) % 7
+
+# Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for year, month
+def monthrange(year, month):
+ day1 = weekday(year, month, 1)
+ ndays = mdays[month] + (month = February and isleap(year))
+ return day1, ndays
+
+# Return a matrix representing a month's calendar
+# Each row represents a week; days outside this month are zero
+def _monthcalendar(year, month):
+ day1, ndays = monthrange(year, month)
+ rows = []
+ r7 = range(7)
+ day = 1 - day1
+ while day <= ndays:
+ row = [0, 0, 0, 0, 0, 0, 0]
+ for i in r7:
+ if 1 <= day <= ndays: row[i] = day
+ day = day + 1
+ rows.append(row)
+ return rows
+
+# Caching interface to _monthcalendar
+mc_cache = {}
+def monthcalendar(year, month):
+ key = `year` + month_abbr[month]
+ try:
+ return mc_cache[key]
+ except RuntimeError:
+ mc_cache[key] = ret = _monthcalendar(year, month)
+ return ret
+
+# Center a string in a field
+def center(str, width):
+ n = width - len(str)
+ if n < 0: return str
+ return ' '*(n/2) + str + ' '*(n-n/2)
+
+# XXX The following code knows that print separates items with space!
+
+# Print a single week (no newline)
+def prweek(week, width):
+ for day in week:
+ if day = 0: print ' '*width,
+ else:
+ if width > 2: print ' '*(width-3),
+ if day < 10: print '',
+ print day,
+
+# Return a header for a week
+def weekheader(width):
+ str = ''
+ for i in range(7):
+ if str: str = str + ' '
+ str = str + day_abbr[i%7][:width]
+ return str
+
+# Print a month's calendar
+def prmonth(year, month):
+ print weekheader(3)
+ for week in monthcalendar(year, month):
+ prweek(week, 3)
+ print
+
+# Spacing between month columns
+spacing = ' '
+
+# 3-column formatting for year calendars
+def format3c(a, b, c):
+ print center(a, 20), spacing, center(b, 20), spacing, center(c, 20)
+
+# Print a year's calendar
+def prcal(year):
+ header = weekheader(2)
+ format3c('', `year`, '')
+ for q in range(January, January+12, 3):
+ print
+ format3c(month_name[q], month_name[q+1], month_name[q+2])
+ format3c(header, header, header)
+ data = []
+ height = 0
+ for month in range(q, q+3):
+ cal = monthcalendar(year, month)
+ if len(cal) > height: height = len(cal)
+ data.append(cal)
+ for i in range(height):
+ for cal in data:
+ if i >= len(cal):
+ print ' '*20,
+ else:
+ prweek(cal[i], 2)
+ print spacing,
+ print
diff --git a/lib/clock.py b/lib/clock.py
new file mode 100644
index 0000000..ec5301e
--- /dev/null
+++ b/lib/clock.py
@@ -0,0 +1,202 @@
+# 'klok' -- A simple alarm clock
+
+# The alarm can be set at 5 minute intervals on a 12 hour basis.
+# It is controlled with the mouse:
+# - Click and drag around the circle to set the alarm.
+# - Click far outside the circle to clear the alarm.
+# - Click near the center to set the alarm at the last time set.
+# The alarm time is indicated by a small triangle just outside the circle,
+# and also by a digital time at the bottom.
+# The indicators disappear when the alarm is not set.
+# When the alarm goes off, it beeps every minute for five minutes,
+# and the clock turns into inverse video.
+# Click or activate the window to turn the ringing off.
+
+import stdwin
+from stdwinevents import WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP, \
+ WE_TIMER, WE_DRAW, WE_SIZE, WE_CLOSE, WE_ACTIVATE
+import time
+from math import sin, cos, atan2, pi, sqrt
+
+DEFWIDTH, DEFHEIGHT = 200, 200
+
+mouse_events = (WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP)
+origin = 0, 0
+faraway = 2000, 2000
+everywhere = origin, faraway
+
+class struct(): pass # A class to declare featureless objects
+
+G = struct() # Global variables (most set in setdimensions())
+G.tzdiff = 5*3600 # THINK computes UCT from local time assuming EST!
+
+A = struct() # Globals used by the alarm
+A.set = 1 # True when alarm is set
+A.time = 11*60 + 40 # Time when alarm must go off
+A.ring = 0 # True when alarm is ringing
+
+def main():
+ try:
+ realmain()
+ except KeyboardInterrupt:
+ print 'KeyboardInterrupt'
+ finally:
+ G.w = 0
+
+def realmain():
+ setdimensions(DEFWIDTH, DEFHEIGHT)
+ stdwin.setdefwinsize(G.farcorner)
+ G.w = stdwin.open('klok')
+ settimer()
+ while 1:
+ type, window, detail = stdwin.getevent()
+ if type = WE_DRAW:
+ drawproc(detail)
+ elif type = WE_TIMER:
+ settimer()
+ drawproc(everywhere)
+ elif type in mouse_events:
+ mouseclick(type, detail)
+ elif type = WE_ACTIVATE:
+ if A.ring:
+ # Turn the ringing off
+ A.ring = 0
+ G.w.begindrawing().invert(G.mainarea)
+ elif type = WE_SIZE:
+ G.w.change(everywhere)
+ width, height = G.w.getwinsize()
+ height = height - stdwin.lineheight()
+ setdimensions(width, height)
+ elif type = WE_CLOSE:
+ break
+
+def setdimensions(width, height):
+ if width < height: size = width
+ else: size = height
+ halfwidth = width/2
+ halfheight = height/2
+ G.center = halfwidth, halfheight
+ G.radius = size*45/100
+ G.width = width
+ G.height = height
+ G.corner = width, height
+ G.mainarea = origin, G.corner
+ G.lineheight = stdwin.lineheight()
+ G.farcorner = width, height + G.lineheight
+ G.statusarea = (0, height), G.farcorner
+ G.fullarea = origin, G.farcorner
+
+def settimer():
+ now = getlocaltime()
+ G.times = calctime(now)
+ delay = 61 - now % 60
+ G.w.settimer(10 * delay)
+ minutes = (now/60) % 720
+ if A.ring:
+ # Is it time to stop the alarm ringing?
+ since = (minutes - A.time + 720) % 720
+ if since >= 5:
+ # Stop it now
+ A.ring = 0
+ else:
+ # Ring again, once every minute
+ stdwin.fleep()
+ elif A.set and minutes = A.time:
+ # Start the alarm ringing
+ A.ring = 1
+ stdwin.fleep()
+
+def drawproc(area):
+ hours, minutes, seconds = G.times
+ d = G.w.begindrawing()
+ d.cliprect(area)
+ d.erase(everywhere)
+ d.circle(G.center, G.radius)
+ d.line(G.center, calcpoint(hours*30 + minutes/2, 0.6))
+ d.line(G.center, calcpoint(minutes*6, 1.0))
+ str = dd(hours) + ':' + dd(minutes)
+ p = (G.width - d.textwidth(str))/2, G.height * 3 / 4
+ d.text(p, str)
+ if A.set:
+ drawalarm(d)
+ drawalarmtime(d)
+ if A.ring:
+ d.invert(G.mainarea)
+
+def mouseclick(type, detail):
+ d = G.w.begindrawing()
+ if A.ring:
+ # First turn the ringing off
+ A.ring = 0
+ d.invert(G.mainarea)
+ h, v = detail[0]
+ ch, cv = G.center
+ x, y = h-ch, cv-v
+ dist = sqrt(x*x + y*y) / float(G.radius)
+ if dist > 1.2:
+ if A.set:
+ drawalarm(d)
+ erasealarmtime(d)
+ A.set = 0
+ elif dist < 0.8:
+ if not A.set:
+ A.set = 1
+ drawalarm(d)
+ drawalarmtime(d)
+ else:
+ # Convert to half-degrees (range 0..720)
+ alpha = atan2(y, x)
+ hdeg = alpha*360.0/pi
+ hdeg = 180.0 - hdeg
+ hdeg = (hdeg + 720.0) % 720.0
+ atime = 5*int(hdeg/5.0 + 0.5)
+ if atime <> A.time or not A.set:
+ if A.set:
+ drawalarm(d)
+ erasealarmtime(d)
+ A.set = 1
+ A.time = atime
+ drawalarm(d)
+ drawalarmtime(d)
+
+def drawalarm(d):
+ p1 = calcpoint(float(A.time)/2.0, 1.02)
+ p2 = calcpoint(float(A.time)/2.0 - 4.0, 1.1)
+ p3 = calcpoint(float(A.time)/2.0 + 4.0, 1.1)
+ d.xorline(p1, p2)
+ d.xorline(p2, p3)
+ d.xorline(p3, p1)
+
+def erasealarmtime(d):
+ d.erase(G.statusarea)
+
+def drawalarmtime(d):
+ # A.time is in the range 0..720 with origin at 12 o'clock
+ # Convert to hours (0..12) and minutes (12*(0..60))
+ hh = A.time/60
+ mm = A.time%60
+ str = 'Alarm@' + dd(hh) + ':' + dd(mm)
+ p1 = (G.width - d.textwidth(str))/2, G.height
+ d.text(p1, str)
+
+def calctime(now):
+ seconds = now % 60
+ minutes = (now/60) % 60
+ hours = (now/3600) % 12
+ return hours, minutes, seconds
+
+def calcpoint(degrees, size):
+ alpha = pi/2.0 - float(degrees) * pi/180.0
+ x, y = cos(alpha), sin(alpha)
+ h, v = G.center
+ r = float(G.radius)
+ return h + int(x*size*r), v - int(y*size*r)
+
+def dd(n):
+ s = `n`
+ return '0'*(2-len(s)) + s
+
+def getlocaltime():
+ return time.time() - G.tzdiff
+
+#main()
diff --git a/lib/cmp.py b/lib/cmp.py
new file mode 100644
index 0000000..8dc315e
--- /dev/null
+++ b/lib/cmp.py
@@ -0,0 +1,61 @@
+# Module 'cmp'
+
+# Efficiently compare files, boolean outcome only (equal / not equal).
+
+# Tricks (used in this order):
+# - Files with identical type, size & mtime are assumed to be clones
+# - Files with different type or size cannot be identical
+# - We keep a cache of outcomes of earlier comparisons
+# - We don't fork a process to run 'cmp' but read the files ourselves
+
+import posix
+
+cache = {}
+
+def cmp(f1, f2): # Compare two files, use the cache if possible.
+ # Return 1 for identical files, 0 for different.
+ # Raise exceptions if either file could not be statted, read, etc.
+ s1, s2 = sig(posix.stat(f1)), sig(posix.stat(f2))
+ if s1[0] <> 8 or s2[0] <> 8:
+ # Either is a not a plain file -- always report as different
+ return 0
+ if s1 = s2:
+ # type, size & mtime match -- report same
+ return 1
+ if s1[:2] <> s2[:2]: # Types or sizes differ, don't bother
+ # types or sizes differ -- report different
+ return 0
+ # same type and size -- look in the cache
+ key = f1 + ' ' + f2
+ try:
+ cs1, cs2, outcome = cache[key]
+ # cache hit
+ if s1 = cs1 and s2 = cs2:
+ # cached signatures match
+ return outcome
+ # stale cached signature(s)
+ except RuntimeError:
+ # cache miss
+ pass
+ # really compare
+ outcome = do_cmp(f1, f2)
+ cache[key] = s1, s2, outcome
+ return outcome
+
+def sig(st): # Return signature (i.e., type, size, mtime) from raw stat data
+ # 0-5: st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid
+ # 6-9: st_size, st_atime, st_mtime, st_ctime
+ type = st[0] / 4096
+ size = st[6]
+ mtime = st[8]
+ return type, size, mtime
+
+def do_cmp(f1, f2): # Compare two files, really
+ bufsize = 8096 # Could be tuned
+ fp1 = open(f1, 'r')
+ fp2 = open(f2, 'r')
+ while 1:
+ b1 = fp1.read(bufsize)
+ b2 = fp2.read(bufsize)
+ if b1 <> b2: return 0
+ if not b1: return 1
diff --git a/lib/cmpcache.py b/lib/cmpcache.py
new file mode 100644
index 0000000..4aea356
--- /dev/null
+++ b/lib/cmpcache.py
@@ -0,0 +1,68 @@
+# Module 'cmpcache'
+#
+# Efficiently compare files, boolean outcome only (equal / not equal).
+#
+# Tricks (used in this order):
+# - Use the statcache module to avoid statting files more than once
+# - Files with identical type, size & mtime are assumed to be clones
+# - Files with different type or size cannot be identical
+# - We keep a cache of outcomes of earlier comparisons
+# - We don't fork a process to run 'cmp' but read the files ourselves
+
+import posix
+from stat import *
+import statcache
+
+
+# The cache.
+#
+cache = {}
+
+
+# Compare two files, use the cache if possible.
+# May raise posix.error if a stat or open of either fails.
+#
+def cmp(f1, f2):
+ # Return 1 for identical files, 0 for different.
+ # Raise exceptions if either file could not be statted, read, etc.
+ s1, s2 = sig(statcache.stat(f1)), sig(statcache.stat(f2))
+ if not S_ISREG(s1[0]) or not S_ISREG(s2[0]):
+ # Either is a not a plain file -- always report as different
+ return 0
+ if s1 = s2:
+ # type, size & mtime match -- report same
+ return 1
+ if s1[:2] <> s2[:2]: # Types or sizes differ, don't bother
+ # types or sizes differ -- report different
+ return 0
+ # same type and size -- look in the cache
+ key = f1 + ' ' + f2
+ if cache.has_key(key):
+ cs1, cs2, outcome = cache[key]
+ # cache hit
+ if s1 = cs1 and s2 = cs2:
+ # cached signatures match
+ return outcome
+ # stale cached signature(s)
+ # really compare
+ outcome = do_cmp(f1, f2)
+ cache[key] = s1, s2, outcome
+ return outcome
+
+# Return signature (i.e., type, size, mtime) from raw stat data.
+#
+def sig(st):
+ return S_IFMT(st[ST_MODE]), st[ST_SIZE], st[ST_MTIME]
+
+# Compare two files, really.
+#
+def do_cmp(f1, f2):
+ #print ' cmp', f1, f2 # XXX remove when debugged
+ bufsize = 8096 # Could be tuned
+ fp1 = open(f1, 'r')
+ fp2 = open(f2, 'r')
+ while 1:
+ b1 = fp1.read(bufsize)
+ b2 = fp2.read(bufsize)
+ if b1 <> b2: return 0
+ if not b1: return 1
diff --git a/lib/commands.py b/lib/commands.py
new file mode 100644
index 0000000..3cb6e69
--- /dev/null
+++ b/lib/commands.py
@@ -0,0 +1,77 @@
+# Module 'commands'
+#
+# Various tools for executing commands and looking at their output and status.
+
+import rand
+import posix
+import stat
+import path
+
+
+# Get 'ls -l' status for an object into a string
+#
+def getstatus(file):
+ return getoutput('ls -ld' + mkarg(file))
+
+
+# Get the output from a shell command into a string.
+# The exit status is ignored; a trailing newline is stripped.
+# Assume the command will work with ' >tempfile 2>&1' appended.
+# XXX This should use posix.popen() instead, should it exist.
+#
+def getoutput(cmd):
+ return getstatusoutput(cmd)[1]
+
+
+# Ditto but preserving the exit status.
+# Returns a pair (sts, output)
+#
+def getstatusoutput(cmd):
+ tmp = '/usr/tmp/wdiff' + `rand.rand()`
+ sts = -1
+ try:
+ sts = posix.system(cmd + ' >' + tmp + ' 2>&1')
+ text = readfile(tmp)
+ finally:
+ altsts = posix.system('rm -f ' + tmp)
+ if text[-1:] = '\n': text = text[:-1]
+ return sts, text
+
+
+# Return a string containing a file's contents.
+#
+def readfile(fn):
+ st = posix.stat(fn)
+ size = st[stat.ST_SIZE]
+ if not size: return ''
+ try:
+ fp = open(fn, 'r')
+ except:
+ raise posix.error, 'readfile(' + fn + '): open failed'
+ try:
+ return fp.read(size)
+ except:
+ raise posix.error, 'readfile(' + fn + '): read failed'
+
+
+# Make command argument from directory and pathname (prefix space, add quotes).
+#
+def mk2arg(head, x):
+ return mkarg(path.cat(head, x))
+
+
+# Make a shell command argument from a string.
+# Two strategies: enclose in single quotes if it contains none;
+# otherwis, enclose in double quotes and prefix quotable characters
+# with backslash.
+#
+def mkarg(x):
+ if '\'' not in x:
+ return ' \'' + x + '\''
+ s = ' "'
+ for c in x:
+ if c in '\\$"':
+ s = s + '\\'
+ s = s + c
+ s = s + '"'
+ return s
diff --git a/lib/dircache.py b/lib/dircache.py
new file mode 100644
index 0000000..c14db3e
--- /dev/null
+++ b/lib/dircache.py
@@ -0,0 +1,36 @@
+# Module 'dircache'
+#
+# Return a sorted list of the files in a POSIX directory, using a cache
+# to avoid reading the directory more often than necessary.
+# Also contains a subroutine to append slashes to directories.
+
+import posix
+import path
+
+cache = {}
+
+def listdir(path): # List directory contents, using cache
+ try:
+ cached_mtime, list = cache[path]
+ del cache[path]
+ except RuntimeError:
+ cached_mtime, list = -1, []
+ try:
+ mtime = posix.stat(path)[8]
+ except posix.error:
+ return []
+ if mtime <> cached_mtime:
+ try:
+ list = posix.listdir(path)
+ except posix.error:
+ return []
+ list.sort()
+ cache[path] = mtime, list
+ return list
+
+opendir = listdir # XXX backward compatibility
+
+def annotate(head, list): # Add '/' suffixes to directories
+ for i in range(len(list)):
+ if path.isdir(path.cat(head, list[i])):
+ list[i] = list[i] + '/'
diff --git a/lib/dircmp.py b/lib/dircmp.py
new file mode 100644
index 0000000..31e712c
--- /dev/null
+++ b/lib/dircmp.py
@@ -0,0 +1,205 @@
+# Module 'dirmp'
+#
+# Defines a class to build directory diff tools on.
+
+import posix
+
+import path
+
+import dircache
+import cmpcache
+import statcache
+from stat import *
+
+# Directory comparison class.
+#
+class dircmp():
+ #
+ def new(dd, (a, b)): # Initialize
+ dd.a = a
+ dd.b = b
+ # Properties that caller may change before callingdd. run():
+ dd.hide = ['.', '..'] # Names never to be shown
+ dd.ignore = ['RCS', 'tags'] # Names ignored in comparison
+ #
+ return dd
+ #
+ def run(dd): # Compare everything except common subdirectories
+ dd.a_list = filter(dircache.listdir(dd.a), dd.hide)
+ dd.b_list = filter(dircache.listdir(dd.b), dd.hide)
+ dd.a_list.sort()
+ dd.b_list.sort()
+ dd.phase1()
+ dd.phase2()
+ dd.phase3()
+ #
+ def phase1(dd): # Compute common names
+ dd.a_only = []
+ dd.common = []
+ for x in dd.a_list:
+ if x in dd.b_list:
+ dd.common.append(x)
+ else:
+ dd.a_only.append(x)
+ #
+ dd.b_only = []
+ for x in dd.b_list:
+ if x not in dd.common:
+ dd.b_only.append(x)
+ #
+ def phase2(dd): # Distinguish files, directories, funnies
+ dd.common_dirs = []
+ dd.common_files = []
+ dd.common_funny = []
+ #
+ for x in dd.common:
+ a_path = path.cat(dd.a, x)
+ b_path = path.cat(dd.b, x)
+ #
+ ok = 1
+ try:
+ a_stat = statcache.stat(a_path)
+ except posix.error, why:
+ # print 'Can\'t stat', a_path, ':', why[1]
+ ok = 0
+ try:
+ b_stat = statcache.stat(b_path)
+ except posix.error, why:
+ # print 'Can\'t stat', b_path, ':', why[1]
+ ok = 0
+ #
+ if ok:
+ a_type = S_IFMT(a_stat[ST_MODE])
+ b_type = S_IFMT(b_stat[ST_MODE])
+ if a_type <> b_type:
+ dd.common_funny.append(x)
+ elif S_ISDIR(a_type):
+ dd.common_dirs.append(x)
+ elif S_ISREG(a_type):
+ dd.common_files.append(x)
+ else:
+ dd.common_funny.append(x)
+ else:
+ dd.common_funny.append(x)
+ #
+ def phase3(dd): # Find out differences between common files
+ xx = cmpfiles(dd.a, dd.b, dd.common_files)
+ dd.same_files, dd.diff_files, dd.funny_files = xx
+ #
+ def phase4(dd): # Find out differences between common subdirectories
+ # A new dircmp object is created for each common subdirectory,
+ # these are stored in a dictionary indexed by filename.
+ # The hide and ignore properties are inherited from the parent
+ dd.subdirs = {}
+ for x in dd.common_dirs:
+ a_x = path.cat(dd.a, x)
+ b_x = path.cat(dd.b, x)
+ dd.subdirs[x] = newdd = dircmp().new(a_x, b_x)
+ newdd.hide = dd.hide
+ newdd.ignore = dd.ignore
+ newdd.run()
+ #
+ def phase4_closure(dd): # Recursively call phase4() on subdirectories
+ dd.phase4()
+ for x in dd.subdirs.keys():
+ dd.subdirs[x].phase4_closure()
+ #
+ def report(dd): # Print a report on the differences between a and b
+ # Assume that phases 1 to 3 have been executed
+ # Output format is purposely lousy
+ print 'diff', dd.a, dd.b
+ if dd.a_only:
+ print 'Only in', dd.a, ':', dd.a_only
+ if dd.b_only:
+ print 'Only in', dd.b, ':', dd.b_only
+ if dd.same_files:
+ print 'Identical files :', dd.same_files
+ if dd.diff_files:
+ print 'Differing files :', dd.diff_files
+ if dd.funny_files:
+ print 'Trouble with common files :', dd.funny_files
+ if dd.common_dirs:
+ print 'Common subdirectories :', dd.common_dirs
+ if dd.common_funny:
+ print 'Common funny cases :', dd.common_funny
+ #
+ def report_closure(dd): # Print reports on dd and on subdirs
+ # If phase 4 hasn't been done, no subdir reports are printed
+ dd.report()
+ try:
+ x = dd.subdirs
+ except NameError:
+ return # No subdirectories computed
+ for x in dd.subdirs.keys():
+ print
+ dd.subdirs[x].report_closure()
+ #
+ def report_phase4_closure(dd): # Report and do phase 4 recursively
+ dd.report()
+ dd.phase4()
+ for x in dd.subdirs.keys():
+ print
+ dd.subdirs[x].report_phase4_closure()
+
+
+# Compare common files in two directories.
+# Return:
+# - files that compare equal
+# - files that compare different
+# - funny cases (can't stat etc.)
+#
+def cmpfiles(a, b, common):
+ res = ([], [], [])
+ for x in common:
+ res[cmp(path.cat(a, x), path.cat(b, x))].append(x)
+ return res
+
+
+# Compare two files.
+# Return:
+# 0 for equal
+# 1 for different
+# 2 for funny cases (can't stat, etc.)
+#
+def cmp(a, b):
+ try:
+ if cmpcache.cmp(a, b): return 0
+ return 1
+ except posix.error:
+ return 2
+
+
+# Remove a list item.
+# NB: This modifies the list argument.
+#
+def remove(list, item):
+ for i in range(len(list)):
+ if list[i] = item:
+ del list[i]
+ break
+
+
+# Return a copy with items that occur in skip removed.
+#
+def filter(list, skip):
+ result = []
+ for item in list:
+ if item not in skip: result.append(item)
+ return result
+
+
+# Demonstration and testing.
+#
+def demo():
+ import sys
+ import getopt
+ options, args = getopt.getopt(sys.argv[1:], 'r')
+ if len(args) <> 2: raise getopt.error, 'need exactly two args'
+ dd = dircmp().new(args[0], args[1])
+ dd.run()
+ if ('-r', '') in options:
+ dd.report_phase4_closure()
+ else:
+ dd.report()
+
+# demo()
diff --git a/lib/dirwin.py b/lib/dirwin.py
new file mode 100644
index 0000000..76e0b5e
--- /dev/null
+++ b/lib/dirwin.py
@@ -0,0 +1,29 @@
+# Module 'dirwin'
+
+# Directory windows, a subclass of listwin
+
+import gwin
+import listwin
+import anywin
+import path
+import dircache
+
+def action(w, string, i, detail):
+ (h, v), clicks, button, mask = detail
+ if clicks = 2:
+ name = path.cat(w.name, string)
+ try:
+ w2 = anywin.open(name)
+ w2.parent = w
+ except posix.error, why:
+ stdwin.message('Can\'t open ' + name + ': ' + why[1])
+
+def open(name):
+ name = path.cat(name, '')
+ list = dircache.opendir(name)[:]
+ list.sort()
+ dircache.annotate(name, list)
+ w = listwin.open(name, list)
+ w.name = name
+ w.action = action
+ return w
diff --git a/lib/dis.py b/lib/dis.py
new file mode 100644
index 0000000..d13b08f
--- /dev/null
+++ b/lib/dis.py
@@ -0,0 +1,176 @@
+# Disassembler
+
+import sys
+import string
+
+def dis():
+ tb = sys.last_traceback
+ while tb.tb_next: tb = tb.tb_next
+ distb(tb)
+
+def distb(tb):
+ disassemble(tb.tb_frame.f_code, tb.tb_lasti)
+
+def disco(co):
+ disassemble(co, -1)
+
+def disassemble(co, lasti):
+ code = co.co_code
+ labels = findlabels(code)
+ n = len(code)
+ i = 0
+ while i < n:
+ c = code[i]
+ op = ord(c)
+ if op = SET_LINENO and i > 0: print # Extra blank line
+ if i = lasti: print '-->',
+ else: print ' ',
+ if i in labels: print '>>',
+ else: print ' ',
+ print string.rjust(`i`, 4),
+ print string.ljust(opname[op], 15),
+ i = i+1
+ if op >= HAVE_ARGUMENT:
+ oparg = ord(code[i]) + ord(code[i+1])*256
+ i = i+2
+ print string.rjust(`oparg`, 5),
+ if op in hasconst:
+ print '(' + `co.co_consts[oparg]` + ')',
+ elif op in hasname:
+ print '(' + co.co_names[oparg] + ')',
+ elif op in hasjrel:
+ print '(to ' + `i + oparg` + ')',
+ print
+
+def findlabels(code):
+ labels = []
+ n = len(code)
+ i = 0
+ while i < n:
+ c = code[i]
+ op = ord(c)
+ i = i+1
+ if op >= HAVE_ARGUMENT:
+ oparg = ord(code[i]) + ord(code[i+1])*256
+ i = i+2
+ label = -1
+ if op in hasjrel:
+ label = i+oparg
+ elif op in hasjabs:
+ label = oparg
+ if label >= 0:
+ if label not in labels:
+ labels.append(label)
+ return labels
+
+hasconst = []
+hasname = []
+hasjrel = []
+hasjabs = []
+
+opname = range(256)
+for op in opname: opname[op] = '<' + `op` + '>'
+
+def def_op(name, op):
+ opname[op] = name
+
+def name_op(name, op):
+ opname[op] = name
+ hasname.append(op)
+
+def jrel_op(name, op):
+ opname[op] = name
+ hasjrel.append(op)
+
+def jabs_op(name, op):
+ opname[op] = name
+ hasjabs.append(op)
+
+# Instruction opcodes for compiled code
+
+def_op('STOP_CODE', 0)
+def_op('POP_TOP', 1)
+def_op('ROT_TWO', 2)
+def_op('ROT_THREE', 3)
+def_op('DUP_TOP', 4)
+
+def_op('UNARY_POSITIVE', 10)
+def_op('UNARY_NEGATIVE', 11)
+def_op('UNARY_NOT', 12)
+def_op('UNARY_CONVERT', 13)
+def_op('UNARY_CALL', 14)
+
+def_op('BINARY_MULTIPLY', 20)
+def_op('BINARY_DIVIDE', 21)
+def_op('BINARY_MODULO', 22)
+def_op('BINARY_ADD', 23)
+def_op('BINARY_SUBTRACT', 24)
+def_op('BINARY_SUBSCR', 25)
+def_op('BINARY_CALL', 26)
+
+def_op('SLICE+0', 30)
+def_op('SLICE+1', 31)
+def_op('SLICE+2', 32)
+def_op('SLICE+3', 33)
+
+def_op('STORE_SLICE+0', 40)
+def_op('STORE_SLICE+1', 41)
+def_op('STORE_SLICE+2', 42)
+def_op('STORE_SLICE+3', 43)
+
+def_op('DELETE_SLICE+0', 50)
+def_op('DELETE_SLICE+1', 51)
+def_op('DELETE_SLICE+2', 52)
+def_op('DELETE_SLICE+3', 53)
+
+def_op('STORE_SUBSCR', 60)
+def_op('DELETE_SUBSCR', 61)
+
+def_op('PRINT_EXPR', 70)
+def_op('PRINT_ITEM', 71)
+def_op('PRINT_NEWLINE', 72)
+
+def_op('BREAK_LOOP', 80)
+def_op('RAISE_EXCEPTION', 81)
+def_op('LOAD_LOCALS', 82)
+def_op('RETURN_VALUE', 83)
+def_op('REQUIRE_ARGS', 84)
+def_op('REFUSE_ARGS', 85)
+def_op('BUILD_FUNCTION', 86)
+def_op('POP_BLOCK', 87)
+def_op('END_FINALLY', 88)
+def_op('BUILD_CLASS', 89)
+
+HAVE_ARGUMENT = 90 # Opcodes from here have an argument:
+
+name_op('STORE_NAME', 90) # Index in name list
+name_op('DELETE_NAME', 91) # ""
+def_op('UNPACK_TUPLE', 92) # Number of tuple items
+def_op('UNPACK_LIST', 93) # Number of list items
+# unused: 94
+name_op('STORE_ATTR', 95) # Index in name list
+name_op('DELETE_ATTR', 96) # ""
+
+def_op('LOAD_CONST', 100) # Index in const list
+hasconst.append(100)
+name_op('LOAD_NAME', 101) # Index in name list
+def_op('BUILD_TUPLE', 102) # Number of tuple items
+def_op('BUILD_LIST', 103) # Number of list items
+def_op('BUILD_MAP', 104) # Always zero for now
+name_op('LOAD_ATTR', 105) # Index in name list
+def_op('COMPARE_OP', 106) # Comparison operator
+name_op('IMPORT_NAME', 107) # Index in name list
+name_op('IMPORT_FROM', 108) # Index in name list
+
+jrel_op('JUMP_FORWARD', 110) # Number of bytes to skip
+jrel_op('JUMP_IF_FALSE', 111) # ""
+jrel_op('JUMP_IF_TRUE', 112) # ""
+jabs_op('JUMP_ABSOLUTE', 113) # Target byte offset from beginning of code
+jrel_op('FOR_LOOP', 114) # Number of bytes to skip
+
+jrel_op('SETUP_LOOP', 120) # Distance to target address
+jrel_op('SETUP_EXCEPT', 121) # ""
+jrel_op('SETUP_FINALLY', 122) # ""
+
+def_op('SET_LINENO', 127) # Current line number
+SET_LINENO = 127
diff --git a/lib/dump.py b/lib/dump.py
new file mode 100644
index 0000000..0510092
--- /dev/null
+++ b/lib/dump.py
@@ -0,0 +1,63 @@
+# Module 'dump'
+#
+# Print python code that reconstructs a variable.
+# This only works in certain cases.
+#
+# It works fine for:
+# - ints and floats (except NaNs and other weird things)
+# - strings
+# - compounds and lists, provided it works for all their elements
+# - imported modules, provided their name is the module name
+#
+# It works for top-level dictionaries but not for dictionaries
+# contained in other objects (could be made to work with some hassle
+# though).
+#
+# It does not work for functions (all sorts), classes, class objects,
+# windows, files etc.
+#
+# Finally, objects referenced by more than one name or contained in more
+# than one other object lose their sharing property (this is bad for
+# strings used as exception identifiers, for instance).
+
+# Dump a whole symbol table
+#
+def dumpsymtab(dict):
+ for key in dict.keys():
+ dumpvar(key, dict[key])
+
+# Dump a single variable
+#
+def dumpvar(name, x):
+ import sys
+ t = type(x)
+ if t = type({}):
+ print name, '= {}'
+ for key in x.keys():
+ item = x[key]
+ if not printable(item):
+ print '#',
+ print name, '[', `key`, '] =', `item`
+ elif t in (type(''), type(0), type(0.0), type([]), type(())):
+ if not printable(x):
+ print '#',
+ print name, '=', `x`
+ elif t = type(sys):
+ print 'import', name, '#', x
+ else:
+ print '#', name, '=', x
+
+# check if a value is printable in a way that can be read back with input()
+#
+def printable(x):
+ t = type(x)
+ if t in (type(''), type(0), type(0.0)):
+ return 1
+ if t in (type([]), type(())):
+ for item in x:
+ if not printable(item):
+ return 0
+ return 1
+ if x = {}:
+ return 1
+ return 0
diff --git a/lib/fact.py b/lib/fact.py
new file mode 100644
index 0000000..ba961c4
--- /dev/null
+++ b/lib/fact.py
@@ -0,0 +1,37 @@
+# Factorize numbers -- slow, could use a table of all primes <= 2*16
+
+import sys
+import math
+
+error = 'fact.error' # exception
+
+def fact(n):
+ if n < 1: raise error # fact() argument should be >= 1
+ if n = 1: return [] # special case
+ res = []
+ _fact(n, 2, res)
+ return res
+
+def _fact(n, lowest, res):
+ highest = int(math.sqrt(float(n+1)))
+ for i in range(lowest, highest+1):
+ if n%i = 0:
+ res.append(i)
+ _fact(n/i, i, res)
+ break
+ else:
+ res.append(n)
+
+def main():
+ if len(sys.argv) > 1:
+ for arg in sys.argv[1:]:
+ n = eval(arg)
+ print n, fact(n)
+ else:
+ try:
+ while 1:
+ print fact(input())
+ except EOFError:
+ pass
+
+main()
diff --git a/lib/filewin.py b/lib/filewin.py
new file mode 100644
index 0000000..ad35ea4
--- /dev/null
+++ b/lib/filewin.py
@@ -0,0 +1,18 @@
+# Module 'filewin'
+# File windows, a subclass of textwin (which is a subclass of gwin)
+
+import textwin
+from util import readfile
+
+
+# FILE WINDOW
+
+def open_readonly(fn): # Open a file window
+ w = textwin.open_readonly(fn, readfile(fn))
+ w.fn = fn
+ return w
+
+def open(fn): # Open a file window
+ w = textwin.open(fn, readfile(fn))
+ w.fn = fn
+ return w
diff --git a/lib/fnmatch.py b/lib/fnmatch.py
new file mode 100644
index 0000000..8f9e318
--- /dev/null
+++ b/lib/fnmatch.py
@@ -0,0 +1,35 @@
+# module 'fnmatch' -- filename matching with shell patterns
+
+# XXX [] patterns are not supported (but recognized)
+
+def fnmatch(name, pat):
+ if '*' in pat or '?' in pat or '[' in pat:
+ return fnmatch1(name, pat)
+ return name = pat
+
+def fnmatch1(name, pat):
+ for i in range(len(pat)):
+ c = pat[i]
+ if c = '*':
+ restpat = pat[i+1:]
+ if '*' in restpat or '?' in restpat or '[' in restpat:
+ for i in range(i, len(name)):
+ if fnmatch1(name[i:], restpat):
+ return 1
+ return 0
+ else:
+ return name[len(name)-len(restpat):] = restpat
+ elif c = '?':
+ if len(name) <= i : return 0
+ elif c = '[':
+ return 0 # XXX
+ else:
+ if name[i:i+1] <> c:
+ return 0
+ return 1
+
+def fnmatchlist(names, pat):
+ res = []
+ for name in names:
+ if fnmatch(name, pat): res.append(name)
+ return res
diff --git a/lib/getopt.py b/lib/getopt.py
new file mode 100644
index 0000000..f343c22
--- /dev/null
+++ b/lib/getopt.py
@@ -0,0 +1,47 @@
+# module getopt -- Standard command line processing.
+
+# Function getopt.getopt() has a different interface but provides the
+# same functionality as the Unix getopt() function.
+
+# It has two arguments: the first should be argv[1:] (it doesn't want
+# the script name), the second the string of option letters as passed
+# to Unix getopt() (i.e., a string of allowable option letters, with
+# options requiring an argument followed by a colon).
+
+# It raises the exception getopt.error with a string argument if it
+# detects an error.
+
+# It returns two items:
+# (1) a list of pairs (option, option_argument) giving the options in
+# the order in which they were specified. (I'd use a dictionary
+# but applications may depend on option order or multiple
+# occurrences.) Boolean options have '' as option_argument.
+# (2) the list of remaining arguments (may be empty).
+
+error = 'getopt error'
+
+def getopt(args, options):
+ list = []
+ while args and args[0][0] = '-' and args[0] <> '-':
+ if args[0] = '--':
+ args = args[1:]
+ break
+ optstring, args = args[0][1:], args[1:]
+ while optstring <> '':
+ opt, optstring = optstring[0], optstring[1:]
+ if classify(opt, options): # May raise exception as well
+ if optstring = '':
+ if not args:
+ raise error, 'option -' + opt + ' requires argument'
+ optstring, args = args[0], args[1:]
+ optarg, optstring = optstring, ''
+ else:
+ optarg = ''
+ list.append('-' + opt, optarg)
+ return list, args
+
+def classify(opt, options): # Helper to check type of option
+ for i in range(len(options)):
+ if opt = options[i] <> ':':
+ return options[i+1:i+2] = ':'
+ raise error, 'option -' + opt + ' not recognized'
diff --git a/lib/glob.py b/lib/glob.py
new file mode 100644
index 0000000..b711094
--- /dev/null
+++ b/lib/glob.py
@@ -0,0 +1,44 @@
+# Module 'glob' -- filename globbing.
+
+import posix
+import path
+import fnmatch
+
+def glob(pathname):
+ if not has_magic(pathname): return [pathname]
+ dirname, basename = path.split(pathname)
+ if dirname[-1:] = '/' and dirname <> '/':
+ dirname = dirname[:-1]
+ if has_magic(dirname):
+ list = glob(dirname)
+ else:
+ list = [dirname]
+ if not has_magic(basename):
+ result = []
+ for dirname in list:
+ if basename or path.isdir(dirname):
+ name = path.cat(dirname, basename)
+ if path.exists(name):
+ result.append(name)
+ else:
+ result = []
+ for dirname in list:
+ sublist = glob1(dirname, basename)
+ for name in sublist:
+ result.append(path.cat(dirname, name))
+ return result
+
+def glob1(dirname, pattern):
+ if not dirname: dirname = '.'
+ try:
+ names = posix.listdir(dirname)
+ except posix.error:
+ return []
+ result = []
+ for name in names:
+ if name[0] <> '.' or pattern[0] = '.':
+ if fnmatch.fnmatch(name, pattern): result.append(name)
+ return result
+
+def has_magic(s):
+ return '*' in s or '?' in s or '[' in s
diff --git a/lib/grep.py b/lib/grep.py
new file mode 100644
index 0000000..66eb08d
--- /dev/null
+++ b/lib/grep.py
@@ -0,0 +1,32 @@
+# 'grep'
+
+import regexp
+import string
+
+def grep(expr, filename):
+ prog = regexp.compile(expr)
+ fp = open(filename, 'r')
+ lineno = 0
+ while 1:
+ line = fp.readline()
+ if not line: break
+ lineno = lineno + 1
+ res = prog.exec(line)
+ if res:
+ #print res
+ start, end = res[0]
+ if line[-1:] = '\n': line = line[:-1]
+ prefix = string.rjust(`lineno`, 3) + ': '
+ print prefix + line
+ if 0:
+ line = line[:start]
+ if '\t' not in line:
+ prefix = ' ' * (len(prefix) + start)
+ else:
+ prefix = ' ' * len(prefix)
+ for c in line:
+ if c <> '\t': c = ' '
+ prefix = prefix + c
+ if start = end: prefix = prefix + '\\'
+ else: prefix = prefix + '^'*(end-start)
+ print prefix
diff --git a/lib/gwin.py b/lib/gwin.py
new file mode 100644
index 0000000..f6291e1
--- /dev/null
+++ b/lib/gwin.py
@@ -0,0 +1,122 @@
+# Module 'gwin'
+# Generic stdwin windows
+
+# This is used as a base class from which to derive other window types.
+# The mainloop() function here is an event dispatcher for all window types.
+
+import stdwin
+from stdwinevents import *
+
+# XXX Old version of stdwinevents, should go
+import stdwinsupport
+S = stdwinsupport # Shorthand
+
+windows = [] # List of open windows
+
+
+# Open a window
+
+def open(title): # Open a generic window
+ w = stdwin.open(title)
+ stdwin.setdefwinsize(0, 0)
+ # Set default event handlers
+ w.draw = nop
+ w.char = nop
+ w.mdown = nop
+ w.mmove = nop
+ w.mup = nop
+ w.m2down = m2down
+ w.m2up = m2up
+ w.size = nop
+ w.move = nop
+ w.activate = w.deactivate = nop
+ w.timer = nop
+ # default command handlers
+ w.close = close
+ w.tab = tab
+ w.enter = enter
+ w.backspace = backspace
+ w.arrow = arrow
+ w.kleft = w.kup = w.kright = w.kdown = nop
+ windows.append(w)
+ return w
+
+
+# Generic event dispatching
+
+def mainloop(): # Handle events until no windows left
+ while windows:
+ treatevent(stdwin.getevent())
+
+def treatevent(e): # Handle a stdwin event
+ type, w, detail = e
+ if type = S.we_draw:
+ w.draw(w, detail)
+ elif type = S.we_menu:
+ m, item = detail
+ m.action[item](w, m, item)
+ elif type = S.we_command:
+ treatcommand(w, detail)
+ elif type = S.we_char:
+ w.char(w, detail)
+ elif type = S.we_mouse_down:
+ if detail[1] > 1: w.m2down(w, detail)
+ else: w.mdown(w, detail)
+ elif type = S.we_mouse_move:
+ w.mmove(w, detail)
+ elif type = S.we_mouse_up:
+ if detail[1] > 1: w.m2up(w, detail)
+ else: w.mup(w, detail)
+ elif type = S.we_size:
+ w.size(w, w.getwinsize())
+ elif type = S.we_activate:
+ w.activate(w)
+ elif type = S.we_deactivate:
+ w.deactivate(w)
+ elif type = S.we_move:
+ w.move(w)
+ elif type = S.we_timer:
+ w.timer(w)
+ elif type = WE_CLOSE:
+ w.close(w)
+
+def treatcommand(w, type): # Handle a we_command event
+ if type = S.wc_close:
+ w.close(w)
+ elif type = S.wc_return:
+ w.enter(w)
+ elif type = S.wc_tab:
+ w.tab(w)
+ elif type = S.wc_backspace:
+ w.backspace(w)
+ elif type in (S.wc_left, S.wc_up, S.wc_right, S.wc_down):
+ w.arrow(w, type)
+
+
+# Methods
+
+def close(w): # Close method
+ for i in range(len(windows)):
+ if windows[i] is w:
+ del windows[i]
+ break
+
+def arrow(w, detail): # Arrow key method
+ if detail = S.wc_left:
+ w.kleft(w)
+ elif detail = S.wc_up:
+ w.kup(w)
+ elif detail = S.wc_right:
+ w.kright(w)
+ elif detail = S.wc_down:
+ w.kdown(w)
+
+
+# Trivial methods
+
+def tab(w): w.char(w, '\t')
+def enter(w): w.char(w, '\n') # 'return' is a Python reserved word
+def backspace(w): w.char(w, '\b')
+def m2down(w, detail): w.mdown(w, detail)
+def m2up(w, detail): w.mup(w, detail)
+def nop(args): pass
diff --git a/lib/lambda.py b/lib/lambda.py
new file mode 100644
index 0000000..7d62b83
--- /dev/null
+++ b/lib/lambda.py
@@ -0,0 +1,151 @@
+# A bit of Lambda Calculus illustrated in Python.
+#
+# This does not use Python's built-in 'eval' or 'exec' functions!
+
+
+# Currying
+#
+# From a function with 2 args, f(x, y), and a value for the 1st arg,
+# we can create a new function with one argument fx(y) = f(x, y).
+# This is called "Currying" (after the idea's inventor, a Mr. Curry).
+#
+# To implement this we create a class member, of which fx is a method;
+# f and x are stored as data attributes of the member.
+
+class _CurryClass():
+ def new(self, (f, x)):
+ self.f = f
+ self.x = x
+ return self
+ def fx(self, y):
+ return self.f(self.x, y)
+
+def CURRY(f, x):
+ # NB: f is not "simple": it has 2 arguments
+ return _CurryClass().new(f, x).fx
+
+
+# Numbers in Lambda Calculus
+#
+# In the lambda calculus, natural numbers are represented by
+# higher-order functions Ntimes(f, x) that yield f applied N
+# times to x, e.g., Twice(f, x) = f(f(x)).
+# As far as I understand, there is no difference in real lambda
+# calculus between
+# lambda f x : f(f(x))
+# and
+# lambda f : f o f # 'o' means function composition
+# but in Python we have to write the first as Twice(f, x) and
+# the second as twice(f).
+
+def Never(f, x): return x
+def Once(f, x): return f(x)
+def Twice(f, x): return f(f(x))
+def Thrice(f, x): return f(f(f(x)))
+def Fourfold(f, x): return f(f(f(f(x))))
+# (etc.)
+
+def never(f): return CURRY(Never, f)
+def once(f): return CURRY(Once, f)
+def twice(f): return CURRY(Twice, f)
+def thrice(f): return CURRY(Thrice, f)
+def fourfold(f): return CURRY(Fourfold, f)
+# (etc.)
+
+
+# NB: actually 'ntimes' is less concrete than 'Ntimes', as
+# 'ntimes' returns a function, while 'Ntimes' returns a value
+# similar to what f(x) returns. 'Ntimes' can be derived
+# from 'ntimes', as follows:
+# def Ntimes(f, x): return ntimes(f)(x)
+# but this doesn't help us since 'ntimes' can only be defined
+# using Ntimes (or a trick like the one used by CURRY).
+
+
+# Arithmetic in Lambda Calculus
+#
+# We can perform simple arithmetic on the un-curried versions, e.g.,
+# Successor(Twice) = Thrice (2+1)
+# Sum(Thrice, Twice) = Fivefold (3+2)
+# Product(Thrice, Twice) = Sixfold (3*2)
+# Power(Thrice, Twice) = Ninefold (3**2)
+#
+# First we define versions that need f and x arguments.
+# They have funny argument forms so the final functions can
+# use CURRY, which only works on functions of exactly 2 arguments.
+
+def SUCCESSOR(Ntimes, (f, x)): return f(Ntimes(f, x))
+def SUCCESSOR(Ntimes, (f, x)): return Ntimes(f, f(x)) # Same effect
+def SUM(Ntimes, (Mtimes, (f, x))): return Ntimes(f, Mtimes(f, x))
+def PRODUCT(Ntimes, (Mtimes, (f, x))): return Ntimes(CURRY(Mtimes, f), x)
+def POWER(Ntimes, (Mtimes, (f, x))):
+ return Mtimes(CURRY(CURRY, Ntimes), f)(x)
+
+def Successor(Ntimes): return CURRY(SUCCESSOR, Ntimes)
+def Sum(Ntimes, Mtimes): return CURRY(CURRY(SUM, Ntimes), Mtimes)
+def Product(Ntimes, Mtimes): return CURRY(CURRY(PRODUCT, Ntimes), Mtimes)
+def Power(Ntimes, Mtimes): return CURRY(CURRY(POWER, Ntimes), Mtimes)
+
+
+# Sorry, I don't have a clue on how to do subtraction or division...
+
+
+# References
+#
+# All I know about lambda calculus is from Roger Penrose's
+# The Emperor's New Mind, Chapter 2.
+
+
+# P.S.: Here is a Lambda function in Python.
+# It uses 'exec' and expects two strings to describe the arguments
+# and the function expression. Example:
+# lambda('x', 'x+1')
+# defines the successor function.
+
+def lambda(args, expr):
+ if '\n' in args or '\n' in expr:
+ raise RuntimeError, 'lambda: no cheating!'
+ stmt = 'def func(' + args + '): return ' + expr + '\n'
+ print 'lambda:', stmt,
+ exec(stmt)
+ return func
+
+
+# P.P.S.S.: Here is a way to construct Ntimes and ntimes directly.
+# Example:
+# GenericNtimes(4)
+# is equivalent to Fourfold.
+
+class _GenericNtimesClass():
+ def new(self, n):
+ self.n = n
+ return self
+ def Ntimes(self, (f, x)):
+ n = self.n
+ while n > 0: x, n = f(x), n-1
+ return x
+
+def GenericNtimes(n):
+ return _GenericNtimesClass().new(n).Ntimes
+
+
+# To construct any 'ntimes' function from the corresponding 'Ntimes',
+# we use a trick as used by CURRY. For example,
+# Ntimes2ntimes(Fourfold)
+# yields a function equivalent to fourfold.
+
+class _Ntimes2ntimesClass():
+ def new(self, Ntimes):
+ self.Ntimes = Ntimes
+ return self
+ def ntimes(self, f):
+ return CURRY(self.Ntimes, f)
+
+def Ntimes2ntimes(Ntimes): return _Ntimes2ntimesClass().new(Ntimes).ntimes
+
+
+# This allows us to construct generic 'ntimes' functions. Example:
+# generic_ntimes(3)
+# is the same as thrice.
+
+def generic_ntimes(n): return Ntimes2ntimes(GenericNtimes(n))
diff --git a/lib/listwin.py b/lib/listwin.py
new file mode 100644
index 0000000..aac127c
--- /dev/null
+++ b/lib/listwin.py
@@ -0,0 +1,47 @@
+# Module 'listwin'
+# List windows, a subclass of gwin
+
+import gwin
+import stdwin
+
+def maxlinewidth(a): # Compute maximum textwidth of lines in a sequence
+ max = 0
+ for line in a:
+ width = stdwin.textwidth(line)
+ if width > max: max = width
+ return max
+
+def action(w, string, i, detail): # Default item selection method
+ pass
+
+def mup(w, detail): # Mouse up method
+ (h, v), clicks, button, mask = detail
+ i = divmod(v, w.lineheight)[0]
+ if 0 <= i < len(w.data):
+ w.action(w, w.data[i], i, detail)
+
+def draw(w, ((left, top), (right, bottom))): # Text window draw method
+ data = w.data
+ d = w.begindrawing()
+ lh = w.lineheight
+ itop = top/lh
+ ibot = (bottom-1)/lh + 1
+ if itop < 0: itop = 0
+ if ibot > len(data): ibot = len(data)
+ for i in range(itop, ibot): d.text((0, i*lh), data[i])
+
+def open(title, data): # Display a list of texts in a window
+ lineheight = stdwin.lineheight()
+ h, v = maxlinewidth(data), len(data)*lineheight
+ h0, v0 = h + stdwin.textwidth(' '), v + lineheight
+ if h0 > stdwin.textwidth(' ')*80: h0 = 0
+ if v0 > stdwin.lineheight()*24: v0 = 0
+ stdwin.setdefwinsize(h0, v0)
+ w = gwin.open(title)
+ w.setdocsize(h, v)
+ w.lineheight = lineheight
+ w.data = data
+ w.draw = draw
+ w.action = action
+ w.mup = mup
+ return w
diff --git a/lib/localtime.py b/lib/localtime.py
new file mode 100644
index 0000000..5ce6529
--- /dev/null
+++ b/lib/localtime.py
@@ -0,0 +1,53 @@
+# module localtime -- Time conversions
+
+import posix
+
+epoch = 1970 # 1 jan 00:00:00, UCT
+day0 = 4 # day 0 was a thursday
+
+day_names = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
+
+month_names = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun')
+month_names = month_names + ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
+
+month_sizes = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+
+def isleap(year):
+ return year % 4 = 0 and (year % 100 <> 0 or year % 400 = 0)
+
+def gmtime(secs): # decode time into UCT
+ mins, secs = divmod(secs, 60)
+ hours, mins = divmod(mins, 60)
+ days, hours = divmod(hours, 24)
+ wday = (day0 + days) % 7
+ year = epoch
+ lp = isleap(year)
+ dpy = 365 + lp
+ while days >= dpy:
+ days = days - dpy
+ year = year + 1
+ lp = isleap(year)
+ dpy = 365 + lp
+ yday = days
+ month = 0
+ dpm = month_sizes[month] + (lp and month = 1)
+ while days >= dpm:
+ days = days - dpm
+ month = month + 1
+ dpm = month_sizes[month] + (lp and month = 1)
+ return (year, month, days+1, hours, mins, secs, yday, wday)
+
+def dd(x):
+ s = `x`
+ while len(s) < 2: s = '0' + s
+ return s
+
+def zd(x):
+ s = `x`
+ while len(s) < 2: s = ' ' + s
+ return s
+
+def format(year, month, days, hours, mins, secs, yday, wday):
+ s = day_names[wday] + ' ' + zd(days) + ' ' + month_names[month] + ' '
+ s = s + dd(hours) + ':' + dd(mins) + ':' + dd(secs)
+ return s
diff --git a/lib/maccache.py b/lib/maccache.py
new file mode 100644
index 0000000..329e514
--- /dev/null
+++ b/lib/maccache.py
@@ -0,0 +1,61 @@
+# Module 'maccache'
+#
+# Maintain a cache of listdir(), isdir(), isfile() or exists() outcomes.
+
+import mac
+import macpath
+
+
+# The cache.
+# Keys are absolute pathnames;
+# values are 0 (nothing), 1 (file) or [...] (dir).
+#
+cache = {}
+
+
+# Current working directory.
+#
+cwd = mac.getcwd()
+
+
+# Constants.
+#
+NONE = 0
+FILE = 1
+LISTTYPE = type([])
+
+def _stat(name):
+ name = macpath.cat(cwd, name)
+ if cache.has_key(name):
+ return cache[name]
+ if macpath.isfile(name):
+ cache[name] = FILE
+ return FILE
+ try:
+ list = mac.listdir(name)
+ except:
+ cache[name] = NONE
+ return NONE
+ cache[name] = list
+ if name[-1:] = ':': cache[name[:-1]] = list
+ else: cache[name+':'] = list
+ return list
+
+def isdir(name):
+ st = _stat(name)
+ return type(st) = LISTTYPE
+
+def isfile(name):
+ st = _stat(name)
+ return st = FILE
+
+def exists(name):
+ st = _stat(name)
+ return st <> NONE
+
+def listdir(name):
+ st = _stat(name)
+ if type(st) = LISTTYPE:
+ return st
+ else:
+ raise RuntimeError, 'list non-directory'
diff --git a/lib/macglob.py b/lib/macglob.py
new file mode 100644
index 0000000..cad2c79
--- /dev/null
+++ b/lib/macglob.py
@@ -0,0 +1,46 @@
+# Module 'macglob' -- version of 'glob' for the Macintosh.
+
+# XXX At least one bug is left: a pattern like '*:' is treated
+# XXX as a relative pathname (and returns as if it was ':*:').
+
+import mac
+import macpath
+import fnmatch
+
+def glob(pathname):
+ if not has_magic(pathname): return [pathname]
+ dirname, basename = macpath.split(pathname)
+ if has_magic(dirname):
+ if dirname[-1:] = ':': dirname = dirname[:-1]
+ list = glob(dirname)
+ else:
+ list = [dirname]
+ if not has_magic(basename):
+ result = []
+ for dirname in list:
+ if basename or macpath.isdir(dirname):
+ name = macpath.cat(dirname, basename)
+ if macpath.exists(name):
+ result.append(name)
+ else:
+ result = []
+ for dirname in list:
+ sublist = glob1(dirname, basename)
+ for name in sublist:
+ result.append(macpath.cat(dirname, name))
+ return result
+
+def glob1(dirname, pattern):
+ if not dirname: dirname = ':'
+ try:
+ names = mac.listdir(dirname)
+ except mac.error:
+ return []
+ result = []
+ for name in names:
+ if name[0] <> '.' or pattern[0] = '.':
+ if fnmatch.fnmatch(name, pattern): result.append(name)
+ return result
+
+def has_magic(s):
+ return '*' in s or '?' in s or '[' in s
diff --git a/lib/macpath.py b/lib/macpath.py
new file mode 100644
index 0000000..99ba6b8
--- /dev/null
+++ b/lib/macpath.py
@@ -0,0 +1,108 @@
+# module 'macpath' -- pathname (or -related) operations for the Macintosh
+
+import mac
+
+from stat import *
+
+
+# Return true if a path is absolute.
+# On the Mac, relative paths begin with a colon,
+# but as a special case, paths with no colons at all are also relative.
+# Anything else is absolute (the string up to the first colon is the
+# volume name).
+
+def isabs(s):
+ return ':' in s and s[0] <> ':'
+
+
+# Concatenate two pathnames.
+# The result is equivalent to what the second pathname would refer to
+# if the first pathname were the current directory.
+
+def cat(s, t):
+ if (not s) or isabs(t): return t
+ if t[:1] = ':': t = t[1:]
+ if ':' not in s:
+ s = ':' + s
+ if s[-1:] <> ':':
+ s = s + ':'
+ return s + t
+
+
+# Split a pathname in two parts: the directory leading up to the final bit,
+# and the basename (the filename, without colons, in that directory).
+# The result (s, t) is such that cat(s, t) yields the original argument.
+
+def split(s):
+ if ':' not in s: return '', s
+ colon = 0
+ for i in range(len(s)):
+ if s[i] = ':': colon = i+1
+ return s[:colon], s[colon:]
+
+
+# Normalize a pathname: get rid of '::' sequences by backing up,
+# e.g., 'foo:bar::bletch' becomes 'foo:bletch'.
+# Raise the exception norm_error below if backing up is impossible,
+# e.g., for '::foo'.
+
+norm_error = 'macpath.norm_error: path cannot be normalized'
+
+def norm(s):
+ import string
+ if ':' not in s:
+ return ':' + s
+ f = string.splitfields(s, ':')
+ pre = []
+ post = []
+ if not f[0]:
+ pre = f[:1]
+ f = f[1:]
+ if not f[len(f)-1]:
+ post = f[-1:]
+ f = f[:-1]
+ res = []
+ for seg in f:
+ if seg:
+ res.append(seg)
+ else:
+ if not res: raise norm_error, 'path starts with ::'
+ del res[len(res)-1]
+ if not (pre or res):
+ raise norm_error, 'path starts with volume::'
+ if pre: res = pre + res
+ if post: res = res + post
+ s = res[0]
+ for seg in res[1:]:
+ s = s + ':' + seg
+ return s
+
+
+# Return true if the pathname refers to an existing directory.
+
+def isdir(s):
+ try:
+ st = mac.stat(s)
+ except mac.error:
+ return 0
+ return S_ISDIR(st[ST_MODE])
+
+
+# Return true if the pathname refers to an existing regular file.
+
+def isfile(s):
+ try:
+ st = mac.stat(s)
+ except mac.error:
+ return 0
+ return S_ISREG(st[ST_MODE])
+
+
+# Return true if the pathname refers to an existing file or directory.
+
+def exists(s):
+ try:
+ st = mac.stat(s)
+ except mac.error:
+ return 0
+ return 1
diff --git a/lib/macshell.py b/lib/macshell.py
new file mode 100644
index 0000000..c96d65b
--- /dev/null
+++ b/lib/macshell.py
@@ -0,0 +1,399 @@
+# Macintosh 'shell'
+# vi:set tabsize=4:
+
+# XXX string quoting in arguments
+# XXX directory stack
+# XXX $macros?
+# XXX ^C during any command
+# XXX 'sh' builtin command?
+# XXX why does open require absolute path? (need to fix chdir.c)
+
+
+import mac
+
+import macpath
+import string
+import glob
+from macpath import isfile, isdir, exists
+import TclUtil # For splitting/quoting mechanisms
+
+class Struct(): pass
+G = Struct()
+
+def reset():
+ G.debug = 0
+ G.ps1 = '$ '
+ G.homedir = mac.getcwd()
+ G.commands = mkcmdtab()
+ G.aliases = {}
+
+def mkcmdtab():
+ tab = {}
+ tab['alias'] = do_alias
+ tab['cd'] = do_cd
+ tab['debug'] = do_debug
+ tab['grep'] = do_grep
+ tab['help'] = do_help
+ tab['ls'] = do_ls
+ tab['mkdir'] = do_mkdir
+ tab['mv'] = do_mv
+ tab['page'] = do_page
+ tab['pwd'] = do_pwd
+ tab['reset'] = do_reset
+ tab['rm'] = do_rm
+ tab['rmdir'] = do_rmdir
+ tab['sync'] = do_sync
+ tab['unalias'] = do_unalias
+ return tab
+
+def main():
+ while 1:
+ try:
+ line = raw_input(G.ps1)
+ except EOFError:
+ print '[EOF]'
+ break
+ except KeyboardInterrupt:
+ print '[Intr]'
+ line = ''
+ if G.debug:
+ print 'line:', `line`
+ words = TclUtil.SplitList(line)
+ if G.debug:
+ print 'words:', words
+ if words and words[0][0] <> '#':
+ run(words)
+
+def run(words):
+ expandaliases(words)
+ cmd = words[0]
+ args = words[1:]
+ if G.commands.has_key(cmd):
+ if args:
+ try:
+ args = expandgloblist(args)
+ except glob_error, msg:
+ print cmd, ': glob error :', msg
+ return
+ G.commands[cmd](args)
+ return
+ if hasglobchar(cmd):
+ if args:
+ print cmd, ': cannot glob pattern with arguments'
+ return
+ try:
+ words = expandglobword(cmd)
+ except glob_error, msg:
+ print cmd, ': glob error :', msg
+ return
+ if len(words) > 1:
+ columnize(words)
+ return
+ cmd = words[0]
+ print cmd
+ if isfile(cmd):
+ if args:
+ print cmd, ': file command expects no arguments'
+ return
+ do_page([cmd])
+ elif isdir(cmd):
+ if args:
+ print cmd, ': directory command expects no arguments'
+ return
+ do_cd([cmd])
+ else:
+ print cmd, ': no such command, file or directory'
+
+glob_error = 'glob error'
+
+def expandgloblist(words):
+ res = []
+ for word in words:
+ if hasglobchar(word):
+ res = res + expandglobword(word)
+ else:
+ res.append(word)
+ return res
+
+def expandglobword(word):
+ names = glob.globlist(mac.listdir(':'), word)
+ if not names: raise glob_error, 'no match for pattern ' + word
+ return names
+
+def hasglobchar(word):
+ return '*' in word or '?' in word
+
+def expandaliases(words):
+ seen = []
+ cmd = words[0]
+ while cmd not in seen and G.aliases.has_key(cmd):
+ seen.append(cmd)
+ words[:1] = G.aliases[cmd]
+ cmd = words[0]
+
+def do_alias(args):
+ if not args:
+ listaliases()
+ elif len(args) = 1:
+ listalias(args[0])
+ else:
+ defalias(args[0], args[1:])
+
+def listaliases():
+ names = G.aliases.keys()
+ names.sort()
+ for name in names: listalias(name)
+
+def listalias(name):
+ if not G.aliases.has_key(name):
+ print name, ': no such alias'
+ return
+ print 'alias', name,
+ printlist(G.aliases[name])
+ print
+
+def defalias(name, expansion):
+ G.aliases[name] = expansion
+
+def do_cd(args):
+ if len(args) > 1:
+ print 'usage: cd [dirname]'
+ elif args:
+ chdirto(args[0])
+ else:
+ chdirto(G.homedir)
+
+def chdirto(dirname):
+ try:
+ mac.chdir(dirname)
+ except mac.error, msg:
+ print dirname, ':', msg
+ return
+
+def do_debug(args):
+ G.debug = (not G.debug)
+
+def do_grep(args):
+ if len(args) < 2:
+ print 'usage: grep regexp file ...'
+ return
+ import regexp
+ try:
+ prog = regexp.compile(args[0])
+ except regexp.error, msg:
+ print 'regexp.compile error for', args[0], ':', msg
+ return
+ for file in args[1:]:
+ grepfile(prog, file)
+
+def grepfile(prog, file):
+ try:
+ fp = open(file, 'r')
+ except RuntimeError, msg:
+ print file, ': cannot open :', msg
+ return
+ lineno = 0
+ while 1:
+ line = fp.readline()
+ if not line: break
+ lineno = lineno+1
+ if prog.exec(line):
+ print file+'('+`lineno`+'):', line,
+
+def do_help(args):
+ if args:
+ print 'usage: help'
+ return
+ names = G.commands.keys()
+ names.sort()
+ columnize(names)
+
+def do_ls(args):
+ if not args:
+ lsdir(':')
+ else:
+ for dirname in args:
+ lsdir(dirname)
+
+def lsdir(dirname):
+ if not isdir(dirname):
+ print dirname, ': no such directory'
+ return
+ names = mac.listdir(dirname)
+ lsfiles(names, dirname)
+
+def lsfiles(names, dirname):
+ names = names[:] # Make a copy so we can modify it
+ for i in range(len(names)):
+ name = names[i]
+ if G.debug: print i, name
+ if isdir(macpath.cat(dirname, name)):
+ names[i] = ':' + name + ':'
+ columnize(names)
+
+def columnize(list):
+ COLUMNS = 80-1
+ n = len(list)
+ colwidth = maxwidth(list)
+ ncols = (COLUMNS + 1) / (colwidth + 1)
+ if ncols < 1: ncols = 1
+ nrows = (n + ncols - 1) / ncols
+ for irow in range(nrows):
+ line = ''
+ for icol in range(ncols):
+ i = irow + nrows*icol
+ if 0 <= i < n:
+ word = list[i]
+ if i+nrows < n:
+ word = string.ljust(word, colwidth)
+ if icol > 0:
+ word = ' ' + word
+ line = line + word
+ print line
+
+def maxwidth(list):
+ width = 0
+ for word in list:
+ if len(word) > width:
+ width = len(word)
+ return width
+
+def do_mv(args):
+ if len(args) <> 2:
+ print 'usage: mv src dst'
+ return
+ src, dst = args[0], args[1]
+ if not exists(src):
+ print src, ': source does not exist'
+ return
+ if exists(dst):
+ print src, ': destination already exists'
+ return
+ try:
+ mac.rename(src, dst)
+ except mac.error, msg:
+ print src, dst, ': rename failed:', msg
+
+def do_mkdir(args):
+ if not args:
+ print 'usage: mkdir name ...'
+ return
+ for name in args:
+ makedir(name)
+
+def makedir(name):
+ if exists(name):
+ print name, ': already exists'
+ return
+ try:
+ mac.mkdir(name, 0777)
+ except mac.error, msg:
+ print name, ': mkdir failed:', msg
+
+def do_page(args):
+ if not args:
+ print 'usage: page file ...'
+ return
+ for name in args:
+ pagefile(name)
+
+def pagefile(name):
+ if not isfile(name):
+ print name, ': no such file'
+ return
+ LINES = 24 - 1
+ # For THINK C 3.0, make the path absolute:
+ # if not macpath.isabs(name):
+ # name = macpath.cat(mac.getcwd(), name)
+ try:
+ fp = open(name, 'r')
+ except:
+ print name, ': cannot open'
+ return
+ line = fp.readline()
+ while line:
+ for i in range(LINES):
+ print line,
+ line = fp.readline()
+ if not line: break
+ if line:
+ try:
+ more = raw_input('[more]')
+ except (EOFError, KeyboardInterrupt):
+ print
+ break
+ if string.strip(more)[:1] in ('q', 'Q'):
+ break
+
+def do_pwd(args):
+ if args:
+ print 'usage: pwd'
+ else:
+ print mac.getcwd()
+
+def do_reset(args):
+ if args:
+ print 'usage: reset'
+ else:
+ reset()
+
+def do_rm(args):
+ if not args:
+ print 'usage: rm file ...'
+ return
+ for name in args:
+ remove(name)
+
+def remove(name):
+ if not isfile(name):
+ print name, ': no such file'
+ return
+ try:
+ mac.unlink(name)
+ except mac.error, msg:
+ print name, ': unlink failed:', msg
+
+def do_rmdir(args):
+ if not args:
+ print 'usage: rmdir dir ...'
+ return
+ for name in args:
+ rmdir(name)
+
+def rmdir(name):
+ if not isdir(name):
+ print name, ': no such directory'
+ return
+ try:
+ mac.rmdir(name)
+ except mac.error, msg:
+ print name, ': rmdir failed:', msg
+
+def do_sync(args):
+ if args:
+ print 'usage: sync'
+ return
+ try:
+ mac.sync()
+ except mac.error, msg:
+ print 'sync failed:', msg
+
+def do_unalias(args):
+ if not args:
+ print 'usage: unalias name ...'
+ return
+ for name in args:
+ unalias(name)
+
+def unalias(name):
+ if not G.aliases.has_key(name):
+ print name, ': no such alias'
+ return
+ del G.aliases[name]
+
+def printlist(list):
+ for word in list:
+ print word,
+
+reset()
+main()
diff --git a/lib/minmax.py b/lib/minmax.py
new file mode 100644
index 0000000..38e20d1
--- /dev/null
+++ b/lib/minmax.py
@@ -0,0 +1,6 @@
+# Module 'minmax'
+# These are now built in functions.
+# For compatibility, we export the builtins
+
+min = min
+max = max
diff --git a/lib/packmail.py b/lib/packmail.py
new file mode 100644
index 0000000..40b5b7a
--- /dev/null
+++ b/lib/packmail.py
@@ -0,0 +1,48 @@
+# Module 'packmail' -- create a shell script out of some files.
+
+import mac
+import macpath
+from stat import ST_MTIME
+
+# Pack one file
+def pack(outfp, file, name):
+ fp = open(file, 'r')
+ outfp.write('sed "s/^X//" >' + name + ' <<"!"\n')
+ while 1:
+ line = fp.readline()
+ if not line: break
+ if line[-1:] <> '\n':
+ line = line + '\n'
+ outfp.write('X' + line)
+ outfp.write('!\n')
+
+# Pack some files from a directory
+def packsome(outfp, dirname, names):
+ for name in names:
+ print name
+ file = macpath.cat(dirname, name)
+ pack(outfp, file, name)
+
+# Pack all files from a directory
+def packall(outfp, dirname):
+ names = mac.listdir(dirname)
+ names.sort()
+ packsome(outfp, dirname, names)
+
+# Pack all files from a directory that are not older than a give one
+def packnotolder(outfp, dirname, oldest):
+ names = mac.listdir(dirname)
+ oldest = macpath.cat(dirname, oldest)
+ st = mac.stat(oldest)
+ mtime = st[ST_MTIME]
+ todo = []
+ for name in names:
+ print name, '...',
+ st = mac.stat(macpath.cat(dirname, name))
+ if st[ST_MTIME] >= mtime:
+ print 'Yes.'
+ todo.append(name)
+ else:
+ print 'No.'
+ todo.sort()
+ packsome(outfp, dirname, todo)
diff --git a/lib/panel.py b/lib/panel.py
new file mode 100644
index 0000000..8b0b45c
--- /dev/null
+++ b/lib/panel.py
@@ -0,0 +1,281 @@
+# Module 'panel'
+#
+# Support for the Panel library.
+# Uses built-in module 'pnl'.
+# Applciations should use 'panel.function' instead of 'pnl.function';
+# most 'pnl' functions are transparently exported by 'panel',
+# but dopanel() is overridden and you have to use this version
+# if you want to use callbacks.
+
+
+import pnl
+
+
+debug = 0
+
+
+# Test if an object is a list.
+#
+def is_list(x):
+ return type(x) = type([])
+
+
+# Reverse a list.
+#
+def reverse(list):
+ res = []
+ for item in list:
+ res.insert(0, item)
+ return res
+
+
+# Get an attribute of a list, which may itself be another list.
+# Don't use 'prop' for name.
+#
+def getattrlist(list, name):
+ for item in list:
+ if item and is_list(item) and item[0] = name:
+ return item[1:]
+ return []
+
+
+# Get a property of a list, which may itself be another list.
+#
+def getproplist(list, name):
+ for item in list:
+ if item and is_list(item) and item[0] = 'prop':
+ if len(item) > 1 and item[1] = name:
+ return item[2:]
+ return []
+
+
+# Test if an actuator description contains the property 'end-of-group'
+#
+def is_endgroup(list):
+ x = getproplist(list, 'end-of-group')
+ return (x and x[0] = '#t')
+
+
+# Neatly display an actuator definition given as S-expression
+# the prefix string is printed before each line.
+#
+def show_actuator(prefix, a):
+ for item in a:
+ if not is_list(item):
+ print prefix, item
+ elif item and item[0] = 'al':
+ print prefix, 'Subactuator list:'
+ for a in item[1:]:
+ show_actuator(prefix + ' ', a)
+ elif len(item) = 2:
+ print prefix, item[0], '=>', item[1]
+ elif len(item) = 3 and item[0] = 'prop':
+ print prefix, 'Prop', item[1], '=>',
+ print item[2]
+ else:
+ print prefix, '?', item
+
+
+# Neatly display a panel.
+#
+def show_panel(prefix, p):
+ for item in p:
+ if not is_list(item):
+ print prefix, item
+ elif item and item[0] = 'al':
+ print prefix, 'Actuator list:'
+ for a in item[1:]:
+ show_actuator(prefix + ' ', a)
+ elif len(item) = 2:
+ print prefix, item[0], '=>', item[1]
+ elif len(item) = 3 and item[0] = 'prop':
+ print prefix, 'Prop', item[1], '=>',
+ print item[2]
+ else:
+ print prefix, '?', item
+
+
+# Exception raised by build_actuator or build_panel.
+#
+panel_error = 'panel error'
+
+
+# Dummy callback used to initialize the callbacks.
+#
+def dummy_callback(arg):
+ pass
+
+
+# Assign attributes to members of the target.
+# Attribute names in exclist are ignored.
+# The member name is the attribute name prefixed with the prefix.
+#
+def assign_members(target, attrlist, exclist, prefix):
+ for item in attrlist:
+ if is_list(item) and len(item) = 2 and item[0] not in exclist:
+ name, value = item[0], item[1]
+ ok = 1
+ if value[0] in '-0123456789':
+ value = eval(value)
+ elif value[0] = '"':
+ value = value[1:-1]
+ elif value = 'move-then-resize':
+ # Strange default set by Panel Editor...
+ ok = 0
+ else:
+ print 'unknown value', value, 'for', name
+ ok = 0
+ if ok:
+ lhs = 'target.' + prefix + name
+ stmt = lhs + '=' + `value`
+ if debug: print 'exec', stmt
+ try:
+ exec(stmt + '\n')
+ except KeyboardInterrupt: # Don't catch this!
+ raise KeyboardInterrupt
+ except:
+ print 'assign failed:', stmt
+
+
+# Build a real actuator from an actuator descriptior.
+# Return a pair (actuator, name).
+#
+def build_actuator(descr):
+ namelist = getattrlist(descr, 'name')
+ if namelist:
+ # Assume it is a string
+ actuatorname = namelist[0][1:-1]
+ else:
+ actuatorname = ''
+ type = descr[0]
+ if type[:4] = 'pnl_': type = type[4:]
+ act = pnl.mkact(type)
+ act.downfunc = act.activefunc = act.upfunc = dummy_callback
+ #
+ assign_members(act, descr[1:], ['al', 'data', 'name'], '')
+ #
+ # Treat actuator-specific data
+ #
+ datalist = getattrlist(descr, 'data')
+ prefix = ''
+ if type[-4:] = 'puck':
+ prefix = 'puck_'
+ elif type = 'mouse':
+ prefix = 'mouse_'
+ assign_members(act, datalist, [], prefix)
+ #
+ return act, actuatorname
+
+
+# Build all sub-actuators and add them to the super-actuator.
+# The super-actuator must already have been added to the panel.
+# Sub-actuators with defined names are added as members to the panel
+# so they can be referenced as p.name.
+#
+# Note: I have no idea how panel.endgroup() works when applied
+# to a sub-actuator.
+#
+def build_subactuators(panel, super_act, al):
+ #
+ # This is nearly the same loop as below in build_panel(),
+ # except a call is made to addsubact() instead of addact().
+ #
+ for a in al:
+ act, name = build_actuator(a)
+ act.addsubact(super_act)
+ if name:
+ stmt = 'panel.' + name + ' = act'
+ if debug: print 'exec', stmt
+ exec(stmt + '\n')
+ if is_endgroup(a):
+ panel.endgroup()
+ sub_al = getattrlist(a, 'al')
+ if sub_al:
+ build_subactuators(panel, act, sub_al)
+ #
+ # Fix the actuator to which whe just added subactuators.
+ # This can't hurt (I hope) and is needed for the scroll actuator.
+ #
+ super_act.fixact()
+
+
+# Build a real panel from a panel definition.
+# Return a panel object p, where for each named actuator a, p.name is a
+# reference to a.
+#
+def build_panel(descr):
+ #
+ # Sanity check
+ #
+ if (not descr) or descr[0] <> 'panel':
+ raise panel_error, 'panel description must start with "panel"'
+ #
+ if debug: show_panel('', descr)
+ #
+ # Create an empty panel
+ #
+ panel = pnl.mkpanel()
+ #
+ # Assign panel attributes
+ #
+ assign_members(panel, descr[1:], ['al'], '')
+ #
+ # Look for actuator list
+ #
+ al = getattrlist(descr, 'al')
+ #
+ # The order in which actuators are created is important
+ # because of the endgroup() operator.
+ # Unfortunately the Panel Editor outputs the actuator list
+ # in reverse order, so we reverse it here.
+ #
+ al = reverse(al)
+ #
+ for a in al:
+ act, name = build_actuator(a)
+ act.addact(panel)
+ if name:
+ stmt = 'panel.' + name + ' = act'
+ exec(stmt + '\n')
+ if is_endgroup(a):
+ panel.endgroup()
+ sub_al = getattrlist(a, 'al')
+ if sub_al:
+ build_subactuators(panel, act, sub_al)
+ #
+ return panel
+
+
+# Wrapper around pnl.dopanel() which calls call-back functions.
+#
+def my_dopanel():
+ # Extract only the first 4 elements to allow for future expansion
+ a, down, active, up = pnl.dopanel()[:4]
+ if down:
+ down.downfunc(down)
+ if active:
+ active.activefunc(active)
+ if up:
+ up.upfunc(up)
+ return a
+
+
+# Create one or more panels from a description file (S-expressions)
+# generated by the Panel Editor.
+#
+def defpanellist(file):
+ import panelparser
+ descrlist = panelparser.parse_file(open(file, 'r'))
+ panellist = []
+ for descr in descrlist:
+ panellist.append(build_panel(descr))
+ return panellist
+
+
+# Import everything from built-in method pnl, so the user can always
+# use panel.foo() instead of pnl.foo().
+# This gives *no* performance penalty once this module is imported.
+#
+from pnl import * # for export
+
+dopanel = my_dopanel # override pnl.dopanel
diff --git a/lib/panelparser.py b/lib/panelparser.py
new file mode 100644
index 0000000..5eb2f2b
--- /dev/null
+++ b/lib/panelparser.py
@@ -0,0 +1,128 @@
+# Module 'parser'
+#
+# Parse S-expressions output by the Panel Editor
+# (which is written in Scheme so it can't help writing S-expressions).
+#
+# See notes at end of file.
+
+
+whitespace = ' \t\n'
+operators = '()\''
+separators = operators + whitespace + ';' + '"'
+
+
+# Tokenize a string.
+# Return a list of tokens (strings).
+#
+def tokenize_string(s):
+ tokens = []
+ while s:
+ c = s[:1]
+ if c in whitespace:
+ s = s[1:]
+ elif c = ';':
+ s = ''
+ elif c = '"':
+ n = len(s)
+ i = 1
+ while i < n:
+ c = s[i]
+ i = i+1
+ if c = '"': break
+ if c = '\\': i = i+1
+ tokens.append(s[:i])
+ s = s[i:]
+ elif c in operators:
+ tokens.append(c)
+ s = s[1:]
+ else:
+ n = len(s)
+ i = 1
+ while i < n:
+ if s[i] in separators: break
+ i = i+1
+ tokens.append(s[:i])
+ s = s[i:]
+ return tokens
+
+
+# Tokenize a whole file (given as file object, not as file name).
+# Return a list of tokens (strings).
+#
+def tokenize_file(fp):
+ tokens = []
+ while 1:
+ line = fp.readline()
+ if not line: break
+ tokens = tokens + tokenize_string(line)
+ return tokens
+
+
+# Exception raised by parse_exr.
+#
+syntax_error = 'syntax error'
+
+
+# Parse an S-expression.
+# Input is a list of tokens as returned by tokenize_*().
+# Return a pair (expr, tokens)
+# where expr is a list representing the s-expression,
+# and tokens contains the remaining tokens.
+# May raise syntax_error.
+#
+def parse_expr(tokens):
+ if (not tokens) or tokens[0] <> '(':
+ raise syntax_error, 'expected "("'
+ tokens = tokens[1:]
+ expr = []
+ while 1:
+ if not tokens:
+ raise syntax_error, 'missing ")"'
+ if tokens[0] = ')':
+ return expr, tokens[1:]
+ elif tokens[0] = '(':
+ subexpr, tokens = parse_expr(tokens)
+ expr.append(subexpr)
+ else:
+ expr.append(tokens[0])
+ tokens = tokens[1:]
+
+
+# Parse a file (given as file object, not as file name).
+# Return a list of parsed S-expressions found at the top level.
+#
+def parse_file(fp):
+ tokens = tokenize_file(fp)
+ exprlist = []
+ while tokens:
+ expr, tokens = parse_expr(tokens)
+ exprlist.append(expr)
+ return exprlist
+
+
+# EXAMPLE:
+#
+# The input
+# '(hip (hop hur-ray))'
+#
+# passed to tokenize_string() returns the token list
+# ['(', 'hip', '(', 'hop', 'hur-ray', ')', ')']
+#
+# When this is passed to parse_expr() it returns the expression
+# ['hip', ['hop', 'hur-ray']]
+# plus an empty token list (because there are no tokens left.
+#
+# When a file containing the example is passed to parse_file() it returns
+# a list whose only element is the output of parse_expr() above:
+# [['hip', ['hop', 'hur-ray']]]
+
+
+# TOKENIZING:
+#
+# Comments start with semicolon (;) and continue till the end of the line.
+#
+# Tokens are separated by whitespace, except the following characters
+# always form a separate token (outside strings):
+# ( ) '
+# Strings are enclosed in double quotes (") and backslash (\) is used
+# as escape character in strings.
diff --git a/lib/path.py b/lib/path.py
new file mode 100644
index 0000000..be2340f
--- /dev/null
+++ b/lib/path.py
@@ -0,0 +1,125 @@
+# Module 'path' -- common operations on POSIX pathnames
+
+import posix
+import stat
+
+
+# Intelligent pathname concatenation.
+# Inserts a '/' unless the first part is empty or already ends in '/'.
+# Ignores the first part altogether if the second part is absolute
+# (begins with '/').
+#
+def cat(a, b):
+ if b[:1] = '/': return b
+ if a = '' or a[-1:] = '/': return a + b
+ return a + '/' + b
+
+
+# Split a path in head (empty or ending in '/') and tail (no '/').
+# The tail will be empty if the path ends in '/'.
+#
+def split(p):
+ head, tail = '', ''
+ for c in p:
+ tail = tail + c
+ if c = '/':
+ head, tail = head + tail, ''
+ return head, tail
+
+
+# Return the tail (basename) part of a path.
+#
+def basename(p):
+ return split(p)[1]
+
+
+# Return the longest prefix of all list elements.
+#
+def commonprefix(m):
+ if not m: return ''
+ prefix = m[0]
+ for item in m:
+ for i in range(len(prefix)):
+ if prefix[:i+1] <> item[:i+1]:
+ prefix = prefix[:i]
+ if i = 0: return ''
+ break
+ return prefix
+
+
+# Does a file/directory exist?
+#
+def exists(path):
+ try:
+ st = posix.stat(path)
+ except posix.error:
+ return 0
+ return 1
+
+
+# Is a path a posix directory?
+#
+def isdir(path):
+ try:
+ st = posix.stat(path)
+ except posix.error:
+ return 0
+ return stat.S_ISDIR(st[stat.ST_MODE])
+
+
+# Is a path a symbolic link?
+# This will always return false on systems where posix.lstat doesn't exist.
+#
+def islink(path):
+ try:
+ st = posix.lstat(path)
+ except (posix.error, NameError):
+ return 0
+ return stat.S_ISLNK(st[stat.ST_MODE])
+
+
+_mounts = []
+
+def _getmounts():
+ import commands, string
+ mounts = []
+ data = commands.getoutput('/etc/mount')
+ lines = string.splitfields(data, '\n')
+ for line in lines:
+ words = string.split(line)
+ if len(words) >= 3 and words[1] = 'on':
+ mounts.append(words[2])
+ return mounts
+
+
+# Is a path a mount point?
+# This only works for normalized, absolute paths,
+# and only if the mount table as printed by /etc/mount is correct.
+# Sorry.
+#
+def ismount(path):
+ if not _mounts:
+ _mounts[:] = _getmounts()
+ return path in _mounts
+
+
+# Directory tree walk.
+# For each directory under top (including top itself),
+# func(arg, dirname, filenames) is called, where dirname
+# is the name of the directory and filenames is the list of
+# files (and subdirectories etc.) in the directory.
+# func may modify the filenames list, to implement a filter,
+# or to impose a different order of visiting.
+#
+def walk(top, func, arg):
+ try:
+ names = posix.listdir(top)
+ except posix.error:
+ return
+ func(arg, top, names)
+ exceptions = ('.', '..')
+ for name in names:
+ if name not in exceptions:
+ name = cat(top, name)
+ if isdir(name):
+ walk(name, func, arg)
diff --git a/lib/poly.py b/lib/poly.py
new file mode 100644
index 0000000..a7c24ec
--- /dev/null
+++ b/lib/poly.py
@@ -0,0 +1,55 @@
+# module 'poly' -- Polynomials
+
+# A polynomial is represented by a list of coefficients, e.g.,
+# [1, 10, 5] represents 1*x**0 + 10*x**1 + 5*x**2 (or 1 + 10x + 5x**2).
+# There is no way to suppress internal zeros; trailing zeros are
+# taken out by normalize().
+
+def normalize(p): # Strip unnecessary zero coefficients
+ n = len(p)
+ while p:
+ if p[n-1]: return p[:n]
+ n = n-1
+ return []
+
+def plus(a, b):
+ if len(a) < len(b): a, b = b, a # make sure a is the longest
+ res = a[:] # make a copy
+ for i in range(len(b)):
+ res[i] = res[i] + b[i]
+ return normalize(res)
+
+def minus(a, b):
+ if len(a) < len(b): a, b = b, a # make sure a is the longest
+ res = a[:] # make a copy
+ for i in range(len(b)):
+ res[i] = res[i] - b[i]
+ return normalize(res)
+
+def one(power, coeff): # Representation of coeff * x**power
+ res = []
+ for i in range(power): res.append(0)
+ return res + [coeff]
+
+def times(a, b):
+ res = []
+ for i in range(len(a)):
+ for j in range(len(b)):
+ res = plus(res, one(i+j, a[i]*b[j]))
+ return res
+
+def power(a, n): # Raise polynomial a to the positive integral power n
+ if n = 0: return [1]
+ if n = 1: return a
+ if n/2*2 = n:
+ b = power(a, n/2)
+ return times(b, b)
+ return times(power(a, n-1), a)
+
+def der(a): # First derivative
+ res = a[1:]
+ for i in range(len(res)):
+ res[i] = res[i] * (i+1)
+ return res
+
+# Computing a primitive function would require rational arithmetic...
diff --git a/lib/rand.py b/lib/rand.py
new file mode 100644
index 0000000..0ca1a53
--- /dev/null
+++ b/lib/rand.py
@@ -0,0 +1,12 @@
+# Module 'rand'
+
+import whrandom
+
+def srand(seed):
+ whrandom.seed(seed%256, seed/256%256, seed/65536%256)
+
+def rand():
+ return int(whrandom.random() * 32768.0) % 32768
+
+def choice(seq):
+ return seq[rand() % len(seq)]
diff --git a/lib/rect.py b/lib/rect.py
new file mode 100644
index 0000000..8f61e11
--- /dev/null
+++ b/lib/rect.py
@@ -0,0 +1,88 @@
+# Module 'rect'.
+#
+# Operations on rectangles.
+# There is some normalization: all results return the object 'empty'
+# if their result would contain no points.
+
+
+# Exception.
+#
+error = 'rect.error'
+
+
+# The empty rectangle.
+#
+empty = (0, 0), (0, 0)
+
+
+# Check if a rectangle is empty.
+#
+def is_empty((left, top), (right, bottom)):
+ return left >= right or top >= bottom
+
+
+# Compute the intersection or two or more rectangles.
+# This works with a list or tuple argument.
+#
+def intersect(list):
+ if not list: raise error, 'intersect called with empty list'
+ if is_empty(list[0]): return empty
+ (left, top), (right, bottom) = list[0]
+ for rect in list[1:]:
+ if is_empty(rect):
+ return empty
+ (l, t), (r, b) = rect
+ if left < l: left = l
+ if top < t: top = t
+ if right > r: right = r
+ if bottom > b: bottom = b
+ if is_empty((left, top), (right, bottom)):
+ return empty
+ return (left, top), (right, bottom)
+
+
+# Compute the smallest rectangle containing all given rectangles.
+# This works with a list or tuple argument.
+#
+def union(list):
+ (left, top), (right, bottom) = empty
+ for (l, t), (r, b) in list[1:]:
+ if not is_empty((l, t), (r, b)):
+ if l < left: left = l
+ if t < top: top = t
+ if r > right: right = r
+ if b > bottom: bottom = b
+ res = (left, top), (right, bottom)
+ if is_empty(res):
+ return empty
+ return res
+
+
+# Check if a point is in a rectangle.
+#
+def pointinrect((h, v), ((left, top), (right, bottom))):
+ return left <= h < right and top <= v < bottom
+
+
+# Return a rectangle that is dh, dv inside another
+#
+def inset(((left, top), (right, bottom)), (dh, dv)):
+ left = left + dh
+ top = top + dv
+ right = right - dh
+ bottom = bottom - dv
+ r = (left, top), (right, bottom)
+ if is_empty(r):
+ return empty
+ else:
+ return r
+
+
+# Conversions between rectangles and 'geometry tuples',
+# given as origin (h, v) and dimensions (width, height).
+#
+def rect2geom((left, top), (right, bottom)):
+ return (left, top), (right-left, bottom-top)
+
+def geom2rect((h, v), (width, height)):
+ return (h, v), (h+width, v+height)
diff --git a/lib/selection.py b/lib/selection.py
new file mode 100644
index 0000000..22a0ea9
--- /dev/null
+++ b/lib/selection.py
@@ -0,0 +1,72 @@
+# DAWKINS' BLIND WATCHMAKER -- "METHINKS IT IS LIKE A WEASEL" EXAMPLE
+#
+# Start with a random phrase. Successively:
+# Breed a next generation by copying with small mutations.
+# Choose the member of the next generation that most closely resembles
+# the target phrase to start breeding the next generation.
+# How many generations will it take before the target is reached?
+
+from whrandom import random
+
+# Parameters determining the problem space
+Target = 'methinks it is like a weasel'
+Alphabet = ' abcdefghijklmnopqrstuvwxyz'
+
+# Parameters to play with
+GenerationSize = 100
+MutationChance = 1.0/28.0
+
+IndexSet = range(len(Target)) # Used to speed up loops
+
+def choice(sequence):
+ n = len(sequence)
+ i = int(random() * float(n)) % n
+ return sequence[i]
+
+def random_phrase():
+ phrase = ''
+ for i in IndexSet:
+ phrase = phrase + choice(Alphabet)
+ return phrase
+
+def mutate_phrase(phrase):
+ mutant = phrase
+ for i in range(int(0.5 + MutationChance*float(len(phrase)))):
+ i = choice(IndexSet)
+ c = choice(Alphabet)
+ mutant = mutant[:i] + c + mutant[i+1:]
+ #print `mutant`
+ return mutant
+
+def matching_factor(phrase):
+ factor = 0
+ for i in IndexSet:
+ if phrase[i] = Target[i]: factor = factor + 1
+ return factor
+
+def breed_generation(phrase):
+ generation = [phrase]
+ while len(generation) < GenerationSize:
+ generation.append(mutate_phrase(phrase))
+ return generation
+
+def select_best(generation):
+ factor, selected = -1, ''
+ for phrase in generation:
+ f = matching_factor(phrase)
+ if f > factor:
+ factor, selected = f, phrase
+ return selected
+
+def main():
+ gen = 0
+ phrase = random_phrase()
+ print gen, `phrase`
+ while phrase <> Target:
+ next_generation = breed_generation(phrase)
+ #print next_generation
+ phrase = select_best(next_generation)
+ gen = gen+1
+ print gen, `phrase`
+
+main()
diff --git a/lib/shutil.py b/lib/shutil.py
new file mode 100644
index 0000000..c01d889
--- /dev/null
+++ b/lib/shutil.py
@@ -0,0 +1,70 @@
+# Module 'shutil' -- utility functions usable in a shell-like program
+
+import posix
+import path
+
+MODEBITS = 010000 # Lower 12 mode bits
+# Change this to 01000 (9 mode bits) to avoid copying setuid etc.
+
+# Copy data from src to dst
+#
+def copyfile(src, dst):
+ fsrc = open(src, 'r')
+ fdst = open(dst, 'w')
+ while 1:
+ buf = fsrc.read(16*1024)
+ if not buf: break
+ fdst.write(buf)
+
+# Copy mode bits from src to dst
+#
+def copymode(src, dst):
+ st = posix.stat(src)
+ mode = divmod(st[0], MODEBITS)[1]
+ posix.chmod(dst, mode)
+
+# Copy all stat info (mode bits, atime and mtime) from src to dst
+#
+def copystat(src, dst):
+ st = posix.stat(src)
+ mode = divmod(st[0], MODEBITS)[1]
+ posix.chmod(dst, mode)
+ posix.utimes(dst, st[7:9])
+
+# Copy data and mode bits ("cp src dst")
+#
+def copy(src, dst):
+ copyfile(src, dst)
+ copymode(src, dst)
+
+# Copy data and all stat info ("cp -p src dst")
+#
+def copy2(src, dst):
+ copyfile(src, dst)
+ copystat(src, dst)
+
+# Recursively copy a directory tree.
+# The destination must not already exist.
+#
+def copytree(src, dst):
+ names = posix.listdir(src)
+ posix.mkdir(dst, 0777)
+ dot_dotdot = '.', '..'
+ for name in names:
+ if name not in dot_dotdot:
+ srcname = path.cat(src, name)
+ dstname = path.cat(dst, name)
+ #print 'Copying', srcname, 'to', dstname
+ try:
+ #if path.islink(srcname):
+ # linkto = posix.readlink(srcname)
+ # posix.symlink(linkto, dstname)
+ #elif path.isdir(srcname):
+ if path.isdir(srcname):
+ copytree(srcname, dstname)
+ else:
+ copy2(srcname, dstname)
+ # XXX What about devices, sockets etc.?
+ except posix.error, why:
+ print 'Could not copy', srcname, 'to', dstname,
+ print '(', why[1], ')'
diff --git a/lib/stat.py b/lib/stat.py
new file mode 100644
index 0000000..83855b6
--- /dev/null
+++ b/lib/stat.py
@@ -0,0 +1,57 @@
+# Module 'stat'
+
+# Defines constants and functions for interpreting stat/lstat struct
+# as returned by posix.stat() and posix.lstat() (if it exists).
+
+# XXX This module may have to be adapted for UNIXoid systems whose
+# <sys/stat.h> deviates from AT&T or BSD UNIX; their S_IF* constants
+# may differ.
+
+# Suggested usage: from stat import *
+
+# Tuple indices for stat struct members
+
+ST_MODE = 0
+ST_INO = 1
+ST_DEV = 2
+ST_NLINK = 3
+ST_UID = 4
+ST_GID = 5
+ST_SIZE = 6
+ST_ATIME = 7
+ST_MTIME = 8
+ST_CTIME = 9
+
+def S_IMODE(mode):
+ return mode%4096
+def S_IFMT(mode):
+ return mode - mode%4096
+
+S_IFDIR = 0040000
+S_IFCHR = 0020000
+S_IFBLK = 0060000
+S_IFREG = 0100000
+S_IFIFO = 0010000
+S_IFLNK = 0120000
+S_IFSOCK = 0140000
+
+def S_ISDIR(mode):
+ return S_IFMT(mode) = S_IFDIR
+
+def S_ISCHR(mode):
+ return S_IFMT(mode) = S_IFCHR
+
+def S_ISBLK(mode):
+ return S_IFMT(mode) = S_IFBLK
+
+def S_ISREG(mode):
+ return S_IFMT(mode) = S_IFREG
+
+def S_ISFIFO(mode):
+ return S_IFMT(mode) = S_IFIFO
+
+def S_ISLNK(mode):
+ return S_IFMT(mode) = S_IFLNK
+
+def S_ISSOCK(mode):
+ return S_IFMT(mode) = S_IFSOCK
diff --git a/lib/statcache.py b/lib/statcache.py
new file mode 100644
index 0000000..0eaf810
--- /dev/null
+++ b/lib/statcache.py
@@ -0,0 +1,86 @@
+# Module 'statcache'
+#
+# Maintain a cache of file stats.
+# There are functions to reset the cache or to selectively remove items.
+
+import posix
+from stat import *
+
+# The cache.
+# Keys are pathnames, values are `posix.stat' outcomes.
+#
+cache = {}
+
+
+# Stat a file, possibly out of the cache.
+#
+def stat(path):
+ if cache.has_key(path):
+ return cache[path]
+ cache[path] = ret = posix.stat(path)
+ return ret
+
+
+# Reset the cache completely.
+# Hack: to reset a global variable, we import this module.
+#
+def reset():
+ import statcache
+ # Check that we really imported the same module
+ if cache is not statcache.cache:
+ raise 'sorry, statcache identity crisis'
+ statcache.cache = {}
+
+
+# Remove a given item from the cache, if it exists.
+#
+def forget(path):
+ if cache.has_key(path):
+ del cache[path]
+
+
+# Remove all pathnames with a given prefix.
+#
+def forget_prefix(prefix):
+ n = len(prefix)
+ for path in cache.keys():
+ if path[:n] = prefix:
+ del cache[path]
+
+
+# Forget about a directory and all entries in it, but not about
+# entries in subdirectories.
+#
+def forget_dir(prefix):
+ if prefix[-1:] = '/' and prefix <> '/':
+ prefix = prefix[:-1]
+ forget(prefix)
+ if prefix[-1:] <> '/':
+ prefix = prefix + '/'
+ n = len(prefix)
+ for path in cache.keys():
+ if path[:n] = prefix:
+ rest = path[n:]
+ if rest[-1:] = '/': rest = rest[:-1]
+ if '/' not in rest:
+ del cache[path]
+
+
+# Remove all pathnames except with a given prefix.
+# Normally used with prefix = '/' after a chdir().
+#
+def forget_except_prefix(prefix):
+ n = len(prefix)
+ for path in cache.keys():
+ if path[:n] <> prefix:
+ del cache[path]
+
+
+# Check for directory.
+#
+def isdir(path):
+ try:
+ st = stat(path)
+ except posix.error:
+ return 0
+ return S_ISDIR(st[ST_MODE])
diff --git a/lib/stdwinevents.py b/lib/stdwinevents.py
new file mode 100644
index 0000000..fc1c1e7
--- /dev/null
+++ b/lib/stdwinevents.py
@@ -0,0 +1,46 @@
+# Module 'stdwinevents' -- Constants for stdwin event types
+#
+# Suggested usage:
+# from stdwinevents import *
+
+# The function stdwin.getevent() returns a tuple containing:
+# (type, window, detail)
+# where detail may be <no value> or a value depending on type, see below:
+
+# Values for type:
+
+WE_NULL = 0 # not reported -- means 'no event' internally
+WE_ACTIVATE = 1 # detail is <no object>
+WE_CHAR = 2 # detail is the character
+WE_COMMAND = 3 # detail is one of the WC_* constants below
+WE_MOUSE_DOWN = 4 # detail is ((h, v), clicks, button, mask)
+WE_MOUSE_MOVE = 5 # ditto
+WE_MOUSE_UP = 6 # ditto
+WE_MENU = 7 # detail is (menu, item)
+WE_SIZE = 8 # detail is (width, height) [???]
+WE_MOVE = 9 # not reported -- reserved for future use
+WE_DRAW = 10 # detail is ((left, top), (right, bottom))
+WE_TIMER = 11 # detail is <no object>
+WE_DEACTIVATE = 12 # detail is <no object>
+WE_EXTERN = 13 # detail is <no object>
+WE_KEY = 14 # detail is ???
+WE_LOST_SEL = 15 # detail is selection number
+WE_CLOSE = 16 # detail is <no object>
+
+# Values for detail when type is WE_COMMAND:
+
+WC_CLOSE = 1 # user hit close box
+WC_LEFT = 2 # left arrow key
+WC_RIGHT = 3 # right arrow key
+WC_UP = 4 # up arrow key
+WC_DOWN = 5 # down arrow key
+WC_CANCEL = 6 # not reported -- turned into KeyboardInterrupt
+WC_BACKSPACE = 7 # backspace key
+WC_TAB = 8 # tab key
+WC_RETURN = 9 # return or enter key
+
+# Selection numbers
+
+WS_CLIPBOARD = 0
+WS_PRIMARY = 1
+WS_SECONDARY = 2
diff --git a/lib/stdwinsupport.py b/lib/stdwinsupport.py
new file mode 100644
index 0000000..1a28072
--- /dev/null
+++ b/lib/stdwinsupport.py
@@ -0,0 +1,34 @@
+# Module 'stdwinsupport'
+
+import stdwin
+
+eventnames = ['null', 'activate', 'char', 'command']
+eventnames = eventnames + ['mouse_down', 'mouse_move', 'mouse_up', 'menu']
+eventnames = eventnames + ['size', 'move', 'draw', 'timer', 'deactivate']
+
+we_null = 0
+we_activate = 1
+we_char = 2
+we_command = 3
+we_mouse_down = 4
+we_mouse_move = 5
+we_mouse_up = 6
+we_menu = 7
+we_size = 8
+we_move = 9
+we_draw = 10
+we_timer = 11
+we_deactivate = 12
+
+commandnames = ['?', 'close', 'left', 'right', 'up', 'down', 'cancel']
+commandnames = commandnames + ['backspace', 'tab', 'return']
+
+wc_close = 1
+wc_left = 2
+wc_right = 3
+wc_up = 4
+wc_down = 5
+wc_cancel = 6 # not reported -- turned into KeyboardInterrupt
+wc_backspace = 7
+wc_tab = 8
+wc_return = 9
diff --git a/lib/string.py b/lib/string.py
new file mode 100644
index 0000000..5262013
--- /dev/null
+++ b/lib/string.py
@@ -0,0 +1,129 @@
+# module 'string' -- A collection of string operations
+
+# XXX Some of these operations are incredibly slow and should be built in
+
+# Some strings for ctype-style character classification
+whitespace = ' \t\n'
+lowercase = 'abcdefghijklmnopqrstuvwxyz'
+uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+letters = lowercase + uppercase
+digits = '0123456789'
+hexdigits = digits + 'abcdef' + 'ABCDEF'
+octdigits = '01234567'
+
+# Case conversion helpers
+_caseswap = {}
+for i in range(26):
+ _caseswap[lowercase[i]] = uppercase[i]
+ _caseswap[uppercase[i]] = lowercase[i]
+del i
+
+# convert UPPER CASE letters to lower case
+def lower(s):
+ res = ''
+ for c in s:
+ if 'A' <= c <= 'Z': c = _caseswap[c]
+ res = res + c
+ return res
+
+# Convert lower case letters to UPPER CASE
+def upper(s):
+ res = ''
+ for c in s:
+ if 'a' <= c <= 'z': c = _caseswap[c]
+ res = res + c
+ return res
+
+# Swap lower case letters and UPPER CASE
+def swapcase(s):
+ res = ''
+ for c in s:
+ if 'a' <= c <= 'z' or 'A' <= c <= 'Z': c = _caseswap[c]
+ res = res + c
+ return res
+
+# Strip leading and trailing tabs and spaces
+def strip(s):
+ i, j = 0, len(s)
+ while i < j and s[i] in whitespace: i = i+1
+ while i < j and s[j-1] in whitespace: j = j-1
+ return s[i:j]
+
+# Split a string into a list of space/tab-separated words
+# NB: split(s) is NOT the same as splitfields(s, ' ')!
+def split(s):
+ res = []
+ i, n = 0, len(s)
+ while i < n:
+ while i < n and s[i] in whitespace: i = i+1
+ if i = n: break
+ j = i
+ while j < n and s[j] not in whitespace: j = j+1
+ res.append(s[i:j])
+ i = j
+ return res
+
+# Split a list into fields separated by a given string
+# NB: splitfields(s, ' ') is NOT the same as split(s)!
+def splitfields(s, sep):
+ res = []
+ ns = len(s)
+ nsep = len(sep)
+ i = j = 0
+ while j+nsep <= ns:
+ if s[j:j+nsep] = sep:
+ res.append(s[i:j])
+ i = j = j + nsep
+ else:
+ j = j + 1
+ res.append(s[i:])
+ return res
+
+# Find substring
+index_error = 'substring not found in string.index'
+def index(s, sub):
+ n = len(sub)
+ for i in range(len(s) + 1 - n):
+ if sub = s[i:i+n]: return i
+ raise index_error, (s, sub)
+
+# Convert string to integer
+atoi_error = 'non-numeric argument to string.atoi'
+def atoi(str):
+ s = str
+ if s[:1] in '+-': s = s[1:]
+ if not s: raise atoi_error, str
+ for c in s:
+ if c not in digits: raise atoi_error, str
+ return eval(str)
+
+# Left-justify a string
+def ljust(s, width):
+ n = len(s)
+ if n >= width: return s
+ return s + ' '*(width-n)
+
+# Right-justify a string
+def rjust(s, width):
+ n = len(s)
+ if n >= width: return s
+ return ' '*(width-n) + s
+
+# Center a string
+def center(s, width):
+ n = len(s)
+ if n >= width: return s
+ return ' '*((width-n)/2) + s + ' '*(width -(width-n)/2)
+
+# Zero-fill a number, e.g., (12, 3) --> '012' and (-3, 3) --> '-03'
+# Decadent feature: the argument may be a string or a number
+# (Use of this is deprecated; it should be a string as with ljust c.s.)
+def zfill(x, width):
+ if type(x) = type(''): s = x
+ else: s = `x`
+ n = len(s)
+ if n >= width: return s
+ sign = ''
+ if s[0] = '-':
+ sign, s = '-', s[1:]
+ return sign + '0'*(width-n) + s
diff --git a/lib/sunaudio.py b/lib/sunaudio.py
new file mode 100644
index 0000000..ed1b6e4
--- /dev/null
+++ b/lib/sunaudio.py
@@ -0,0 +1,54 @@
+# Module 'sunaudio' -- interpret sun audio headers
+
+import audio
+
+MAGIC = '.snd'
+
+error = 'sunaudio sound header conversion error'
+
+
+# convert a 4-char value to integer
+
+def c2i(data):
+ if type(data) <> type('') or len(data) <> 4:
+ raise error, 'c2i: bad arg (not string[4])'
+ bytes = audio.chr2num(data)
+ for i in (1, 2, 3):
+ if bytes[i] < 0:
+ bytes[i] = bytes[i] + 256
+ return ((bytes[0]*256 + bytes[1])*256 + bytes[2])*256 + bytes[3]
+
+
+# read a sound header from an open file
+
+def gethdr(fp):
+ if fp.read(4) <> MAGIC:
+ raise error, 'gethdr: bad magic word'
+ hdr_size = c2i(fp.read(4))
+ data_size = c2i(fp.read(4))
+ encoding = c2i(fp.read(4))
+ sample_rate = c2i(fp.read(4))
+ channels = c2i(fp.read(4))
+ excess = hdr_size - 24
+ if excess < 0:
+ raise error, 'gethdr: bad hdr_size'
+ if excess > 0:
+ info = fp.read(excess)
+ else:
+ info = ''
+ return (data_size, encoding, sample_rate, channels, info)
+
+
+# read and print the sound header of a named file
+
+def printhdr(file):
+ hdr = gethdr(open(file, 'r'))
+ data_size, encoding, sample_rate, channels, info = hdr
+ while info[-1:] = '\0':
+ info = info[:-1]
+ print 'File name: ', file
+ print 'Data size: ', data_size
+ print 'Encoding: ', encoding
+ print 'Sample rate:', sample_rate
+ print 'Channels: ', channels
+ print 'Info: ', `info`
diff --git a/lib/tablewin.py b/lib/tablewin.py
new file mode 100644
index 0000000..df06476
--- /dev/null
+++ b/lib/tablewin.py
@@ -0,0 +1,237 @@
+# Module 'tablewin'
+
+# Display a table, with per-item actions:
+
+# A1 | A2 | A3 | .... | AN
+# B1 | B2 | B3 | .... | BN
+# C1 | C2 | C3 | .... | CN
+# .. | .. | .. | .... | ..
+# Z1 | Z2 | Z3 | .... | ZN
+
+# Not all columns need to have the same length.
+# The data structure is a list of columns;
+# each column is a list of items.
+# Each item is a pair of a string and an action procedure.
+# The first item may be a column title.
+
+import stdwin
+import gwin
+
+def open(title, data): # Public function to open a table window
+ #
+ # Set geometry parameters (one day, these may be changeable)
+ #
+ margin = stdwin.textwidth(' ')
+ lineheight = stdwin.lineheight()
+ #
+ # Geometry calculations
+ #
+ colstarts = [0]
+ totwidth = 0
+ maxrows = 0
+ for coldata in data:
+ # Height calculations
+ rows = len(coldata)
+ if rows > maxrows: maxrows = rows
+ # Width calculations
+ width = colwidth(coldata) + margin
+ totwidth = totwidth + width
+ colstarts.append(totwidth)
+ #
+ # Calculate document and window height
+ #
+ docwidth, docheight = totwidth, maxrows*lineheight
+ winwidth, winheight = docwidth, docheight
+ if winwidth > stdwin.textwidth('n')*100: winwidth = 0
+ if winheight > stdwin.lineheight()*30: winheight = 0
+ #
+ # Create the window
+ #
+ stdwin.setdefwinsize(winwidth, winheight)
+ w = gwin.open(title)
+ #
+ # Set properties and override methods
+ #
+ w.data = data
+ w.margin = margin
+ w.lineheight = lineheight
+ w.colstarts = colstarts
+ w.totwidth = totwidth
+ w.maxrows = maxrows
+ w.selection = (-1, -1)
+ w.lastselection = (-1, -1)
+ w.selshown = 0
+ w.setdocsize(docwidth, docheight)
+ w.draw = draw
+ w.mup = mup
+ w.arrow = arrow
+ #
+ # Return
+ #
+ return w
+
+def update(w, data): # Change the data
+ #
+ # Hide selection
+ #
+ hidesel(w, w.begindrawing())
+ #
+ # Get old geometry parameters
+ #
+ margin = w.margin
+ lineheight = w.lineheight
+ #
+ # Geometry calculations
+ #
+ colstarts = [0]
+ totwidth = 0
+ maxrows = 0
+ for coldata in data:
+ # Height calculations
+ rows = len(coldata)
+ if rows > maxrows: maxrows = rows
+ # Width calculations
+ width = colwidth(coldata) + margin
+ totwidth = totwidth + width
+ colstarts.append(totwidth)
+ #
+ # Calculate document and window height
+ #
+ docwidth, docheight = totwidth, maxrows*lineheight
+ #
+ # Set changed properties and change window size
+ #
+ w.data = data
+ w.colstarts = colstarts
+ w.totwidth = totwidth
+ w.maxrows = maxrows
+ w.change((0, 0), (10000, 10000))
+ w.setdocsize(docwidth, docheight)
+ w.change((0, 0), (docwidth, docheight))
+ #
+ # Show selection, or forget it if out of range
+ #
+ showsel(w, w.begindrawing())
+ if not w.selshown: w.selection = (-1, -1)
+
+def colwidth(coldata): # Subroutine to calculate column width
+ maxwidth = 0
+ for string, action in coldata:
+ width = stdwin.textwidth(string)
+ if width > maxwidth: maxwidth = width
+ return maxwidth
+
+def draw(w, ((left, top), (right, bottom))): # Draw method
+ ileft = whichcol(w, left)
+ iright = whichcol(w, right-1) + 1
+ if iright > len(w.data): iright = len(w.data)
+ itop = divmod(top, w.lineheight)[0]
+ if itop < 0: itop = 0
+ ibottom, remainder = divmod(bottom, w.lineheight)
+ if remainder: ibottom = ibottom + 1
+ d = w.begindrawing()
+ if ileft <= w.selection[0] < iright:
+ if itop <= w.selection[1] < ibottom:
+ hidesel(w, d)
+ d.erase((left, top), (right, bottom))
+ for i in range(ileft, iright):
+ col = w.data[i]
+ jbottom = len(col)
+ if ibottom < jbottom: jbottom = ibottom
+ h = w.colstarts[i]
+ v = itop * w.lineheight
+ for j in range(itop, jbottom):
+ string, action = col[j]
+ d.text((h, v), string)
+ v = v + w.lineheight
+ showsel(w, d)
+
+def mup(w, detail): # Mouse up method
+ (h, v), nclicks, button, mask = detail
+ icol = whichcol(w, h)
+ if 0 <= icol < len(w.data):
+ irow = divmod(v, w.lineheight)[0]
+ col = w.data[icol]
+ if 0 <= irow < len(col):
+ string, action = col[irow]
+ action(w, string, (icol, irow), detail)
+
+def whichcol(w, h): # Return column number (may be >= len(w.data))
+ for icol in range(0, len(w.data)):
+ if h < w.colstarts[icol+1]:
+ return icol
+ return len(w.data)
+
+def arrow(w, type):
+ import stdwinsupport
+ S = stdwinsupport
+ if type = S.wc_left:
+ incr = -1, 0
+ elif type = S.wc_up:
+ incr = 0, -1
+ elif type = S.wc_right:
+ incr = 1, 0
+ elif type = S.wc_down:
+ incr = 0, 1
+ else:
+ return
+ icol, irow = w.lastselection
+ icol = icol + incr[0]
+ if icol < 0: icol = len(w.data)-1
+ if icol >= len(w.data): icol = 0
+ if 0 <= icol < len(w.data):
+ irow = irow + incr[1]
+ if irow < 0: irow = len(w.data[icol]) - 1
+ if irow >= len(w.data[icol]): irow = 0
+ else:
+ irow = 0
+ if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
+ w.lastselection = icol, irow
+ string, action = w.data[icol][irow]
+ detail = (0, 0), 1, 1, 1
+ action(w, string, (icol, irow), detail)
+
+
+# Selection management
+# TO DO: allow multiple selected entries
+
+def select(w, selection): # Public function to set the item selection
+ d = w.begindrawing()
+ hidesel(w, d)
+ w.selection = selection
+ showsel(w, d)
+ if w.selshown: lastselection = selection
+
+def hidesel(w, d): # Hide the selection, if shown
+ if w.selshown: invertsel(w, d)
+
+def showsel(w, d): # Show the selection, if hidden
+ if not w.selshown: invertsel(w, d)
+
+def invertsel(w, d): # Invert the selection, if valid
+ icol, irow = w.selection
+ if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]):
+ left = w.colstarts[icol]
+ right = w.colstarts[icol+1]
+ top = irow * w.lineheight
+ bottom = (irow+1) * w.lineheight
+ d.invert((left, top), (right, bottom))
+ w.selshown = (not w.selshown)
+
+
+# Demonstration
+
+def demo_action(w, string, (icol, irow), detail): # Action function for demo
+ select(w, (irow, icol))
+
+def demo(): # Demonstration
+ da = demo_action # shorthand
+ col0 = [('a1', da), ('bbb1', da), ('c1', da)]
+ col1 = [('a2', da), ('bbb2', da)]
+ col2 = [('a3', da), ('b3', da), ('c3', da), ('d4', da), ('d5', da)]
+ col3 = []
+ for i in range(1, 31): col3.append('xxx' + `i`, da)
+ data = [col0, col1, col2, col3]
+ w = open('tablewin.demo', data)
+ gwin.mainloop()
+ return w
diff --git a/lib/tb.py b/lib/tb.py
new file mode 100644
index 0000000..9f2c92c
--- /dev/null
+++ b/lib/tb.py
@@ -0,0 +1,220 @@
+# Print tracebacks, with a dump of local variables.
+# Also an interactive stack trace browser.
+
+import sys
+try:
+ import mac
+ os = mac
+except NameError:
+ import posix
+ os = posix
+from stat import *
+import string
+
+def br(): browser(sys.last_traceback)
+
+def tb(): printtb(sys.last_traceback)
+
+def browser(tb):
+ if not tb:
+ print 'No traceback.'
+ return
+ tblist = []
+ while tb:
+ tblist.append(tb)
+ tb = tb.tb_next
+ ptr = len(tblist)-1
+ tb = tblist[ptr]
+ while 1:
+ if tb <> tblist[ptr]:
+ tb = tblist[ptr]
+ print `ptr` + ':',
+ printtbheader(tb)
+ try:
+ line = raw_input('TB: ')
+ except KeyboardInterrupt:
+ print '\n[Interrupted]'
+ break
+ except EOFError:
+ print '\n[EOF]'
+ break
+ cmd = string.strip(line)
+ if cmd:
+ if cmd = 'quit':
+ break
+ elif cmd = 'list':
+ browserlist(tb)
+ elif cmd = 'up':
+ if ptr-1 >= 0: ptr = ptr-1
+ else: print 'Bottom of stack.'
+ elif cmd = 'down':
+ if ptr+1 < len(tblist): ptr = ptr+1
+ else: print 'Top of stack.'
+ elif cmd = 'locals':
+ printsymbols(tb.tb_frame.f_locals)
+ elif cmd = 'globals':
+ printsymbols(tb.tb_frame.f_globals)
+ elif cmd in ('?', 'help'):
+ browserhelp()
+ else:
+ browserexec(tb, cmd)
+
+def browserlist(tb):
+ filename = tb.tb_frame.f_code.co_filename
+ lineno = tb.tb_lineno
+ last = lineno
+ first = max(1, last-10)
+ for i in range(first, last+1):
+ if i = lineno: prefix = '***' + string.rjust(`i`, 4) + ':'
+ else: prefix = string.rjust(`i`, 7) + ':'
+ line = readfileline(filename, i)
+ if line[-1:] = '\n': line = line[:-1]
+ print prefix + line
+
+def browserexec(tb, cmd):
+ locals = tb.tb_frame.f_locals
+ globals = tb.tb_frame.f_globals
+ try:
+ exec(cmd+'\n', globals, locals)
+ except:
+ print '*** Exception:',
+ print sys.exc_type,
+ if sys.exc_value <> None:
+ print ':', sys.exc_value,
+ print
+ print 'Type help to get help.'
+
+def browserhelp():
+ print
+ print ' This is the traceback browser. Commands are:'
+ print ' up : move one level up in the call stack'
+ print ' down : move one level down in the call stack'
+ print ' locals : print all local variables at this level'
+ print ' globals : print all global variables at this level'
+ print ' list : list source code around the failure'
+ print ' help : print help (what you are reading now)'
+ print ' quit : back to command interpreter'
+ print ' Typing any other 1-line statement will execute it'
+ print ' using the current level\'s symbol tables'
+ print
+
+def printtb(tb):
+ while tb:
+ print1tb(tb)
+ tb = tb.tb_next
+
+def print1tb(tb):
+ printtbheader(tb)
+ if tb.tb_frame.f_locals is not tb.tb_frame.f_globals:
+ printsymbols(tb.tb_frame.f_locals)
+
+def printtbheader(tb):
+ filename = tb.tb_frame.f_code.co_filename
+ lineno = tb.tb_lineno
+ info = '"' + filename + '"(' + `lineno` + ')'
+ line = readfileline(filename, lineno)
+ if line:
+ info = info + ': ' + string.strip(line)
+ print info
+
+def printsymbols(d):
+ keys = d.keys()
+ keys.sort()
+ for name in keys:
+ print ' ' + string.ljust(name, 12) + ':',
+ printobject(d[name], 4)
+ print
+
+def printobject(v, maxlevel):
+ if v = None:
+ print 'None',
+ elif type(v) in (type(0), type(0.0)):
+ print v,
+ elif type(v) = type(''):
+ if len(v) > 20:
+ print `v[:17] + '...'`,
+ else:
+ print `v`,
+ elif type(v) = type(()):
+ print '(',
+ printlist(v, maxlevel)
+ print ')',
+ elif type(v) = type([]):
+ print '[',
+ printlist(v, maxlevel)
+ print ']',
+ elif type(v) = type({}):
+ print '{',
+ printdict(v, maxlevel)
+ print '}',
+ else:
+ print v,
+
+def printlist(v, maxlevel):
+ n = len(v)
+ if n = 0: return
+ if maxlevel <= 0:
+ print '...',
+ return
+ for i in range(min(6, n)):
+ printobject(v[i], maxlevel-1)
+ if i+1 < n: print ',',
+ if n > 6: print '...',
+
+def printdict(v, maxlevel):
+ keys = v.keys()
+ n = len(keys)
+ if n = 0: return
+ if maxlevel <= 0:
+ print '...',
+ return
+ keys.sort()
+ for i in range(min(6, n)):
+ key = keys[i]
+ print `key` + ':',
+ printobject(v[key], maxlevel-1)
+ if i+1 < n: print ',',
+ if n > 6: print '...',
+
+_filecache = {}
+
+def readfileline(filename, lineno):
+ try:
+ stat = os.stat(filename)
+ except os.error, msg:
+ print 'Cannot stat', filename, '--', msg
+ return ''
+ cache_ok = 0
+ if _filecache.has_key(filename):
+ cached_stat, lines = _filecache[filename]
+ if stat[ST_SIZE] = cached_stat[ST_SIZE] and \
+ stat[ST_MTIME] = cached_stat[ST_MTIME]:
+ cache_ok = 1
+ else:
+ print 'Stale cache entry for', filename
+ del _filecache[filename]
+ if not cache_ok:
+ lines = readfilelines(filename)
+ if not lines:
+ return ''
+ _filecache[filename] = stat, lines
+ if 0 <= lineno-1 < len(lines):
+ return lines[lineno-1]
+ else:
+ print 'Line number out of range, last line is', len(lines)
+ return ''
+
+def readfilelines(filename):
+ try:
+ fp = open(filename, 'r')
+ except:
+ print 'Cannot open', filename
+ return []
+ lines = []
+ while 1:
+ line = fp.readline()
+ if not line: break
+ lines.append(line)
+ if not lines:
+ print 'Empty file', filename
+ return lines
diff --git a/lib/testall.py b/lib/testall.py
new file mode 100644
index 0000000..d9fd7e5
--- /dev/null
+++ b/lib/testall.py
@@ -0,0 +1,416 @@
+# Module 'testall'
+#
+# Python test set, should exercise:
+# - all lexical and grammatical constructs
+# - all opcodes from "opcode.h"
+# - all operations on all object types
+# - all builtin functions
+# Ideally also:
+# - all possible exception situations (Thank God we've got 'try')
+# - all boundary cases
+
+
+TestFailed = 'testall -- test failed' # Exception
+
+
+#########################################################
+# Part 1. Test all lexical and grammatical constructs.
+# This just tests whether the parser accepts them all.
+#########################################################
+
+print '1. Parser'
+
+print '1.1 Tokens'
+
+print '1.1.1 Backslashes'
+
+# Backslash means line continuation:
+x = 1 \
++ 1
+if x <> 2: raise TestFailed, 'backslash for line continuation'
+
+# Backslash does not means continuation in comments :\
+x = 0
+if x <> 0: raise TestFailed, 'backslash ending comment'
+
+print '1.1.2 Number formats'
+
+if 0xff <> 255: raise TestFailed, 'hex number'
+if 0377 <> 255: raise TestFailed, 'octal number'
+x = 3.14
+x = 0.314
+x = 3e14
+x = 3E14
+x = 3e-14
+
+print '1.2 Grammar'
+
+print 'single_input' # NEWLINE | simple_stmt | compound_stmt NEWLINE
+# XXX can't test in a script -- this rule is only used when interactive
+
+print 'file_input' # (NEWLINE | stmt)* ENDMARKER
+# Being tested as this very moment this very module
+
+print 'expr_input' # testlist NEWLINE
+# XXX Hard to test -- used only in calls to input()
+
+print 'eval_input' # testlist ENDMARKER
+x = eval('1, 0 or 1')
+
+print 'funcdef' # 'def' NAME parameters ':' suite
+### parameters: '(' [fplist] ')'
+### fplist: fpdef (',' fpdef)*
+### fpdef: NAME | '(' fplist ')'
+def f1(): pass
+def f2(one_argument): pass
+def f3(two, arguments): pass
+def f4(two, (compound, (arguments))): pass
+
+### stmt: simple_stmt | compound_stmt
+### simple_stmt: expr_stmt | print_stmt | pass_stmt | del_stmt | flow_stmt | import_stmt
+# Tested below
+
+print 'expr_stmt' # (exprlist '=')* exprlist NEWLINE
+1
+1, 2, 3
+x = 1
+x = 1, 2, 3
+x = y = z = 1, 2, 3
+x, y, z = 1, 2, 3
+abc = a, b, c = x, y, z = xyz = 1, 2, (3, 4)
+# NB these variables are deleted below
+
+print 'print_stmt' # 'print' (test ',')* [test] NEWLINE
+print 1, 2, 3
+print 1, 2, 3,
+print
+print 0 or 1, 0 or 1,
+print 0 or 1
+
+print 'del_stmt' # 'del' exprlist NEWLINE
+del abc
+del x, y, (z, xyz)
+
+print 'pass_stmt' # 'pass' NEWLINE
+pass
+
+print 'flow_stmt' # break_stmt | return_stmt | raise_stmt
+# Tested below
+
+print 'break_stmt' # 'break' NEWLINE
+while 1: break
+
+print 'return_stmt' # 'return' [testlist] NEWLINE
+def g1(): return
+def g2(): return 1
+g1()
+x = g2()
+
+print 'raise_stmt' # 'raise' expr [',' expr] NEWLINE
+try: raise RuntimeError, 'just testing'
+except RuntimeError: pass
+try: raise KeyboardInterrupt
+except KeyboardInterrupt: pass
+
+print 'import_stmt' # 'import' NAME (',' NAME)* NEWLINE | 'from' NAME 'import' ('*' | NAME (',' NAME)*) NEWLINE
+[1]
+import sys
+[2]
+import time, math
+[3]
+from time import sleep
+[4]
+from math import *
+[5]
+from sys import modules, path
+[6]
+
+### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef
+# Tested below
+
+print 'if_stmt' # 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
+if 1: pass
+if 1: pass
+else: pass
+if 0: pass
+elif 0: pass
+if 0: pass
+elif 0: pass
+elif 0: pass
+elif 0: pass
+else: pass
+
+print 'while_stmt' # 'while' test ':' suite ['else' ':' suite]
+while 0: pass
+while 0: pass
+else: pass
+
+print 'for_stmt' # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite]
+[1]
+for i in 1, 2, 3: pass
+[2]
+for i, j, k in (): pass
+else: pass
+[3]
+
+print 'try_stmt' # 'try' ':' suite (except_clause ':' suite)* ['finally' ':' suite]
+### except_clause: 'except' [expr [',' expr]]
+try: pass
+try: 1/0
+except RuntimeError: pass
+try: 1/0
+except EOFError: pass
+except TypeError, msg: pass
+except RuntimeError, msg: pass
+except: pass
+try: pass
+finally: pass
+try: 1/0
+except: pass
+finally: pass
+
+print 'suite' # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT
+if 1: pass
+if 1:
+ pass
+if 1:
+ #
+ #
+ #
+ pass
+ pass
+ #
+ pass
+ #
+
+print 'test' # and_test ('or' and_test)*
+### and_test: not_test ('and' not_test)*
+### not_test: 'not' not_test | comparison
+### comparison: expr (comp_op expr)*
+### comp_op: '<'|'>'|'='|'>' '='|'<' '='|'<' '>'|'in'|'not' 'in'|'is'|'is' 'not'
+if 1: pass
+if 1 = 1: pass
+if 1 < 1 > 1 = 1 >= 1 <= 1 <> 1 in 1 not in 1 is 1 is not 1: pass
+if not 1 = 1 = 1: pass
+if not 1 = 1 and 1 and 1: pass
+if 1 and 1 or 1 and 1 and 1 or not 1 = 1 = 1 and 1: pass
+
+print 'expr' # term (('+'|'-') term)*
+x = 1
+x = 1 + 1
+x = 1 - 1 - 1
+x = 1 - 1 + 1 - 1 + 1
+
+print 'term' # factor (('*'|'/'|'%') factor)*
+x = 1 * 1
+x = 1 / 1
+x = 1 % 1
+x = 1 / 1 * 1 % 1
+
+print 'factor' # ('+'|'-') factor | atom trailer*
+### trailer: '(' [testlist] ')' | '[' subscript ']' | '.' NAME
+### subscript: expr | [expr] ':' [expr]
+x = +1
+x = -1
+x = 1
+c = sys.path[0]
+x = time.time()
+x = sys.modules['time'].time()
+a = '01234'
+c = a[0]
+c = a[0:5]
+c = a[:5]
+c = a[0:]
+c = a[:]
+c = a[-5:]
+c = a[:-1]
+c = a[-4:-3]
+
+print 'atom' # '(' [testlist] ')' | '[' [testlist] ']' | '{' '}' | '`' testlist '`' | NAME | NUMBER | STRING
+x = (1)
+x = (1 or 2 or 3)
+x = (1 or 2 or 3, 2, 3)
+x = []
+x = [1]
+x = [1 or 2 or 3]
+x = [1 or 2 or 3, 2, 3]
+x = []
+x = {}
+x = `x`
+x = x
+x = 'x'
+x = 123
+
+### exprlist: expr (',' expr)* [',']
+### testlist: test (',' test)* [',']
+# These have been exercised enough above
+
+print 'classdef' # 'class' NAME parameters ['=' baselist] ':' suite
+### baselist: atom arguments (',' atom arguments)*
+### arguments: '(' [testlist] ')'
+class B(): pass
+class C1() = B(): pass
+class C2() = B(): pass
+class D() = C1(), C2(), B(): pass
+class C():
+ def meth1(self): pass
+ def meth2(self, arg): pass
+ def meth3(self, (a1, a2)): pass
+
+
+#########################################################
+# Part 2. Test all opcodes from "opcode.h"
+#########################################################
+
+print '2. Opcodes'
+print 'XXX Not yet fully implemented'
+
+print '2.1 try inside for loop'
+n = 0
+for i in range(10):
+ n = n+i
+ try: 1/0
+ except NameError: pass
+ except RuntimeError: pass
+ except TypeError: pass
+ finally: pass
+ try: pass
+ except: pass
+ try: pass
+ finally: pass
+ n = n+i
+if n <> 90:
+ raise TestFailed, 'try inside for'
+
+
+#########################################################
+# Part 3. Test all operations on all object types
+#########################################################
+
+print '3. Object types'
+print 'XXX Not yet implemented'
+
+
+#########################################################
+# Part 4. Test all built-in functions
+#########################################################
+
+print '4. Built-in functions'
+
+print 'abs'
+if abs(0) <> 0: raise TestFailed, 'abs(0)'
+if abs(1234) <> 1234: raise TestFailed, 'abs(1234)'
+if abs(-1234) <> 1234: raise TestFailed, 'abs(-1234)'
+if abs(0.0) <> 0.0: raise TestFailed, 'abs(0.0)'
+if abs(3.14) <> 3.14: raise TestFailed, 'abs(3.14)'
+if abs(-3.14) <> 3.14: raise TestFailed, 'abs(-3.14)'
+
+print 'dir'
+if 'x' not in dir(): raise TestFailed, 'dir()'
+if 'modules' not in dir(sys): raise TestFailed, 'dir(sys)'
+
+print 'divmod'
+if divmod(12, 7) <> (1, 5): raise TestFailed, 'divmod(12, 7)'
+if divmod(-12, 7) <> (-2, 2): raise TestFailed, 'divmod(-12, 7)'
+if divmod(12, -7) <> (-2, -2): raise TestFailed, 'divmod(12, -7)'
+if divmod(-12, -7) <> (1, -5): raise TestFailed, 'divmod(-12, -7)'
+
+print 'eval'
+if eval('1+1') <> 2: raise TestFailed, 'eval(\'1+1\')'
+
+print 'exec'
+exec('z=1+1\n')
+if z <> 2: raise TestFailed, 'exec(\'z=1+1\'\\n)'
+
+print 'float'
+if float(3.14) <> 3.14: raise TestFailed, 'float(3.14)'
+if float(314) <> 314.0: raise TestFailed, 'float(314)'
+
+print 'input'
+# Can't test in a script
+
+print 'int'
+if int(100) <> 100: raise TestFailed, 'int(100)'
+if int(3.14) <> 3: raise TestFailed, 'int(3.14)'
+
+print 'len'
+if len('123') <> 3: raise TestFailed, 'len(\'123\')'
+if len(()) <> 0: raise TestFailed, 'len(())'
+if len((1, 2, 3, 4)) <> 4: raise TestFailed, 'len((1, 2, 3, 4))'
+if len([1, 2, 3, 4]) <> 4: raise TestFailed, 'len([1, 2, 3, 4])'
+if len({}) <> 0: raise TestFailed, 'len({})'
+
+print 'min'
+if min('123123') <> '1': raise TestFailed, 'min(\'123123\')'
+if min(1, 2, 3) <> 1: raise TestFailed, 'min(1, 2, 3)'
+if min((1, 2, 3, 1, 2, 3)) <> 1: raise TestFailed, 'min((1, 2, 3, 1, 2, 3))'
+if min([1, 2, 3, 1, 2, 3]) <> 1: raise TestFailed, 'min([1, 2, 3, 1, 2, 3])'
+
+print 'max'
+if max('123123') <> '3': raise TestFailed, 'max(\'123123\')'
+if max(1, 2, 3) <> 3: raise TestFailed, 'max(1, 2, 3)'
+if max((1, 2, 3, 1, 2, 3)) <> 3: raise TestFailed, 'max((1, 2, 3, 1, 2, 3))'
+if max([1, 2, 3, 1, 2, 3]) <> 3: raise TestFailed, 'max([1, 2, 3, 1, 2, 3])'
+
+print 'open'
+print 'NB! This test creates a file named "@test" in the current directory.'
+fp = open('@test', 'w')
+fp.write('The quick brown fox jumps over the lazy dog')
+fp.write('.\n')
+fp.write('Dear John\n')
+fp.write('XXX'*100)
+fp.write('YYY'*100)
+fp.close()
+del fp
+fp = open('@test', 'r')
+if fp.readline() <> 'The quick brown fox jumps over the lazy dog.\n':
+ raise TestFailed, 'readline()'
+if fp.readline(4) <> 'Dear': raise TestFailed, 'readline(4) # short'
+if fp.readline(100) <> ' John\n': raise TestFailed, 'readline(100)'
+if fp.read(300) <> 'XXX'*100: raise TestFailed, 'read(300)'
+if fp.read(1000) <> 'YYY'*100: raise TestFailed, 'read(1000) # truncate'
+fp.close()
+del fp
+
+print 'range'
+if range(3) <> [0, 1, 2]: raise TestFailed, 'range(3)'
+if range(1, 5) <> [1, 2, 3, 4]: raise TestFailed, 'range(1, 5)'
+if range(0) <> []: raise TestFailed, 'range(0)'
+if range(-3) <> []: raise TestFailed, 'range(-3)'
+if range(1, 10, 3) <> [1, 4, 7]: raise TestFailed, 'range(1, 10, 3)'
+if range(5, -5, -3) <> [5, 2, -1, -4]: raise TestFailed, 'range(5, -5, -3)'
+
+print 'raw_input'
+savestdin = sys.stdin
+try:
+ sys.stdin = open('@test', 'r')
+ if raw_input() <> 'The quick brown fox jumps over the lazy dog.':
+ raise TestFailed, 'raw_input()'
+ if raw_input('testing\n') <> 'Dear John':
+ raise TestFailed, 'raw_input(\'testing\\n\')'
+finally:
+ sys.stdin = savestdin
+
+print 'reload'
+import string
+reload(string)
+
+print 'type'
+if type('') <> type('123') or type('') = type(()):
+ raise TestFailed, 'type()'
+
+
+print 'Passed all tests.'
+
+try:
+ import mac
+ unlink = mac.unlink
+except NameError:
+ try:
+ import posix
+ unlink = posix.unlink
+ except NameError:
+ pass
+
+unlink('@test')
+print 'Unlinked @test'
diff --git a/lib/textwin.py b/lib/textwin.py
new file mode 100644
index 0000000..d12342f
--- /dev/null
+++ b/lib/textwin.py
@@ -0,0 +1,119 @@
+# Module 'textwin'
+
+# Text windows, a subclass of gwin
+
+import stdwin
+import stdwinsupport
+import gwin
+
+S = stdwinsupport # Shorthand
+
+
+def fixsize(w):
+ docwidth, docheight = w.text.getrect()[1]
+ winheight = w.getwinsize()[1]
+ if winheight > docheight: docheight = winheight
+ w.setdocsize(0, docheight)
+ fixeditmenu(w)
+
+def cut(w, m, id):
+ s = w.text.getfocustext()
+ if s:
+ stdwin.setcutbuffer(0, s)
+ w.text.replace('')
+ fixsize(w)
+
+def copy(w, m, id):
+ s = w.text.getfocustext()
+ if s:
+ stdwin.setcutbuffer(0, s)
+ fixeditmenu(w)
+
+def paste(w, m, id):
+ w.text.replace(stdwin.getcutbuffer(0))
+ fixsize(w)
+
+def addeditmenu(w):
+ m = w.editmenu = w.menucreate('Edit')
+ m.action = []
+ m.additem('Cut', 'X')
+ m.action.append(cut)
+ m.additem('Copy', 'C')
+ m.action.append(copy)
+ m.additem('Paste', 'V')
+ m.action.append(paste)
+
+def fixeditmenu(w):
+ m = w.editmenu
+ f = w.text.getfocus()
+ can_copy = (f[0] < f[1])
+ m.enable(1, can_copy)
+ if not w.readonly:
+ m.enable(0, can_copy)
+ m.enable(2, (stdwin.getcutbuffer(0) <> ''))
+
+def draw(w, area): # Draw method
+ w.text.draw(area)
+
+def size(w, newsize): # Size method
+ w.text.move((0, 0), newsize)
+ fixsize(w)
+
+def close(w): # Close method
+ del w.text # Break circular ref
+ gwin.close(w)
+
+def char(w, c): # Char method
+ w.text.replace(c)
+ fixsize(w)
+
+def backspace(w): # Backspace method
+ void = w.text.event(S.we_command, w, S.wc_backspace)
+ fixsize(w)
+
+def arrow(w, detail): # Arrow method
+ w.text.arrow(detail)
+ fixeditmenu(w)
+
+def mdown(w, detail): # Mouse down method
+ void = w.text.event(S.we_mouse_down, w, detail)
+ fixeditmenu(w)
+
+def mmove(w, detail): # Mouse move method
+ void = w.text.event(S.we_mouse_move, w, detail)
+
+def mup(w, detail): # Mouse up method
+ void = w.text.event(S.we_mouse_up, w, detail)
+ fixeditmenu(w)
+
+def activate(w): # Activate method
+ fixeditmenu(w)
+
+def open(title, str): # Display a string in a window
+ w = gwin.open(title)
+ w.readonly = 0
+ w.text = w.textcreate((0, 0), w.getwinsize())
+ w.text.replace(str)
+ w.text.setfocus(0, 0)
+ addeditmenu(w)
+ fixsize(w)
+ w.draw = draw
+ w.size = size
+ w.close = close
+ w.mdown = mdown
+ w.mmove = mmove
+ w.mup = mup
+ w.char = char
+ w.backspace = backspace
+ w.arrow = arrow
+ w.activate = activate
+ return w
+
+def open_readonly(title, str): # Same with char input disabled
+ w = open(title, str)
+ w.readonly = 1
+ w.char = w.backspace = gwin.nop
+ # Disable Cut and Paste menu item; leave Copy alone
+ w.editmenu.enable(0, 0)
+ w.editmenu.enable(2, 0)
+ return w
diff --git a/lib/util.py b/lib/util.py
new file mode 100644
index 0000000..0b7778f
--- /dev/null
+++ b/lib/util.py
@@ -0,0 +1,30 @@
+# Module 'util' -- some useful functions that don't fit elsewhere
+
+
+# Remove an item from a list.
+# No complaints if it isn't in the list at all.
+# If it occurs more than once, remove the first occurrence.
+#
+def remove(item, list):
+ for i in range(len(list)):
+ if list[i] = item:
+ del list[i]
+ break
+
+
+# Return a string containing a file's contents.
+#
+def readfile(fn):
+ return readopenfile(open(fn, 'r'))
+
+
+# Read an open file until EOF.
+#
+def readopenfile(fp):
+ BUFSIZE = 512*8
+ data = ''
+ while 1:
+ buf = fp.read(BUFSIZE)
+ if not buf: break
+ data = data + buf
+ return data
diff --git a/lib/whrandom.py b/lib/whrandom.py
new file mode 100644
index 0000000..ee8dc67
--- /dev/null
+++ b/lib/whrandom.py
@@ -0,0 +1,74 @@
+# WICHMANN-HILL RANDOM NUMBER GENERATOR
+#
+# Wichmann, B. A. & Hill, I. D. (1982)
+# Algorithm AS 183:
+# An efficient and portable pseudo-random number generator
+# Applied Statistics 31 (1982) 188-190
+#
+# see also:
+# Correction to Algorithm AS 183
+# Applied Statistics 33 (1984) 123
+#
+# McLeod, A. I. (1985)
+# A remark on Algorithm AS 183
+# Applied Statistics 34 (1985),198-200
+#
+#
+# USE:
+# whrandom.random() yields double precision random numbers
+# uniformly distributed between 0 and 1.
+#
+# whrandom.seed() must be called before whrandom.random()
+# to seed the generator
+
+
+# Translated by Guido van Rossum from C source provided by
+# Adrian Baddeley.
+
+
+# The seed
+#
+_seed = [0, 0, 0]
+
+
+# Set the seed
+#
+def seed(x, y, z):
+ _seed[:] = [x, y, z]
+
+
+# Return the next random number in the range [0.0 .. 1.0)
+#
+def random():
+ from math import floor # floor() function
+ #
+ [x, y, z] = _seed
+ x = 171 * (x % 177) - 2 * (x/177)
+ y = 172 * (y % 176) - 35 * (y/176)
+ z = 170 * (z % 178) - 63 * (z/178)
+ #
+ if x < 0: x = x + 30269
+ if y < 0: y = y + 30307
+ if z < 0: z = z + 30323
+ #
+ _seed[:] = [x, y, z]
+ #
+ term = float(x)/30269.0 + float(y)/30307.0 + float(z)/30323.0
+ rand = term - floor(term)
+ #
+ if rand >= 1.0: rand = 0.0 # floor() inaccuracy?
+ #
+ return rand
+
+
+# Initialize from the current time
+#
+def init():
+ import time
+ t = time.time()
+ seed(t%256, t/256%256, t/65536%256)
+
+
+# Make sure the generator is preset to a nonzero value
+#
+init()
diff --git a/lib/zmod.py b/lib/zmod.py
new file mode 100644
index 0000000..3b38df9
--- /dev/null
+++ b/lib/zmod.py
@@ -0,0 +1,94 @@
+# module 'zmod'
+
+# Compute properties of mathematical "fields" formed by taking
+# Z/n (the whole numbers modulo some whole number n) and an
+# irreducible polynomial (i.e., a polynomial with only complex zeros),
+# e.g., Z/5 and X**2 + 2.
+#
+# The field is formed by taking all possible linear combinations of
+# a set of d base vectors (where d is the degree of the polynomial).
+#
+# Note that this procedure doesn't yield a field for all combinations
+# of n and p: it may well be that some numbers have more than one
+# inverse and others have none. This is what we check.
+#
+# Remember that a field is a ring where each element has an inverse.
+# A ring has commutative addition and multiplication, a zero and a one:
+# 0*x = x*0 = 0, 0+x = x+0 = x, 1*x = x*1 = x. Also, the distributive
+# property holds: a*(b+c) = a*b + b*c.
+# (XXX I forget if this is an axiom or follows from the rules.)
+
+import poly
+
+
+# Example N and polynomial
+
+N = 5
+P = poly.plus(poly.one(0, 2), poly.one(2, 1)) # 2 + x**2
+
+
+# Return x modulo y. Returns >= 0 even if x < 0.
+
+def mod(x, y):
+ return divmod(x, y)[1]
+
+
+# Normalize a polynomial modulo n and modulo p.
+
+def norm(a, n, p):
+ a = poly.modulo(a, p)
+ a = a[:]
+ for i in range(len(a)): a[i] = mod(a[i], n)
+ a = poly.normalize(a)
+ return a
+
+
+# Make a list of all n^d elements of the proposed field.
+
+def make_all(mat):
+ all = []
+ for row in mat:
+ for a in row:
+ all.append(a)
+ return all
+
+def make_elements(n, d):
+ if d = 0: return [poly.one(0, 0)]
+ sub = make_elements(n, d-1)
+ all = []
+ for a in sub:
+ for i in range(n):
+ all.append(poly.plus(a, poly.one(d-1, i)))
+ return all
+
+def make_inv(all, n, p):
+ x = poly.one(1, 1)
+ inv = []
+ for a in all:
+ inv.append(norm(poly.times(a, x), n, p))
+ return inv
+
+def checkfield(n, p):
+ all = make_elements(n, len(p)-1)
+ inv = make_inv(all, n, p)
+ all1 = all[:]
+ inv1 = inv[:]
+ all1.sort()
+ inv1.sort()
+ if all1 = inv1: print 'BINGO!'
+ else:
+ print 'Sorry:', n, p
+ print all
+ print inv
+
+def rj(s, width):
+ if type(s) <> type(''): s = `s`
+ n = len(s)
+ if n >= width: return s
+ return ' '*(width - n) + s
+
+def lj(s, width):
+ if type(s) <> type(''): s = `s`
+ n = len(s)
+ if n >= width: return s
+ return s + ' '*(width - n)