summaryrefslogtreecommitdiffstats
path: root/Demo/ibrowse/ibrowse.py
diff options
context:
space:
mode:
Diffstat (limited to 'Demo/ibrowse/ibrowse.py')
-rwxr-xr-xDemo/ibrowse/ibrowse.py617
1 files changed, 617 insertions, 0 deletions
diff --git a/Demo/ibrowse/ibrowse.py b/Demo/ibrowse/ibrowse.py
new file mode 100755
index 0000000..41574ad
--- /dev/null
+++ b/Demo/ibrowse/ibrowse.py
@@ -0,0 +1,617 @@
+# 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