removing more stdwin users
This commit is contained in:
parent
9ab06ee7b2
commit
81b4e41cd9
|
@ -1,213 +0,0 @@
|
|||
# Interface to the interactive CWI library catalog.
|
||||
|
||||
import sys
|
||||
import stdwin
|
||||
from stdwinevents import *
|
||||
import select
|
||||
import telnetlib
|
||||
import vt100win
|
||||
from form import Form
|
||||
|
||||
|
||||
# Main program
|
||||
|
||||
def main():
|
||||
vt = vt100win.VT100win()
|
||||
#
|
||||
host = 'biefstuk.cwi.nl'
|
||||
tn = telnetlib.Telnet(host, 0)
|
||||
#
|
||||
try:
|
||||
vt.send(tn.read_until('login: ', 10))
|
||||
tn.write('cwilib\r')
|
||||
#
|
||||
vt.send(tn.read_until('Hit <RETURN> to continue...', 10))
|
||||
tn.write('\r')
|
||||
#
|
||||
vt.send(tn.read_until('QUIT', 20))
|
||||
except EOFError:
|
||||
sys.stderr.write('Connection closed prematurely\n')
|
||||
sys.exit(1)
|
||||
#
|
||||
define_screens(vt)
|
||||
matches = vt.which_screens()
|
||||
if 'menu' not in matches:
|
||||
sys.stderr.write('Main menu does not appear\n')
|
||||
sys.exit(1)
|
||||
#
|
||||
tn.write('\r\r')
|
||||
vt.open('Progress -- CWI Library')
|
||||
vt.set_debuglevel(0)
|
||||
ui = UserInterface()
|
||||
#
|
||||
while 1:
|
||||
try:
|
||||
data = tn.read_very_eager()
|
||||
except EOFError:
|
||||
stdwin.message('Connection closed--goodbye')
|
||||
break
|
||||
if data:
|
||||
print 'send...'
|
||||
vt.send(data)
|
||||
print 'send...done'
|
||||
continue
|
||||
event = stdwin.pollevent()
|
||||
if event:
|
||||
type, window, detail = event
|
||||
if window == None and type == WE_LOST_SEL:
|
||||
window = ui.queryform.window
|
||||
event = type, window, detail
|
||||
if type == WE_CLOSE:
|
||||
break
|
||||
if window in ui.windows:
|
||||
ui.dispatch(type, window, detail)
|
||||
elif window == vt.window:
|
||||
if type == WE_NULL:
|
||||
pass
|
||||
elif type == WE_COMMAND:
|
||||
if detail == WC_RETURN:
|
||||
tn.write('\r')
|
||||
elif detail == WC_BACKSPACE:
|
||||
tn.write('\b')
|
||||
elif detail == WC_TAB:
|
||||
tn.write('\t')
|
||||
elif detail == WC_UP:
|
||||
tn.write('\033[A')
|
||||
elif detail == WC_DOWN:
|
||||
tn.write('\033[B')
|
||||
elif detail == WC_RIGHT:
|
||||
tn.write('\033[C')
|
||||
elif detail == WC_LEFT:
|
||||
tn.write('\033[D')
|
||||
else:
|
||||
print '*** Command:', detail
|
||||
elif type == WE_CHAR:
|
||||
tn.write(detail)
|
||||
elif type == WE_DRAW:
|
||||
vt.draw(detail)
|
||||
elif type in (WE_ACTIVATE, WE_DEACTIVATE):
|
||||
pass
|
||||
else:
|
||||
print '*** VT100 event:', type, detail
|
||||
else:
|
||||
print '*** Alien event:', type, window, detail
|
||||
continue
|
||||
rfd, wfd, xfd = select.select([tn, stdwin], [], [])
|
||||
|
||||
|
||||
# Subroutine to define our screen recognition patterns
|
||||
|
||||
def define_screens(vt):
|
||||
vt.define_screen('menu', {
|
||||
'title': ('search', 0, 0, 80,
|
||||
' SEARCH FUNCTIONS +OTHER FUNCTIONS '),
|
||||
})
|
||||
vt.define_screen('search', {
|
||||
'title': ('search', 0, 0, 80, ' Search '),
|
||||
})
|
||||
vt.define_screen('shortlist', {'title': ('search', 0, 0, 80,
|
||||
' Short-list')})
|
||||
vt.define_screen('showrecord', {
|
||||
'title': ('search', 0, 0, 80, ' Show record '),
|
||||
})
|
||||
vt.define_screen('timelimit', {
|
||||
'limit': ('search', 12, 0, 80, ' TIME LIMIT '),
|
||||
})
|
||||
vt.define_screen('attention', {
|
||||
'BASE': ('copy', 0, 0, 0, 'search'),
|
||||
'title': ('search', 10, 0, 80, ' ATTENTION ')})
|
||||
vt.define_screen('syntaxerror', {
|
||||
'BASE': ('copy', 0, 0, 0, 'attention'),
|
||||
'message': ('search', 12, 0, 80, ' Syntax error'),
|
||||
})
|
||||
vt.define_screen('emptyerror', {
|
||||
'BASE': ('copy', 0, 0, 0, 'attention'),
|
||||
'message': ('search', 12, 0, 80,
|
||||
' Check your input. Search at least one term'),
|
||||
})
|
||||
vt.define_screen('unsortedwarning', {
|
||||
'BASE': ('copy', 0, 0, 0, 'attention'),
|
||||
'message': ('search', 12, 0, 80,
|
||||
' Number of records exceeds sort limit'),
|
||||
})
|
||||
vt.define_screen('thereismore', {
|
||||
'BASE': ('copy', 0, 0, 0, 'showrecord'),
|
||||
'message': ('search', 15, 0, 80,
|
||||
'There is more within this record. Use the arrow keys'),
|
||||
})
|
||||
vt.define_screen('nofurther', {
|
||||
'BASE': ('copy', 0, 0, 0, 'showrecord'),
|
||||
'message': ('search', 17, 0, 80, 'You cannot go further\.'),
|
||||
})
|
||||
vt.define_screen('nofurtherback', {
|
||||
'BASE': ('copy', 0, 0, 0, 'showrecord'),
|
||||
'message': ('search', 17, 0, 80,
|
||||
'You cannot go further back'),
|
||||
})
|
||||
|
||||
|
||||
# Class to implement our user interface.
|
||||
|
||||
class UserInterface:
|
||||
|
||||
def __init__(self):
|
||||
stdwin.setfont('7x14')
|
||||
self.queryform = QueryForm()
|
||||
self.listform = ListForm()
|
||||
self.recordform = RecordForm()
|
||||
self.forms = [self.queryform, self.listform, self.recordform]
|
||||
define_query_fields(self.queryform)
|
||||
self.windows = []
|
||||
for form in self.forms:
|
||||
if form.formheight > 0:
|
||||
form.open()
|
||||
self.windows.append(form.window)
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
for form in self.forms:
|
||||
form.close()
|
||||
|
||||
def dispatch(self, type, window, detail):
|
||||
for form in self.forms:
|
||||
if window == form.window:
|
||||
form.dispatch(type, detail)
|
||||
|
||||
|
||||
def define_query_fields(f):
|
||||
f.define_field('name', 'Name auth./ed.', 1, 60)
|
||||
f.define_field('title', 'Title', 4, 60)
|
||||
f.define_field('shelfmark', 'Shelf mark', 1, 60)
|
||||
f.define_field('class', 'Prim. classif.', 1, 60)
|
||||
f.define_field('series', 'Series', 1, 60)
|
||||
f.define_field('congress', 'Congr. pl./year', 1, 60)
|
||||
f.define_field('type', 'Type', 1, 60)
|
||||
|
||||
|
||||
class QueryForm(Form):
|
||||
|
||||
def __init__(self):
|
||||
Form.__init__(self, 'Query form -- CWI Library')
|
||||
|
||||
def dispatch(self, type, detail):
|
||||
if type == WE_COMMAND and detail == WC_RETURN:
|
||||
print '*** SUBMIT ***'
|
||||
else:
|
||||
Form.dispatch(self, type, detail)
|
||||
|
||||
|
||||
class ListForm(Form):
|
||||
|
||||
def __init__(self):
|
||||
Form.__init__(self, 'Short list -- CWI Library')
|
||||
|
||||
|
||||
class RecordForm(Form):
|
||||
|
||||
def __init__(self):
|
||||
Form.__init__(self, 'Record detail -- CWI Library')
|
||||
|
||||
|
||||
main()
|
|
@ -1,170 +0,0 @@
|
|||
# Fill-out form window
|
||||
|
||||
import stdwin
|
||||
from stdwinevents import *
|
||||
|
||||
|
||||
class Form:
|
||||
|
||||
def __init__(self, title):
|
||||
self.title = title
|
||||
self.window = None
|
||||
self.fields = {}
|
||||
self.fieldnames = []
|
||||
self.formwidth = self.formheight = 0
|
||||
self.focusname = None
|
||||
self.tefocus = None
|
||||
|
||||
def define_field(self, name, label, lines, chars):
|
||||
self.fieldnames.append(name)
|
||||
lh = stdwin.lineheight()
|
||||
cw = stdwin.textwidth('m')
|
||||
left = 20*cw
|
||||
top = self.formheight + 4
|
||||
right = left + chars*cw
|
||||
bottom = top + lines*lh
|
||||
te = None
|
||||
self.fields[name] = (label, left, top, right, bottom, te)
|
||||
self.formheight = bottom + 2
|
||||
self.formwidth = max(self.formwidth, right + 4)
|
||||
|
||||
def open(self):
|
||||
if self.window: return
|
||||
self.formwidth = max(100, self.formwidth)
|
||||
self.formheight = max(50, self.formheight)
|
||||
stdwin.setdefwinsize(self.formwidth, self.formheight)
|
||||
stdwin.setdefscrollbars(0, 0)
|
||||
self.window = stdwin.open(self.title)
|
||||
self.window.setdocsize(self.formwidth, self.formheight)
|
||||
for name in self.fieldnames:
|
||||
label, left, top, right, bottom, te = \
|
||||
self.fields[name]
|
||||
rect = (left, top), (right, bottom)
|
||||
te = self.window.textcreate(rect)
|
||||
te.setactive(0)
|
||||
te.setview(rect)
|
||||
self.fields[name] = \
|
||||
label, left, top, right, bottom, te
|
||||
if self.fieldnames:
|
||||
self.setfocus(self.fieldnames[0])
|
||||
|
||||
def setfocus(self, name):
|
||||
if name <> self.focusname and self.tefocus:
|
||||
self.tefocus.setactive(0)
|
||||
self.focusname = name
|
||||
if self.focusname:
|
||||
self.tefocus = self.fields[self.focusname][-1]
|
||||
self.tefocus.setactive(1)
|
||||
else:
|
||||
self.tefocus = None
|
||||
|
||||
def dispatch(self, type, detail):
|
||||
event = type, self.window, detail
|
||||
if type == WE_NULL:
|
||||
pass
|
||||
elif type == WE_DRAW:
|
||||
self.draw(detail)
|
||||
elif type == WE_MOUSE_DOWN:
|
||||
x, y = detail[0]
|
||||
for name in self.fieldnames:
|
||||
label, left, top, right, bottom, te = \
|
||||
self.fields[name]
|
||||
if left <= x < right and \
|
||||
top <= y < bottom:
|
||||
self.setfocus(name)
|
||||
break
|
||||
else:
|
||||
stdwin.fleep()
|
||||
return
|
||||
if self.tefocus:
|
||||
(left, top), (right, bottom) = \
|
||||
self.tefocus.getrect()
|
||||
if x < left: x = left
|
||||
if x >= right: x = right-1
|
||||
if y < top: y = top
|
||||
if y >= bottom:
|
||||
y = bottom-1
|
||||
x = right-1
|
||||
event = type, self.window, ((x,y),)+detail[1:]
|
||||
if not self.tefocus.event(event):
|
||||
stdwin.fleep()
|
||||
elif type in (WE_MOUSE_MOVE, WE_MOUSE_UP, WE_CHAR):
|
||||
if not self.tefocus or not self.tefocus.event(event):
|
||||
stdwin.fleep()
|
||||
elif type == WE_MOUSE_UP:
|
||||
button = detail[2]
|
||||
if button == 2:
|
||||
self.paste_selection()
|
||||
else:
|
||||
self.make_selection()
|
||||
elif type == WE_COMMAND:
|
||||
if detail in (WC_BACKSPACE, WC_UP, WC_DOWN,
|
||||
WC_LEFT, WC_RIGHT):
|
||||
if not self.tefocus or \
|
||||
not self.tefocus.event(event):
|
||||
stdwin.fleep()
|
||||
elif detail == WC_RETURN:
|
||||
print '*** Submit query'
|
||||
elif detail == WC_TAB:
|
||||
if not self.fields:
|
||||
stdwin.fleep()
|
||||
return
|
||||
if not self.focusname:
|
||||
i = 0
|
||||
else:
|
||||
i = self.fieldnames.index(
|
||||
self.focusname)
|
||||
i = (i+1) % len(self.fieldnames)
|
||||
self.setfocus(self.fieldnames[i])
|
||||
self.tefocus.setfocus(0, 0x7fff)
|
||||
self.make_selection()
|
||||
elif type in (WE_ACTIVATE, WE_DEACTIVATE):
|
||||
pass
|
||||
elif type == WE_LOST_SEL:
|
||||
if self.tefocus:
|
||||
a, b = self.tefocus.getfocus()
|
||||
self.tefocus.setfocus(a, a)
|
||||
else:
|
||||
print 'Form.dispatch(%d, %s)' % (type, `detail`)
|
||||
|
||||
def draw(self, detail):
|
||||
d = self.window.begindrawing()
|
||||
d.cliprect(detail)
|
||||
d.erase(detail)
|
||||
self.drawform(d, detail)
|
||||
d.noclip()
|
||||
d.close()
|
||||
# Stupid textedit objects can't draw with open draw object...
|
||||
self.drawtextedit(detail)
|
||||
|
||||
def drawform(self, d, detail):
|
||||
for name in self.fieldnames:
|
||||
label, left, top, right, bottom, te = self.fields[name]
|
||||
d.text((0, top), label)
|
||||
d.box((left-3, top-2), (right+4, bottom+2))
|
||||
|
||||
def drawtextedit(self, detail):
|
||||
for name in self.fieldnames:
|
||||
label, left, top, right, bottom, te = self.fields[name]
|
||||
te.draw(detail)
|
||||
|
||||
def make_selection(self):
|
||||
s = self.tefocus.getfocustext()
|
||||
if not s:
|
||||
return
|
||||
stdwin.rotatecutbuffers(1)
|
||||
stdwin.setcutbuffer(0, s)
|
||||
if not self.window.setselection(WS_PRIMARY, s):
|
||||
stdwin.fleep()
|
||||
|
||||
def paste_selection(self):
|
||||
if not self.tefocus:
|
||||
stdwin.fleep()
|
||||
return
|
||||
s = stdwin.getselection(WS_PRIMARY)
|
||||
if not s:
|
||||
s = stdwin.getcutbuffer(0)
|
||||
if not s:
|
||||
stdwin.fleep()
|
||||
return
|
||||
self.tefocus.replace(s)
|
|
@ -1,328 +0,0 @@
|
|||
# VT100 terminal emulator.
|
||||
# This is incomplete and slow, but will do for now...
|
||||
# It shouldn't be difficult to extend it to be a more-or-less complete
|
||||
# VT100 emulator. And little bit of profiling could go a long way...
|
||||
|
||||
from array import array
|
||||
import regex
|
||||
import string
|
||||
|
||||
# Tunable parameters
|
||||
DEBUGLEVEL = 1
|
||||
|
||||
# Symbolic constants
|
||||
ESC = '\033'
|
||||
|
||||
|
||||
# VT100 emulation class
|
||||
|
||||
class VT100:
|
||||
|
||||
def __init__(self):
|
||||
self.debuglevel = DEBUGLEVEL
|
||||
# Unchangeable parameters (for now)
|
||||
self.width = 80
|
||||
self.height = 24
|
||||
self.blankline = array('c', ' '*self.width)
|
||||
self.blankattr = array('b', '\0'*self.width)
|
||||
# Set mutable display state
|
||||
self.reset()
|
||||
# Set parser state
|
||||
self.unfinished = ''
|
||||
# Set screen recognition state
|
||||
self.reset_recognizer()
|
||||
|
||||
def msg(self, msg, *args):
|
||||
if self.debuglevel > 0:
|
||||
print 'VT100:', msg%args
|
||||
|
||||
def set_debuglevel(self, debuglevel):
|
||||
self.debuglevel = debuglevel
|
||||
|
||||
def reset(self):
|
||||
self.lines = []
|
||||
self.attrs = []
|
||||
self.fill_bottom()
|
||||
self.x = 0
|
||||
self.y = 0
|
||||
self.curattrs = []
|
||||
|
||||
def show(self):
|
||||
lineno = 0
|
||||
for line in self.lines:
|
||||
lineno = lineno + 1
|
||||
i = len(line)
|
||||
while i > 0 and line[i-1] == ' ': i = i-1
|
||||
print line[:i]
|
||||
print 'CURSOR:', self.x, self.y
|
||||
|
||||
def fill_bottom(self):
|
||||
while len(self.lines) < self.height:
|
||||
self.lines.append(self.blankline[:])
|
||||
self.attrs.append(self.blankattr[:])
|
||||
|
||||
def fill_top(self):
|
||||
while len(self.lines) < self.height:
|
||||
self.lines.insert(0, self.blankline[:])
|
||||
self.attrs.insert(0, self.blankattr[:])
|
||||
|
||||
def clear_all(self):
|
||||
self.lines = []
|
||||
self.attrs = []
|
||||
self.fill_bottom()
|
||||
|
||||
def clear_below(self):
|
||||
del self.lines[self.y:]
|
||||
del self.attrs[self.y:]
|
||||
self.fill_bottom()
|
||||
|
||||
def clear_above(self):
|
||||
del self.lines[:self.y]
|
||||
del self.attrs[:self.y]
|
||||
self.fill_top()
|
||||
|
||||
def send(self, buffer):
|
||||
self.msg('send: unfinished=%s, buffer=%s',
|
||||
`self.unfinished`, `buffer`)
|
||||
self.unfinished = self.unfinished + buffer
|
||||
i = 0
|
||||
n = len(self.unfinished)
|
||||
while i < n:
|
||||
c = self.unfinished[i]
|
||||
i = i+1
|
||||
if c != ESC:
|
||||
self.add_char(c)
|
||||
continue
|
||||
if i >= n:
|
||||
i = i-1
|
||||
break
|
||||
c = self.unfinished[i]
|
||||
i = i+1
|
||||
if c == 'c':
|
||||
self.reset()
|
||||
continue
|
||||
if c <> '[':
|
||||
self.msg('unrecognized: ESC %s', `c`)
|
||||
continue
|
||||
argstr = ''
|
||||
while i < n:
|
||||
c = self.unfinished[i]
|
||||
i = i+1
|
||||
if c not in '0123456789;':
|
||||
break
|
||||
argstr = argstr + c
|
||||
else:
|
||||
i = i - len(argstr) - 2
|
||||
break
|
||||
## self.msg('found ESC [ %s %s' % (`argstr`, `c`))
|
||||
args = string.splitfields(argstr, ';')
|
||||
for j in range(len(args)):
|
||||
s = args[j]
|
||||
while s[:1] == '0': s = s[1:]
|
||||
if s: args[j] = eval(s)
|
||||
else: args[j] = 0
|
||||
p1 = p2 = 0
|
||||
if args: p1 = args[0]
|
||||
if args[1:]: p2 = args[1]
|
||||
if c in '@ABCDH':
|
||||
if not p1: p1 = 1
|
||||
if c in 'H':
|
||||
if not p2: p2 = 1
|
||||
if c == '@':
|
||||
for j in range(p1):
|
||||
self.add_char(' ')
|
||||
elif c == 'A':
|
||||
self.move_by(0, -p1)
|
||||
elif c == 'B':
|
||||
self.move_by(0, p1)
|
||||
elif c == 'C':
|
||||
self.move_by(p1, 0)
|
||||
elif c == 'D':
|
||||
self.move_by(-p1, 0)
|
||||
elif c == 'H':
|
||||
self.move_to(p2-1, p1-1)
|
||||
elif c == 'J':
|
||||
if p1 == 0: self.clear_above()
|
||||
elif p1 == 1: self.clear_below()
|
||||
elif p1 == 2: self.clear_all()
|
||||
else: self.msg('weird ESC [ %d J', p1)
|
||||
elif c == 'K':
|
||||
if p1 == 0: self.erase_right()
|
||||
elif p1 == 1: self.erase_left()
|
||||
elif p1 == 2: self.erase_line()
|
||||
else: self.msg('weird ESC [ %d K', p1)
|
||||
elif c == 'm':
|
||||
if p1 == 0:
|
||||
self.curattrs = []
|
||||
else:
|
||||
if p1 not in self.curattrs:
|
||||
self.curattrs.append(p1)
|
||||
self.curattrs.sort()
|
||||
else:
|
||||
self.msg('unrecognized: ESC [ %s', `argstr+c`)
|
||||
self.unfinished = self.unfinished[i:]
|
||||
|
||||
def add_char(self, c):
|
||||
if c == '\r':
|
||||
self.move_to(0, self.y)
|
||||
return
|
||||
if c in '\n\f\v':
|
||||
self.move_to(self.x, self.y + 1)
|
||||
if self.y >= self.height:
|
||||
self.scroll_up(1)
|
||||
self.move_to(self.x, self.height - 1)
|
||||
return
|
||||
if c == '\b':
|
||||
self.move_by(-1, 0)
|
||||
return
|
||||
if c == '\a':
|
||||
self.msg('BELL')
|
||||
return
|
||||
if c == '\t':
|
||||
self.move_to((self.x+8)/8*8, self.y)
|
||||
return
|
||||
if c == '\0':
|
||||
return
|
||||
if c < ' ' or c > '~':
|
||||
self.msg('ignored control char: %s', `c`)
|
||||
return
|
||||
if self.x >= self.width:
|
||||
self.move_to(0, self.y + 1)
|
||||
if self.y >= self.height:
|
||||
self.scroll_up(1)
|
||||
self.move_to(self.x, self.height - 1)
|
||||
self.lines[self.y][self.x] = c
|
||||
if self.curattrs:
|
||||
self.attrs[self.y][self.x] = max(self.curattrs)
|
||||
else:
|
||||
self.attrs[self.y][self.x] = 0
|
||||
self.move_by(1, 0)
|
||||
|
||||
def move_to(self, x, y):
|
||||
self.x = min(max(0, x), self.width)
|
||||
self.y = min(max(0, y), self.height)
|
||||
|
||||
def move_by(self, dx, dy):
|
||||
self.move_to(self.x + dx, self.y + dy)
|
||||
|
||||
def scroll_up(self, nlines):
|
||||
del self.lines[:max(0, nlines)]
|
||||
del self.attrs[:max(0, nlines)]
|
||||
self.fill_bottom()
|
||||
|
||||
def scroll_down(self, nlines):
|
||||
del self.lines[-max(0, nlines):]
|
||||
del self.attrs[-max(0, nlines):]
|
||||
self.fill_top()
|
||||
|
||||
def erase_left(self):
|
||||
x = min(self.width-1, x)
|
||||
y = min(self.height-1, y)
|
||||
self.lines[y][:x] = self.blankline[:x]
|
||||
self.attrs[y][:x] = self.blankattr[:x]
|
||||
|
||||
def erase_right(self):
|
||||
x = min(self.width-1, x)
|
||||
y = min(self.height-1, y)
|
||||
self.lines[y][x:] = self.blankline[x:]
|
||||
self.attrs[y][x:] = self.blankattr[x:]
|
||||
|
||||
def erase_line(self):
|
||||
self.lines[y][:] = self.blankline
|
||||
self.attrs[y][:] = self.blankattr
|
||||
|
||||
# The following routines help automating the recognition of
|
||||
# standard screens. A standard screen is characterized by
|
||||
# a number of fields. A field is part of a line,
|
||||
# characterized by a (lineno, begin, end) tuple;
|
||||
# e.g. the first 10 characters of the second line are
|
||||
# specified by the tuple (1, 0, 10). Fields can be:
|
||||
# - regex: desired contents given by a regular expression,
|
||||
# - extract: can be extracted,
|
||||
# - cursor: screen is only valid if cursor in field,
|
||||
# - copy: identical to another screen (position is ignored).
|
||||
# A screen is defined as a dictionary full of fields. Screens
|
||||
# also have names and are placed in a dictionary.
|
||||
|
||||
def reset_recognizer(self):
|
||||
self.screens = {}
|
||||
|
||||
def define_screen(self, screenname, fields):
|
||||
fieldscopy = {}
|
||||
# Check if the fields make sense
|
||||
for fieldname in fields.keys():
|
||||
field = fields[fieldname]
|
||||
ftype, lineno, begin, end, extra = field
|
||||
if ftype in ('match', 'search'):
|
||||
extra = regex.compile(extra)
|
||||
elif ftype == 'extract':
|
||||
extra = None
|
||||
elif ftype == 'cursor':
|
||||
extra = None
|
||||
elif ftype == 'copy':
|
||||
if not self.screens.has_key(extra):
|
||||
raise ValueError, 'bad copy ref'
|
||||
else:
|
||||
raise ValueError, 'bad ftype: %s' % `ftype`
|
||||
fieldscopy[fieldname] = (
|
||||
ftype, lineno, begin, end, extra)
|
||||
self.screens[screenname] = fieldscopy
|
||||
|
||||
def which_screens(self):
|
||||
self.busy = []
|
||||
self.okay = []
|
||||
self.fail = []
|
||||
for name in self.screens.keys():
|
||||
ok = self.match_screen(name)
|
||||
return self.okay[:]
|
||||
|
||||
def match_screen(self, name):
|
||||
if name in self.busy: raise RuntimeError, 'recursive match'
|
||||
if name in self.okay: return 1
|
||||
if name in self.fail: return 0
|
||||
self.busy.append(name)
|
||||
fields = self.screens[name]
|
||||
ok = 0
|
||||
for key in fields.keys():
|
||||
field = fields[key]
|
||||
ftype, lineno, begin, end, extra = field
|
||||
if ftype == 'copy':
|
||||
if not self.match_screen(extra): break
|
||||
elif ftype == 'search':
|
||||
text = self.lines[lineno][begin:end].tostring()
|
||||
if extra.search(text) < 0:
|
||||
break
|
||||
elif ftype == 'match':
|
||||
text = self.lines[lineno][begin:end].tostring()
|
||||
if extra.match(text) < 0:
|
||||
break
|
||||
elif ftype == 'cursor':
|
||||
if self.x != lineno or not \
|
||||
begin <= self.y < end:
|
||||
break
|
||||
else:
|
||||
ok = 1
|
||||
if ok:
|
||||
self.okay.append(name)
|
||||
else:
|
||||
self.fail.append(name)
|
||||
self.busy.remove(name)
|
||||
return ok
|
||||
|
||||
def extract_field(self, screenname, fieldname):
|
||||
ftype, lineno, begin, end, extra = \
|
||||
self.screens[screenname][fieldname]
|
||||
return stripright(self.lines[lineno][begin:end].tostring())
|
||||
|
||||
def extract_rect(self, left, top, right, bottom):
|
||||
lines = []
|
||||
for i in range(top, bottom):
|
||||
lines.append(stripright(self.lines[i][left:right])
|
||||
.tostring())
|
||||
return lines
|
||||
|
||||
|
||||
def stripright(line):
|
||||
i = len(line)
|
||||
while i > 0 and line[i-1] in string.whitespace: i = i-1
|
||||
return line[:i]
|
|
@ -1,78 +0,0 @@
|
|||
# VT100 terminal emulator in a STDWIN window.
|
||||
|
||||
import stdwin
|
||||
from stdwinevents import *
|
||||
from vt100 import VT100
|
||||
|
||||
class VT100win(VT100):
|
||||
|
||||
def __init__(self):
|
||||
VT100.__init__(self)
|
||||
self.window = None
|
||||
self.last_x = -1
|
||||
self.last_y = -1
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def open(self, title):
|
||||
stdwin.setfont('7x14')
|
||||
self.charwidth = stdwin.textwidth('m')
|
||||
self.lineheight = stdwin.lineheight()
|
||||
self.docwidth = self.width * self.charwidth
|
||||
self.docheight = self.height * self.lineheight
|
||||
stdwin.setdefwinsize(self.docwidth + 2, self.docheight + 2)
|
||||
stdwin.setdefscrollbars(0, 0)
|
||||
self.window = stdwin.open(title)
|
||||
self.window.setdocsize(self.docwidth + 2, self.docheight + 2)
|
||||
|
||||
def close(self):
|
||||
if self.window:
|
||||
self.window.close()
|
||||
self.window = None
|
||||
|
||||
def show(self):
|
||||
if not self.window: return
|
||||
self.window.change(((-10, -10),
|
||||
(self.docwidth+10, self.docheight+10)))
|
||||
|
||||
def draw(self, detail):
|
||||
d = self.window.begindrawing()
|
||||
fg = stdwin.getfgcolor()
|
||||
red = stdwin.fetchcolor('red')
|
||||
d.cliprect(detail)
|
||||
d.erase(detail)
|
||||
lh = self.lineheight
|
||||
cw = self.charwidth
|
||||
for y in range(self.height):
|
||||
d.text((0, y*lh), self.lines[y].tostring())
|
||||
if self.attrs[y] <> self.blankattr:
|
||||
for x in range(len(self.attrs[y])):
|
||||
if self.attrs[y][x] == 7:
|
||||
p1 = x*cw, y*lh
|
||||
p2 = (x+1)*cw, (y+1)*lh
|
||||
d.invert((p1, p2))
|
||||
x = self.x * cw
|
||||
y = self.y * lh
|
||||
d.setfgcolor(red)
|
||||
d.invert((x, y), (x+cw, y+lh))
|
||||
d.setfgcolor(fg)
|
||||
d.close()
|
||||
|
||||
def move_to(self, x, y):
|
||||
VT100.move_to(self, x, y)
|
||||
if not self.window: return
|
||||
if self.y != self.last_y:
|
||||
self.window.change((0, self.last_y * self.lineheight),
|
||||
(self.width*self.charwidth,
|
||||
(self.last_y+1) * self.lineheight))
|
||||
self.last_x = self.x
|
||||
self.last_y = y
|
||||
self.window.change((0, self.y * self.lineheight),
|
||||
(self.width*self.charwidth,
|
||||
(self.y+1) * self.lineheight))
|
||||
|
||||
def send(self, str):
|
||||
VT100.send(self, str)
|
||||
## self.show()
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
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.
|
|
@ -1,2 +0,0 @@
|
|||
: ${ARCH}=`arch`
|
||||
exec /ufs/guido/bin/$ARCH/python ib.py ${1+"$@"}
|
|
@ -1,21 +0,0 @@
|
|||
#! /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()
|
|
@ -1,719 +0,0 @@
|
|||
This file documents the ibrowse program. -*-Text-*-
|
||||
The H command of ibrowse goes to the node Help in this file.
|
||||
|
||||
File: ibrowse Node: Top Up: (DIR) Next: Expert
|
||||
|
||||
Ibrowse is a program for reading documentation, which you are using now.
|
||||
** Ibrowse uses the file format of the Emacs Info program, and its
|
||||
** commands are similar, but not identical.
|
||||
|
||||
To learn how to use Ibrowse, type the command "h". It will bring you
|
||||
to a programmed instruction sequence.
|
||||
|
||||
* Menu:
|
||||
|
||||
* Expert:: Advanced Ibrowse commands: c, k, g, s, 1 - 9, arrows.
|
||||
* Add:: Describes how to add new nodes to the hierarchy.
|
||||
Also tells what nodes look like.
|
||||
* Menus:: How to add to or create menus in Info nodes.
|
||||
* Cross-refs:: How to add cross-references to Info nodes.
|
||||
* Tags:: How to make tag tables for Info files.
|
||||
* Checking:: How to check the consistency of an Info file.
|
||||
* Texinfo: (texinfo).
|
||||
How to generate an Info file and a printed manual
|
||||
from the same source file.
|
||||
|
||||
File: ibrowse Node: Summary Next: Help
|
||||
|
||||
Ibrowse is a Python program for browsing through the Emacs Info
|
||||
documentation tree. Documentation in Info is divided into "nodes",
|
||||
each of which discusses one topic and contains references to other
|
||||
nodes which discuss related topics. Ibrowse has commands to follow the
|
||||
references and show you other nodes.
|
||||
|
||||
h Invoke the Ibrowse tutorial.
|
||||
? Display this Summary node.
|
||||
q Quit Ibrowse.
|
||||
w Close current window.
|
||||
|
||||
Selecting other nodes:
|
||||
n Move to the "next" node of this node.
|
||||
p Move to the "previous" node of this node.
|
||||
m Pick menu item specified by name (or abbreviation).
|
||||
1-9 Pick first..ninth in node's menu.
|
||||
Menu items select nodes that are "subsections" of this node.
|
||||
u Move "up" from this node (i.e., from a subsection to a section).
|
||||
f Follow a cross reference by name (or abbrev). Type `l' to get back.
|
||||
l Move back to the last node you were in.
|
||||
|
||||
Moving within a node:
|
||||
Space Scroll forward a full screen. DEL, BS Scroll backward.
|
||||
b Go to beginning of node.
|
||||
|
||||
Advanced commands:
|
||||
k Clone current window (create an independent duplicate).
|
||||
c Copy text selection to clipboard (for paste in another application).
|
||||
g Move to node specified by name.
|
||||
You may include a filename as well, as (FILENAME)NODENAME.
|
||||
d Go to the main directory of Info files.
|
||||
t Go to Top node of this file.
|
||||
s Search through this Info file for node with specified regexp.
|
||||
|
||||
File: ibrowse Node: Help-Small-Screen Next: Help
|
||||
|
||||
Since your terminal has an unusually small number of lines on its
|
||||
screen, it is necessary to give you special advice at the beginning.
|
||||
|
||||
If you see the text "--All----" at near the bottom right corner of
|
||||
the screen, it means the entire text you are looking at fits on the
|
||||
screen. If you see "--Top----" instead, it means that there is more
|
||||
text below that does not fit. To move forward through the text and
|
||||
see another screen full, press the Space bar. To move back up, press
|
||||
the key labeled Rubout or Delete or DEL.
|
||||
|
||||
Here are 40 lines of junk, so you can try Spaces and Rubout and
|
||||
see what they do. At the end are instructions of what you should do
|
||||
next.
|
||||
|
||||
This is line 17
|
||||
This is line 18
|
||||
This is line 19
|
||||
This is line 20
|
||||
This is line 21
|
||||
This is line 22
|
||||
This is line 23
|
||||
This is line 24
|
||||
This is line 25
|
||||
This is line 26
|
||||
This is line 27
|
||||
This is line 28
|
||||
This is line 29
|
||||
This is line 30
|
||||
This is line 31
|
||||
This is line 32
|
||||
This is line 33
|
||||
This is line 34
|
||||
This is line 35
|
||||
This is line 36
|
||||
This is line 37
|
||||
This is line 38
|
||||
This is line 39
|
||||
This is line 40
|
||||
This is line 41
|
||||
This is line 42
|
||||
This is line 43
|
||||
This is line 44
|
||||
This is line 45
|
||||
This is line 46
|
||||
This is line 47
|
||||
This is line 48
|
||||
This is line 49
|
||||
This is line 50
|
||||
This is line 51
|
||||
This is line 52
|
||||
This is line 53
|
||||
This is line 54
|
||||
This is line 55
|
||||
This is line 56
|
||||
|
||||
If you have managed to get here, go back to the beginning with
|
||||
Rubout, and come back here again, then you understand Space and
|
||||
Rubout. So now type an "n"--just one character; don't type the
|
||||
quotes and don't type a Return afterward-- to get to the normal start
|
||||
of the course.
|
||||
|
||||
File: ibrowse Node: Help Next: Help-P Previous: Help-Small-Screen
|
||||
|
||||
You are talking to the program Ibrowse, for reading documentation.
|
||||
|
||||
Right now you are looking at one "Node" of Information.
|
||||
A node contains text describing a specific topic at a specific
|
||||
level of detail. This node's topic is "how to use Ibrowse".
|
||||
|
||||
The top line of a node is its "header". This node's header (look at
|
||||
it now) says that it is the node named "Help" in the file "ibrowse".
|
||||
It says that the Next node after this one is the node called "Help-P".
|
||||
An advanced Ibrowse command lets you go to any node whose name you know.
|
||||
|
||||
Besides a "Next", a node can have a "Previous" or an "Up".
|
||||
This node has a "Previous" but no "Up", as you can see.
|
||||
|
||||
Now it's time to move on to the Next node, named "Help-P".
|
||||
|
||||
>> Type "n" to move there. Type just one character;
|
||||
don't type the quotes and don't type a Return afterward.
|
||||
|
||||
">>" in the margin means it is really time to try a command.
|
||||
|
||||
File: ibrowse Node: Help-P Next: Help-Page Previous: Help
|
||||
|
||||
This node is called "Help-P". The "Previous" node, as you see, is
|
||||
"Help", which is the one you just came from using the "N" command.
|
||||
Another "N" command now would take you to the Next node, "Help-Page".
|
||||
|
||||
>> But don't do that yet. First, try the "p" command, which takes
|
||||
you to the Previous node. When you get there, you can do an "n"
|
||||
again to return here.
|
||||
|
||||
This all probably seems insultingly simple so far, but DON'T be
|
||||
led into skimming. Things will get more complicated soon. Also,
|
||||
don't try a new command until you are told it's time to. Otherwise,
|
||||
you may make Ibrowse skip past an important warning that was coming up.
|
||||
|
||||
>> Now do an "n" to get to the node "Help-Page" and learn more.
|
||||
|
||||
File: ibrowse Node: Help-Page Next: Help-M Previous: Help-P
|
||||
|
||||
Space, Backspace, and B commands.
|
||||
|
||||
This node's header tells you that you are now at node "Help-Page", and
|
||||
that "P" would get you back to "Help-P". The line starting "Space,"
|
||||
is a "Title", saying what the node is about (most nodes have titles).
|
||||
|
||||
This is a big node and it doesn't all fit on your display screen.
|
||||
You can tell that there is more that isn't visible because you
|
||||
the scroll bar on the side of the window has become active (gray).
|
||||
|
||||
The Space, Backspace and B commands exist to allow you to "move
|
||||
around" in a node that doesn't all fit on the screen at once.
|
||||
Space moves forward, to show what was below the bottom of the screen.
|
||||
Backspace moves backward, to show what was above the top of the screen
|
||||
(there isn't anything above the top until you have typed some spaces).
|
||||
|
||||
>> Now try typing a Space (afterward, type a Backspace to return here).
|
||||
|
||||
When you type the space, the two lines that were at the bottom of the
|
||||
screen appear at the top, followed by more lines. Backspace takes the
|
||||
two lines from the top and moves them to the bottom, USUALLY, but if
|
||||
there are not a full screen's worth of lines above them they may not
|
||||
make it all the way to the bottom.
|
||||
|
||||
If you type a Space when there is no more to see, it will ring the
|
||||
bell and otherwise do nothing. The same goes for a Backspace when
|
||||
the header of the node is visible.
|
||||
|
||||
Of course you can use the mouse and directly move the scroll bar
|
||||
as well, but Ibrowse has keyboard commands for almost everything,
|
||||
including scrolling. These keyboard commands are called "shortcuts",
|
||||
because it generally takes less effort to press a key on the
|
||||
keyboard than to move the mouse. On the other hand, if you are
|
||||
an infrequent user of Ibrowse, you can do everything with the
|
||||
mouse that you can do with the keyboard. Just look in the menus
|
||||
(I'm sure you must know how to use the menus on this system, or
|
||||
else you couldn't have gotten this far...). In fact you'll see that
|
||||
the commands and shortcuts listed in the menus are the same as those
|
||||
described in this course. You can use the shortcuts either with or
|
||||
without the "Command" or "Meta" key.
|
||||
|
||||
Two menus are always available: the "Ibrowse" menu contains commands
|
||||
pertaining to the Ibrowse program at large, while the "Navigation" menu
|
||||
contains commands that move around between nodes. There may be other
|
||||
menus; these will be explained later.
|
||||
|
||||
To move back to the beginning of the node you are on, you can type
|
||||
a lot of Backspaces. You can also type simply "b" for beginning.
|
||||
>> Try that now. (I have put in enough verbiage to make sure you are
|
||||
not on the first screenful now). Then come back, with Spaces.
|
||||
|
||||
You have just learned a considerable number of commands. If you
|
||||
want to use one but have trouble remembering which, just pull down
|
||||
the menus to get a summary of commands and shortcuts. Some additional
|
||||
shortcuts (not listed in the menus) are listed by the "Short help"
|
||||
command. This brings up a dialog box which you can acknowledge
|
||||
by clicking the OK button or pressing the Return key.
|
||||
|
||||
From now on, you will encounter large nodes without warning, and
|
||||
will be expected to know how to use Space and Backspace to move
|
||||
around in them without being told. Since you could change the
|
||||
size of the window used, it would be impossible to warn you anyway.
|
||||
|
||||
>> Now type "n" to see the description of the "m" command.
|
||||
|
||||
File: ibrowse Node: Help-M Next: Help-Adv Previous: Help-Page
|
||||
|
||||
Menus and the "m" command
|
||||
|
||||
With only the "n" and "p" commands for moving between nodes, nodes
|
||||
are restricted to a linear sequence. Menus allow a branching
|
||||
structure. A menu is a list of other nodes you can move to. It is
|
||||
actually just part of the text of the node formatted specially so that
|
||||
Ibrowse can interpret it. The beginning of a menu is always identified
|
||||
by a line which starts with "* Menu:". A node contains a menu if and
|
||||
only if it has a line in it which starts that way. The only menu you
|
||||
can use at any moment is the one in the node you are in. To use a
|
||||
menu in any other node, you must move to that node first.
|
||||
|
||||
(There is an unfortunate confusion of terms here. "Menu" may refer
|
||||
to one of the Ibrowse menus at the top, such as as the "Ibrowse" and
|
||||
"Navigation" menus explained in the previous node, or to the menu in
|
||||
a node. Where confusion is possible, these will be disambiguated by
|
||||
calling them "Ibrowse menus" or "node menu".)
|
||||
|
||||
After the start of the menu, each line that starts with a "*"
|
||||
identifies one subtopic. The line will usually contain a brief name
|
||||
for the subtopic (followed by a ":"), the name of the node that talks
|
||||
about that subtopic, and optionally some further description of the
|
||||
subtopic. Lines in the menu that don't start with a "*" have no
|
||||
special meaning - they are only for the human reader's benefit and do
|
||||
not define additional subtopics. Here is an example:
|
||||
* Foo: FOO's Node This tells about FOO
|
||||
The subtopic name is Foo, and the node describing it is "FOO's Node".
|
||||
The rest of the line is just for the reader's Information.
|
||||
[[ But this line is not a real menu item, simply because there is
|
||||
no line above it which starts with "* Menu:".]]
|
||||
|
||||
When you use a menu to go to another node (in a way that will be
|
||||
described soon), what you specify is the subtopic name, the first
|
||||
thing in the menu line. Ibrowse uses it to find the menu line, extracts
|
||||
the node name from it, and goes to that node. The reason that there
|
||||
is both a subtopic name and a node name is that the node name must be
|
||||
meaningful to the computer and may therefore have to be ugly looking.
|
||||
The subtopic name can be chosen just to be convenient for the user to
|
||||
specify. Often the node name is convenient for the user to specify
|
||||
and so both it and the subtopic name are the same. There is an
|
||||
abbreviation for this:
|
||||
* Foo:: This tells about FOO
|
||||
This means that the subtopic name and node name are the same; they are
|
||||
both "Foo".
|
||||
|
||||
>> Now use Spaces to find the menu in this node, then come back to
|
||||
the front with a "b". As you see, a menu is actually visible
|
||||
in its node. If you can't find a menu in a node by looking at it,
|
||||
then the node doesn't have a menu and the "m" command is not available.
|
||||
|
||||
(Actually, a quicker way to see if there is a node menu, is to look
|
||||
for an Ibrowse menu at the top named "Menu".)
|
||||
|
||||
The command to go to one of the subnodes is "m" - but DON'T DO IT
|
||||
YET! Before you use "m", you must understand the difference between
|
||||
commands and arguments. So far, you have learned several commands
|
||||
that do not need arguments. When you type one, Ibrowse processes it and
|
||||
is instantly ready for another command. The "m" command is different:
|
||||
it is incomplete without the NAME OF THE SUBTOPIC. Once you have
|
||||
typed "m", Ibrowse wants to read the subtopic name.
|
||||
|
||||
Thanks to modern user interface technology, this will be obvious:
|
||||
you are prompted for the subtopic name in a dialog box. When you are
|
||||
finished typing the name, press Return or click the OK button. You can
|
||||
cancel the dialog box by clicking the Cancel button. The first subtopic
|
||||
is provided as a default choice, so if you want to go there, you can
|
||||
just press Return.
|
||||
|
||||
You can abbreviate the subtopic name. If the abbreviation is not
|
||||
unique, the first matching subtopic is chosen. Some menus will put
|
||||
the shortest possible abbreviation for each subtopic name in capital
|
||||
letters, so you can see how much you need to type. It does not
|
||||
matter whether you use upper case or lower case when you type the
|
||||
subtopic. You should not put any spaces at the end, or inside of the
|
||||
item name, except for one space where a space appears in the item in
|
||||
the menu.
|
||||
|
||||
Here is a menu to give you a chance to practice.
|
||||
|
||||
* Menu: The menu starts here.
|
||||
|
||||
This menu gives you three ways of going to one place, Help-FOO.
|
||||
|
||||
* Foo: Help-FOO A node you can visit for fun
|
||||
* Bar: Help-FOO Strange! two ways to get to the same place.
|
||||
* Help-FOO:: And yet another!
|
||||
|
||||
>> Now type just an "m" and see what happens. (Read ahead before
|
||||
>> trying this out, as the dialog box will probably cover these
|
||||
>> instructions!)
|
||||
|
||||
Now you are "inside" an "m" command. Commands can't be used now;
|
||||
the next thing you will type must be the name of a subtopic.
|
||||
|
||||
You can change your mind about doing the "m" by clicking the Cancel
|
||||
button.
|
||||
>> Try that now; notice the dialog box disappear.
|
||||
>> Then type another "m".
|
||||
|
||||
>> Now type "BAR", the item name. Don't type Return yet.
|
||||
|
||||
While you are typing the item name, you can use the Backspace
|
||||
key to cancel one character at a time if you make a mistake.
|
||||
>> Type one to cancel the "R". You could type another "R" to
|
||||
replace it. You don't have to, since "BA" is a valid abbreviation.
|
||||
>> Now you are ready to go. Type a Return.
|
||||
|
||||
After visiting Help-FOO, you should return here (it will tell how).
|
||||
|
||||
>> Type "n" to see more commands.
|
||||
|
||||
File: ibrowse Node: Help-FOO Up: Help-M
|
||||
|
||||
The "u" command
|
||||
|
||||
Congratulations! This is the node Help-FOO. Unlike the other
|
||||
nodes you have seen, this one has an "Up": "Help-M", the node you
|
||||
just came from via the "m" command. This is the usual convention--
|
||||
the nodes you reach from a menu have Ups that lead back to the menu.
|
||||
Menus move Down in the tree, and Up moves Up. Previous, on the other
|
||||
hand, is usually used to "stay on the same level but go backwards".
|
||||
|
||||
You can go back to the node Help-M by typing the command
|
||||
"u" for "Up". That will put you at the FRONT of the node - to get
|
||||
back to where you were reading you will have to type some Spaces.
|
||||
|
||||
>> Now type "u" to move back up to Help-M.
|
||||
|
||||
File: ibrowse Node: Help-Adv Next: Help-Q Previous: Help-M
|
||||
|
||||
Some advanced Ibrowse commands
|
||||
|
||||
The course is almost over, so please stick with it to the end.
|
||||
|
||||
If you have been moving around to different nodes and wish to
|
||||
retrace your steps, the "l" command ("l" for "last") will do that, one
|
||||
node at a time. If you have been following directions, an "l" command
|
||||
now will get you back to Help-M. Another "l" command would undo the "u"
|
||||
and get you back to Help-FOO. Another "l" would undo the M and get you
|
||||
back to Help-M.
|
||||
|
||||
>> Try typing three "l"'s, pausing in between to see what each "l" does.
|
||||
Then follow directions again and you will end up back here.
|
||||
|
||||
Note the difference between "l" and "p": "l" moves to where YOU
|
||||
last were, whereas "p" always moves to the node which the header says
|
||||
is the "Previous" node (from this node, to Help-M).
|
||||
|
||||
The "d" command gets you instantly to the Directory node.
|
||||
This node, which is the first one you saw when you entered Ibrowse,
|
||||
has a menu which leads (directly, or indirectly through other menus),
|
||||
to all the nodes that exist.
|
||||
|
||||
>> Try doing a "d", then do an "l" to return here (yes, DO return).
|
||||
|
||||
Sometimes, in Ibrowse documentation, you will see a cross reference.
|
||||
Cross references look like this: *Note Cross: Help-Cross. That is a
|
||||
real, live cross reference which is named "Cross" and points at the
|
||||
node named "Help-Cross".
|
||||
|
||||
If you wish to follow a cross reference, you must use the "f"
|
||||
command. The "f" prompts for the cross reference name (in this case,
|
||||
"Cross") with a dialog box.
|
||||
|
||||
>> Type "f", followed by "Cross", and a Return.
|
||||
|
||||
The "f" command allows abbreviations just like "m".
|
||||
|
||||
To get a list of all the cross references in the current node,
|
||||
look in the Ibrowse menu at the top labeled "Footnotes". This menu is
|
||||
only present if there are cross references in the current node, and
|
||||
can be used to directly follow a cross reference, just like the "Menu"
|
||||
menu is another way to choose an item of the node's menu.
|
||||
|
||||
>> Now type "n" to see the last node of the course.
|
||||
|
||||
File: ibrowse Node: Help-Cross
|
||||
|
||||
This is the node reached by the cross reference named "Cross".
|
||||
|
||||
While this node is specifically intended to be reached by a cross
|
||||
reference, most cross references lead to nodes that "belong" someplace
|
||||
else far away in the structure of Ibrowse. So you can't expect the
|
||||
footnote to have a Next, Previous or Up pointing back to where you
|
||||
came from. In general, the "l" (el) command is the only way to get
|
||||
back there.
|
||||
|
||||
>> Type "l" to return to the node where the cross reference was.
|
||||
|
||||
File: ibrowse Node: Help-Q Previous: Help-Adv Up: Top
|
||||
|
||||
To get out of Ibrowse, type "q" for "Quit". All Ibrowse windows
|
||||
will be closed (on UNIX, only those managed by the same process).
|
||||
To close just one window, use the standard method of closing windows
|
||||
on your system; you can also use "w".
|
||||
|
||||
This is the end of the course on using Ibrowse. There are some other
|
||||
commands that are not essential or meant for experienced users; they
|
||||
are useful, and you can find them by looking in the directory for
|
||||
documentation on Ibrowse. Finding them will be a good exercise in using
|
||||
Ibrowse in the usual manner.
|
||||
|
||||
>> Close this window and find back the window where you typed "h"
|
||||
to enter this tutorial.
|
||||
Then type "d" to go to the Ibrowse directory node if necessary,
|
||||
and choose the "Ibrowse" menu item, to get to the node about
|
||||
Ibrowse and see what other help is available.
|
||||
|
||||
File: ibrowse, Node: Expert, Up: Top, Previous: Top, Next: Add
|
||||
|
||||
Some Advanced Ibrowse Commands ("c", "k", "g", "s", "1" - "9", arrows).
|
||||
|
||||
The "c" command lets you copy text from the window to the clipboard.
|
||||
You must first select the text to be copied with the mouse.
|
||||
|
||||
The "k" command means "klone" (we are running out of letters now...).
|
||||
It creates a new Ibrowse window, showing the same node as the current.
|
||||
You can then make an excursion in the new window to different nodes or
|
||||
files, while the old window keeps showing the original node. Each
|
||||
window has its own history for use by the "l" command.
|
||||
|
||||
If you know a node's name, you can go there with the "g" command.
|
||||
This prompts for a node name with a dialog box. Entering, "Top"
|
||||
would go to the node called Top in this file (its directory node).
|
||||
Pressing "g" again and entering "Expert" would come back here.
|
||||
|
||||
Unlike "m", "g" does not allow the use of abbreviations.
|
||||
|
||||
To go to a node in another file, you can include the filename in the
|
||||
node name by putting it at the front, in parentheses. Thus,
|
||||
"(dir)Top" would go to the Ibrowse Directory node, which is
|
||||
node Top in the file dir.
|
||||
|
||||
The node name "*" specifies the whole file. So you can look at all
|
||||
of the current file by typing "*" or all of any other file
|
||||
with "(FILENAME)*".
|
||||
|
||||
File names are converted to lower case before they are tried; this
|
||||
is necessary to be compatible with Emacs Info. (File names are
|
||||
generally relative to the Info directory, but needn't be.)
|
||||
|
||||
The "s" command allows you to search a whole file for a regular
|
||||
expression. Unlike the corresponding Emacs Info command, it will
|
||||
not search beyond the end of the current node.
|
||||
|
||||
Regular expressions are like in UNIX egrep; if you don't know what
|
||||
regular expressions are, limit your search strings to letters, digits
|
||||
and spaces. Searches in Ibrowse are case-sensitive; searching for
|
||||
"foo" will not find "Foo" or "FOO"!
|
||||
|
||||
A description of regular expressions as they occur in Emacs is
|
||||
available. (*Note Emacs Regular Expressions: (regex)syntax.)
|
||||
Ibrowse regular expressions are slightly different: the meaning
|
||||
of \( \| \) is swapped with that of ( | ), and there are no
|
||||
escapes to handle "words" specially.
|
||||
|
||||
Searching starts after the current focus position. The "B" command
|
||||
resets the focus to the beginning of the file, but space and backspace
|
||||
leave it unchanged (so they may render the focus invisible).
|
||||
|
||||
If you grudge the system each character of type-in it requires,
|
||||
you might like to use the commands "1", "2", "3", through "9".
|
||||
They are short for the first nine entries of the node menu.
|
||||
|
||||
The left, right and up arrow keys are duplicates of "p", "n" and "u".
|
||||
|
||||
The down arrow key, as well as the Return key, goes to the first item
|
||||
of the node's menu if there is one, else it executes "n". This is a
|
||||
quick way to visit all nodes in a tree in pre-order: use Return to go
|
||||
down and right as far as possible, then use "u" and "n" to go right
|
||||
at the next higher level.
|
||||
|
||||
File: ibrowse, Node: Add, Up: Top, Previous: Expert, Next: Menus
|
||||
|
||||
To add a new topic to the list in the directory, you must
|
||||
1) enter the Emacs text editor. *Note Emacs: (emacs).
|
||||
2) create a node, in some file, to document that topic.
|
||||
3) put that topic in the menu in the directory. *Note Menu: Menus.
|
||||
|
||||
The new node can live in an existing documentation file, or in a new
|
||||
one. It must have a ^_ character before it (invisible to the user;
|
||||
this node has one but you can't see it), and it ends with either a ^_,
|
||||
or the end of file. A nice way to make a node boundary be a
|
||||
page boundary as well is to put a ^L RIGHT AFTER the ^_.
|
||||
|
||||
The ^_ starting a node must be followed by a newline or a ^L newline,
|
||||
after which comes the node's header line. The header line must give
|
||||
the node's name (by which Ibrowse will find it), and state the names of
|
||||
the Next, Previous, and Up nodes (if there are any). As you can see,
|
||||
this node's Up node is the node Top, which points at all the
|
||||
documentation for Ibrowse. The Next node is "Menus".
|
||||
|
||||
The keywords "Node", "Previous", "Up" and "Next", may appear in
|
||||
any order, anywhere in the header line, but the recommended order is
|
||||
the one in this sentence. Each keyword must be followed by a colon,
|
||||
spaces and tabs, and then the appropriate name. The name may be
|
||||
terminated with a tab, a comma, or a newline. A space does not end
|
||||
it; node names may contain spaces. The case of letters in the names
|
||||
is insignificant. "Previous" can be abbreviated to "Prev".
|
||||
|
||||
A node name has two forms. A node in the current file is named by
|
||||
what appears after the "Node: " in that node's first line. For
|
||||
example, this node's name is "Add". A node in another file is named
|
||||
by "(FILENAME)NODE-WITHIN-FILE", as in "(ibrowse)Add" for this node.
|
||||
If the file name is relative, it is taken starting from the standard
|
||||
Info file directory of your site. The name "(FILENAME)Top" can be
|
||||
abbreviated to just "(FILENAME)". By convention, the name "Top" is
|
||||
used for the "highest" node in any single file - the node whose "Up"
|
||||
points out of the file. The Directory node is "(dir)". The Top node
|
||||
of a document file listed in the Directory should have an "Up: (dir)"
|
||||
in it.
|
||||
|
||||
The node name "*" is special: it refers to the entire file. Thus,
|
||||
g* will show you the whole current file. The use of the node * is to
|
||||
make it possible to make old-fashioned, unstructured files into nodes
|
||||
of the tree. Footnotes and node menus appearing in a file are disabled
|
||||
when it is viewed in this way.
|
||||
|
||||
The "Node:" name, in which a node states its own name, must not
|
||||
contain a filename, since Ibrowse when searching for a node does not
|
||||
expect one to be there. The Next, Previous and Up names may contain
|
||||
them. In this node, since the Up node is in the same file, it was not
|
||||
necessary to use one.
|
||||
|
||||
Note that the nodes in this file have a File name in the header
|
||||
line. The File names are ignored by Ibrowse, but they serve as
|
||||
comments to help identify the node for the user.
|
||||
|
||||
File: ibrowse, Node: Menus, Previous: Add, Up: Top, Next: Cross-refs
|
||||
|
||||
How to Create Menus:
|
||||
|
||||
Any node in the Ibrowse hierarchy may have a MENU--a list of subnodes.
|
||||
The "m" command searches the current node's menu for the topic which it
|
||||
reads from the terminal.
|
||||
|
||||
A menu begins with a line starting with "* Menu:". The rest of the
|
||||
line is a comment. After the starting line, every line that begins
|
||||
with a "* " lists a single topic. The name of the topic--the arg
|
||||
that the user must give to the "m" command to select this topic--
|
||||
comes right after the star and space, and is followed by
|
||||
a colon, spaces and tabs, and the name of the node which discusses
|
||||
that topic. The node name, like node names following Next,
|
||||
Previous and Up, may be terminated with a tab, comma, or newline;
|
||||
it may also be terminated with a period.
|
||||
|
||||
If the node name and topic name are the same, than rather than
|
||||
giving the name twice, the abbreviation "* NAME::" may be used
|
||||
(and should be used, whenever possible, as it reduces the visual
|
||||
clutter in the menu).
|
||||
|
||||
It is considerate to choose the topic names so that they differ
|
||||
from each other very near the beginning--this allows the user to type
|
||||
short abbreviations. In a long menu, it is a good idea to capitalize
|
||||
the beginning of each item name which is the minimum acceptable
|
||||
abbreviation for it (a long menu is more than 5 or so entries).
|
||||
|
||||
The node's listed in a node's menu are called its "subnodes", and
|
||||
it is their "superior". They should each have an "Up:" pointing at
|
||||
the superior. It is often useful to arrange all or most of the
|
||||
subnodes in a sequence of Next's/Previous's so that someone who
|
||||
wants to see them all need not keep revisiting the Menu.
|
||||
|
||||
The Info Directory is simply the menu of the node "(dir)Top"--that
|
||||
is, node Top in file .../info/dir. You can put new entries in that
|
||||
menu just like any other menu. The Info Directory is NOT the same as
|
||||
the file directory called "info". It happens that many of Ibrowse's
|
||||
files live on that file directory, but they don't have to; and files
|
||||
on that directory are not automatically listed in the Info Directory
|
||||
node.
|
||||
|
||||
The Ibrowse program uses a second directory called .../ibrowse,
|
||||
which contains versions of the "dir" and "info" files adapted to
|
||||
Ibrowse (the latter renamed to "ibrowse", obviously). It searches
|
||||
any file first in the "ibrowse", then in the "info" directory.
|
||||
(Actually, the search path is configurable.)
|
||||
|
||||
Also, although the Info node graph is claimed to be a "hierarchy",
|
||||
in fact it can be ANY directed graph. Shared structures and pointer
|
||||
cycles are perfectly possible, and can be used if they are
|
||||
appropriate to the meaning to be expressed. There is no need for all
|
||||
the nodes in a file to form a connected structure. In fact, this
|
||||
file has two connected components. You are in one of them, which is
|
||||
under the node Top; the other contains the node Help which the "h"
|
||||
command goes to. In fact, since there is no garbage collector,
|
||||
nothing terrible happens if a substructure is not pointed to, but
|
||||
such a substructure will be rather useless since nobody will ever
|
||||
find out that it exists.
|
||||
|
||||
File: ibrowse, Node: Cross-refs, Previous: Menus, Up: Top, Next: Tags
|
||||
|
||||
Creating Cross References:
|
||||
|
||||
A cross reference can be placed anywhere in the text, unlike a menu
|
||||
item which must go at the front of a line. A cross reference looks
|
||||
like a menu item except that it has "*note" instead of "*". It CANNOT
|
||||
be terminated by a ")", because ")"'s are so often part of node names.
|
||||
If you wish to enclose a cross reference in parentheses, terminate it
|
||||
with a period first. Here are two examples of cross references pointers:
|
||||
|
||||
*Note details: commands. (See *note 3: Full Proof.)
|
||||
|
||||
They are just examples. The places they "lead to" don't really exist!
|
||||
|
||||
File: ibrowse, Node: Tags, Previous: Cross-refs, Up: Top, Next: Checking
|
||||
|
||||
Tag Tables for Info Files:
|
||||
|
||||
You can speed up the access to nodes of a large Info file by giving
|
||||
it a tag table. Unlike the tag table for a program, the tag table for
|
||||
an Info file lives inside the file itself and will automatically be
|
||||
used whenever Ibrowse reads in the file.
|
||||
|
||||
To make a tag table, go to a node in the file using Emacs Info and type
|
||||
M-x Info-tagify. Then you must use C-x C-s to save the file.
|
||||
|
||||
Once the Info file has a tag table, you must make certain it is up
|
||||
to date. If, as a result of deletion of text, any node moves back
|
||||
more than a thousand characters in the file from the position
|
||||
recorded in the tag table, Ibrowse will no longer be able to find that
|
||||
node. To update the tag table, use the Info-tagify command again.
|
||||
|
||||
An Info file tag table appears at the end of the file and looks like
|
||||
this:
|
||||
|
||||
^_^L
|
||||
Tag Table:
|
||||
File: ibrowse, Node: Cross-refs21419
|
||||
File: ibrowse, Node: Tags22145
|
||||
^_
|
||||
End Tag Table
|
||||
|
||||
Note that it contains one line per node, and this line contains
|
||||
the beginning of the node's header (ending just after the node name),
|
||||
a rubout (DEL) character, and the character position in the file of the
|
||||
beginning of the node. The words "Tag Table" may occur in lower case
|
||||
as well.
|
||||
|
||||
It is also possible for an extra level of indirection to be present.
|
||||
In this case, the first line of the Tag table contains the string
|
||||
"(Indirect)", and preceding the tag table is another "pseudo node"
|
||||
whose header reads "Indirect:". Each following line has the form
|
||||
"filename: offset", meaning that nodes at that offset or larger (but
|
||||
less than the offset in the next line) really occur in the file named
|
||||
here, and that the file's offset should be subtracted from the node's
|
||||
offset. (Indirect tables are created by texinfo for large files.
|
||||
*Note Texinfo: (texinfo). *Note Splitting files: (texinfo)Splitting.)
|
||||
|
||||
File: ibrowse, Node: Checking, Previous: Tags, Up: Top
|
||||
|
||||
Checking an Info File:
|
||||
|
||||
When creating an Info file, it is easy to forget the name of a node
|
||||
when you are making a pointer to it from another node. If you put in
|
||||
the wrong name for a node, this will not be detected until someone
|
||||
tries to go through the pointer using Ibrowse. Verification of the Info
|
||||
file is an automatic process which checks all pointers to nodes and
|
||||
reports any pointers which are invalid. Every Next, Previous, and Up
|
||||
is checked, as is every menu item and every cross reference. In addition,
|
||||
any Next which doesn't have a Previous pointing back is reported.
|
||||
Only pointers within the file are checked, because checking pointers
|
||||
to other files would be terribly slow. But those are usually few.
|
||||
|
||||
To check an Info file, do M-x Info-validate while looking at any
|
||||
node of the file with Emacs Info.
|
||||
|
||||
Tag table:
|
||||
Node: Top117
|
||||
Node: Summary952
|
||||
Node: Help-Small-Screen997
|
||||
Node: Help2628
|
||||
Node: Help-P3588
|
||||
Node: Help-Page4348
|
||||
Node: Help-M7763
|
||||
Node: Help-FOO13183
|
||||
Node: Help-Adv13887
|
||||
Node: Help-Cross15923
|
||||
Node: Help-Q16443
|
||||
Node: Expert17326
|
||||
Node: Add20280
|
||||
Node: Menus23273
|
||||
Node: Cross-refs26394
|
||||
Node: Tags27050
|
||||
Node: Checking28966
|
||||
|
||||
End tag table
|
|
@ -1,617 +0,0 @@
|
|||
# Browser for "Info files" as used by the Emacs documentation 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.)
|
||||
|
||||
|
||||
# NB: this is not a self-executing script. You must startup Python,
|
||||
# import ibrowse, and call ibrowse.main(). On UNIX, the script 'ib'
|
||||
# runs the browser.
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
# This is a fairly large Python program, split in the following modules:
|
||||
#
|
||||
# ibrowse.py Main program and user interface.
|
||||
# This is the only module that imports stdwin.
|
||||
#
|
||||
# ifile.py This module knows about the format of Info files.
|
||||
# It is imported by all of the others.
|
||||
#
|
||||
# itags.py This module knows how to read prebuilt tag tables,
|
||||
# including indirect ones used by large texinfo files.
|
||||
#
|
||||
# icache.py Caches tag tables and visited nodes.
|
||||
|
||||
|
||||
# XXX There should really be a different tutorial, as the user interface
|
||||
# XXX differs considerably from Emacs...
|
||||
|
||||
|
||||
import sys
|
||||
import regexp
|
||||
import stdwin
|
||||
from stdwinevents import *
|
||||
import string
|
||||
from ifile import NoSuchFile, NoSuchNode
|
||||
import icache
|
||||
|
||||
|
||||
# Default font.
|
||||
# This should be an acceptable argument for stdwin.setfont();
|
||||
# on the Mac, this can be a pair (fontname, pointsize), while
|
||||
# under X11 it should be a standard X11 font name.
|
||||
# For best results, use a constant width font like Courier;
|
||||
# many Info files contain tabs that don't align with other text
|
||||
# unless all characters have the same width.
|
||||
#
|
||||
#FONT = ('Monaco', 9) # Mac
|
||||
FONT = '-schumacher-clean-medium-r-normal--14-140-75-75-c-70-iso8859-1' # X11
|
||||
|
||||
|
||||
# Try not to destroy the list of windows when reload() is used.
|
||||
# This is useful during debugging, and harmless in production...
|
||||
#
|
||||
try:
|
||||
dummy = windows
|
||||
del dummy
|
||||
except NameError:
|
||||
windows = []
|
||||
|
||||
|
||||
# Default main function -- start at the '(dir)' node.
|
||||
#
|
||||
def main():
|
||||
start('(dir)')
|
||||
|
||||
|
||||
# Start at an arbitrary node.
|
||||
# The default file is 'ibrowse'.
|
||||
#
|
||||
def start(ref):
|
||||
stdwin.setdefscrollbars(0, 1)
|
||||
stdwin.setfont(FONT)
|
||||
stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight())
|
||||
makewindow('ibrowse', ref)
|
||||
mainloop()
|
||||
|
||||
|
||||
# Open a new browser window.
|
||||
# Arguments specify the default file and a node reference
|
||||
# (if the node reference specifies a file, the default file is ignored).
|
||||
#
|
||||
def makewindow(file, ref):
|
||||
win = stdwin.open('Info file Browser, by Guido van Rossum')
|
||||
win.mainmenu = makemainmenu(win)
|
||||
win.navimenu = makenavimenu(win)
|
||||
win.textobj = win.textcreate((0, 0), win.getwinsize())
|
||||
win.file = file
|
||||
win.node = ''
|
||||
win.last = []
|
||||
win.pat = ''
|
||||
win.dispatch = idispatch
|
||||
win.nodemenu = None
|
||||
win.footmenu = None
|
||||
windows.append(win)
|
||||
imove(win, ref)
|
||||
|
||||
# Create the 'Ibrowse' menu for a new browser window.
|
||||
#
|
||||
def makemainmenu(win):
|
||||
mp = win.menucreate('Ibrowse')
|
||||
mp.callback = []
|
||||
additem(mp, 'New window (clone)', 'K', iclone)
|
||||
additem(mp, 'Help (tutorial)', 'H', itutor)
|
||||
additem(mp, 'Command summary', '?', isummary)
|
||||
additem(mp, 'Close this window', 'W', iclose)
|
||||
additem(mp, '', '', None)
|
||||
additem(mp, 'Copy to clipboard', 'C', icopy)
|
||||
additem(mp, '', '', None)
|
||||
additem(mp, 'Search regexp...', 'S', isearch)
|
||||
additem(mp, '', '', None)
|
||||
additem(mp, 'Reset node cache', '', iresetnodecache)
|
||||
additem(mp, 'Reset entire cache', '', iresetcache)
|
||||
additem(mp, '', '', None)
|
||||
additem(mp, 'Quit', 'Q', iquit)
|
||||
return mp
|
||||
|
||||
# Create the 'Navigation' menu for a new browser window.
|
||||
#
|
||||
def makenavimenu(win):
|
||||
mp = win.menucreate('Navigation')
|
||||
mp.callback = []
|
||||
additem(mp, 'Menu item...', 'M', imenu)
|
||||
additem(mp, 'Follow reference...', 'F', ifollow)
|
||||
additem(mp, 'Go to node...', 'G', igoto)
|
||||
additem(mp, '', '', None)
|
||||
additem(mp, 'Next node in tree', 'N', inext)
|
||||
additem(mp, 'Previous node in tree', 'P', iprev)
|
||||
additem(mp, 'Up in tree', 'U', iup)
|
||||
additem(mp, 'Last visited node', 'L', ilast)
|
||||
additem(mp, 'Top of tree', 'T', itop)
|
||||
additem(mp, 'Directory node', 'D', idir)
|
||||
return mp
|
||||
|
||||
# Add an item to a menu, and a function to its list of callbacks.
|
||||
# (Specifying all in one call is the only way to keep the menu
|
||||
# and the list of callbacks in synchrony.)
|
||||
#
|
||||
def additem(mp, text, shortcut, function):
|
||||
if shortcut:
|
||||
mp.additem(text, shortcut)
|
||||
else:
|
||||
mp.additem(text)
|
||||
mp.callback.append(function)
|
||||
|
||||
|
||||
# Stdwin event processing main loop.
|
||||
# Return when there are no windows left.
|
||||
# Note that windows not in the windows list don't get their events.
|
||||
#
|
||||
def mainloop():
|
||||
while windows:
|
||||
event = stdwin.getevent()
|
||||
if event[1] in windows:
|
||||
try:
|
||||
event[1].dispatch(event)
|
||||
except KeyboardInterrupt:
|
||||
# The user can type Control-C (or whatever)
|
||||
# to leave the browser without closing
|
||||
# the window. Mainly useful for
|
||||
# debugging.
|
||||
break
|
||||
except:
|
||||
# During debugging, it was annoying if
|
||||
# every mistake in a callback caused the
|
||||
# whole browser to crash, hence this
|
||||
# handler. In a production version
|
||||
# it may be better to disable this.
|
||||
#
|
||||
msg = sys.exc_type
|
||||
if sys.exc_value:
|
||||
val = sys.exc_value
|
||||
if type(val) <> type(''):
|
||||
val = `val`
|
||||
msg = msg + ': ' + val
|
||||
msg = 'Oops, an exception occurred: ' + msg
|
||||
event = None
|
||||
stdwin.message(msg)
|
||||
event = None
|
||||
|
||||
|
||||
# Handle one event. The window is taken from the event's window item.
|
||||
# This function is placed as a method (named 'dispatch') on the window,
|
||||
# so the main loop will be able to handle windows of a different kind
|
||||
# as well, as long as they are all placed in the list of windows.
|
||||
#
|
||||
def idispatch(event):
|
||||
type, win, detail = event
|
||||
if type == WE_CHAR:
|
||||
if not keybindings.has_key(detail):
|
||||
detail = string.lower(detail)
|
||||
if keybindings.has_key(detail):
|
||||
keybindings[detail](win)
|
||||
return
|
||||
if detail in '0123456789':
|
||||
i = eval(detail) - 1
|
||||
if i < 0: i = len(win.menu) + i
|
||||
if 0 <= i < len(win.menu):
|
||||
topic, ref = win.menu[i]
|
||||
imove(win, ref)
|
||||
return
|
||||
stdwin.fleep()
|
||||
return
|
||||
if type == WE_COMMAND:
|
||||
if detail == WC_LEFT:
|
||||
iprev(win)
|
||||
elif detail == WC_RIGHT:
|
||||
inext(win)
|
||||
elif detail == WC_UP:
|
||||
iup(win)
|
||||
elif detail == WC_DOWN:
|
||||
idown(win)
|
||||
elif detail == WC_BACKSPACE:
|
||||
ibackward(win)
|
||||
elif detail == WC_RETURN:
|
||||
idown(win)
|
||||
else:
|
||||
stdwin.fleep()
|
||||
return
|
||||
if type == WE_MENU:
|
||||
mp, item = detail
|
||||
if mp == None:
|
||||
pass # A THINK C console menu was selected
|
||||
elif mp in (win.mainmenu, win.navimenu):
|
||||
mp.callback[item](win)
|
||||
elif mp == win.nodemenu:
|
||||
topic, ref = win.menu[item]
|
||||
imove(win, ref)
|
||||
elif mp == win.footmenu:
|
||||
topic, ref = win.footnotes[item]
|
||||
imove(win, ref)
|
||||
return
|
||||
if type == WE_SIZE:
|
||||
win.textobj.move((0, 0), win.getwinsize())
|
||||
(left, top), (right, bottom) = win.textobj.getrect()
|
||||
win.setdocsize(0, bottom)
|
||||
return
|
||||
if type == WE_CLOSE:
|
||||
iclose(win)
|
||||
return
|
||||
if not win.textobj.event(event):
|
||||
pass
|
||||
|
||||
|
||||
# Paging callbacks
|
||||
|
||||
def ibeginning(win):
|
||||
win.setorigin(0, 0)
|
||||
win.textobj.setfocus(0, 0) # To restart searches
|
||||
|
||||
def iforward(win):
|
||||
lh = stdwin.lineheight() # XXX Should really use the window's...
|
||||
h, v = win.getorigin()
|
||||
docwidth, docheight = win.getdocsize()
|
||||
width, height = win.getwinsize()
|
||||
if v + height >= docheight:
|
||||
stdwin.fleep()
|
||||
return
|
||||
increment = max(lh, ((height - 2*lh) / lh) * lh)
|
||||
v = v + increment
|
||||
win.setorigin(h, v)
|
||||
|
||||
def ibackward(win):
|
||||
lh = stdwin.lineheight() # XXX Should really use the window's...
|
||||
h, v = win.getorigin()
|
||||
if v <= 0:
|
||||
stdwin.fleep()
|
||||
return
|
||||
width, height = win.getwinsize()
|
||||
increment = max(lh, ((height - 2*lh) / lh) * lh)
|
||||
v = max(0, v - increment)
|
||||
win.setorigin(h, v)
|
||||
|
||||
|
||||
# Ibrowse menu callbacks
|
||||
|
||||
def iclone(win):
|
||||
stdwin.setdefwinsize(win.getwinsize())
|
||||
makewindow(win.file, win.node)
|
||||
|
||||
def itutor(win):
|
||||
# The course looks best at 76x22...
|
||||
stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight())
|
||||
makewindow('ibrowse', 'Help')
|
||||
|
||||
def isummary(win):
|
||||
stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight())
|
||||
makewindow('ibrowse', 'Summary')
|
||||
|
||||
def iclose(win):
|
||||
#
|
||||
# Remove the window from the windows list so the mainloop
|
||||
# will notice if all windows are gone.
|
||||
# Delete the textobj since it constitutes a circular reference
|
||||
# to the window which would prevent it from being closed.
|
||||
# (Deletion is done by assigning None to avoid crashes
|
||||
# when closing a half-initialized window.)
|
||||
#
|
||||
if win in windows:
|
||||
windows.remove(win)
|
||||
win.textobj = None
|
||||
|
||||
def icopy(win):
|
||||
focustext = win.textobj.getfocustext()
|
||||
if not focustext:
|
||||
stdwin.fleep()
|
||||
else:
|
||||
stdwin.rotatecutbuffers(1)
|
||||
stdwin.setcutbuffer(0, focustext)
|
||||
# XXX Should also set the primary selection...
|
||||
|
||||
def isearch(win):
|
||||
try:
|
||||
pat = stdwin.askstr('Search pattern:', win.pat)
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
if not pat:
|
||||
pat = win.pat
|
||||
if not pat:
|
||||
stdwin.message('No previous pattern')
|
||||
return
|
||||
try:
|
||||
cpat = regexp.compile(pat)
|
||||
except regexp.error, msg:
|
||||
stdwin.message('Bad pattern: ' + msg)
|
||||
return
|
||||
win.pat = pat
|
||||
f1, f2 = win.textobj.getfocus()
|
||||
text = win.text
|
||||
match = cpat.match(text, f2)
|
||||
if not match:
|
||||
stdwin.fleep()
|
||||
return
|
||||
a, b = match[0]
|
||||
win.textobj.setfocus(a, b)
|
||||
|
||||
|
||||
def iresetnodecache(win):
|
||||
icache.resetnodecache()
|
||||
|
||||
def iresetcache(win):
|
||||
icache.resetcache()
|
||||
|
||||
def iquit(win):
|
||||
for win in windows[:]:
|
||||
iclose(win)
|
||||
|
||||
|
||||
# Navigation menu callbacks
|
||||
|
||||
def imenu(win):
|
||||
ichoice(win, 'Menu item (abbreviated):', win.menu, whichmenuitem(win))
|
||||
|
||||
def ifollow(win):
|
||||
ichoice(win, 'Follow reference named (abbreviated):', \
|
||||
win.footnotes, whichfootnote(win))
|
||||
|
||||
def igoto(win):
|
||||
try:
|
||||
choice = stdwin.askstr('Go to node (full name):', '')
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
if not choice:
|
||||
stdwin.message('Sorry, Go to has no default')
|
||||
return
|
||||
imove(win, choice)
|
||||
|
||||
def inext(win):
|
||||
prev, next, up = win.header
|
||||
if next:
|
||||
imove(win, next)
|
||||
else:
|
||||
stdwin.fleep()
|
||||
|
||||
def iprev(win):
|
||||
prev, next, up = win.header
|
||||
if prev:
|
||||
imove(win, prev)
|
||||
else:
|
||||
stdwin.fleep()
|
||||
|
||||
def iup(win):
|
||||
prev, next, up = win.header
|
||||
if up:
|
||||
imove(win, up)
|
||||
else:
|
||||
stdwin.fleep()
|
||||
|
||||
def ilast(win):
|
||||
if not win.last:
|
||||
stdwin.fleep()
|
||||
else:
|
||||
i = len(win.last)-1
|
||||
lastnode, lastfocus = win.last[i]
|
||||
imove(win, lastnode)
|
||||
if len(win.last) > i+1:
|
||||
# The move succeeded -- restore the focus
|
||||
win.textobj.setfocus(lastfocus)
|
||||
# Delete the stack top even if the move failed,
|
||||
# else the whole stack would remain unreachable
|
||||
del win.last[i:] # Delete the entry pushed by imove as well!
|
||||
|
||||
def itop(win):
|
||||
imove(win, '')
|
||||
|
||||
def idir(win):
|
||||
imove(win, '(dir)')
|
||||
|
||||
|
||||
# Special and generic callbacks
|
||||
|
||||
def idown(win):
|
||||
if win.menu:
|
||||
default = whichmenuitem(win)
|
||||
for topic, ref in win.menu:
|
||||
if default == topic:
|
||||
break
|
||||
else:
|
||||
topic, ref = win.menu[0]
|
||||
imove(win, ref)
|
||||
else:
|
||||
inext(win)
|
||||
|
||||
def ichoice(win, prompt, list, default):
|
||||
if not list:
|
||||
stdwin.fleep()
|
||||
return
|
||||
if not default:
|
||||
topic, ref = list[0]
|
||||
default = topic
|
||||
try:
|
||||
choice = stdwin.askstr(prompt, default)
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
if not choice:
|
||||
return
|
||||
choice = string.lower(choice)
|
||||
n = len(choice)
|
||||
for topic, ref in list:
|
||||
topic = string.lower(topic)
|
||||
if topic[:n] == choice:
|
||||
imove(win, ref)
|
||||
return
|
||||
stdwin.message('Sorry, no topic matches ' + `choice`)
|
||||
|
||||
|
||||
# Follow a reference, in the same window.
|
||||
#
|
||||
def imove(win, ref):
|
||||
savetitle = win.gettitle()
|
||||
win.settitle('Looking for ' + ref + '...')
|
||||
#
|
||||
try:
|
||||
file, node, header, menu, footnotes, text = \
|
||||
icache.get_node(win.file, ref)
|
||||
except NoSuchFile, file:
|
||||
win.settitle(savetitle)
|
||||
stdwin.message(\
|
||||
'Sorry, I can\'t find a file named ' + `file` + '.')
|
||||
return
|
||||
except NoSuchNode, node:
|
||||
win.settitle(savetitle)
|
||||
stdwin.message(\
|
||||
'Sorry, I can\'t find a node named ' + `node` + '.')
|
||||
return
|
||||
#
|
||||
win.settitle('Found (' + file + ')' + node + '...')
|
||||
#
|
||||
if win.file and win.node:
|
||||
lastnode = '(' + win.file + ')' + win.node
|
||||
win.last.append(lastnode, win.textobj.getfocus())
|
||||
win.file = file
|
||||
win.node = node
|
||||
win.header = header
|
||||
win.menu = menu
|
||||
win.footnotes = footnotes
|
||||
win.text = text
|
||||
#
|
||||
win.setorigin(0, 0) # Scroll to the beginnning
|
||||
win.textobj.settext(text)
|
||||
win.textobj.setfocus(0, 0)
|
||||
(left, top), (right, bottom) = win.textobj.getrect()
|
||||
win.setdocsize(0, bottom)
|
||||
#
|
||||
if win.footmenu: win.footmenu.close()
|
||||
if win.nodemenu: win.nodemenu.close()
|
||||
win.footmenu = None
|
||||
win.nodemenu = None
|
||||
#
|
||||
win.menu = menu
|
||||
if menu:
|
||||
win.nodemenu = win.menucreate('Menu')
|
||||
digit = 1
|
||||
for topic, ref in menu:
|
||||
if digit < 10:
|
||||
win.nodemenu.additem(topic, `digit`)
|
||||
else:
|
||||
win.nodemenu.additem(topic)
|
||||
digit = digit + 1
|
||||
#
|
||||
win.footnotes = footnotes
|
||||
if footnotes:
|
||||
win.footmenu = win.menucreate('Footnotes')
|
||||
for topic, ref in footnotes:
|
||||
win.footmenu.additem(topic)
|
||||
#
|
||||
win.settitle('(' + win.file + ')' + win.node)
|
||||
|
||||
|
||||
# Find menu item at focus
|
||||
#
|
||||
findmenu = regexp.compile('^\* [mM]enu:').match
|
||||
findmenuitem = regexp.compile( \
|
||||
'^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match
|
||||
#
|
||||
def whichmenuitem(win):
|
||||
if not win.menu:
|
||||
return ''
|
||||
match = findmenu(win.text)
|
||||
if not match:
|
||||
return ''
|
||||
a, b = match[0]
|
||||
i = b
|
||||
f1, f2 = win.textobj.getfocus()
|
||||
lastmatch = ''
|
||||
while i < len(win.text):
|
||||
match = findmenuitem(win.text, i)
|
||||
if not match:
|
||||
break
|
||||
(a, b), (a1, b1), (a2, b2) = match
|
||||
if a > f1:
|
||||
break
|
||||
lastmatch = win.text[a1:b1]
|
||||
i = b
|
||||
return lastmatch
|
||||
|
||||
|
||||
# Find footnote at focus
|
||||
#
|
||||
findfootnote = \
|
||||
regexp.compile('\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match
|
||||
#
|
||||
def whichfootnote(win):
|
||||
if not win.footnotes:
|
||||
return ''
|
||||
i = 0
|
||||
f1, f2 = win.textobj.getfocus()
|
||||
lastmatch = ''
|
||||
while i < len(win.text):
|
||||
match = findfootnote(win.text, i)
|
||||
if not match:
|
||||
break
|
||||
(a, b), (a1, b1), (a2, b2) = match
|
||||
if a > f1:
|
||||
break
|
||||
lastmatch = win.text[a1:b1]
|
||||
i = b
|
||||
return lastmatch
|
||||
|
||||
|
||||
# Now all the "methods" are defined, we can initialize the table
|
||||
# of key bindings.
|
||||
#
|
||||
keybindings = {}
|
||||
|
||||
# Window commands
|
||||
|
||||
keybindings['k'] = iclone
|
||||
keybindings['h'] = itutor
|
||||
keybindings['?'] = isummary
|
||||
keybindings['w'] = iclose
|
||||
|
||||
keybindings['c'] = icopy
|
||||
|
||||
keybindings['s'] = isearch
|
||||
|
||||
keybindings['q'] = iquit
|
||||
|
||||
# Navigation commands
|
||||
|
||||
keybindings['m'] = imenu
|
||||
keybindings['f'] = ifollow
|
||||
keybindings['g'] = igoto
|
||||
|
||||
keybindings['n'] = inext
|
||||
keybindings['p'] = iprev
|
||||
keybindings['u'] = iup
|
||||
keybindings['l'] = ilast
|
||||
keybindings['d'] = idir
|
||||
keybindings['t'] = itop
|
||||
|
||||
# Paging commands
|
||||
|
||||
keybindings['b'] = ibeginning
|
||||
keybindings['.'] = ibeginning
|
||||
keybindings[' '] = iforward
|
|
@ -1,74 +0,0 @@
|
|||
# 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
|
|
@ -1,328 +0,0 @@
|
|||
# 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.time()
|
||||
tags = make_tags(f)
|
||||
t2 = time.time()
|
||||
print 'Making tag table for', `TESTFILE`, 'took', t2-t1, 'sec.'
|
|
@ -1,127 +0,0 @@
|
|||
# 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
|
|
@ -1,103 +0,0 @@
|
|||
# Window interface to (some of) the CD player's vital audio functions
|
||||
|
||||
import cd
|
||||
import stdwin
|
||||
from stdwinevents import *
|
||||
import mainloop
|
||||
|
||||
def main():
|
||||
player = cd.open()
|
||||
stdwin.setdefscrollbars(0, 0)
|
||||
win = stdwin.open('CD')
|
||||
win.player = player
|
||||
win.dispatch = cddispatch
|
||||
mainloop.register(win)
|
||||
win.settimer(10)
|
||||
mainloop.mainloop()
|
||||
|
||||
def cddispatch(type, win, detail):
|
||||
if type == WE_NULL:
|
||||
pass
|
||||
elif type == WE_CLOSE:
|
||||
mainloop.unregister(win)
|
||||
win.close()
|
||||
elif type == WE_DRAW:
|
||||
draw(win)
|
||||
elif type == WE_TIMER:
|
||||
update(win)
|
||||
elif type == WE_MOUSE_UP:
|
||||
left, top, right, bottom, v1, v2 = getgeo(win)
|
||||
h, v = detail[0]
|
||||
if left < h < right:
|
||||
if top < v < v1:
|
||||
but1(win)
|
||||
elif v1 < v < v2:
|
||||
but2(win)
|
||||
elif v2 < v < bottom:
|
||||
but3(win)
|
||||
else:
|
||||
stdwin.fleep()
|
||||
|
||||
def but1(win):
|
||||
update(win)
|
||||
|
||||
def but2(win):
|
||||
state = win.player.getstatus()[0]
|
||||
if state == cd.ready:
|
||||
win.player.play(1, 1)
|
||||
elif state in (cd.playing, cd.paused):
|
||||
win.player.togglepause()
|
||||
else:
|
||||
stdwin.fleep()
|
||||
update(win)
|
||||
|
||||
def but3(win):
|
||||
win.player.stop()
|
||||
update(win)
|
||||
|
||||
def update(win):
|
||||
d = win.begindrawing()
|
||||
drawstatus(win, d)
|
||||
d.enddrawing()
|
||||
win.settimer(10)
|
||||
|
||||
statedict = ['ERROR', 'NODISK', 'READY', 'PLAYING', 'PAUSED', 'STILL']
|
||||
|
||||
def draw(win):
|
||||
left, top, right, bottom, v1, v2 = getgeo(win)
|
||||
d = win.begindrawing()
|
||||
drawstatus(win, d)
|
||||
box(d, left, v1, right, v2, 'Play/Pause')
|
||||
box(d, left, v2, right, bottom, 'Stop')
|
||||
d.enddrawing()
|
||||
|
||||
def drawstatus(win, d):
|
||||
left, top, right, bottom, v1, v2 = getgeo(win)
|
||||
state, track, curtime, abstime, totaltime, first, last, \
|
||||
scsi_audio, cur_block, dummy = win.player.getstatus()
|
||||
if 0 <= state < len(statedict):
|
||||
message = statedict[state]
|
||||
else:
|
||||
message = `status`
|
||||
message = message + ' track ' + `track` + ' of ' + `last`
|
||||
d.erase((left, top), (right, v1))
|
||||
box(d, left, top, right, v1, message)
|
||||
|
||||
def box(d, left, top, right, bottom, label):
|
||||
R = (left+1, top+1), (right-1, bottom-1)
|
||||
width = d.textwidth(label)
|
||||
height = d.lineheight()
|
||||
h = (left + right - width) / 2
|
||||
v = (top + bottom - height) / 2
|
||||
d.box(R)
|
||||
d.cliprect(R)
|
||||
d.text((h, v), label)
|
||||
d.noclip()
|
||||
|
||||
def getgeo(win):
|
||||
(left, top), (right, bottom) = (0, 0), win.getwinsize()
|
||||
v1 = top + (bottom - top) / 3
|
||||
v2 = top + (bottom - top) * 2 / 3
|
||||
return left, top, right, bottom, v1, v2
|
||||
|
||||
main()
|
|
@ -1,80 +0,0 @@
|
|||
# Display digits of pi in a window, calculating in a separate thread.
|
||||
# Compare ../scripts/pi.py.
|
||||
|
||||
import sys
|
||||
import time
|
||||
import thread
|
||||
import stdwin
|
||||
from stdwinevents import *
|
||||
|
||||
ok = 1
|
||||
|
||||
digits = []
|
||||
|
||||
def worker():
|
||||
k, a, b, a1, b1 = 2l, 4l, 1l, 12l, 4l
|
||||
while ok:
|
||||
# Next approximation
|
||||
p, q, k = k*k, 2l*k+1l, k+1l
|
||||
a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1
|
||||
# Print common digits
|
||||
d, d1 = a/b, a1/b1
|
||||
#print a, b, a1, b1
|
||||
while d == d1:
|
||||
digits.append(`int(d)`)
|
||||
a, a1 = 10l*(a%b), 10l*(a1%b1)
|
||||
d, d1 = a/b, a1/b1
|
||||
|
||||
def main():
|
||||
global ok
|
||||
digits_seen = 0
|
||||
thread.start_new_thread(worker, ())
|
||||
tw = stdwin.textwidth('0 ')
|
||||
lh = stdwin.lineheight()
|
||||
stdwin.setdefwinsize(20 * tw, 20 * lh)
|
||||
stdwin.setdefscrollbars(0, 1)
|
||||
win = stdwin.open('digits of pi')
|
||||
options = win.menucreate('Options')
|
||||
options.additem('Auto scroll')
|
||||
autoscroll = 1
|
||||
options.check(0, autoscroll)
|
||||
while 1:
|
||||
win.settimer(1)
|
||||
type, w, detail = stdwin.getevent()
|
||||
if type == WE_CLOSE:
|
||||
ok = 0
|
||||
sys.exit(0)
|
||||
elif type == WE_DRAW:
|
||||
(left, top), (right, bottom) = detail
|
||||
digits_seen = len(digits)
|
||||
d = win.begindrawing()
|
||||
for i in range(digits_seen):
|
||||
h = (i % 20) * tw
|
||||
v = (i / 20) * lh
|
||||
if top-lh < v < bottom:
|
||||
d.text((h, v), digits[i])
|
||||
d.close()
|
||||
elif type == WE_TIMER:
|
||||
n = len(digits)
|
||||
if n > digits_seen:
|
||||
win.settitle(`n` + ' digits of pi')
|
||||
d = win.begindrawing()
|
||||
for i in range(digits_seen, n):
|
||||
h = (i % 20) * tw
|
||||
v = (i / 20) * lh
|
||||
d.text((h, v), digits[i])
|
||||
d.close()
|
||||
digits_seen = n
|
||||
height = (v + 20*lh) / (20*lh) * (20*lh)
|
||||
win.setdocsize(0, height)
|
||||
if autoscroll:
|
||||
win.show((0, v), (h+tw, v+lh))
|
||||
elif type == WE_MENU:
|
||||
menu, item = detail
|
||||
if menu == options:
|
||||
if item == 0:
|
||||
autoscroll = (not autoscroll)
|
||||
options.check(0, autoscroll)
|
||||
|
||||
|
||||
main()
|
Loading…
Reference in New Issue