home *** CD-ROM | disk | FTP | other *** search
- # 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
-