home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pyos2bin.zip / Demo / ibrowse / ibrowse.py < prev    next >
Text File  |  1995-04-10  |  15KB  |  618 lines

  1. # Browser for "Info files" as used by the Emacs documentation system.
  2. #
  3. # Now you can read Info files even if you can't spare the memory, time or
  4. # disk space to run Emacs.  (I have used this extensively on a Macintosh
  5. # with 1 Megabyte main memory and a 20 Meg harddisk.)
  6. #
  7. # You can give this to someone with great fear of complex computer
  8. # systems, as long as they can use a mouse.
  9. #
  10. # Another reason to use this is to encourage the use of Info for on-line
  11. # documentation of software that is not related to Emacs or GNU.
  12. # (In particular, I plan to redo the Python and STDWIN documentation
  13. # in texinfo.)
  14.  
  15.  
  16. # NB: this is not a self-executing script.  You must startup Python,
  17. # import ibrowse, and call ibrowse.main().  On UNIX, the script 'ib'
  18. # runs the browser.
  19.  
  20.  
  21. # Configuration:
  22. #
  23. # - The pathname of the directory (or directories) containing
  24. #   the standard Info files should be set by editing the
  25. #   value assigned to INFOPATH in module ifile.py.
  26. #
  27. # - The default font should be set by editing the value of FONT
  28. #   in this module (ibrowse.py).
  29. #
  30. # - For fastest I/O, you may look at BLOCKSIZE and a few other
  31. #   constants in ifile.py.
  32.  
  33.  
  34. # This is a fairly large Python program, split in the following modules:
  35. #
  36. # ibrowse.py    Main program and user interface.
  37. #        This is the only module that imports stdwin.
  38. #
  39. # ifile.py    This module knows about the format of Info files.
  40. #        It is imported by all of the others.
  41. #
  42. # itags.py    This module knows how to read prebuilt tag tables,
  43. #        including indirect ones used by large texinfo files.
  44. #
  45. # icache.py    Caches tag tables and visited nodes.
  46.  
  47.  
  48. # XXX There should really be a different tutorial, as the user interface
  49. # XXX differs considerably from Emacs...
  50.  
  51.  
  52. import sys
  53. import regexp
  54. import stdwin
  55. from stdwinevents import *
  56. import string
  57. from ifile import NoSuchFile, NoSuchNode
  58. import icache
  59.  
  60.  
  61. # Default font.
  62. # This should be an acceptable argument for stdwin.setfont();
  63. # on the Mac, this can be a pair (fontname, pointsize), while
  64. # under X11 it should be a standard X11 font name.
  65. # For best results, use a constant width font like Courier;
  66. # many Info files contain tabs that don't align with other text
  67. # unless all characters have the same width.
  68. #
  69. #FONT = ('Monaco', 9)        # Mac
  70. FONT = '-schumacher-clean-medium-r-normal--14-140-75-75-c-70-iso8859-1'    # X11
  71.  
  72.  
  73. # Try not to destroy the list of windows when reload() is used.
  74. # This is useful during debugging, and harmless in production...
  75. #
  76. try:
  77.     dummy = windows
  78.     del dummy
  79. except NameError:
  80.     windows = []
  81.  
  82.  
  83. # Default main function -- start at the '(dir)' node.
  84. #
  85. def main():
  86.     start('(dir)')
  87.  
  88.  
  89. # Start at an arbitrary node.
  90. # The default file is 'ibrowse'.
  91. #
  92. def start(ref):
  93.     stdwin.setdefscrollbars(0, 1)
  94.     stdwin.setfont(FONT)
  95.     stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight())
  96.     makewindow('ibrowse', ref)
  97.     mainloop()
  98.  
  99.  
  100. # Open a new browser window.
  101. # Arguments specify the default file and a node reference
  102. # (if the node reference specifies a file, the default file is ignored).
  103. #
  104. def makewindow(file, ref):
  105.     win = stdwin.open('Info file Browser, by Guido van Rossum')
  106.     win.mainmenu = makemainmenu(win)
  107.     win.navimenu = makenavimenu(win)
  108.     win.textobj = win.textcreate((0, 0), win.getwinsize())
  109.     win.file = file
  110.     win.node = ''
  111.     win.last = []
  112.     win.pat = ''
  113.     win.dispatch = idispatch
  114.     win.nodemenu = None
  115.     win.footmenu = None
  116.     windows.append(win)
  117.     imove(win, ref)
  118.  
  119. # Create the 'Ibrowse' menu for a new browser window.
  120. #
  121. def makemainmenu(win):
  122.     mp = win.menucreate('Ibrowse')
  123.     mp.callback = []
  124.     additem(mp, 'New window (clone)',    'K', iclone)
  125.     additem(mp, 'Help (tutorial)',        'H', itutor)
  126.     additem(mp, 'Command summary',        '?', isummary)
  127.     additem(mp, 'Close this window',    'W', iclose)
  128.     additem(mp, '', '', None)
  129.     additem(mp, 'Copy to clipboard',    'C', icopy)
  130.     additem(mp, '', '', None)
  131.     additem(mp, 'Search regexp...',        'S', isearch)
  132.     additem(mp, '', '', None)
  133.     additem(mp, 'Reset node cache',        '',  iresetnodecache)
  134.     additem(mp, 'Reset entire cache',    '',  iresetcache)
  135.     additem(mp, '', '', None)
  136.     additem(mp, 'Quit',            'Q', iquit)
  137.     return mp
  138.  
  139. # Create the 'Navigation' menu for a new browser window.
  140. #
  141. def makenavimenu(win):
  142.     mp = win.menucreate('Navigation')
  143.     mp.callback = []
  144.     additem(mp, 'Menu item...',        'M', imenu)
  145.     additem(mp, 'Follow reference...',    'F', ifollow)
  146.     additem(mp, 'Go to node...',        'G', igoto)
  147.     additem(mp, '', '', None)
  148.     additem(mp, 'Next node in tree',    'N', inext)
  149.     additem(mp, 'Previous node in tree',    'P', iprev)
  150.     additem(mp, 'Up in tree',        'U', iup)
  151.     additem(mp, 'Last visited node',    'L', ilast)
  152.     additem(mp, 'Top of tree',        'T', itop)
  153.     additem(mp, 'Directory node',        'D', idir)
  154.     return mp
  155.  
  156. # Add an item to a menu, and a function to its list of callbacks.
  157. # (Specifying all in one call is the only way to keep the menu
  158. # and the list of callbacks in synchrony.)
  159. #
  160. def additem(mp, text, shortcut, function):
  161.     if shortcut:
  162.         mp.additem(text, shortcut)
  163.     else:
  164.         mp.additem(text)
  165.     mp.callback.append(function)
  166.  
  167.  
  168. # Stdwin event processing main loop.
  169. # Return when there are no windows left.
  170. # Note that windows not in the windows list don't get their events.
  171. #
  172. def mainloop():
  173.     while windows:
  174.         event = stdwin.getevent()
  175.         if event[1] in windows:
  176.             try:
  177.                 event[1].dispatch(event)
  178.             except KeyboardInterrupt:
  179.                 # The user can type Control-C (or whatever)
  180.                 # to leave the browser without closing
  181.                 # the window.  Mainly useful for
  182.                 # debugging.
  183.                 break
  184.             except:
  185.                 # During debugging, it was annoying if
  186.                 # every mistake in a callback caused the
  187.                 # whole browser to crash, hence this
  188.                 # handler.  In a production version
  189.                 # it may be better to disable this.
  190.                 #
  191.                 msg = sys.exc_type
  192.                 if sys.exc_value:
  193.                     val = sys.exc_value
  194.                     if type(val) <> type(''):
  195.                         val = `val`
  196.                     msg = msg + ': ' + val
  197.                 msg = 'Oops, an exception occurred: ' + msg
  198.                 event = None
  199.                 stdwin.message(msg)
  200.         event = None
  201.  
  202.  
  203. # Handle one event.  The window is taken from the event's window item.
  204. # This function is placed as a method (named 'dispatch') on the window,
  205. # so the main loop will be able to handle windows of a different kind
  206. # as well, as long as they are all placed in the list of windows.
  207. #
  208. def idispatch(event):
  209.     type, win, detail = event
  210.     if type == WE_CHAR:
  211.         if not keybindings.has_key(detail):
  212.             detail = string.lower(detail)
  213.         if keybindings.has_key(detail):
  214.             keybindings[detail](win)
  215.             return
  216.         if detail in '0123456789':
  217.             i = eval(detail) - 1
  218.             if i < 0: i = len(win.menu) + i
  219.             if 0 <= i < len(win.menu):
  220.                 topic, ref = win.menu[i]
  221.                 imove(win, ref)
  222.                 return
  223.         stdwin.fleep()
  224.         return
  225.     if type == WE_COMMAND:
  226.         if detail == WC_LEFT:
  227.             iprev(win)
  228.         elif detail == WC_RIGHT:
  229.             inext(win)
  230.         elif detail == WC_UP:
  231.             iup(win)
  232.         elif detail == WC_DOWN:
  233.             idown(win)
  234.         elif detail == WC_BACKSPACE:
  235.             ibackward(win)
  236.         elif detail == WC_RETURN:
  237.             idown(win)
  238.         else:
  239.             stdwin.fleep()
  240.         return
  241.     if type == WE_MENU:
  242.         mp, item = detail
  243.         if mp == None:
  244.             pass # A THINK C console menu was selected
  245.         elif mp in (win.mainmenu, win.navimenu):
  246.             mp.callback[item](win)
  247.         elif mp == win.nodemenu:
  248.             topic, ref = win.menu[item]
  249.             imove(win, ref)
  250.         elif mp == win.footmenu:
  251.             topic, ref = win.footnotes[item]
  252.             imove(win, ref)
  253.         return
  254.     if type == WE_SIZE:
  255.         win.textobj.move((0, 0), win.getwinsize())
  256.         (left, top), (right, bottom) = win.textobj.getrect()
  257.         win.setdocsize(0, bottom)
  258.         return
  259.     if type == WE_CLOSE:
  260.         iclose(win)
  261.         return
  262.     if not win.textobj.event(event):
  263.         pass
  264.  
  265.  
  266. # Paging callbacks
  267.  
  268. def ibeginning(win):
  269.     win.setorigin(0, 0)
  270.     win.textobj.setfocus(0, 0) # To restart searches
  271.  
  272. def iforward(win):
  273.     lh = stdwin.lineheight() # XXX Should really use the window's...
  274.     h, v = win.getorigin()
  275.     docwidth, docheight = win.getdocsize()
  276.     width, height = win.getwinsize()
  277.     if v + height >= docheight:
  278.         stdwin.fleep()
  279.         return
  280.     increment = max(lh, ((height - 2*lh) / lh) * lh)
  281.     v = v + increment
  282.     win.setorigin(h, v)
  283.  
  284. def ibackward(win):
  285.     lh = stdwin.lineheight() # XXX Should really use the window's...
  286.     h, v = win.getorigin()
  287.     if v <= 0:
  288.         stdwin.fleep()
  289.         return
  290.     width, height = win.getwinsize()
  291.     increment = max(lh, ((height - 2*lh) / lh) * lh)
  292.     v = max(0, v - increment)
  293.     win.setorigin(h, v)
  294.  
  295.  
  296. # Ibrowse menu callbacks
  297.  
  298. def iclone(win):
  299.     stdwin.setdefwinsize(win.getwinsize())
  300.     makewindow(win.file, win.node)
  301.  
  302. def itutor(win):
  303.     # The course looks best at 76x22...
  304.     stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight())
  305.     makewindow('ibrowse', 'Help')
  306.  
  307. def isummary(win):
  308.     stdwin.setdefwinsize(76*stdwin.textwidth('x'), 22*stdwin.lineheight())
  309.     makewindow('ibrowse', 'Summary')
  310.  
  311. def iclose(win):
  312.     #
  313.     # Remove the window from the windows list so the mainloop
  314.     # will notice if all windows are gone.
  315.     # Delete the textobj since it constitutes a circular reference
  316.     # to the window which would prevent it from being closed.
  317.     # (Deletion is done by assigning None to avoid crashes
  318.     # when closing a half-initialized window.)
  319.     #
  320.     if win in windows:
  321.         windows.remove(win)
  322.     win.textobj = None
  323.  
  324. def icopy(win):
  325.     focustext = win.textobj.getfocustext()
  326.     if not focustext:
  327.         stdwin.fleep()
  328.     else:
  329.         stdwin.rotatecutbuffers(1)
  330.         stdwin.setcutbuffer(0, focustext)
  331.         # XXX Should also set the primary selection...
  332.  
  333. def isearch(win):
  334.     try:
  335.         pat = stdwin.askstr('Search pattern:', win.pat)
  336.     except KeyboardInterrupt:
  337.         return
  338.     if not pat:
  339.         pat = win.pat
  340.         if not pat:
  341.             stdwin.message('No previous pattern')
  342.             return
  343.     try:
  344.         cpat = regexp.compile(pat)
  345.     except regexp.error, msg:
  346.         stdwin.message('Bad pattern: ' + msg)
  347.         return
  348.     win.pat = pat
  349.     f1, f2 = win.textobj.getfocus()
  350.     text = win.text
  351.     match = cpat.match(text, f2)
  352.     if not match:
  353.         stdwin.fleep()
  354.         return
  355.     a, b = match[0]
  356.     win.textobj.setfocus(a, b)
  357.  
  358.  
  359. def iresetnodecache(win):
  360.     icache.resetnodecache()
  361.  
  362. def iresetcache(win):
  363.     icache.resetcache()
  364.  
  365. def iquit(win):
  366.     for win in windows[:]:
  367.         iclose(win)
  368.  
  369.  
  370. # Navigation menu callbacks
  371.  
  372. def imenu(win):
  373.     ichoice(win, 'Menu item (abbreviated):', win.menu, whichmenuitem(win))
  374.  
  375. def ifollow(win):
  376.     ichoice(win, 'Follow reference named (abbreviated):', \
  377.         win.footnotes, whichfootnote(win))
  378.  
  379. def igoto(win):
  380.     try:
  381.         choice = stdwin.askstr('Go to node (full name):', '')
  382.     except KeyboardInterrupt:
  383.         return
  384.     if not choice:
  385.         stdwin.message('Sorry, Go to has no default')
  386.         return
  387.     imove(win, choice)
  388.  
  389. def inext(win):
  390.     prev, next, up = win.header
  391.     if next:
  392.         imove(win, next)
  393.     else:
  394.         stdwin.fleep()
  395.  
  396. def iprev(win):
  397.     prev, next, up = win.header
  398.     if prev:
  399.         imove(win, prev)
  400.     else:
  401.         stdwin.fleep()
  402.  
  403. def iup(win):
  404.     prev, next, up = win.header
  405.     if up:
  406.         imove(win, up)
  407.     else:
  408.         stdwin.fleep()
  409.  
  410. def ilast(win):
  411.     if not win.last:
  412.         stdwin.fleep()
  413.     else:
  414.         i = len(win.last)-1
  415.         lastnode, lastfocus = win.last[i]
  416.         imove(win, lastnode)
  417.         if len(win.last) > i+1:
  418.             # The move succeeded -- restore the focus
  419.             win.textobj.setfocus(lastfocus)
  420.         # Delete the stack top even if the move failed,
  421.         # else the whole stack would remain unreachable
  422.         del win.last[i:] # Delete the entry pushed by imove as well!
  423.  
  424. def itop(win):
  425.     imove(win, '')
  426.  
  427. def idir(win):
  428.     imove(win, '(dir)')
  429.  
  430.  
  431. # Special and generic callbacks
  432.  
  433. def idown(win):
  434.     if win.menu:
  435.         default = whichmenuitem(win)
  436.         for topic, ref in win.menu:
  437.             if default == topic:
  438.                 break
  439.         else:
  440.             topic, ref = win.menu[0]
  441.         imove(win, ref)
  442.     else:
  443.         inext(win)
  444.  
  445. def ichoice(win, prompt, list, default):
  446.     if not list:
  447.         stdwin.fleep()
  448.         return
  449.     if not default:
  450.         topic, ref = list[0]
  451.         default = topic
  452.     try:
  453.         choice = stdwin.askstr(prompt, default)
  454.     except KeyboardInterrupt:
  455.         return
  456.     if not choice:
  457.         return
  458.     choice = string.lower(choice)
  459.     n = len(choice)
  460.     for topic, ref in list:
  461.         topic = string.lower(topic)
  462.         if topic[:n] == choice:
  463.             imove(win, ref)
  464.             return
  465.     stdwin.message('Sorry, no topic matches ' + `choice`)
  466.  
  467.  
  468. # Follow a reference, in the same window.
  469. #
  470. def imove(win, ref):
  471.     savetitle = win.gettitle()
  472.     win.settitle('Looking for ' + ref + '...')
  473.     #
  474.     try:
  475.         file, node, header, menu, footnotes, text = \
  476.             icache.get_node(win.file, ref)
  477.     except NoSuchFile, file:
  478.         win.settitle(savetitle)
  479.         stdwin.message(\
  480.         'Sorry, I can\'t find a file named ' + `file` + '.')
  481.         return
  482.     except NoSuchNode, node:
  483.         win.settitle(savetitle)
  484.         stdwin.message(\
  485.         'Sorry, I can\'t find a node named ' + `node` + '.')
  486.         return
  487.     #
  488.     win.settitle('Found (' + file + ')' + node + '...')
  489.     #
  490.     if win.file and win.node:
  491.         lastnode = '(' + win.file + ')' + win.node
  492.         win.last.append(lastnode, win.textobj.getfocus())
  493.     win.file = file
  494.     win.node = node
  495.     win.header = header
  496.     win.menu = menu
  497.     win.footnotes = footnotes
  498.     win.text = text
  499.     #
  500.     win.setorigin(0, 0) # Scroll to the beginnning
  501.     win.textobj.settext(text)
  502.     win.textobj.setfocus(0, 0)
  503.     (left, top), (right, bottom) = win.textobj.getrect()
  504.     win.setdocsize(0, bottom)
  505.     #
  506.     if win.footmenu: win.footmenu.close()
  507.     if win.nodemenu: win.nodemenu.close()
  508.     win.footmenu = None
  509.     win.nodemenu = None
  510.     #
  511.     win.menu = menu
  512.     if menu:
  513.         win.nodemenu = win.menucreate('Menu')
  514.         digit = 1
  515.         for topic, ref in menu:
  516.             if digit < 10:
  517.                 win.nodemenu.additem(topic, `digit`)
  518.             else:
  519.                 win.nodemenu.additem(topic)
  520.             digit = digit + 1
  521.     #
  522.     win.footnotes = footnotes
  523.     if footnotes:
  524.         win.footmenu = win.menucreate('Footnotes')
  525.         for topic, ref in footnotes:
  526.             win.footmenu.additem(topic)
  527.     #
  528.     win.settitle('(' + win.file + ')' + win.node)
  529.  
  530.  
  531. # Find menu item at focus
  532. #
  533. findmenu = regexp.compile('^\* [mM]enu:').match
  534. findmenuitem = regexp.compile( \
  535.     '^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match
  536. #
  537. def whichmenuitem(win):
  538.     if not win.menu:
  539.         return ''
  540.     match = findmenu(win.text)
  541.     if not match:
  542.         return ''
  543.     a, b = match[0]
  544.     i = b
  545.     f1, f2 = win.textobj.getfocus()
  546.     lastmatch = ''
  547.     while i < len(win.text):
  548.         match = findmenuitem(win.text, i)
  549.         if not match:
  550.             break
  551.         (a, b), (a1, b1), (a2, b2) = match
  552.         if a > f1:
  553.             break
  554.         lastmatch = win.text[a1:b1]
  555.         i = b
  556.     return lastmatch
  557.  
  558.  
  559. # Find footnote at focus
  560. #
  561. findfootnote = \
  562.     regexp.compile('\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match
  563. #
  564. def whichfootnote(win):
  565.     if not win.footnotes:
  566.         return ''
  567.     i = 0
  568.     f1, f2 = win.textobj.getfocus()
  569.     lastmatch = ''
  570.     while i < len(win.text):
  571.         match = findfootnote(win.text, i)
  572.         if not match:
  573.             break
  574.         (a, b), (a1, b1), (a2, b2) = match
  575.         if a > f1:
  576.             break
  577.         lastmatch = win.text[a1:b1]
  578.         i = b
  579.     return lastmatch
  580.  
  581.  
  582. # Now all the "methods" are defined, we can initialize the table
  583. # of key bindings.
  584. #
  585. keybindings = {}
  586.  
  587. # Window commands
  588.  
  589. keybindings['k'] = iclone
  590. keybindings['h'] = itutor
  591. keybindings['?'] = isummary
  592. keybindings['w'] = iclose
  593.  
  594. keybindings['c'] = icopy
  595.  
  596. keybindings['s'] = isearch
  597.  
  598. keybindings['q'] = iquit
  599.  
  600. # Navigation commands
  601.  
  602. keybindings['m'] = imenu
  603. keybindings['f'] = ifollow
  604. keybindings['g'] = igoto
  605.  
  606. keybindings['n'] = inext
  607. keybindings['p'] = iprev
  608. keybindings['u'] = iup
  609. keybindings['l'] = ilast
  610. keybindings['d'] = idir
  611. keybindings['t'] = itop
  612.  
  613. # Paging commands
  614.  
  615. keybindings['b'] = ibeginning
  616. keybindings['.'] = ibeginning
  617. keybindings[' '] = iforward
  618.