Commit dab6cb8f authored by Guido van Rossum's avatar Guido van Rossum

removing more stdwin users

parent ede8c6ee
Three programs that provide a user interface based upon STDWIN to the
audio device of the SGI 4D/25. These scripts also demonstrate the power
of a set of window interface classes implemented in Python that simplify
the construction of all sorts of buttons, etc.
XXX This hardware is already obsolete; see ../al for examples of audio
XXX on the Indigo and 4D/35.
jukebox Browses a directory full of sound samples and lets you
play selected ones. (Probably not fully functional, it
requires a conversion program.)
rec A tape recorder that lets you record a sound sample,
play it back, and save it to a file. Various options to
set sampling rate, volume etc. When idle it doubles
as a VU meter.
vumeter A VU meter that displays a history of the volume of
sound recently sampled from the microphone.
#! /usr/bin/env python
# JUKEBOX: browse directories full of sampled sound files.
#
# One or more "list windows" display the files and subdirectories of
# the arguments. Double-clicking on a subdirectory opens a new window
# displaying its contents (and so on recursively). Double clicking
# on a file plays it as a sound file (assuming it is one).
#
# Playing is asynchronous: the application keeps listening to events
# while the sample is playing, so you can change the volume (gain)
# during playing, cancel playing or start a new sample right away.
#
# The control window displays the current output gain and a primitive
# "stop button" to cancel the current play request.
#
# Sound files must currently be in Dik Winter's compressed Mac format.
# Since decompression is costly, decompressed samples are saved in
# /usr/tmp/@j* until the application is left. The files are read
# afresh each time, though.
import audio
import sunaudio
import commands
import getopt
import path
import posix
import rand
import stdwin
from stdwinevents import *
import string
import sys
from WindowParent import WindowParent
from HVSplit import VSplit
from Buttons import PushButton
from Sliders import ComplexSlider
# Pathnames
HOME_BIN_SGI = '/ufs/guido/bin/sgi/' # Directory where macsound2sgi lives
DEF_DB = '/ufs/dik/sounds/Mac/HCOM' # Default directory of sounds
# Global variables
class struct: pass # Class to define featureless structures
G = struct() # Holds writable global variables
# Main program
def main():
G.synchronous = 0 # If set, use synchronous audio.write()
G.debug = 0 # If set, print debug messages
G.gain = 75 # Output gain
G.rate = 3 # Sampling rate
G.busy = 0 # Set while asynchronous playing is active
G.windows = [] # List of open windows (except control)
G.mode = 'mac' # Macintosh mode
G.tempprefix = '/usr/tmp/@j' + `rand.rand()` + '-'
#
optlist, args = getopt.getopt(sys.argv[1:], 'dg:r:sSa')
for optname, optarg in optlist:
if optname == '-d':
G.debug = 1
elif optname == '-g':
G.gain = string.atoi(optarg)
if not (0 < G.gain < 256):
raise optarg.error, '-g gain out of range'
elif optname == '-r':
G.rate = string.atoi(optarg)
if not (1 <= G.rate <= 3):
raise optarg.error, '-r rate out of range'
elif optname == '-s':
G.synchronous = 1
elif optname == '-S':
G.mode = 'sgi'
elif optname == '-a':
G.mode = 'sun'
#
if not args:
args = [DEF_DB]
#
G.cw = opencontrolwindow()
for dirname in args:
G.windows.append(openlistwindow(dirname))
#
#
savegain = audio.getoutgain()
try:
# Initialize stdaudio
audio.setoutgain(0)
audio.start_playing('')
dummy = audio.wait_playing()
audio.setoutgain(0)
maineventloop()
finally:
audio.setoutgain(savegain)
audio.done()
clearcache()
def maineventloop():
mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
while G.windows:
type, w, detail = event = stdwin.getevent()
if w == G.cw.win:
if type == WE_CLOSE:
return
G.cw.dispatch(event)
else:
if type == WE_DRAW:
w.drawproc(w, detail)
elif type in mouse_events:
w.mouse(w, type, detail)
elif type == WE_CLOSE:
w.close(w)
del w, event
else:
if G.debug: print type, w, detail
# Control window -- to set gain and cancel play operations in progress
def opencontrolwindow():
cw = WindowParent().create('Jukebox', (0, 0))
v = VSplit().create(cw)
#
gain = ComplexSlider().define(v)
gain.setminvalmax(0, G.gain, 255)
gain.settexts(' ', ' ')
gain.sethook(gain_setval_hook)
#
stop = PushButton().definetext(v, 'Stop')
stop.hook = stop_hook
#
cw.realize()
return cw
def gain_setval_hook(self):
G.gain = self.val
if G.busy: audio.setoutgain(G.gain)
def stop_hook(self):
if G.busy:
audio.setoutgain(0)
dummy = audio.stop_playing()
G.busy = 0
# List windows -- to display list of files and subdirectories
def openlistwindow(dirname):
list = posix.listdir(dirname)
list.sort()
i = 0
while i < len(list):
if list[i] == '.' or list[i] == '..':
del list[i]
else:
i = i+1
for i in range(len(list)):
name = list[i]
if path.isdir(path.join(dirname, name)):
list[i] = list[i] + '/'
width = maxwidth(list)
width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
height = len(list) * stdwin.lineheight()
stdwin.setdefwinsize(width, min(height, 500))
w = stdwin.open(dirname)
stdwin.setdefwinsize(0, 0)
w.setdocsize(width, height)
w.drawproc = drawlistwindow
w.mouse = mouselistwindow
w.close = closelistwindow
w.dirname = dirname
w.list = list
w.selected = -1
return w
def maxwidth(list):
width = 1
for name in list:
w = stdwin.textwidth(name)
if w > width: width = w
return width
def drawlistwindow(w, area):
d = w.begindrawing()
d.erase((0, 0), (1000, 10000))
lh = d.lineheight()
h, v = 0, 0
for name in w.list:
d.text((h, v), name)
v = v + lh
showselection(w, d)
def hideselection(w, d):
if w.selected >= 0:
invertselection(w, d)
def showselection(w, d):
if w.selected >= 0:
invertselection(w, d)
def invertselection(w, d):
lh = d.lineheight()
h1, v1 = p1 = 0, w.selected*lh
h2, v2 = p2 = 1000, v1 + lh
d.invert(p1, p2)
def mouselistwindow(w, type, detail):
(h, v), clicks, button = detail[:3]
d = w.begindrawing()
lh = d.lineheight()
if 0 <= v < lh*len(w.list):
i = v / lh
else:
i = -1
if w.selected <> i:
hideselection(w, d)
w.selected = i
showselection(w, d)
if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
name = path.join(w.dirname, w.list[i])
if name[-1:] == '/':
if clicks == 2:
G.windows.append(openlistwindow(name[:-1]))
else:
playfile(name)
def closelistwindow(w):
remove(G.windows, w)
def remove(list, item):
for i in range(len(list)):
if list[i] == item:
del list[i]
break
# Playing tools
cache = {}
def clearcache():
for x in cache.keys():
try:
sts = posix.system('rm -f ' + cache[x])
if sts:
print cmd
print 'Exit status', sts
except:
print cmd
print 'Exception?!'
del cache[x]
def playfile(name):
if G.mode <> 'mac':
tempname = name
elif cache.has_key(name):
tempname = cache[name]
else:
tempname = G.tempprefix + `rand.rand()`
cmd = HOME_BIN_SGI + 'macsound2sgi'
cmd = cmd + ' ' + commands.mkarg(name)
cmd = cmd + ' >' + tempname
if G.debug: print cmd
sts = posix.system(cmd)
if sts:
print cmd
print 'Exit status', sts
stdwin.fleep()
return
cache[name] = tempname
fp = open(tempname, 'r')
try:
hdr = sunaudio.gethdr(fp)
except sunaudio.error, msg:
hdr = ()
if hdr:
data_size = hdr[0]
data = fp.read(data_size)
# XXX this doesn't work yet, need to convert from uLAW!!!
del fp
else:
del fp
data = readfile(tempname)
if G.debug: print len(data), 'bytes read from', tempname
if G.busy:
G.busy = 0
dummy = audio.stop_playing()
#
# Completely reset the audio device
audio.setrate(G.rate)
audio.setduration(0)
audio.setoutgain(G.gain)
#
if G.synchronous:
audio.write(data)
audio.setoutgain(0)
else:
try:
audio.start_playing(data)
G.busy = 1
except:
stdwin.fleep()
del data
def readfile(filename):
return readfp(open(filename, 'r'))
def readfp(fp):
data = ''
while 1:
buf = fp.read(102400) # Reads most samples in one fell swoop
if not buf:
return data
data = data + buf
main()
#! /usr/bin/env python
import sys
import audio
import stdwin
import string
import getopt
from stdwinevents import *
from Buttons import *
from Sliders import *
#from Soundogram import Soundogram
from VUMeter import VUMeter
from WindowParent import WindowParent, MainLoop
from HVSplit import HSplit, VSplit
class TimeOutToggleButton(ToggleButton):
def define(self, parent):
self = ToggleButton.define(self, parent)
self.parent.need_timer(self)
self.timer_hook = 0
return self
def timer(self):
if self.timer_hook:
self.timer_hook(self)
K = 1024
BUFSIZE = 30*8*K
Rates = [0, 32*K, 16*K, 8*K]
Magics = ['', '0032', '0016', '0008']
class Struct: pass
G = Struct()
def main():
#
# Turn off scroll bars
#
stdwin.setdefscrollbars(0, 0)
#
# Set default state
#
G.gain = 60
G.rate = 3
G.nomuting = 0
G.savefile = '@rec'
#
# Set default values
#
G.data = ''
G.playing = 0
G.recording = 0
G.sogram = 0
#
# Parse options
#
optlist, args = getopt.getopt(sys.argv[1:], 'mdg:r:')
#
for optname, optarg in optlist:
if 0: # (So all cases start with elif)
pass
elif optname == '-d':
G.debug = 1
elif optname == '-g':
G.gain = string.atoi(optarg)
if not (0 < G.gain < 256):
raise optarg.error, '-g gain out of range'
elif optname == '-m':
G.nomuting = (not G.nomuting)
elif optname == '-r':
G.rate = string.atoi(optarg)
if not (1 <= G.rate <= 3):
raise optarg.error, '-r rate out of range'
#
if args:
G.savefile = args[0]
#
# Initialize the sound package
#
audio.setoutgain(G.nomuting * G.gain) # Silence the speaker
audio.setrate(G.rate)
#
# Create the WindowParent and VSplit
#
G.window = WindowParent().create('Recorder', (0, 0))
w = G.vsplit = VSplit().create(G.window)
#
# VU-meter
#
G.vubtn = VUMeter().define(w)
#
# Radiobuttons for rates
#
r1btn = RadioButton().definetext(w, '32 K/sec')
r1btn.on_hook = rate_hook
r1btn.rate = 1
#
r2btn = RadioButton().definetext(w, '16 K/sec')
r2btn.on_hook = rate_hook
r2btn.rate = 2
#
r3btn = RadioButton().definetext(w, '8 K/sec')
r3btn.on_hook = rate_hook
r3btn.rate = 3
#
radios = [r1btn, r2btn, r3btn]
r1btn.group = r2btn.group = r3btn.group = radios
for r in radios:
if r.rate == G.rate: r.select(1)
#
# Other controls
#
G.recbtn = TimeOutToggleButton().definetext(w, 'Record')
G.recbtn.on_hook = record_on_hook
G.recbtn.timer_hook = record_timer_hook
G.recbtn.off_hook = record_off_hook
#
G.mutebtn = CheckButton().definetext(w, 'Mute')
G.mutebtn.select(not G.nomuting)
G.mutebtn.hook = mute_hook
#
G.playbtn = TimeOutToggleButton().definetext(w, 'Playback')
G.playbtn.on_hook = play_on_hook
G.playbtn.timer_hook = play_timer_hook
G.playbtn.off_hook = play_off_hook
#
G.gainbtn = ComplexSlider().define(w)
G.gainbtn.settexts(' Volume: ', ' ')
G.gainbtn.setminvalmax(0, G.gain, 255)
G.gainbtn.sethook(gain_hook)
#
G.sizebtn = Label().definetext(w, `len(G.data)` + ' bytes')
#
#G.showbtn = PushButton().definetext(w, 'Sound-o-gram...')
#G.showbtn.hook = show_hook
#
G.savebtn = PushButton().definetext(w, 'Save...')
G.savebtn.hook = save_hook
#
G.quitbtn = PushButton().definetext(w, 'Quit')
G.quitbtn.hook = quit_hook
G.playbtn.enable(0)
G.savebtn.enable(0)
#G.showbtn.enable(0)
start_vu()
G.window.realize()
#
# Event loop
#
MainLoop()
# XXX Disabled...
def show_hook(self):
savetext = self.text
self.settext('Be patient...')
close_sogram()
stdwin.setdefwinsize(400, 300)
win = stdwin.open('Sound-o-gram')
G.sogram = Soundogram().define(win, G.data)
win.buttons = [G.sogram]
self.settext(savetext)
def close_sogram():
if G.sogram:
# Break circular references
G.sogram.win.buttons[:] = []
del G.sogram.win
G.sogram = 0
def mute_hook(self):
G.nomuting = (not self.selected)
audio.setoutgain(G.nomuting * G.gain)
def rate_hook(self):
G.rate = self.rate
audio.setrate(G.rate)
def record_on_hook(self):
stop_vu()
close_sogram()
audio.setrate(G.rate)
audio.setoutgain(G.nomuting * G.gain)
audio.start_recording(BUFSIZE)
G.recording = 1
G.playbtn.enable(0)
G.window.settimer(10 * BUFSIZE / Rates[G.rate])
def record_timer_hook(self):
if G.recording:
if audio.poll_recording():
self.hilite(0)
record_off_hook(self)
else:
self.parent.settimer(5)
def record_off_hook(self):
if not G.recording:
return
G.data = audio.stop_recording()
G.recording = 0
G.sizebtn.settext(`len(G.data)` + ' bytes')
audio.setoutgain(G.nomuting * G.gain)
G.playbtn.enable((len(G.data) > 0))
G.savebtn.enable((len(G.data) > 0))
#G.showbtn.enable((len(G.data) > 0))
G.window.settimer(0)
start_vu()
def play_on_hook(self):
stop_vu()
audio.setrate(G.rate)
audio.setoutgain(G.gain)
audio.start_playing(G.data)
G.playing = 1
G.recbtn.enable(0)
G.window.settimer(max(10 * len(G.data) / Rates[G.rate], 1))
def play_timer_hook(self):
if G.playing:
if audio.poll_playing():
self.hilite(0)
play_off_hook(self)
else:
self.parent.settimer(5)
def play_off_hook(self):
if not G.playing:
return
x = audio.stop_playing()
G.playing = 0
audio.setoutgain(G.nomuting * G.gain)
G.recbtn.enable(1)
G.window.settimer(0)
start_vu()
def gain_hook(self):
G.gain = self.val
if G.playing or G.nomuting: audio.setoutgain(G.gain)
def save_hook(self):
if not G.data:
stdwin.fleep()
else:
prompt = 'Store sampled data on file: '
try:
G.savefile = stdwin.askfile(prompt, G.savefile, 1)
except KeyboardInterrupt:
return
try:
fp = open(G.savefile, 'w')
fp.write(Magics[G.rate] + G.data)
except:
stdwin.message('Cannot create ' + file)
def stop_vu():
G.vubtn.stop()
def start_vu():
G.vubtn.start()
def quit_hook(self):
G.window.delayed_destroy()
try:
main()
finally:
audio.setoutgain(0)
#! /usr/bin/env python
import audio
import stdwin
from VUMeter import VUMeter
from WindowParent import WindowParent
import MainLoop
NBUFS=20
BUFSIZE = NBUFS*48
SCALE=128
class MyVUMeter(VUMeter):
def init_reactivity(self):
self.parent.need_mouse(self)
def mouse_down(self, detail):
if self.enabled:
self.stop()
else:
self.start()
def mouse_move(self, detail): pass
def mouse_up(self, detail): pass
def main():
audio.setrate(3)
audio.setoutgain(0)
w = WindowParent().create('VU Meter', (200, 100))
v = MyVUMeter().define(w)
v.start()
w.realize()
while 1:
w.dispatch(stdwin.getevent())
main()
import stdwingl
import stdwin
from stdwinevents import *
def main():
size = 12
w = stdwin.open('Font chart ' + `size`)
while 1:
type, window, detail = stdwin.getevent()
if type == WE_CLOSE:
break
if type == WE_DRAW:
width, height = w.getwinsize()
d = w.begindrawing()
d.setsize(size)
h, v = 0, 0
for c in range(32, 256):
ch = chr(c)
chw = d.textwidth(ch)
if h + chw > width:
v = v + d.lineheight()
h = 0
if v >= height:
break
d.text((h, v), ch)
h = h + chw
del d
if type == WE_MOUSE_UP:
size = size + 1
w.settitle('Font chart ' + `size`)
w.change((0, 0), (2000, 2000))
main()
# Define drawing operations for GL stdwin
import gl
import fm
from GL import LO_XOR, LO_SRC
from glstdwin import MASK
class DrawingObject:
#
def _init(self, win):
self.fg = win._fg
self.bg = win._bg
self.font = win._font
self.size = win._size
self.width, self.height = win._area[1]
gl.winset(win._gid)
gl.color(self.fg)
return self
#
def setfont(self, fontname):
self.font = fm.findfont(fontname).scalefont(self.size)
#
def setsize(self, size):
ratio = float(size) / float(self.size)
self.size = size
self.font = self.font.scalefont(ratio)
#
def setfgcolor(self, color):
self.fg = color
gl.color(self.fg)
#
def setbgcolor(self, color):
self.bg = color
#
def cliprect(self, area):
#print 'cliprect', area
(left, top), (right, bottom) = area
gl.scrmask(left, right, self.height-bottom, self.height-top)
#
def noclip(self):
#print 'noclip()'
gl.scrmask(0, self.width, 0, self.height)
#
def paint(self, ((left, top), (right, bottom))):
gl.rectf(left, top, right, bottom)
#
def box(self, ((left, top), (right, bottom))):
#print 'box', ((left, top), (right, bottom))
gl.rect(left, top, right, bottom)
#
def circle(self, (h, v), radius):
gl.circ(h, v, radius)
#
def elarc(self, center, (rh, rv), (a1, a2)):
pass # XXX
#
def erase(self, ((left, top), (right, bottom))):
#print 'erase', ((left, top), (right, bottom))
gl.color(self.bg)
gl.rectf(left, top, right, bottom)
gl.color(self.fg)
#
def invert(self, ((left, top), (right, bottom))):
#print 'invert', ((h0, v0), (h1, v1))
gl.logicop(LO_XOR)
gl.color(self.bg)
gl.rectf(left, top, right, bottom)
gl.color(self.fg)
gl.logicop(LO_SRC)
#
def line(self, (h0, v0), (h1, v1)):
#print 'line', ((h0, v0), (h1, v1))
gl.bgnline()
gl.v2i(h0, v0)
gl.v2i(h1, v1)
gl.endline()
#
def xorline(self, (h0, v0), (h1, v1)):
#print 'xorline', ((h0, v0), (h1, v1))
gl.logicop(LO_XOR)
gl.color(self.bg)
gl.bgnline()
gl.v2i(h0, v0)
gl.v2i(h1, v1)
gl.endline()
gl.color(self.fg)
gl.logicop(LO_SRC)
#
def point(self, (h, v)):
#print 'point', (h, v)
gl.bgnpoint()
gl.v2i(h, v)
gl.endpoint()
#
def text(self, (h, v), string):
#print 'text', ((h, v), string)
if h < 0:
# If the point is outside the window
# the whole string isn't drawn.
# Skip the beginning of the string.
# XXX What if the font is bigger than 20 pixels?
i, n = 0, len(string)
while h < -MASK and i < n:
h = h + self.font.getstrwidth(string[i])
i = i + 1
string = string[i:]
gl.cmov2(h, v + self.baseline())
self.font.setfont()
fm.prstr(string)
#
def shade(self, (h, v), percent):
pass # XXX
#
def baseline(self):
(printermatched, fixed_width, xorig, yorig, xsize, ysize, \
height, nglyphs) = self.font.getfontinfo()
return height - yorig
#
def lineheight(self):
(printermatched, fixed_width, xorig, yorig, xsize, ysize, \
height, nglyphs) = self.font.getfontinfo()
return height
#
def textbreak(self, string, width):
# XXX Slooooow!
n = len(string)
nwidth = self.textwidth(string[:n])
while nwidth > width:
n = n-1
nwidth = self.textwidth(string[:n])
return n
#
def textwidth(self, string):
return self.font.getstrwidth(string)
#
# GL STDWIN
#
# See stdwingl for a convenient hack to use this instead of built-in stdwin
# without modifying your application, except for one line in the main file.
#
# Intrinsic differences with built-in stdwin (hard or impossible to fix):
# - Need to call w.close() to close a window !!!
# - Need to call m.close() to remove a menu !!!
# - Doesn't enforce the existence of at most one drawing object
# - No textedit package
# - No X11 selections
#
# Not yet implemented:
# - shade drawing
# - elliptical arc drawing (need to play with transformation)
# - more than one mouse button
# - scroll bars (need to redo viewport handling to get this)
# - partial redraws
# - dialog boxes
# - timer events
# - cursors
#
# Extra features:
# - color (for now, you need to know the colormap index)
import gl
import fm
from GL import *
from DEVICE import *
from stdwinevents import *
# Customizable constants
#
DEF_FONT = 'Times-Roman' # Default font
DEF_SIZE = 12 # Default font size (points)
MASK = 20 # Viewport minus scrmask
# A structure to hold global variables
#
class Struct: pass
G = Struct()
#
G.queue = [] # Pending STDWIN events
G.drawqueue = [] # Windows that need WE_REDRAW
G.windowmap = {} # Map window id to window object
G.windowmap['0'] = None # For convenience
G.focus = None # Input focus
G.fg = BLACK # Foreground color
G.bg = WHITE # Background color
G.def_size = 0, 0 # Default window size
G.def_pos = 0, 0 # Default window position
#
G.size = DEF_SIZE
G.font = fm.findfont(DEF_FONT).scalefont(G.size)
# Initialize GL
#
gl.foreground()
gl.noport()
dummygid = gl.winopen('')
# Ask for all sorts of events
#
# Both REDRAW (= resize and/or redraw!) and INPUTCHANGE are implicitly queued
#qdevice(REDRAW)
#qdevice(INPUTCHANGE)
#
# Keyboard
gl.qdevice(KEYBD)
gl.qdevice(LEFTARROWKEY)
gl.qdevice(RIGHTARROWKEY)
gl.qdevice(UPARROWKEY)
gl.qdevice(DOWNARROWKEY)
gl.qdevice(LEFTALTKEY)
gl.qdevice(RIGHTALTKEY)
#
# Mouse
gl.qdevice(LEFTMOUSE)
#gl.qdevice(MIDDLEMOUSE)
gl.qdevice(RIGHTMOUSE) # Menu button
# NB MOUSEX, MOUSEY events are queued on button down
#
# Window close requests
gl.qdevice(WINQUIT)
gl.qdevice(WINSHUT)
#
# These aren't needed
#gl.qdevice(TIMER0)
#gl.qdevice(WINFREEZE)
#gl.qdevice(WINTHAW)
#gl.qdevice(REDRAWICONIC)
# STDWIN: create a new window
#
def open(title):
h, v = G.def_pos
width, height = G.def_size
if h > 0 or v > 0:
# Choose arbitrary defaults
if h < 0: h = 10
if v < 0: v = 30
if width <= 0: width = 400
if height <= 0: height = 300
gl.prefposition(h, h+width, 1024-v, 1024-v-height)
elif width > 0 or height > 0:
if width <= 0: width = 400
if height <= 0: height = 300
gl.prefsize(width, height)
from glstdwwin import WindowObject
win = WindowObject()._init(title)
G.windowmap[`win._gid`] = win
return win
# STDWIN: set default initial window position (0 means use default)
#
def setdefwinpos(h, v):
G.def_pos = h, v
# STDWIN: set default window size (0 means use default)
#
def setdefwinsize(width, height):
G.def_size = width, height
# STDWIN: beep or ring the bell
#
def fleep():
gl.ringbell()
# STDWIN: set default foreground color
#
def setfgcolor(color):
G.fg = color
# STDWIN: set default background color
#
def setbgcolor(color):
G.bg = color
# STDWIN: get default foreground color
#
def getfgcolor():
return G.fgcolor
# STDWIN: get default background color
#
def getbgcolor():
return G.bgcolor
# Table mapping characters to key codes
#
key2code = key = {}
key['A'] = AKEY
key['B'] = BKEY
key['C'] = CKEY
key['D'] = DKEY
key['E'] = EKEY
key['F'] = FKEY
key['G'] = GKEY
key['H'] = HKEY
key['I'] = IKEY
key['J'] = JKEY
key['K'] = KKEY
key['L'] = LKEY
key['M'] = MKEY
key['N'] = NKEY
key['O'] = OKEY
key['P'] = PKEY
key['Q'] = QKEY
key['R'] = RKEY
key['S'] = SKEY
key['T'] = TKEY
key['U'] = UKEY
key['V'] = VKEY
key['W'] = WKEY
key['X'] = XKEY
key['Y'] = YKEY
key['Z'] = ZKEY
key['0'] = ZEROKEY
key['1'] = ONEKEY
key['2'] = TWOKEY
key['3'] = THREEKEY
key['4'] = FOURKEY
key['5'] = FIVEKEY
key['6'] = SIXKEY
key['7'] = SEVENKEY
key['8'] = EIGHTKEY
key['9'] = NINEKEY
del key
#
code2key = {}
codelist = []
for key in key2code.keys():
code = key2code[key]
code2key[`code`] = key
codelist.append(code)
del key
# STDWIN: wait for the next event
#
commands = {}
commands['\r'] = WC_RETURN
commands['\b'] = WC_BACKSPACE
commands['\t'] = WC_TAB
#
def getevent():
while 1:
#
# Get next event from the processed queue, if any
#
if G.queue:
event = G.queue[0]
del G.queue[0]
#print 'getevent from queue -->', event
return event
#
# Get next event from the draw queue, if any,
# but only if there is nothing in the system queue.
#
if G.drawqueue and not gl.qtest():
win = G.drawqueue[0]
del G.drawqueue[0]
gl.winset(win._gid)
gl.color(win._bg)
gl.clear()
event = WE_DRAW, win, win._area
#print 'getevent from drawqueue -->', event
return event
#
# Get next event from system queue, blocking if necessary
# until one is available.
# Some cases immediately return the event, others do nothing
# or append one or more events to the processed queue.
#
dev, val = gl.qread()
#
if dev == REDRAW:
win = G.windowmap[`val`]
old_area = win._area
win._fixviewport()
win._needredraw()
if old_area <> win._area:
#print 'getevent --> WE_SIZE'
return WE_SIZE, win, None
elif dev == KEYBD:
if val == 3:
raise KeyboardInterrupt # Control-C in window
character = chr(val)
if commands.has_key(character):
return WE_COMMAND, G.focus, commands[character]
return WE_CHAR, G.focus, character
elif dev == LEFTARROWKEY:
if val:
return WE_COMMAND, G.focus, WC_LEFT
elif dev == RIGHTARROWKEY:
if val:
return WE_COMMAND, G.focus, WC_RIGHT
elif dev == UPARROWKEY:
if val:
return WE_COMMAND, G.focus, WC_UP
elif dev == DOWNARROWKEY:
if val:
return WE_COMMAND, G.focus, WC_DOWN
elif dev in (LEFTALTKEY, RIGHTALTKEY):
if val:
for code in codelist:
gl.qdevice(code)
else:
for code in codelist:
gl.unqdevice(code)
elif dev in codelist:
if val:
event = G.focus._doshortcut(code2key[`dev`])
if event:
return event
elif dev == LEFTMOUSE:
G.mousex = gl.getvaluator(MOUSEX)
G.mousey = gl.getvaluator(MOUSEY)
if val:
type = WE_MOUSE_DOWN
gl.qdevice(MOUSEX)
gl.qdevice(MOUSEY)
else:
type = WE_MOUSE_UP
gl.unqdevice(MOUSEX)
gl.unqdevice(MOUSEY)
return _mouseevent(type)
elif dev == MOUSEX:
G.mousex = val
return _mouseevent(WE_MOUSE_MOVE)
elif dev == MOUSEY:
G.mousey = val
return _mouseevent(WE_MOUSE_MOVE)
elif dev == RIGHTMOUSE: # Menu button press/release
if val: # Press
event = G.focus._domenu()
if event:
return event
elif dev == INPUTCHANGE:
if G.focus:
G.queue.append(WE_DEACTIVATE, G.focus, None)
G.focus = G.windowmap[`val`]
if G.focus:
G.queue.append(WE_ACTIVATE, G.focus, None)
elif dev in (WINSHUT, WINQUIT):
return WE_CLOSE, G.windowmap[`val`], None
else:
print '*** qread() --> dev:', dev, 'val:', val
# Helper routine to construct a mouse (up, move or down) event
#
def _mouseevent(type):
gl.winset(G.focus._gid)
orgx, orgy = gl.getorigin()
sizex, sizey = gl.getsize()
x = G.mousex - orgx
y = G.mousey - orgy
return type, G.focus, ((x, sizey-y), 1, 0, 0)
# STDWIN: text measuring functions
def baseline():
(printermatched, fixed_width, xorig, yorig, xsize, ysize, \
height, nglyphs) = G.font.getfontinfo()
return height - yorig
def lineheight():
(printermatched, fixed_width, xorig, yorig, xsize, ysize, \
height, nglyphs) = G.font.getfontinfo()
return height
def textbreak(string, width):
# XXX Slooooow!
n = len(string)
nwidth = textwidth(string[:n])
while nwidth > width:
n = n-1
nwidth = textwidth(string[:n])
return n
def textwidth(string):
return G.font.getstrwidth(string)
# STDWIN: set default font and size
def setfont(fontname):
G.font = fm.findfont(fontname).scalefont(G.size)
def setsize(size):
ratio = float(size) / float(G.size)
G.size = size
G.font = G.font.scalefont(ratio)
# Utility functions
# Exclusive-or of two BYTES
#
def xor(x, y):
a = bits(x)
b = bits(y)
c = [0, 0, 0, 0, 0, 0, 0, 0]
for i in range(8):
c[i] = (a[i] + b[i]) % 2
return stib(c)
# Return the bits of a byte as a list of 8 integers
#
def bits(x):
b = [0, 0, 0, 0, 0, 0, 0, 0]
for i in range(8):
x, b[i] = divmod(x, 2)
return b
# Convert a list of 8 integers (0|1) to a byte
#
def stib(b):
x = 0
shift = 1
for i in range(8):
x = x + b[i]*shift
shift = shift*2
return x
# Define menu operations for GL stdwin
import gl
from glstdwin import key2code
class MenuObject:
#
def _init(self, win, title):
self._win = win
self._title = title
self._items = []
return self
#
def close(self):
self._win.remove(self)
del self._win
#
def additem(self, *args):
if len(args) == 2:
text, shortcut = args
elif len(args) == 1:
text, shortcut = args[0], None
else:
raise TypeError, 'arg count'
self._items.append([text, shortcut, 1, 0])
#
def setitem(self, i, text):
self._items[i][0] = text
#
def enable(self, i, flag):
self._items[i][2] = flag
#
def check(self, i, flag):
self._items[i][3] = flag
#
def _makepup(self, firstitem):
pup = gl.newpup()
if self._title:
gl.addtopup(pup, self._title + '%t', 0)
for item in self._items:
text = item[0]
if not item[2]: # Disabled
text = ' ( ' + text + ' )%x-1'
else:
if item[3]: # Check mark
text = '-> ' + text
else:
text = ' ' + text
if key2code.has_key(item[1]):
text = text + ' [Alt-' + item[1] + ']'
text = text + '%x' + `firstitem`
gl.addtopup(pup, text, 0)
firstitem = firstitem + 1
return pup
#
def _checkshortcut(self, char):
for i in range(len(self._items)):
item = self._items[i]
if item[2] and item[1] == char:
return i
return -1
#
# Define window operations for STDWIN
import gl
from stdwinevents import *
from glstdwin import G # Global variables
from glstdwin import MASK # Tunable constant
class WindowObject:
#
def _init(self, title):
self._docsize = (0, 0)
self._fg = G.fg
self._bg = G.bg
self._title = title
self._font = G.font
self._size = G.size
self._menus = []
self._gid = gl.winopen(title)
gl.winconstraints() # To remove prefsize() effect
self._fixviewport()
self._needredraw()
return self
#
def close(self):
del G.windowmap[`self._gid`]
gl.winclose(self._gid)
self._gid = 0
#
def _needredraw(self):
if self in G.drawqueue:
G.drawqueue.remove(self)
G.drawqueue.append(self)
#
def begindrawing(self):
from glstdwdraw import DrawingObject
return DrawingObject()._init(self)
#
def change(self, area):
self._needredraw()
# XXX Should record the area to be drawn?
#
def gettitle(self):
return self._title
#
def getdocsize(self):
return self._docsize
#
def getorigin(self):
return self._area[0]
#
def getwinsize(self):
return self._area[1]
#
def scroll(self, area, by):
# XXX ought to use gl.rectcopy()
if by <> (0, 0):
self.change(area)
#
def setdocsize(self, docsize):
self._docsize = docsize
#
def setorigin(self, origin):
pass # XXX
#
def settimer(self, decisecs):
pass # XXX
#
def settitle(self, title):
self._title = title
gl.wintitle(title)
#
def show(self, area):
pass # XXX
#
def _fixviewport(self):
#
# Called after redraw or resize, and initially.
#
# Fix the coordinate system so that (0, 0) is top left,
# units are pixels, and positive axes point right and down.
#
# Make the viewport slightly larger than the window,
# and set the screenmask exactly to the window; this
# help fixing character clipping.
#
# Set self._area to the window rectangle in STDWIN coords.
#
gl.winset(self._gid)
gl.reshapeviewport()
x0, x1, y0, y1 = gl.getviewport()
width, height = x1-x0, y1-y0
gl.viewport(x0-MASK, x1+MASK, y0-MASK, y1+MASK)
gl.scrmask(x0, x1, y0, y1)
gl.ortho2(-MASK, width+MASK, height+MASK, -MASK)
self._area = (0, 0), (width, height)
#
def menucreate(self, title):
from glstdwmenu import MenuObject
menu = MenuObject()._init(self, title)
self._menus.append(menu)
return menu
#
def _domenu(self):
if not self._menus:
return None
if len(self._menus) == 1:
pup = self._menus[0]._makepup(0)
val = gl.dopup(pup)
gl.freepup(pup)
if val < 0:
return None
return WE_MENU, self, (self._menus[0], val)
#
# More than one menu: use nested menus.
#
pups = []
firstitem = 0
for menu in self._menus:
pups.append(menu._makepup(firstitem))
firstitem = firstitem + 100
pup = gl.newpup()
for i in range(len(self._menus)):
gl.addtopup(pup, self._menus[i]._title + '%m', pups[i])
val = gl.dopup(pup)
gl.freepup(pup)
for pup in pups:
gl.freepup(pup)
if val < 0:
return None
i_menu, i_item = divmod(val, 100)
return WE_MENU, self, (self._menus[i_menu], i_item)
#
def _doshortcut(self, char):
for menu in self._menus:
i = menu._checkshortcut(char)
if i >= 0:
return WE_MENU, self, (menu, i)
return None
#
# If you put 'import stdwin_gl' in front of the main program of a program
# using stdwin (before it has a chance to import the real stdwin!),
# it will use glstdwin and think it is stdwin.
import sys
if sys.modules.has_key('stdwin'):
raise RuntimeError, 'too late -- stdwin has already been imported'
import glstdwin
sys.modules['stdwin'] = glstdwin
# Try colors -- display all 256 possible colors, with their color index
import stdwingl
import stdwin
from stdwinevents import *
NROWS = 16
NCOLS = 16
def main():
stdwin.setdefwinsize(NCOLS * stdwin.textwidth('12345'), \
NROWS * stdwin.lineheight() * 3)
w = stdwin.open('TestColors')
#
while 1:
type, window, detail = stdwin.getevent()
if type == WE_CLOSE:
print 'Bye.'
break
elif type == WE_SIZE:
w.change((0,0), (10000, 10000))
elif type == WE_DRAW:
width, height = w.getwinsize()
d = w.begindrawing()
for row in range(NROWS):
for col in range(NCOLS):
color = row*NCOLS + col
d.setfgcolor(color)
p = col*width/NCOLS, row*height/NROWS
q = (col+1)*width/NCOLS, \
(row+1)*height/NROWS
d.paint((p, q))
d.setfgcolor(0)
d.box((p, q))
d.text(p, `color`)
p = p[0] , p[1]+ d.lineheight()
d.setfgcolor(7)
d.text(p, `color`)
del d
#
main()
import sys
if len(sys.argv) < 2:
import stdwingl
color = 1
needclose = 1
else:
color = 0
needclose = 0
import stdwin
import time
from stdwinevents import *
from GL import BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE
def main():
#
stdwin.setdefwinsize(300, 300)
stdwin.setdefwinpos(0, 0)
if color: stdwin.setbgcolor(YELLOW)
w1 = stdwin.open('Hello, world')
w1.box = (10, 10), (90, 90)
#
stdwin.setdefwinsize(0, 0)
stdwin.setdefwinpos(50, 50)
if color: stdwin.setbgcolor(GREEN)
w2 = stdwin.open('Second window')
w2.box = (10, 10), (90, 90)
#
while w1 or w2:
type, window, detail = stdwin.getevent()
if type == WE_DRAW:
d = window.begindrawing()
if window == w1:
if color: d.setfgcolor(BLACK)
d.box(((50, 50), (250, 250)))
if color: d.setfgcolor(RED)
d.cliprect(((50, 50), (250, 250)))
d.paint(w1.box)
d.noclip()
if color: d.setfgcolor(BLUE)
d.line((0, 0), w1.box[0])
elif window == w2:
if color: d.setfgcolor(WHITE)
d.box(w2.box)
if color: d.setfgcolor(BLACK)
d.text(w2.box[0], 'Hello world')
else:
print 'Strange draw???', window, detail
del d
elif type == WE_CLOSE:
if needclose: window.close()
if window == w1:
w1 = None
elif window == w2:
w2 = None
else:
print 'weird close event???', window, detail
elif type in (WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP):
h, v = detail[0]
window.box = (h, v), (h+80, v+80)
window.change(((0,0), (2000, 2000)))
elif type == WE_CHAR:
print 'character', `detail`
else:
print type, window, detail
#
main()
print 'Done.'
# Test menus
import stdwingl
import stdwin
from stdwinevents import *
def main():
w = stdwin.open('TestMenus')
#
items1 = 'Aap', 'Noot', 'Mies'
m1 = w.menucreate('Menu-1')
for item in items1:
m1.additem(item, item[0])
#
items2 = 'Wim', 'Zus', 'Jet', 'Teun', 'Vuur'
m2 = w.menucreate('Menu-2')
for item in items2:
m2.additem(item, `len(item)`)
#
m1.enable(1, 0)
m2.check(1, 1)
#
while 1:
type, window, detail = stdwin.getevent()
if type == WE_CLOSE:
break
elif type == WE_DRAW:
d = w.begindrawing()
d.box(((50,50), (100,100)))
del d
elif type == WE_MENU:
mp, i = detail
if mp == m1:
print 'Choice:', items1[i]
elif mp == m2:
print 'Choice:', items2[i]
else:
print 'Not one of my menus!'
elif type == WE_CHAR:
print 'Character', `detail`
#
main()
#! /usr/bin/env python
testlabels = 'Name', 'Address', 'City', 'Country', 'Comments'
def main():
import stdwin
from WindowParent import WindowParent, MainLoop
from FormSplit import FormSplit
from Buttons import Label
from TextEdit import TextEdit
#
stdwin.setdefscrollbars(0, 0)
#
w = WindowParent().create('FormTest', (0, 0))
f = FormSplit().create(w)
#
h, v = 0, 0
for label in testlabels:
f.placenext(h, v)
lbl = Label().definetext(f, label)
f.placenext(h + 100, v)
txt = TextEdit().createboxed(f, (40, 2), (2, 2))
#txt = TextEdit().create(f, (40, 2))
v = v + 2*stdwin.lineheight() + 10
#
w.realize()
#
MainLoop()
main()
Contents of this directory:
FormTest.py Show how a form can be built to enter multiple fields
RadioGroups.py Show how to use multiple groups of radio buttons
TestCSplit.py Test CSplit widget (a clock-like split)
TestDirList.py Test DirList widget (lists directory contents)
TestFormSplit.py Test FormSplit widget (arbitrary grouping)
TestSched.py Test WindowSched widget (event scheduling)
TestTextEdit.py Test TextEdit widget (probably doen't work any more)
clock.py A simple clock, with alarm
jukebox.py Play audio files (SGI only, needs SOX and SFPLAY)
lpwin.py Watch line printer queues
microedit.py The smallest window editor
miniedit.py A small multi-window editor
python.py A window interface to the Python interpreter
wdiff.py A window-based directory diff
See ../ibrowse for another demo written using stdwin.
#! /usr/bin/env python
# radiogroups.py
#
# Demonstrate multiple groups of radio buttons
import stdwin
from Buttons import *
from WindowParent import WindowParent, MainLoop
from HVSplit import HSplit, VSplit
def main():
#
# Create the widget hierarchy, top-down
#
# 1. Create the window
#
window = WindowParent().create('Radio Groups', (0, 0))
#
# 2. Create a horizontal split to contain the groups
#
topsplit = HSplit().create(window)
#
# 3. Create vertical splits, one for each group
#
group1 = VSplit().create(topsplit)
group2 = VSplit().create(topsplit)
group3 = VSplit().create(topsplit)
#
# 4. Create individual radio buttons, each in their own split
#
b11 = RadioButton().definetext(group1, 'Group 1 button 1')
b12 = RadioButton().definetext(group1, 'Group 1 button 2')
b13 = RadioButton().definetext(group1, 'Group 1 button 3')
#
b21 = RadioButton().definetext(group2, 'Group 2 button 1')
b22 = RadioButton().definetext(group2, 'Group 2 button 2')
b23 = RadioButton().definetext(group2, 'Group 2 button 3')
#
b31 = RadioButton().definetext(group3, 'Group 3 button 1')
b32 = RadioButton().definetext(group3, 'Group 3 button 2')
b33 = RadioButton().definetext(group3, 'Group 3 button 3')
#
# 5. Define the grouping for the radio buttons.
# Note: this doesn't have to be the same as the
# grouping is splits (although it usually is).
# Also set the 'hook' procedure for each button
#
list1 = [b11, b12, b13]
list2 = [b21, b22, b23]
list3 = [b31, b32, b33]
#
for b in list1:
b.group = list1
b.on_hook = myhook
for b in list2:
b.group = list2
b.on_hook = myhook
for b in list3:
b.group = list3
b.on_hook = myhook
#
# 6. Select a default button in each group
#
b11.select(1)
b22.select(1)
b33.select(1)
#
# 6. Realize the window
#
window.realize()
#
# 7. Dispatch events until the window is closed
#
MainLoop()
#
# 8. Report final selections
#
print 'You selected the following choices:'
if b11.selected: print '1.1'
if b12.selected: print '1.2'
if b13.selected: print '1.3'
if b21.selected: print '2.1'
if b22.selected: print '2.2'
if b23.selected: print '2.3'
if b31.selected: print '3.1'
if b32.selected: print '3.2'
if b33.selected: print '3.3'
# My 'hook' procedure
# This is placed as 'hook' attribute on each button.
# The example just prints the title of the selected button.
#
def myhook(self):
print 'Selected:', self.text
main()
#! /usr/bin/env python
# TestCSplit
import stdwin
from WindowParent import WindowParent, MainLoop
from Buttons import PushButton
def main(n):
from CSplit import CSplit
#
stdwin.setdefscrollbars(0, 0)
#
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()
#
MainLoop()
main(12)
#! /usr/bin/env python
# TestDirList
from DirList import DirListWindow
from WindowParent import MainLoop
def main():
import sys
args = sys.argv[1:]
if not args:
args = ['.']
# Mac: args = [':']
for arg in args:
w = DirListWindow().create(arg)
MainLoop()
main()
#! /usr/bin/env python
# TestFormSplit
import stdwin
from WindowParent import WindowParent, MainLoop
from Buttons import PushButton
def main(n):
from FormSplit import FormSplit
#
stdwin.setdefscrollbars(1, 1)
#
the_window = WindowParent().create('TestFormSplit', (0, 0))
the_form = FormSplit().create(the_window)
#
for i in range(n):
if i % 3 == 0:
the_form.placenext(i*40, 0)
the_child = PushButton().define(the_form)
the_child.settext('XXX-' + `i` + '-YYY')
#
the_window.realize()
#
MainLoop()
main(6)
#! /usr/bin/env python
# TestSched
import stdwin
from WindowParent import WindowParent, MainLoop
import WindowSched
from Buttons import PushButton
def my_ringer(child):
child.my_id = None
stdwin.fleep()
def my_hook(child):
# schedule for the bell to ring in N seconds; cancel previous
if child.my_id:
WindowSched.cancel(child.my_id)
child.my_id = \
WindowSched.enter(child.my_number*1000, 0, my_ringer, (child,))
def main(n):
from CSplit import CSplit
window = WindowParent().create('TestSched', (0, 0))
csplit = CSplit().create(window)
for i in range(n):
child = PushButton().define(csplit)
child.my_number = i
child.my_id = None
child.settext(`(i+n-1)%n+1`)
child.hook = my_hook
window.realize()
WindowSched.run()
main(12)
#! /usr/bin/env python
# Test TextEdit widgets
def main():
from TextEdit import TextEdit
from WindowParent import WindowParent, MainLoop
w = WindowParent().create('Test TextEdit', (0, 0))
t = TextEdit().create(w, (40, 4))
w.realize()
MainLoop()
main()
#! /usr/bin/env python
# 'clock' -- 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 mainloop
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
def main():
win = makewindow()
del win
mainloop.mainloop()
def makewindow():
stdwin.setdefwinsize(DEFWIDTH, DEFHEIGHT + stdwin.lineheight())
win = stdwin.open('clock')
setdimensions(win)
win.set = 1 # True when alarm is set
win.time = 11*60 + 40 # Time when alarm must go off
win.ring = 0 # True when alarm is ringing
win.dispatch = cdispatch
mainloop.register(win)
settimer(win)
return win
def cdispatch(event):
type, win, detail = event
if type == WE_DRAW:
drawproc(win, detail)
elif type == WE_TIMER:
settimer(win)
drawproc(win, EVERYWHERE)
elif type in MOUSE_EVENTS:
mouseclick(win, type, detail)
elif type == WE_ACTIVATE:
if win.ring:
# Turn the ringing off
win.ring = 0
win.begindrawing().invert(win.mainarea)
elif type == WE_SIZE:
win.change(EVERYWHERE)
setdimensions(win)
elif type == WE_CLOSE:
mainloop.unregister(win)
win.close()
def setdimensions(win):
width, height = win.getwinsize()
height = height - stdwin.lineheight()
if width < height: size = width
else: size = height
halfwidth = width/2
halfheight = height/2
win.center = halfwidth, halfheight
win.radius = size*45/100
win.width = width
win.height = height
win.corner = width, height
win.mainarea = ORIGIN, win.corner
win.lineheight = stdwin.lineheight()
win.farcorner = width, height + win.lineheight
win.statusarea = (0, height), win.farcorner
win.fullarea = ORIGIN, win.farcorner
def settimer(win):
now = time.time()
hours, minutes, seconds = win.times = calctime(now)
delay = 61 - seconds
win.settimer(10 * delay)
minutes = minutes + hours*60
if win.ring:
# Is it time to stop the alarm ringing?
since = (minutes - win.time + 720) % 720
if since >= 5:
# Stop it now
win.ring = 0
else:
# Ring again, once every minute
stdwin.fleep()
elif win.set and minutes == win.time:
# Start the alarm ringing
win.ring = 1
stdwin.fleep()
def drawproc(win, area):
hours, minutes, seconds = win.times
d = win.begindrawing()
d.cliprect(area)
d.erase(EVERYWHERE)
d.circle(win.center, win.radius)
d.line(win.center, calcpoint(win, hours*30 + minutes/2, 0.6))
d.line(win.center, calcpoint(win, minutes*6, 1.0))
str = "%02d:%02d" % (hours, minutes)
p = (win.width - d.textwidth(str))/2, win.height * 3 / 4
d.text(p, str)
if win.set:
drawalarm(win, d)
drawalarmtime(win, d)
if win.ring:
d.invert(win.mainarea)
def mouseclick(win, type, detail):
d = win.begindrawing()
if win.ring:
# First turn the ringing off
win.ring = 0
d.invert(win.mainarea)
h, v = detail[0]
ch, cv = win.center
x, y = h-ch, cv-v
dist = sqrt(x*x + y*y) / float(win.radius)
if dist > 1.2:
if win.set:
drawalarm(win, d)
erasealarmtime(win, d)
win.set = 0
elif dist < 0.8:
if not win.set:
win.set = 1
drawalarm(win, d)
drawalarmtime(win, 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 <> win.time or not win.set:
if win.set:
drawalarm(win, d)
erasealarmtime(win, d)
win.set = 1
win.time = atime
drawalarm(win, d)
drawalarmtime(win, d)
def drawalarm(win, d):
p1 = calcpoint(win, float(win.time)/2.0, 1.02)
p2 = calcpoint(win, float(win.time)/2.0 - 4.0, 1.1)
p3 = calcpoint(win, float(win.time)/2.0 + 4.0, 1.1)
d.xorline(p1, p2)
d.xorline(p2, p3)
d.xorline(p3, p1)
def erasealarmtime(win, d):
d.erase(win.statusarea)
def drawalarmtime(win, d):
# win.time is in the range 0..720 with origin at 12 o'clock
# Convert to hours (0..12) and minutes (12*(0..60))
hh = win.time/60
mm = win.time%60
str = 'Alarm@%02d:%02d' % (hh, mm)
p1 = (win.width - d.textwidth(str))/2, win.height
d.text(p1, str)
def calcpoint(win, degrees, size):
alpha = pi/2.0 - float(degrees) * pi/180.0
x, y = cos(alpha), sin(alpha)
h, v = win.center
r = float(win.radius)
return h + int(x*size*r), v - int(y*size*r)
def calctime(now):
hours, minutes, seconds = time.localtime(now)[3:6]
hours = hours % 12
return hours, minutes, seconds
main()
This directory contains a browser written in Python for "Info files"
as used by the Emacs documentation system. The browser requires that
Python is built with the "stdwin" option and runs under X11 or the
Mac window system.
Now you can read Info files even if you can't spare the memory, time or
disk space to run Emacs. (I have used this extensively on a Macintosh
with 1 Megabyte main memory and a 20 Meg harddisk.)
You can give this to someone with great fear of complex computer
systems, as long as they can use a mouse.
Another reason to use this is to encourage the use of Info for on-line
documentation of software that is not related to Emacs or GNU.
(In particular, I plan to redo the Python and STDWIN documentation
in texinfo.)
The main program is in file "ib.py"; this accepts a file name and a
node name as optional command line arguments, i.e., its usage is
python ib.py [file [node]]
Configuration:
- The pathname of the directory (or directories) containing
the standard Info files should be set by editing the
value assigned to INFOPATH in module ifile.py.
- The default font should be set by editing the value of FONT
in this module (ibrowse.py).
- For fastest I/O, you may look at BLOCKSIZE and a few other
constants in ifile.py.
-*- Text -*-
This is the file .../ibrowse/dir, which contains the topmost node of the
Info hierarchy. The first time you invoke Ibrowse you start off
looking at that node, which is (dir)Top. (This is a copy of the Info
dir node, except that the reference to Info is replaced by one to Ibrowse.)

File: dir Node: Top This is the top of the INFO tree
This (the Directory node) gives a menu of major topics.
Typing "d" returns here, "q" exits, "?" lists all INFO commands, "h"
gives a primer for first-timers, "mTexinfo<Return>" visits Texinfo topic,
etc.
--- PLEASE ADD DOCUMENTATION TO THIS TREE. (See INFO topic first.) ---
* Menu: The list of major topics begins on the next line.
* Ibrowse: (ibrowse). Documentation browsing system.
* Emacs: (emacs). The extensible self-documenting text editor.
* VIP: (vip). A VI-emulation for Emacs.
* Texinfo: (texinfo).
With one source file, make either a printed manual
(through TeX) or an Info file (through texinfo).
Full documentation in this menu item.
* Termcap: (termcap).
The termcap library, which enables application programs
to handle all types of character-display terminals.
* Regex: (regex).
The GNU regular expression library.
* Bison: (bison.info).
The GNU yacc-compatible parser generator.
* GCC: (gcc.info).
The GNU C compiler.
* G++: (g-whiz).
The GNU C++ compiler.
* LibG++: (libg++).
The GNU C++ library.
* GDB: (gdb.info).
The GNU debugger.
* CPP: (cpp.info).
The GNU C preprocessor.
* Lispref: (lispref).
The GNU Emacs Lisp reference manual.
* Make: (make-info).
The GNU make program.
* M4: (m4).
The GNU m4 program.
* Gawk: (gawk-info).
GNU awk.
: ${ARCH}=`arch`
exec /ufs/guido/bin/$ARCH/python ib.py ${1+"$@"}
#! /usr/bin/env python
# Call ibrowse (the info file browser) under UNIX.
import sys
import ibrowse
if len(sys.argv) > 1:
file = sys.argv[1]
if len(sys.argv) > 2:
if len(sys.argv) > 3:
sys.stdout = sys.stderr
print 'usage:', sys.argv[0], '[file [node]]'
sys.exit(2)
else:
node = sys.argv[2]
else:
node = ''
ibrowse.start('(' + file + ')' + node)
else:
ibrowse.main()
This diff is collapsed.
This diff is collapsed.
# Cache management for info file processing.
# The function get_node() is the standard interface;
# its signature is the same as ifile.get_node() but it uses
# the cache and supports indirect tag tables.
import string
import ifile
from ifile import NoSuchNode, NoSuchFile
import itags
# Special hack to save the cache when using reload().
# This can just be "cache = {}" in a production version.
#
try:
dummy = cache
del dummy
except NameError:
cache = {}
# Clear the entire cache.
#
def resetcache():
for key in cache.keys():
del cache[key]
# Clear the node info from the cache (the most voluminous data).
#
def resetnodecache():
for key in cache.keys():
tags, nodes = cache[key]
cache[key] = tags, {}
# Get a node.
#
def get_node(curfile, ref):
file, node = ifile.parse_ref(curfile, ref)
file = string.lower(file)
node = string.lower(node)
if node == '*':
# Don't cache whole file references;
# reading the data is faster than displaying it anyway.
return ifile.get_whole_file(file) # May raise NoSuchFile
if not cache.has_key(file):
cache[file] = get_tags(file), {} # May raise NoSuchFile
tags, nodes = cache[file]
if not nodes.has_key(node):
if not tags.has_key(node):
raise NoSuchNode, ref
file1, offset, line = tags[node]
if not file1:
file1 = file
file1, node1, header, menu, footnotes, text = \
ifile.get_file_node(file1, offset, node)
nodes[node] = file, node1, header, menu, footnotes, text
return nodes[node]
# Get the tag table for a file.
# Either construct one or get the one found in the file.
# Raise NoSuchFile if the file isn't found.
#
def get_tags(file):
f = ifile.try_open(file) # May raise NoSuchFile
tags = itags.get_tags(f)
if not tags:
###print 'Scanning file...'
f.seek(0)
tags = ifile.make_tags(f)
return tags
# Tools for info file processing.
# XXX Need to be more careful with reading ahead searching for nodes.
import regexp
import string
# Exported exceptions.
#
NoSuchFile = 'no such file'
NoSuchNode = 'no such node'
# The search path for info files; this is site-specific.
# Directory names should end in a partname delimiter,
# so they can simply be concatenated to a relative pathname.
#
#INFOPATH = ['', ':Info.Ibrowse:', ':Info:'] # Mac
INFOPATH = ['', '/usr/local/emacs/info/'] # X11 on UNIX
# Tunable constants.
#
BLOCKSIZE = 512 # Qty to align reads to, if possible
FUZZ = 2*BLOCKSIZE # Qty to back-up before searching for a node
CHUNKSIZE = 4*BLOCKSIZE # Qty to read at once when reading lots of data
# Regular expressions used.
# Note that it is essential that Python leaves unrecognized backslash
# escapes in a string so they can be seen by regexp.compile!
#
findheader = regexp.compile('\037\014?\n(.*\n)').match
findescape = regexp.compile('\037').match
parseheader = regexp.compile('[nN]ode:[ \t]*([^\t,\n]*)').match
findfirstline = regexp.compile('^.*\n').match
findnode = regexp.compile('[nN]ode:[ \t]*([^\t,\n]*)').match
findprev = regexp.compile('[pP]rev[ious]*:[ \t]*([^\t,\n]*)').match
findnext = regexp.compile('[nN]ext:[ \t]*([^\t,\n]*)').match
findup = regexp.compile('[uU]p:[ \t]*([^\t,\n]*)').match
findmenu = regexp.compile('^\* [mM]enu:').match
findmenuitem = regexp.compile( \
'^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match
findfootnote = regexp.compile( \
'\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match
parsenoderef = regexp.compile('^\((.*)\)(.*)$').match
# Get a node and all information pertaining to it.
# This doesn't work if there is an indirect tag table,
# and in general you are better off using icache.get_node() instead.
# Functions get_whole_file() and get_file_node() provide part
# functionality used by icache.
# Raise NoSuchFile or NoSuchNode as appropriate.
#
def get_node(curfile, ref):
file, node = parse_ref(curfile, ref)
if node == '*':
return get_whole_file(file)
else:
return get_file_node(file, 0, node)
#
def get_whole_file(file):
f = try_open(file) # May raise NoSuchFile
text = f.read()
header, menu, footnotes = ('', '', ''), [], []
return file, '*', header, menu, footnotes, text
#
def get_file_node(file, offset, node):
f = try_open(file) # May raise NoSuchFile
text = find_node(f, offset, node) # May raise NoSuchNode
node, header, menu, footnotes = analyze_node(text)
return file, node, header, menu, footnotes, text
# Parse a node reference into a file (possibly default) and node name.
# Possible reference formats are: "NODE", "(FILE)", "(FILE)NODE".
# Default file is the curfile argument; default node is Top.
# A node value of '*' is a special case: the whole file should
# be interpreted (by the caller!) as a single node.
#
def parse_ref(curfile, ref):
match = parsenoderef(ref)
if not match:
file, node = curfile, ref
else:
(a, b), (a1, b1), (a2, b2) = match
file, node = ref[a1:b1], ref[a2:b2]
if not file:
file = curfile # (Is this necessary?)
if not node:
node = 'Top'
return file, node
# Extract node name, links, menu and footnotes from the node text.
#
def analyze_node(text):
#
# Get node name and links from the header line
#
match = findfirstline(text)
if match:
(a, b) = match[0]
line = text[a:b]
else:
line = ''
node = get_it(text, findnode)
prev = get_it(text, findprev)
next = get_it(text, findnext)
up = get_it(text, findup)
#
# Get the menu items, if there is a menu
#
menu = []
match = findmenu(text)
if match:
(a, b) = match[0]
while 1:
match = findmenuitem(text, b)
if not match:
break
(a, b), (a1, b1), (a2, b2) = match
topic, ref = text[a1:b1], text[a2:b2]
if ref == ':':
ref = topic
menu.append((topic, ref))
#
# Get the footnotes
#
footnotes = []
b = 0
while 1:
match = findfootnote(text, b)
if not match:
break
(a, b), (a1, b1), (a2, b2) = match
topic, ref = text[a1:b1], text[a2:b2]
if ref == ':':
ref = topic
footnotes.append((topic, ref))
#
return node, (prev, next, up), menu, footnotes
#
def get_it(line, matcher):
match = matcher(line)
if not match:
return ''
else:
(a, b), (a1, b1) = match
return line[a1:b1]
# Find a node in an open file.
# The offset (from the tags table) is a hint about the node's position.
# Pass zero if there is no tags table.
# Raise NoSuchNode if the node isn't found.
# NB: This seeks around in the file.
#
def find_node(f, offset, node):
node = string.lower(node) # Just to be sure
#
# Position a little before the given offset,
# so we may find the node even if it has moved around
# in the file a little.
#
offset = max(0, ((offset-FUZZ) / BLOCKSIZE) * BLOCKSIZE)
f.seek(offset)
#
# Loop, hunting for a matching node header.
#
while 1:
buf = f.read(CHUNKSIZE)
if not buf:
break
i = 0
while 1:
match = findheader(buf, i)
if match:
(a,b), (a1,b1) = match
start = a1
line = buf[a1:b1]
i = b
match = parseheader(line)
if match:
(a,b), (a1,b1) = match
key = string.lower(line[a1:b1])
if key == node:
# Got it! Now read the rest.
return read_node(f, buf[start:])
elif findescape(buf, i):
next = f.read(CHUNKSIZE)
if not next:
break
buf = buf + next
else:
break
#
# If we get here, we didn't find it. Too bad.
#
raise NoSuchNode, node
# Finish off getting a node (subroutine for find_node()).
# The node begins at the start of buf and may end in buf;
# if it doesn't end there, read additional data from f.
#
def read_node(f, buf):
i = 0
match = findescape(buf, i)
while not match:
next = f.read(CHUNKSIZE)
if not next:
end = len(buf)
break
i = len(buf)
buf = buf + next
match = findescape(buf, i)
else:
# Got a match
(a, b) = match[0]
end = a
# Strip trailing newlines
while end > 0 and buf[end-1] == '\n':
end = end-1
buf = buf[:end]
return buf
# Read reverse starting at offset until the beginning of a node is found.
# Then return a buffer containing the beginning of the node,
# with f positioned just after the buffer.
# The buffer will contain at least the full header line of the node;
# the caller should finish off with read_node() if it is the right node.
# (It is also possible that the buffer extends beyond the node!)
# Return an empty string if there is no node before the given offset.
#
def backup_node(f, offset):
start = max(0, ((offset-CHUNKSIZE) / BLOCKSIZE) * BLOCKSIZE)
end = offset
while start < end:
f.seek(start)
buf = f.read(end-start)
i = 0
hit = -1
while 1:
match = findheader(buf, i)
if match:
(a,b), (a1,b1) = match
hit = a1
i = b
elif end < offset and findescape(buf, i):
next = f.read(min(offset-end, BLOCKSIZE))
if not next:
break
buf = buf + next
end = end + len(next)
else:
break
if hit >= 0:
return buf[hit:]
end = start
start = max(0, end - CHUNKSIZE)
return ''
# Make a tag table for the given file by scanning the file.
# The file must be open for reading, and positioned at the beginning
# (or wherever the hunt for tags must begin; it is read till the end).
#
def make_tags(f):
tags = {}
while 1:
offset = f.tell()
buf = f.read(CHUNKSIZE)
if not buf:
break
i = 0
while 1:
match = findheader(buf, i)
if match:
(a,b), (a1,b1) = match
start = offset+a1
line = buf[a1:b1]
i = b
match = parseheader(line)
if match:
(a,b), (a1,b1) = match
key = string.lower(line[a1:b1])
if tags.has_key(key):
print 'Duplicate node:',
print key
tags[key] = '', start, line
elif findescape(buf, i):
next = f.read(CHUNKSIZE)
if not next:
break
buf = buf + next
else:
break
return tags
# Try to open a file, return a file object if succeeds.
# Raise NoSuchFile if the file can't be opened.
# Should treat absolute pathnames special.
#
def try_open(file):
for dir in INFOPATH:
try:
return open(dir + file, 'r')
except IOError:
pass
raise NoSuchFile, file
# A little test for the speed of make_tags().
#
TESTFILE = 'texinfo-1'
def test_make_tags():
import time
f = try_open(TESTFILE)
t1 = time.millitimer()
tags = make_tags(f)
t2 = time.millitimer()
print 'Making tag table for', `TESTFILE`, 'took', t2-t1, 'msec.'
# Utility module for 'icache.py': interpret tag tables and indirect nodes.
# (This module is a bit chatty when confronted with the unexpected.)
import regexp
import string
import ifile
# Get the tag table of an open file, as a dictionary.
# Seeks around in the file; after reading, the position is undefined.
# Return an empty tag table if none is found.
#
def get_tags(f):
#
# First see if the last "node" is the end of tag table marker.
#
f.seek(0, 2) # Seek to EOF
end = f.tell()
buf = ifile.backup_node(f, end)
if not labelmatch(buf, 0, 'end tag table\n'):
return {} # No succes
#
# Next backup to the previous "node" -- the tag table itself.
#
###print 'Getting prebuilt tag table...'
end = f.tell() - len(buf)
buf = ifile.backup_node(f, end)
label = 'tag table:\n'
if not labelmatch(buf, 0, label):
print 'Weird: end tag table marker but no tag table?'
print 'Node begins:', `buf[:50]`
return {}
#
# Now read the whole tag table.
#
end = f.tell() - len(buf) # Do this first!
buf = ifile.read_node(f, buf)
#
# First check for an indirection table.
#
indirlist = []
if labelmatch(buf, len(label), '(indirect)\n'):
indirbuf = ifile.backup_node(f, end)
if not labelmatch(indirbuf, 0, 'indirect:\n'):
print 'Weird: promised indirection table not found'
print 'Node begins:', `indirbuf[:50]`
# Carry on. Things probably won't work though.
else:
indirbuf = ifile.read_node(f, indirbuf)
indirlist = parse_indirlist(indirbuf)
#
# Now parse the tag table.
#
findtag = regexp.compile('^(.*[nN]ode:[ \t]*(.*))\177([0-9]+)$').match
i = 0
tags = {}
while 1:
match = findtag(buf, i)
if not match:
break
(a,b), (a1,b1), (a2,b2), (a3,b3) = match
i = b
line = buf[a1:b1]
node = string.lower(buf[a2:b2])
offset = eval(buf[a3:b3]) # XXX What if it overflows?
if tags.has_key(node):
print 'Duplicate key in tag table:', `node`
file, offset = map_offset(offset, indirlist)
tags[node] = file, offset, line
#
return tags
# Return true if buf[i:] begins with a label, after lower case conversion.
# The label argument must be in lower case.
#
def labelmatch(buf, i, label):
return string.lower(buf[i:i+len(label)]) == label
# Parse the indirection list.
# Return a list of (filename, offset) pairs ready for use.
#
def parse_indirlist(buf):
list = []
findindir = regexp.compile('^(.+):[ \t]*([0-9]+)$').match
i = 0
while 1:
match = findindir(buf, i)
if not match:
break
(a,b), (a1,b1), (a2,b2) = match
file = buf[a1:b1]
offset = eval(buf[a2:b2]) # XXX What if this gets overflow?
list.append((file, offset))
i = b
return list
# Map an offset through the indirection list.
# Return (filename, new_offset).
# If the list is empty, return the given offset and an empty file name.
#
def map_offset(offset, indirlist):
if not indirlist:
return '', offset
#
# XXX This could be done more elegant.
#
filex, offx = indirlist[0]
for i in range(len(indirlist)):
file1, off1 = indirlist[i]
if i+1 >= len(indirlist):
file2, off2 = '', 0x7fffffff
else:
file2, off2 = indirlist[i+1]
if off1 <= offset < off2:
# Add offx+2 to compensate for extra header.
# No idea whether this is always correct.
return file1, offset-off1 + offx+2
#
# XXX Shouldn't get here.
#
print 'Oops, map_offset fell through'
return '', offset # Not likely to get good results
#! /usr/bin/env python
# XXX This only works on SGIs running IRIX 4.0 or higher
# JUKEBOX: browse directories full of sampled sound files.
#
# One or more "list windows" display the files and subdirectories of
# the arguments. Double-clicking on a subdirectory opens a new window
# displaying its contents (and so on recursively). Double clicking
# on a file plays it as a sound file (assuming it is one).
#
# Playing is asynchronous: the application keeps listening for events
# while the sample is playing, so you can cancel playing or start a
# new sample right away. Synchronous playing is available through the
# -s option.
#
# The control window displays a "stop button" that cancel the current
# play request.
#
# Most sound file formats recognized by SOX or SFPLAY are recognized.
# Since conversion is costly, converted files are cached in
# /usr/tmp/@j* until the user quits or changes the sampling rate via
# the Rate menu.
import commands
import getopt
import os
from stat import *
import rand
import stdwin
from stdwinevents import *
import sys
import tempfile
import sndhdr
from WindowParent import WindowParent
from Buttons import PushButton
# Pathnames
DEF_DB = '/usr/local/sounds' # Default directory of sounds
SOX = '/usr/local/bin/sox' # Sound format conversion program
SFPLAY = '/usr/sbin/sfplay' # Sound playing program
# Global variables
class struct: pass # Class to define featureless structures
G = struct() # Holds writable global variables
# Main program
def main():
G.synchronous = 0 # If set, use synchronous audio.write()
G.debug = 0 # If set, print debug messages
G.busy = 0 # Set while asynchronous playing is active
G.windows = [] # List of open windows, except control
G.mode = '' # File type (default any that sfplay knows)
G.rate = 0 # Sampling rate (default " " " ")
G.tempprefix = tempfile.mktemp()
#
try:
optlist, args = getopt.getopt(sys.argv[1:], 'dr:st:')
except getopt.error, msg:
sys.stdout = sys.stderr
print msg
print 'usage: jukebox [-d] [-s] [-t type] [-r rate]'
print ' -d debugging (-dd event debugging)'
print ' -s synchronous playing'
print ' -t type file type'
print ' -r rate sampling rate'
sys.exit(2)
#
for optname, optarg in optlist:
if optname == '-d':
G.debug = G.debug + 1
elif optname == '-r':
G.rate = int(eval(optarg))
elif optname == '-s':
G.synchronous = 1
elif optname == '-t':
G.mode = optarg
#
if G.debug:
for name in G.__dict__.keys():
print 'G.' + name, '=', `G.__dict__[name]`
#
if not args:
args = [DEF_DB]
#
G.cw = opencontrolwindow()
for dirname in args:
G.windows.append(openlistwindow(dirname))
#
#
try:
maineventloop()
finally:
clearcache()
killchild()
# Entries in Rate menu:
rates = ['default', '7350', \
'8000', '11025', '16000', '22050', '32000', '41000', '48000']
def maineventloop():
mouse_events = WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP
while G.windows:
try:
type, w, detail = event = stdwin.getevent()
except KeyboardInterrupt:
killchild()
continue
if w == G.cw.win:
if type == WE_CLOSE:
return
if type == WE_TIMER:
checkchild()
if G.busy:
G.cw.win.settimer(1)
elif type == WE_MENU:
menu, item = detail
if menu is G.ratemenu:
clearcache()
if item == 0:
G.rate = 0
else:
G.rate = eval(rates[item])
for i in range(len(rates)):
menu.check(i, (i == item))
else:
G.cw.dispatch(event)
else:
if type == WE_DRAW:
w.drawproc(w, detail)
elif type in mouse_events:
w.mouse(w, type, detail)
elif type == WE_CLOSE:
w.close(w)
del w, event
else:
if G.debug > 1: print type, w, detail
def checkchild():
if G.busy:
waitchild(1)
def killchild():
if G.busy:
os.kill(G.busy, 9)
waitchild(0)
def waitchild(options):
pid, sts = os.waitpid(G.busy, options)
if pid == G.busy:
G.busy = 0
G.stop.enable(0)
# Control window -- to set gain and cancel play operations in progress
def opencontrolwindow():
stdwin.setdefscrollbars(0, 0)
cw = WindowParent().create('Jukebox', (0, 0))
#
stop = PushButton().definetext(cw, ' Stop ')
stop.hook = stop_hook
stop.enable(0)
G.stop = stop
#
cw.realize()
#
G.ratemenu = cw.win.menucreate('Rate')
for r in rates:
G.ratemenu.additem(r)
if G.rate == 0:
G.ratemenu.check(0, 1)
else:
for i in len(range(rates)):
if rates[i] == `G.rate`:
G.ratemenu.check(i, 1)
#
return cw
def stop_hook(self):
killchild()
# List windows -- to display list of files and subdirectories
def openlistwindow(dirname):
list = os.listdir(dirname)
list.sort()
i = 0
while i < len(list):
if list[i][0] == '.':
del list[i]
else:
i = i+1
for i in range(len(list)):
fullname = os.path.join(dirname, list[i])
if os.path.isdir(fullname):
info = '/'
else:
try:
size = os.stat(fullname)[ST_SIZE]
info = `(size + 1023)/1024` + 'k'
except IOError:
info = '???'
info = '(' + info + ')'
list[i] = list[i], info
width = maxwidth(list)
# width = width + stdwin.textwidth(' ') # XXX X11 stdwin bug workaround
height = len(list) * stdwin.lineheight()
stdwin.setdefwinsize(width, min(height, 500))
stdwin.setdefscrollbars(0, 1)
w = stdwin.open(dirname)
stdwin.setdefwinsize(0, 0)
w.setdocsize(width, height)
w.drawproc = drawlistwindow
w.mouse = mouselistwindow
w.close = closelistwindow
w.dirname = dirname
w.list = list
w.selected = -1
return w
def maxwidth(list):
width = 1
for name, info in list:
w = stdwin.textwidth(name + ' ' + info)
if w > width: width = w
return width
def drawlistwindow(w, area):
## (left, top), (right, bottom) = area
d = w.begindrawing()
d.erase((0, 0), (1000, 10000))
lh = d.lineheight()
h, v = 0, 0
for name, info in w.list:
if info == '/':
text = name + '/'
else:
text = name + ' ' + info
d.text((h, v), text)
v = v + lh
showselection(w, d)
d.close()
def hideselection(w, d):
if w.selected >= 0:
invertselection(w, d)
def showselection(w, d):
if w.selected >= 0:
invertselection(w, d)
def invertselection(w, d):
lh = d.lineheight()
h1, v1 = p1 = 0, w.selected*lh
h2, v2 = p2 = 1000, v1 + lh
d.invert(p1, p2)
def mouselistwindow(w, type, detail):
(h, v), clicks, button = detail[:3]
d = w.begindrawing()
lh = d.lineheight()
if 0 <= v < lh*len(w.list):
i = v / lh
else:
i = -1
if w.selected <> i:
hideselection(w, d)
w.selected = i
showselection(w, d)
d.close()
if type == WE_MOUSE_DOWN and clicks >= 2 and i >= 0:
setcursors('watch')
name, info = w.list[i]
fullname = os.path.join(w.dirname, name)
if info == '/':
if clicks == 2:
G.windows.append(openlistwindow(fullname))
else:
playfile(fullname)
setcursors('cross')
def closelistwindow(w):
G.windows.remove(w)
def setcursors(cursor):
for w in G.windows:
w.setwincursor(cursor)
G.cw.win.setwincursor(cursor)
# Playing tools
cache = {}
def clearcache():
for x in cache.keys():
cmd = 'rm -f ' + cache[x]
if G.debug: print cmd
sts = os.system(cmd)
if sts:
print cmd
print 'Exit status', sts
del cache[x]
validrates = (8000, 11025, 16000, 22050, 32000, 44100, 48000)
def playfile(filename):
killchild()
try:
tuple = sndhdr.what(filename)
except IOError, msg:
print 'Can\'t open', filename, msg
stdwin.fleep()
return
raw = 0
if tuple:
mode, rate = tuple[:2]
if rate == 0:
rate = G.rate
if rate == 0:
rate = 8000
else:
mode = G.mode
rate = G.rate
if G.debug: print 'mode =', mode, 'rate =', rate
if mode in ('au', 'aiff', 'wav', 'aifc', 'ul', 'ub', 'sb') and \
rate in validrates:
tempname = filename
if mode in ('ul', 'ub', 'sb'):
raw = 1
elif cache.has_key(filename):
tempname = cache[filename]
else:
tempname = G.tempprefix + `rand.rand()` + '.aiff'
cmd = SOX
if G.debug:
cmd = cmd + ' -V'
if mode <> '':
cmd = cmd + ' -t ' + mode
cmd = cmd + ' ' + commands.mkarg(filename)
cmd = cmd + ' -t aiff'
if rate not in validrates:
rate = 32000
if rate:
cmd = cmd + ' -r ' + `rate`
cmd = cmd + ' ' + tempname
if G.debug: print cmd
sts = os.system(cmd)
if sts:
print cmd
print 'Exit status', sts
stdwin.fleep()
try:
os.unlink(tempname)
except:
pass
return
cache[filename] = tempname
if raw:
pid = sfplayraw(tempname, tuple)
else:
pid = sfplay(tempname, [])
if G.synchronous:
sts = os.wait(pid, 0)
else:
G.busy = pid
G.stop.enable(1)
G.cw.win.settimer(1)
def sfplayraw(filename, tuple):
args = ['-i']
type, rate, channels, frames, bits = tuple
if type == 'ul':
args.append('mulaw')
elif type == 'ub':
args = args + ['integer', '8', 'unsigned']
elif type == 'sb':
args = args + ['integer', '8', '2scomp']
else:
print 'sfplayraw: warning: unknown type in', tuple
if channels > 1:
args = args + ['channels', `channels`]
if not rate:
rate = G.rate
if rate:
args = args + ['rate', `rate`]
args.append('end')
return sfplay(filename, args)
def sfplay(filename, args):
if G.debug:
args = ['-p'] + args
args = [SFPLAY, '-r'] + args + [filename]
if G.debug: print 'sfplay:', args
pid = os.fork()
if pid == 0:
# Child
os.execv(SFPLAY, args)
# NOTREACHED
else:
# Parent
return pid
main()
#! /usr/bin/env python
# Watch line printer queues (only works with BSD 4.3 lpq).
#
# This brings up a window containing one line per printer argument.
#
# Each line gives a small summary of the printer's status and queue.
# The status tries to give as much relevant information as possible,
# and gives extra info if you have jobs in the queue.
#
# The line's background color gives a hint at the status: navajo white
# for idle, green if your job is now printing, yellow/orange for
# small/large queue, red for errors.
#
# To reduce the duration of the unresponsive time while it is waiting
# for an lpq subprocess to finish, it polls one printer every
# delay/len(printers) seconds. A tiny dot indicates the last printer
# updated. Hit the mouse button in the window to update the next one.
#
# To do:
# - add an argument to override the default delay
# - add arguments to override the default colors
# - better heuristic for small/large queue (and more colors!)
# - mouse clicks should update the printer clicked in
# - better visual appearance, e.g., boxes around the lines?
import posix
import sys
import time
import string
import stdwin
from stdwinevents import *
import mainloop
# Default parameters
DEF_PRINTER = 'oce' # This is CWI specific!
DEF_DELAY = 10
# Color assignments
c_unknown = stdwin.fetchcolor('white')
c_idle = stdwin.fetchcolor('navajo white')
c_ontop = stdwin.fetchcolor('green')
c_smallqueue = stdwin.fetchcolor('yellow')
c_bigqueue = stdwin.fetchcolor('orange')
c_error = stdwin.fetchcolor('red')
def main():
delay = DEF_DELAY
#
try:
thisuser = posix.environ['LOGNAME']
except:
thisuser = posix.environ['USER']
#
printers = sys.argv[1:]
if printers:
# Strip '-P' from printer names just in case
# the user specified it...
for i in range(len(printers)):
if printers[i][:2] == '-P':
printers[i] = printers[i][2:]
else:
if posix.environ.has_key('PRINTER'):
printers = [posix.environ['PRINTER']]
else:
printers = [DEF_PRINTER]
#
width = stdwin.textwidth('in')*20
height = len(printers) * stdwin.lineheight() + 5
stdwin.setdefwinsize(width, height)
stdwin.setdefscrollbars(0, 0)
#
win = stdwin.open('lpwin')
#
win.printers = printers
win.colors = [c_unknown] * len(printers)
win.texts = printers[:]
win.next = 0
win.delay = DEF_DELAY
win.thisuser = thisuser
win.dispatch = lpdispatch
#
win.settimer(1)
#
mainloop.register(win)
mainloop.mainloop()
def lpdispatch(event):
type, win, detail = event
if type == WE_CLOSE or type == WE_CHAR and detail in ('q', 'Q'):
mainloop.unregister(win)
elif type == WE_DRAW:
drawproc(win)
elif type == WE_TIMER:
update(win)
win.change((0,0), (10000, 10000))
elif type == WE_MOUSE_UP:
win.settimer(1)
def drawproc(win):
d = win.begindrawing()
offset = d.textwidth('.')
h, v = 0, 0
for i in range(len(win.printers)):
text = win.texts[i]
color = win.colors[i]
d.setbgcolor(color)
d.erase((h, v), (h+10000, v+d.lineheight()))
if (i+1) % len(win.printers) == win.next and color <> c_unknown:
d.text((h, v), '.')
d.text((h+offset, v), text)
v = v + d.lineheight()
def update(win):
i = win.next
win.next = (i+1) % len(win.printers)
win.texts[i], win.colors[i] = makestatus(win.printers[i], win.thisuser)
win.settimer(int(win.delay * 10.0 / len(win.printers)))
def makestatus(name, thisuser):
pipe = posix.popen('lpq -P' + name + ' 2>&1', 'r')
lines = []
users = {}
aheadbytes = 0
aheadjobs = 0
userseen = 0
totalbytes = 0
totaljobs = 0
color = c_unknown
while 1:
line = pipe.readline()
if not line: break
fields = string.split(line)
n = len(fields)
if len(fields) >= 6 and fields[n-1] == 'bytes':
rank = fields[0]
user = fields[1]
job = fields[2]
files = fields[3:-2]
bytes = eval(fields[n-2])
if user == thisuser:
userseen = 1
if aheadjobs == 0:
color = c_ontop
elif not userseen:
aheadbytes = aheadbytes + bytes
aheadjobs = aheadjobs + 1
totalbytes = totalbytes + bytes
totaljobs = totaljobs + 1
if color == c_unknown:
color = c_smallqueue
elif color == c_smallqueue:
color = c_bigqueue
if users.has_key(user):
ujobs, ubytes = users[user]
else:
ujobs, ubytes = 0, 0
ujobs = ujobs + 1
ubytes = ubytes + bytes
users[user] = ujobs, ubytes
else:
if fields and fields[0] <> 'Rank':
line = string.strip(line)
if line == 'no entries':
line = name + ': idle'
if color == c_unknown:
color = c_idle
elif line[-22:] == ' is ready and printing':
line = line[:-22]
else:
line = name + ': ' + line
color = c_error
lines.append(line)
#
if totaljobs:
line = `(totalbytes+1023)/1024` + ' K'
if totaljobs <> len(users):
line = line + ' (' + `totaljobs` + ' jobs)'
if len(users) == 1:
line = line + ' for ' + users.keys()[0]
else:
line = line + ' for ' + `len(users)` + ' users'
if userseen:
if aheadjobs == 0:
line = line + ' (' + thisuser + ' first)'
else:
line = line + ' (' + `(aheadbytes+1023)/1024`
line = line + ' K before ' + thisuser + ')'
lines.append(line)
#
sts = pipe.close()
if sts:
lines.append('lpq exit status ' + `sts`)
color = c_error
return string.joinfields(lines, ': '), color
main()
#! /usr/bin/env python
# A minimal single-window text editor using STDWIN's text objects.
#
# Usage: microedit file
#
# This is not intended as a real application but as an introduction
# to STDWIN programming in Python, especially text objects.
# Once you understand microedit.py, study miniedit.py to learn
# about multiple windows and menus, cut and paste, etc.
import sys
import stdwin
from stdwinevents import *
# Main program
#
def main():
#
# Get the filename argument and read its contents as one very
# large string.
# An exception will terminate the program if there is no argument
# or if the file could not be read...
#
filename = sys.argv[1]
fp = open(filename, 'r')
contents = fp.read()
del fp # Close the file
#
# Create the window, using the filename as window title
#
window = stdwin.open(filename)
#
# Add a simple File menu to the window with two items
#
filemenu = window.menucreate('File')
filemenu.additem('Save', 'S') # Item 0 (shortcut Meta-S)
filemenu.additem('Save As...') # Item 1
#
# Create a text object occupying the entire window
# and fill it with the file's contents
#
corner = window.getwinsize() # (width, height)
area = (0, 0), corner # Rectangle as large as the window
text = window.textcreate(area)
text.settext(contents)
del contents # Get rid of contents object
fix_textsize(window, text) # Set document size accordingly
#
# Main event loop -- stop if a close request comes in.
#
# STDWIN applications should regularly call stdwin.getevent()
# otherwise the windows won't function as expected.
#
while 1:
#
# Get the next event
#
type, w, detail = e = stdwin.getevent()
#
# Event decoding switch
#
if type == WE_CLOSE:
break # Stop (no check for saved file!)
elif type == WE_SIZE:
#
# The window was resized --
# let the text object recompute the line breaks
# and change the document size accordingly,
# so scroll bars will work
#
fix_textsize(window, text)
elif type == WE_MENU:
#
# Execute a file menu request (our only menu)
#
menu, item = detail
if item == 0:
#
# "Save": save to the current filename
#
dummy = save_file(window, text, filename)
elif item == 1:
#
# "Save As": ask a new filename, save to it,
# and make it the current filename
#
# NB: askfile raises KeyboardInterrupt
# if the user cancels the dialog, hence
# the try statement
#
try:
newfile = stdwin.askfile( \
'Save as:', filename, 1)
except KeyboardInterrupt:
newfile = ''
if newfile:
if save_file(window, text, newfile):
filename = newfile
window.settitle(filename)
elif text.event(e):
#
# The text object has handled the event.
# Fix the document size if necessary.
# Note: this sometimes fixes the size
# unnecessarily, e.g., for arrow keys.
#
if type in (WE_CHAR, WE_COMMAND):
fix_docsize(window, text)
# Save the window's contents to the filename.
# If the open() fails, put up a warning message and return 0;
# if the save succeeds, return 1.
#
def save_file(window, text, filename):
#
# Open the file for writing, handling exceptions
#
try:
fp = open(filename, 'w')
except RuntimeError:
stdwin.message('Cannot create ' + filename)
return 0
#
# Get the contents of the text object as one very long string
#
contents = text.gettext()
#
# Write the contents to the file
#
fp.write(contents)
#
# The file is automatically closed when this routine returns
#
return 1
# Change the size of the text object to fit in the window,
# and then fix the window's document size to fit around the text object.
#
def fix_textsize(window, text):
#
# Compute a rectangle as large as the window
#
corner = window.getwinsize() # (width, height)
area = (0, 0), (corner)
#
# Move the text object to this rectangle.
# Note: text.move() ignores the bottom coordinate!
#
text.move(area)
#
# Now fix the document size accordingly
#
fix_docsize(window, text)
# Fix the document size, after the text has changed
#
def fix_docsize(window, text):
#
# Get the actual rectangle occupied by the text object.
# This has the same left, top and right, but a different bottom.
#
area = text.getrect()
#
# Compute the true height of the text object
#
origin, corner = area
width, height = corner
#
# Set the document height to the text object's height.
# The width is zero since we don't want a horizontal scroll bar.
#
window.setdocsize(0, height)
# Once all functions are defined, call main()
#
main()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment