home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / x / volume5 / xinfo / part02 / Info.c
Encoding:
C/C++ Source or Header  |  1993-04-28  |  51.9 KB  |  1,978 lines

  1. #ifndef lint
  2. static char *rcsid = "$Header: /usr1/ben/jkh/src/xinfo/RCS/Info.c,v 1.2 90/01/29 11:29:19 jkh Exp $";
  3. #endif
  4.  
  5. #include <X11/IntrinsicP.h>
  6. #include <X11/Shell.h>
  7. #include <X11/StringDefs.h>
  8. #include <X11/Xaw/AsciiText.h>
  9. #include <X11/Xaw/Box.h>
  10. #include <X11/Xaw/Command.h>
  11. #include <X11/Xaw/Label.h>
  12. #include <X11/Xaw/List.h>
  13. #include <X11/Xaw/Viewport.h>
  14.  
  15. #include <sys/stat.h>
  16. #include <stdio.h>
  17. #include <ctype.h>
  18. #include <pwd.h>
  19.  
  20. /* #include <X11/Xaw/InfoP.h> */
  21. #include "InfoP.h"
  22.  
  23. /*
  24.  *
  25.  *                   Copyright 1989, 1990
  26.  *                    Jordan K. Hubbard
  27.  *
  28.  *                PCS Computer Systeme, GmbH.
  29.  *                   Munich, West Germany
  30.  *
  31.  *
  32.  * This file is part of GNU Info widget.
  33.  *
  34.  * The GNU Info widget is free software; you can redistribute it and/or
  35.  * modify it under the terms of the GNU General Public License as published
  36.  * by the Free Software Foundation; either version 1, or (at your option)
  37.  * any later version.
  38.  *
  39.  * This software is distributed in the hope that it will be useful,
  40.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  41.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  42.  * GNU General Public License for more details.
  43.  *
  44.  * You should have received a copy of the GNU General Public License
  45.  * along with this software; see the file COPYING.  If not, write to
  46.  * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
  47.  *
  48.  *
  49.  */
  50.  
  51. /*
  52.  *
  53.  * $Log:    Info.c,v $
  54.  * Revision 1.2  90/01/29  11:29:19  jkh
  55.  * *** empty log message ***
  56.  * 
  57.  * Revision 1.1  90/01/27  22:43:22  jkh
  58.  * Initial revision
  59.  * 
  60.  *
  61.  */
  62.  
  63. #define offset(name)    XtOffset(InfoWidget, info.name)
  64.  
  65. Local XtResource resources[] = {
  66.      { XtNinfoHelp, XtCInfoHelp, XtRString, sizeof(String), offset(helpFile),
  67.         XtRString, HELPFILE },
  68.      { XtNinfoPath, XtCInfoPath, XtRString, sizeof(String), offset(path),
  69.         XtRString, XtDefaultInfoPath },
  70.      { XtNinfoFile, XtCInfoFile, XtRString, sizeof(String), offset(file),
  71.         XtRString, XtDefaultInfoFile },
  72.      { XtNinfoNode, XtCInfoNode, XtRString, sizeof(String), offset(node),
  73.         XtRString, XtDefaultInfoNode },
  74.      { XtNcallback, XtCCallback, XtRCallback, sizeof(caddr_t),
  75.         offset(callback), XtRCallback, NULL},
  76.      { XtNprintCommand, XtCPrintCommand, XtRString, sizeof(String),
  77.         offset(printCmd), XtRString, XtDefaultPrintCommand },
  78. };
  79.  
  80. #undef offset
  81.  
  82. Local void Initialize(), Destroy();
  83. Local Boolean SetValues();
  84.  
  85. Local void NodeDir(), NodeNext(), NodePrev(), NodeUp(), NodeTop(),
  86.      NodeLast(),  NodeXRef(), NodeGoto(), NodeSearch(), NodeQuit(),
  87.      NodeMenuSelectByNumber(), NodePrint(), NodeHelp(), NodeTutorial();
  88.  
  89. Local XtActionsRec actions[] =
  90. {
  91.      { "dir",        NodeDir },
  92.      { "next",        NodeNext },
  93.      { "prev",        NodePrev },
  94.      { "up",        NodeUp },
  95.      { "top",        NodeTop },
  96.      { "last",        NodeLast },
  97.      { "xref",        NodeXRef },
  98.      { "goto",        NodeGoto },
  99.      { "search",    NodeSearch },
  100.      { "menusel",    NodeMenuSelectByNumber },
  101.      { "print",        NodePrint },
  102.      { "quit",        NodeQuit },
  103.      { "tutorial",    NodeTutorial },
  104.      { "popupHelp",    NodeHelp },
  105.      { NULL,        NULL }
  106. };
  107.  
  108. Local char deflTrans[] = "\
  109. <Key>1:        menusel(1)\n\
  110. <Key>2:        menusel(2)\n\
  111. <Key>3:        menusel(3)\n\
  112. <Key>4:        menusel(4)\n\
  113. <Key>5:        menusel(5)\n\
  114. <Key>6:        menusel(6)\n\
  115. <Key>7:        menusel(7)\n\
  116. <Key>8:        menusel(8)\n\
  117. <Key>9:        menusel(9)\n\
  118. <Key>Help:    popupHelp()\n\
  119. <Key>question:    popupHelp()\n\
  120. Meta<Key>P:    print()\n\
  121. None<Key>d:    dir()\n\
  122. None<Key>f:    xref()\n\
  123. None<Key>g:    goto()\n\
  124. None<Key>h:    tutorial()\n\
  125. None<Key>l:    last()\n\
  126. None<Key>m:    menusel(0)\n\
  127. None<Key>n:    next()\n\
  128. None<Key>p:    prev()\n\
  129. None<Key>q:    quit()\n\
  130. None<Key>s:    search()\n\
  131. None<Key>t:    top()\n\
  132. None<Key>u:    up()\n\
  133. ";
  134.  
  135. /* page movement translations for text areas */
  136. Local char textTrans[] = "\
  137. None<Key>b:    beginning-of-file()\n\
  138. <Key>Home:    beginning-of-file()\n\
  139. <Key>Delete:    previous-page()\n\
  140. <Key>Prior:    previous-page()\n\
  141. <Key>Next:    next-page()\n\
  142. <Key>space:    next-page()\n\
  143. ";
  144.  
  145. Export InfoClassRec infoClassRec = {
  146.   { /* core fields */
  147.     /* superclass        */    (WidgetClass) &panedClassRec,
  148.     /* class_name        */    "Info",
  149.     /* widget_size        */    sizeof(InfoRec),
  150.     /* class_initialize        */    NULL,
  151.     /* class_part_initialize    */    NULL,
  152.     /* class_inited        */    FALSE,
  153.     /* initialize        */    Initialize,
  154.     /* initialize_hook        */    NULL,
  155.     /* realize            */    XtInheritRealize,
  156.     /* actions            */    actions,
  157.     /* num_actions        */    XtNumber(actions),
  158.     /* resources        */    resources,
  159.     /* num_resources        */    XtNumber(resources),
  160.     /* xrm_class        */    NULLQUARK,
  161.     /* compress_motion        */    TRUE,
  162.     /* compress_exposure    */    TRUE,
  163.     /* compress_enterleave    */    TRUE,
  164.     /* visible_interest        */    FALSE,
  165.     /* destroy            */    Destroy,
  166.     /* resize            */    XtInheritResize,
  167.     /* expose            */    XtInheritExpose,
  168.     /* set_values        */    SetValues,
  169.     /* set_values_hook        */    NULL,
  170.     /* set_values_almost    */    XtInheritSetValuesAlmost,
  171.     /* get_values_hook        */    NULL,
  172.     /* accept_focus        */    XtInheritAcceptFocus,
  173.     /* version            */    XtVersion,
  174.     /* callback_private        */    NULL,
  175.     /* tm_table            */    deflTrans,
  176.     /* query_geometry        */    XtInheritQueryGeometry,
  177.     /* display_accelerator    */    XtInheritDisplayAccelerator,
  178.     /* extension        */    NULL
  179.   },
  180.   { /* composite_class fields    */
  181.     /* geometry_manager        */    XtInheritGeometryManager,
  182.     /* change_managed        */    XtInheritChangeManaged,
  183.     /* insert_child        */    XtInheritInsertChild,
  184.     /* delete_child        */    XtInheritDeleteChild,
  185.     /* extension        */    NULL
  186.   },
  187.   { /* constraint_class fields    */
  188.     /* subresourses        */    NULL,
  189.     /* subresource_count    */    0,
  190.     /* constraint_size        */    sizeof(InfoConstraintsRec),
  191.     /* initialize        */    NULL,
  192.     /* destroy            */    NULL,
  193.     /* set_values        */    NULL,
  194.     /* extension        */    NULL
  195.   },
  196.   { /* paned_class fields    */
  197.     /* empty            */    0
  198.   },
  199.   { /* info fields        */
  200.     /* empty            */    0
  201.   }
  202. };
  203.  
  204. Export WidgetClass infoWidgetClass = (WidgetClass)&infoClassRec;
  205.  
  206. Local int findNode();
  207. Local int iindex();
  208. Local int strcomp();
  209. Local int strncomp();
  210. Local void parseIndirect();
  211. Local void message();
  212. Local void strccpy();
  213. Local void parseNode();
  214. Local void parseHeader();
  215. Local void parseMenu();
  216. Local void parseXRefs();
  217. Local void displayNode();
  218. Local void displayHeader();
  219. Local void showStatus();
  220. Local String getFile();
  221. Local String find_file();
  222. Local String offsetToString();
  223. Local String file_name();
  224. Local String eat_whitespace();
  225. Local String search();
  226. Local String search_back();
  227. Local String strconcat();
  228. Local String substr();
  229. Local String reverse();
  230. Local String get_arg();
  231. Local String trueName();
  232. Local String downcase();
  233. Local String strip_evil();
  234. Local Boolean getNode();
  235. Local Boolean parseTags();
  236. Local NodeInfo *pushNode();
  237. Local NodeInfo *popNode();
  238.  
  239. Local void do_prev(), do_up(), do_next(), do_menu(),
  240.      do_xref(), do_menu_sel(), do_xref_sel(), do_goto(), do_search();
  241.  
  242. Import char tolower();
  243.  
  244. Local XtCallbackRec cb[2];
  245. #define XtSetCbk(argarray, rtn, arg) \
  246.      cb[0].callback = rtn; \
  247.      cb[0].closure = (caddr_t)arg; \
  248.      XtSetArg(argarray, XtNcallback, cb)
  249.  
  250. Local void Initialize(request, new)
  251. Widget request;
  252. Widget new;
  253. {
  254.      Arg args[10];
  255.      Cardinal i;
  256.      InfoWidget iw = (InfoWidget)new;
  257.      Widget box1, box2, vport, vport2;
  258.      char blanks[MAXSTR];
  259.      XtTranslations defltrans, texttrans;
  260.      Import char *bzero();
  261.  
  262.      /* parse the transation tables */
  263.      defltrans = XtParseTranslationTable(deflTrans);
  264.      texttrans = XtParseTranslationTable(textTrans);
  265.  
  266.      /* create a blank filled string as a placeholder for certain labels */
  267.      for (i = 0; i < MAXSTR - 1; i++)
  268.       blanks[i] = ' ';
  269.      blanks[i] = '\0';
  270.  
  271.      /*
  272.       * Create top row of "main control" buttons and labels.
  273.       */
  274.      i = 0;
  275.      box1 = XtCreateManagedWidget("box1", boxWidgetClass, new, args, i);
  276.  
  277.      i = 0;
  278.      XtSetArg(args[i], XtNjustify, XtJustifyLeft);            i++;
  279.      XtSetArg(args[i], XtNlabel, "File: ");                i++;
  280.      iw->info.fileLabel = XtCreateManagedWidget("file", labelWidgetClass,
  281.                         box1, args, i);
  282.      i = 0;
  283.      XtSetArg(args[i], XtNjustify, XtJustifyLeft);            i++;
  284.      XtSetArg(args[i], XtNlabel, "Node: ");                i++;
  285.      iw->info.nodeLabel = XtCreateManagedWidget("node", labelWidgetClass,
  286.                         box1, args, i);
  287.      i = 0;
  288.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  289.      XtSetArg(args[i], XtNjustify, XtJustifyLeft);            i++;
  290.      XtSetArg(args[i], XtNlabel, "Prev: ");                i++;
  291.      XtSetCbk(args[i], do_prev, iw);                    i++;
  292.      iw->info.prevCmd = XtCreateManagedWidget("prev", commandWidgetClass,
  293.                           box1, args, i);
  294.      i = 0;
  295.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  296.      XtSetArg(args[i], XtNjustify, XtJustifyLeft);            i++;
  297.      XtSetArg(args[i], XtNlabel, "Up: ");                i++;
  298.      XtSetCbk(args[i], do_up, iw);                    i++;
  299.      iw->info.upCmd = XtCreateManagedWidget("up", commandWidgetClass,
  300.                         box1, args, i);
  301.      i = 0;
  302.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  303.      XtSetArg(args[i], XtNjustify, XtJustifyLeft);            i++;
  304.      XtSetArg(args[i], XtNlabel, "Next: ");                i++;
  305.      XtSetCbk(args[i], do_next, iw);                    i++;
  306.      iw->info.nextCmd = XtCreateManagedWidget("next", commandWidgetClass,
  307.                           box1, args, i);
  308.  
  309.      /* Create the menu pane */
  310.      i = 0;
  311.      XtSetArg(args[i], XtNallowVert, TRUE);                i++;
  312.      XtSetArg(args[i], XtNallowResize, TRUE);                i++;
  313.      vport = XtCreateManagedWidget("vport1", viewportWidgetClass,
  314.                    new, args, i);
  315.      i = 0;
  316.      XtSetArg(args[i], XtNheight, 150);                    i++;
  317.      XtSetArg(args[i], XtNpasteBuffer, TRUE);                i++;
  318.      XtSetArg(args[i], XtNcolumnSpacing,  8);                i++;
  319.      XtSetCbk(args[i], do_menu_sel, iw);                i++;
  320.      iw->info.menuList = XtCreateManagedWidget("menu", listWidgetClass,
  321.                            vport, args, i);
  322.  
  323.      /*
  324.       * Create the text area for displaying node contents.
  325.       */
  326.      i = 0;
  327.      XtSetArg(args[i], XtNheight, 500);                    i++;
  328.      XtSetArg(args[i], XtNstring, "/dev/null");                i++;
  329.      XtSetArg(args[i], XtNeditType, XawtextEdit);            i++;
  330.      XtSetArg(args[i], XtNtype, XawAsciiFile);                i++;
  331.      iw->info.nodeText = XtCreateManagedWidget("nodeText",
  332.                            asciiTextWidgetClass,
  333.                            new, args, i);
  334.      XtOverrideTranslations(iw->info.nodeText, defltrans);
  335.      XtOverrideTranslations(iw->info.nodeText, texttrans);
  336.  
  337.  
  338.      i = 0;
  339.      XtSetArg(args[i], XtNheight, 20);                    i++;
  340.      XtSetArg(args[i], XtNmin, 20);                    i++;
  341.      XtSetArg(args[i], XtNallowVert, TRUE);                i++;
  342.      XtSetArg(args[i], XtNallowResize, TRUE);                i++;
  343.      vport2 = XtCreateManagedWidget("vport2", viewportWidgetClass,
  344.                     new, args, i);
  345.      /* Create the xref pane */
  346.      i = 0;
  347.      XtSetArg(args[i], XtNpasteBuffer, TRUE);                i++;
  348.      XtSetArg(args[i], XtNdefaultColumns, 6);                i++;
  349.      XtSetArg(args[i], XtNcolumnSpacing,  8);                i++;
  350.      XtSetCbk(args[i], do_xref_sel, iw);                i++;
  351.      iw->info.xrefList = XtCreateManagedWidget("xref", listWidgetClass,
  352.                            vport2, args, i);
  353.  
  354.      /*
  355.       * Create the bottom "auxilliary" command button group.
  356.       */
  357.      i = 0;
  358.      XtSetArg(args[i], XtNheight, 30);                    i++;
  359.      XtSetArg(args[i], XtNmin, 30);                    i++;
  360.      box2 = XtCreateManagedWidget("box2", boxWidgetClass,
  361.                   new, args, i);
  362.      i = 0;
  363.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  364.      XtSetCbk(args[i], do_menu, iw);                    i++;
  365.      iw->info.xrefCmd = XtCreateManagedWidget("menu", commandWidgetClass,
  366.                           box2, args, i);
  367.      i = 0;
  368.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  369.      XtSetCbk(args[i], do_xref, iw);                    i++;
  370.      iw->info.xrefCmd = XtCreateManagedWidget("xref", commandWidgetClass,
  371.                           box2, args, i);
  372.      i = 0;
  373.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  374.      XtSetCbk(args[i], do_goto, iw);                    i++;
  375.      iw->info.gotoCmd = XtCreateManagedWidget("goto", commandWidgetClass,
  376.                           box2, args, i);
  377.      i = 0;
  378.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  379.      XtSetCbk(args[i], do_search, iw);                    i++;
  380.      iw->info.searchCmd = XtCreateManagedWidget("search", commandWidgetClass,
  381.                         box2, args, i);
  382.      i = 0;
  383.      bzero(iw->info.arg, ARGLEN);
  384.      XtSetArg(args[i], XtNstring, iw->info.arg);            i++;
  385.      XtSetArg(args[i], XtNlength, ARGLEN);                i++;
  386.      XtSetArg(args[i], XtNwidth, 200);                    i++;
  387.      XtSetArg(args[i], XtNallowResize, TRUE);                i++;
  388.      XtSetArg(args[i], XtNresize, TRUE);                i++;
  389.      XtSetArg(args[i], XtNborderWidth, 2);                i++;
  390.      XtSetArg(args[i], XtNeditType, XawtextEdit);            i++;
  391.      iw->info.argText = XtCreateManagedWidget("arg", asciiTextWidgetClass,
  392.                           box2, args, i);
  393.  
  394.      /*
  395.       * Create the status and message area labels.
  396.       */
  397.  
  398.      i = 0;
  399.      XtSetArg(args[i], XtNheight, 20);                    i++;
  400.      XtSetArg(args[i], XtNmin, 20);                    i++;
  401.      XtSetArg(args[i], XtNmax, 20);                    i++;
  402.      XtSetArg(args[i], XtNresize, FALSE);                i++;
  403.      XtSetArg(args[i], XtNlabel, blanks);                i++;
  404.      XtSetArg(args[i], XtNborderWidth, 0);                i++;
  405.      iw->info.statusLabel = XtCreateManagedWidget("status", labelWidgetClass,
  406.                           new, args, i);
  407.      i = 0;
  408.      XtSetArg(args[i], XtNheight, 20);                    i++;
  409.      XtSetArg(args[i], XtNmin, 20);                    i++;
  410.      XtSetArg(args[i], XtNmax, 20);                    i++;
  411.      XtSetArg(args[i], XtNresize, FALSE);                i++;
  412.      XtSetArg(args[i], XtNlabel, blanks);                i++;
  413.      XtSetArg(args[i], XtNborderWidth, 0);                i++;
  414.      iw->info.messageLabel = XtCreateManagedWidget("message", labelWidgetClass,
  415.                            new, args, i);
  416.  
  417.      /* set the initial node information */
  418.      ZERO_TABLE(INDIRECT(iw));
  419.      ZERO_TABLE(TAGTABLE(iw));
  420.      DATA(iw) = NULL;
  421.      CURNODE(iw) = NULL;
  422.  
  423.      /* decide who can get bigger */
  424.      XawPanedAllowResize(vport, TRUE);
  425.      XawPanedAllowResize(vport2, TRUE);
  426.      XawPanedAllowResize(iw->info.xrefList, TRUE);
  427.  
  428.      iw->info.file = XtNewString(iw->info.file);
  429.      iw->info.node = XtNewString(iw->info.node);
  430.      iw->info.tempfile = NULL;
  431.  
  432.      if (getNode(iw, iw->info.file, iw->info.node, NULL) == FALSE)
  433.       message(iw, "?Can't find initial file/node.");
  434. }
  435.  
  436. Local void Destroy(w)
  437. Widget w;
  438. {
  439.      InfoWidget iw = (InfoWidget)w;
  440.  
  441.      if (INDIRECT(iw).table)
  442.       FREE_TAG_TABLE(INDIRECT(iw));
  443.      if (TAGTABLE(iw).table)
  444.       FREE_TAG_TABLE(TAGTABLE(iw));
  445.      while (popNode(iw));    /* popNode will free contents */
  446.      /* free the last one */
  447.      if (CURNODE(iw)) {
  448.       XtFree(CURNODE(iw)->file);
  449.       XtFree(CURNODE(iw)->node);
  450.       XtFree(CURNODE(iw));
  451.      }
  452.      if (iw->info.file)
  453.       XtFree(iw->info.file);
  454.      if (iw->info.node)
  455.       XtFree(iw->info.node);
  456. }
  457.  
  458. Local Boolean SetValues(current, request, new)
  459. Widget current, request, new;
  460. {
  461.      InfoWidget cw = (InfoWidget)current;
  462.      InfoWidget nw = (InfoWidget)new;
  463.  
  464.      if (cw->info.file != nw->info.file
  465.      || strcomp(cw->info.file, nw->info.file)
  466.      || cw->info.node != nw->info.node
  467.      || strcomp(cw->info.node, nw->info.node)) {
  468.       XtFree(cw->info.file);
  469.       XtFree(cw->info.node);
  470.       getNode(nw, nw->info.file, nw->info.node, NULL);
  471.      }
  472.      /* getNode() does the redisplay implicitly */
  473.      return(FALSE);
  474. }
  475.  
  476. /*****************************************************************************
  477.  * Info file manipulation routines.                                          *
  478.  *****************************************************************************/
  479.  
  480. /* Here is the main guy. Handles all navigation within the info tree. */
  481. Local Boolean getNode(iw, file, node, pushTo)
  482. InfoWidget iw;
  483. String file, node;
  484. NodeInfo *pushTo;
  485. {
  486.      NodeInfo *cur;
  487.      int offset;
  488.      Boolean status = FALSE;
  489.  
  490.      if (node && index(node, '(') && index(node, ')')) {
  491.       file = substr(node, iindex(node, '(') + 1,
  492.             iindex(node, ')') - 1);
  493.       node = index(node, ')') + 1;
  494.      }
  495.      if (!node || !*node)
  496.       node = "Top";
  497.  
  498.      if (!file)
  499.       file = iw->info.file;
  500.  
  501.      if (!DATA(iw) || strcomp(file_name(file), file_name(iw->info.file))) {
  502.       /* get a new file */
  503.       if ((file = getFile(iw, file, FALSE)) != NULL) {
  504.            if (file && iw->info.file != file) {
  505.             XtFree(iw->info.file);
  506.             iw->info.file = XtNewString(file);
  507.            }
  508.            iw->info.subFile = NULL;
  509.       }
  510.      }
  511.      else if (!strcomp(node, iw->info.node))
  512.       return(TRUE);    /* we're already there */
  513.      else {
  514.       XtFree(iw->info.node);
  515.       iw->info.node = XtNewString(node);
  516.      }
  517.      if (file && (offset = findNode(iw, node)) >= 0) {
  518.       if (!pushTo) {
  519.            cur = pushNode(iw, iw->info.file, iw->info.node, offset);
  520.            parseNode(iw, cur, offset);
  521.       }
  522.       else
  523.            cur = pushTo;
  524.       displayNode(iw, cur);
  525.       message(iw, NULL);
  526.       showStatus(iw, cur);
  527.       status = TRUE;
  528.      }
  529.      else {
  530.       /* Failed to get the new node, go back (but only once) */
  531.       if (!pushTo && CURNODE(iw))
  532.            getNode(iw, CURNODE(iw)->file, CURNODE(iw)->node, CURNODE(iw));
  533.      }
  534.      return(status);
  535. }
  536.  
  537. /* Loads in file "name" and tag/indirect info, if any. */
  538. Local String getFile(iw, name, subfilep)
  539. InfoWidget iw;
  540. String name;
  541. Boolean subfilep;
  542. {
  543.      String ret;
  544.  
  545.      FILE *fp;
  546.  
  547.      ret = find_file(iw->info.path, name);
  548.      if (ret) {
  549.       Import int stat();
  550.       struct stat sb;
  551.  
  552.       if (!stat(ret, &sb) && (fp = fopen(ret, "r"))) {
  553.            if (DATA(iw))
  554.             XtFree(DATA(iw));
  555.            DATA(iw) = XtMalloc(sb.st_size);
  556.            if (fread(DATA(iw), 1, sb.st_size, fp) == sb.st_size) {
  557.             fclose(fp);
  558.             DATASIZE(iw) = sb.st_size;
  559.             if (!subfilep) {
  560.              Boolean needIndirect;
  561.  
  562.              needIndirect = parseTags(iw);
  563.              parseIndirect(iw, needIndirect);
  564.             }
  565.            }
  566.            else {
  567.             message(iw, "?Read error on %s.", name);
  568.             XtFree(DATA(iw));
  569.             ret = NULL;
  570.            }
  571.       }
  572.       else
  573.            ret = NULL;
  574.      }
  575.      return(ret);
  576. }
  577.  
  578. /* Look through tag table (and/or current buffer) for a node */
  579. Local int findNode(iw, name)
  580. InfoWidget iw;
  581. String name;
  582. {
  583.      ID_P i;
  584.      int offset = -1;
  585.      String s, srch;
  586.  
  587.      /* A node name of "*" means the whole file */
  588.      if (!strcomp(name, "*"))
  589.       return(0);
  590.  
  591.      if (TAGTABLE(iw).table) {
  592.       for (i = TAGTABLE(iw).table; I_NAME(*i); i++) {
  593.            if (!strcomp(I_NAME(*i), name)) {
  594.             offset = I_OFFSET(*i);
  595.             break;
  596.            }
  597.       }
  598.       /* if we found the tag and there's an indirect table, adjust */
  599.       if (offset > 0 && INDIRECT(iw).table) {
  600.            String sub;
  601.  
  602.            for (i = INDIRECT(iw).table; I_NAME(*i); i++) {
  603.             if (I_OFFSET(*i) > offset)    /* got it */
  604.              break;
  605.            }
  606.            sub = I_NAME(*(--i));
  607.            if (strcomp(sub, iw->info.subFile)) {
  608.             if (!getFile(iw, sub, TRUE))
  609.              return(0);
  610.             else
  611.              iw->info.subFile = sub;
  612.            }
  613.            offset -= I_OFFSET(*i);
  614.            /* compensate for header */
  615.            offset += HDRSIZE(iw);
  616.       }
  617.      }
  618.      /*
  619.       * Now search forward for the node name. Note that this will
  620.       * work whether or not we found the tag in the tag table. Having
  621.       * found the tag only insures that we search a little less (personally,
  622.       * I think that this whole indirect/tag nonsense is a bad idea. If
  623.       * one could count on tags always being accurate, I wouldn't mind so
  624.       * much, but one can't. As it is, it's just a pain in the ass on fast VM
  625.       * systems where file size and search time aren't such significant issues
  626.       * for files <~600K. Think how much simpler this code would be if
  627.       * we didn't have to deal with all the indirect/tag crap).
  628.       */
  629.  
  630.      s = START(iw);
  631.      if (offset > 0)
  632.       s += offset;
  633.  
  634.      /*
  635.       * since bogus tags can leave us *after* the node start as well as
  636.       * before it, we risk a little extra searching and back up to the
  637.       * closest node marker above. Es tut mir leid, but this is what you
  638.       * get with out-of-date tags!
  639.       */
  640.      while (s > START(iw) && *s != INFO_CHAR)
  641.       --s;
  642.      srch = strconcat(NODE_TOKEN, name);
  643.      while (s) {
  644.       if ((s = search(iw, s, END(iw), srch, TRUE)) != NULL) {
  645.            /* If not an exact match, keep looking */
  646.            if (!index(NAME, *s))
  647.             continue;
  648.            offset = INTOFF(START(iw), s);
  649.            /* found it, move to the beginning */ 
  650.            while(START(iw)[offset - 1] != INFO_CHAR)
  651.             offset--;
  652.            s = NULL;
  653.       }
  654.       else
  655.            offset = -1;
  656.      }
  657.      return(offset);
  658. }
  659.      
  660. /* Push a node onto the history list */
  661. Local NodeInfo *pushNode(iw, file, node, offset)
  662. InfoWidget iw;
  663. String file, node;
  664. int offset;
  665. {
  666.      NodeInfo *tmp;
  667.  
  668.      tmp = (NodeInfo *)XtMalloc(sizeof(NodeInfo));
  669.      tmp->file = XtNewString(file);
  670.      tmp->node = XtNewString(node);
  671.      tmp->start = offset;
  672.      tmp->nextNode = CURNODE(iw);
  673.      CURNODE(iw) = tmp;
  674.      return(tmp);
  675. }
  676.  
  677. /* Pop a node off the history list */
  678. Local NodeInfo *popNode(iw)
  679. InfoWidget iw;
  680. {
  681.      NodeInfo *tmp = NULL;
  682.  
  683.      if (CURNODE(iw) && CURNODE(iw)->nextNode) {
  684.       tmp = CURNODE(iw)->nextNode;
  685.       XtFree(CURNODE(iw)->file);
  686.       XtFree(CURNODE(iw)->node);
  687.       XtFree(CURNODE(iw));
  688.       CURNODE(iw) = tmp;
  689.      }
  690.      return(tmp);
  691. }
  692.  
  693. /* Parse out all the header/menu/xref information for a node. */
  694. Local void parseNode(iw, n, offset)
  695. InfoWidget iw;
  696. NodeInfo *n;
  697. int offset;
  698. {
  699.      register String start = START(iw) + offset;
  700.  
  701.      /* was the whole file ("*") selected? */
  702.      if (offset == 0) {
  703.       n->length = DATASIZE(iw);
  704.       I_START(n->name) = I_LEN(n->name) = 0;
  705.       I_START(n->prev) = I_LEN(n->prev) = 0;
  706.       I_START(n->up) = I_LEN(n->up) = 0;
  707.       I_START(n->next) = I_LEN(n->next) = 0;
  708.       I_START(n->text) = 0;
  709.       I_LEN(n->text) = n->length;
  710.      }
  711.      else {
  712.       /* find the end of the node */
  713.       n->length = 0;
  714.       start = START(iw) + offset;
  715.       while (start < END(iw) && *start != INFO_CHAR) {
  716.            n->length++;
  717.            start++;
  718.       }
  719.      }
  720.      /* get the header */
  721.      parseHeader(iw, n);
  722.      /* get the menu items */
  723.      parseMenu(iw, n);
  724.      /* get the cross reference entries */
  725.      parseXRefs(iw, n);
  726. }
  727.  
  728. Local void parseHeader(iw, n)
  729. InfoWidget iw;
  730. NodeInfo *n;
  731. {
  732.      String strpbrk(), tmp;
  733.  
  734.      /* first, get the node name offset */
  735.      I_START(n->name) = INTOFF(START(iw), NSEARCH(iw, n, NODE_TOKEN));
  736.      I_LEN(n->name) = INTOFF(START(iw), strpbrk(START(iw) + I_START(n->name),
  737.                            NAME_END_TOKEN)) -
  738.                             I_START(n->name);
  739.  
  740.      /* now the prev, if any */
  741.      if ((I_START(n->prev) = INTOFF(START(iw),
  742.                     NSEARCH(iw, n, PREV_TOKEN))) > 0)
  743.       I_LEN(n->prev) = INTOFF(START(iw),
  744.                   strpbrk(START(iw) + I_START(n->prev),
  745.                             NAME_END_TOKEN)) -
  746.                              I_START(n->prev);
  747.      else
  748.       I_LEN(n->prev) = I_START(n->prev) = 0;
  749.  
  750.      /* and the up, if any */
  751.      if ((I_START(n->up) = INTOFF(START(iw),
  752.                   NSEARCH(iw, n, UP_TOKEN))) > 0)
  753.       I_LEN(n->up) = INTOFF(START(iw), strpbrk(START(iw) + I_START(n->up),
  754.                           NAME_END_TOKEN)) -
  755.                                I_START(n->up);
  756.      else
  757.       I_LEN(n->up) = I_START(n->up) = 0;
  758.  
  759.      /* the next, if any */
  760.      if ((I_START(n->next) = INTOFF(START(iw),
  761.                     NSEARCH(iw, n, NEXT_TOKEN))) > 0)
  762.       I_LEN(n->next) = INTOFF(START(iw),
  763.                   strpbrk(START(iw) + I_START(n->next),
  764.                             NAME_END_TOKEN)) -
  765.                              I_START(n->next);
  766.      else
  767.       I_LEN(n->next) = I_START(n->next) = 0;
  768.  
  769.      /* And finally skip over the header and set the text offset there */
  770.      tmp = START(iw) + I_START(n->name);
  771.      while (*tmp != '\n')
  772.       tmp++;
  773.      I_START(n->text) = INTOFF(START(iw), tmp + 1);
  774.      I_LEN(n->text) = n->length - (I_START(n->text) - n->start);
  775. }
  776.  
  777. Local void parseMenu(iw, n)
  778. InfoWidget iw;
  779. NodeInfo *n;
  780. {
  781.      register String mstart;
  782.  
  783.      /* start clean */
  784.      ZERO_LIST(n->menu);
  785.  
  786.      /* Does node have a menu? */
  787.      if ((mstart = NSEARCH(iw, n, MENU_TOKEN)) != NULL) {
  788.       /* Initialize table and string list */
  789.       ALLOC_LIST(n->menu);
  790.  
  791.       /* go looking for menu items */
  792.       while (mstart = search(iw, mstart, NEND(iw, n), MENU_SEP_TOKEN,
  793.                  FALSE)) {
  794.            MAYBE_BUMP_LIST(n->menu);
  795.            I_LEN(TPOS(n->menu.t)) = 0;
  796.            I_START(TPOS(n->menu.t)) = INTOFF(START(iw), mstart);
  797.            while (*(mstart++) != ':')
  798.             I_LEN(TPOS(n->menu.t))++;
  799.            /* save the menu name as a string */
  800.            LPOS(n->menu) = XtMalloc(I_LEN(TPOS(n->menu.t)) + 1);
  801.            strncpy(LPOS(n->menu), START(iw) + I_START(TPOS(n->menu.t)),
  802.                I_LEN(TPOS(n->menu.t)));
  803.            LPOS(n->menu)[I_LEN(TPOS(n->menu.t))] = '\0';
  804.            strip_evil(LPOS(n->menu));
  805.            /* Is the menu name not the node name? */
  806.            if (*mstart != ':') {
  807.             mstart = eat_whitespace(mstart);
  808.             I_START(TPOS(n->menu.t)) = INTOFF(START(iw), mstart);
  809.             I_LEN(TPOS(n->menu.t)) =
  810.              INTOFF(START(iw), strpbrk(mstart, NAME_END_TOKEN)) -
  811.                   I_START(TPOS(n->menu.t));
  812.            }
  813.            INCP(n->menu.t);
  814.       }
  815.       ROUND_LIST(n->menu);
  816.      }
  817. }
  818.       
  819. Local void parseXRefs(iw, n)
  820. InfoWidget iw;
  821. NodeInfo *n;
  822. {
  823.      register String nstart;
  824.  
  825.      /* start clean */
  826.      ZERO_LIST(n->xref);
  827.  
  828.      /* Do we have any cross-reference entries? */
  829.      if ((nstart = search(iw, NSTART(iw, n), NEND(iw, n), NOTE_TOKEN, TRUE))
  830.      != NULL) {
  831.       ALLOC_LIST(n->xref);
  832.       nstart = NSTART(iw, n);
  833.  
  834.       /*
  835.        * Go looking for cross-references (including the one we just
  836.        * found; wasteful, but avoiding it would make for grotty code).
  837.         */
  838.       while (nstart = search(iw, nstart, NEND(iw, n), NOTE_TOKEN, TRUE)) {
  839.            /* skip over whitespace */
  840.            nstart = eat_whitespace(nstart);
  841.            MAYBE_BUMP_LIST(n->xref);
  842.            I_LEN(TPOS(n->xref.t)) = 0;
  843.            I_START(TPOS(n->xref.t)) = INTOFF(START(iw), nstart);
  844.            while (*(nstart++) != ':')
  845.             I_LEN(TPOS(n->xref.t))++;
  846.            /* save the note name as a string */
  847.            LPOS(n->xref) = XtMalloc(I_LEN(TPOS(n->xref.t)) + 1);
  848.            strncpy(LPOS(n->xref), START(iw) + I_START(TPOS(n->xref.t)),
  849.                I_LEN(TPOS(n->xref.t)));
  850.            LPOS(n->xref)[I_LEN(TPOS(n->xref.t))] = '\0';
  851.            strip_evil(LPOS(n->xref));
  852.            /* Is the note name not the first part? */
  853.            if (*nstart != ':') {
  854.             nstart = eat_whitespace(nstart + 1);
  855.             I_START(TPOS(n->xref.t)) = INTOFF(START(iw), nstart);
  856.             I_LEN(TPOS(n->xref.t)) =
  857.              INTOFF(START(iw), strpbrk(nstart, NAME_END_TOKEN)) -
  858.                   I_START(TPOS(n->xref.t));
  859.            }
  860.            INCP(n->xref.t);
  861.       }
  862.       ROUND_LIST(n->xref);
  863.      }
  864. }           
  865.  
  866. /* Put the node information on the screen */
  867. Local void displayNode(iw, n)
  868. InfoWidget iw;
  869. NodeInfo *n;
  870. {
  871.      Arg args[5];
  872.      Cardinal i, lst_size;
  873.      String *lst;
  874.      Local char *nolist[] = { "", NULL };     /* make the list widget happy */
  875.      Local char tmpfile[256];
  876.  
  877.      /* show the header */
  878.      displayHeader(iw, n);
  879.  
  880.      /* show the menu */
  881.      if (!n->menu.l) {
  882.       lst = nolist;
  883.       lst_size = 1;
  884.      }
  885.      else {
  886.       lst = n->menu.l;
  887.       lst_size = IDX(n->menu.t);
  888.      }
  889.      XawPanedAllowResize(iw->info.menuList, TRUE);
  890.      XawListChange(iw->info.menuList, lst, lst_size, 0, TRUE);
  891.  
  892.      /* change the xref list */
  893.      if (!n->xref.l) {
  894.       lst = nolist;
  895.       lst_size = 1;
  896.      }
  897.      else {
  898.       lst = n->xref.l;
  899.       lst_size = IDX(n->xref.t);
  900.      }
  901.      XawPanedAllowResize(iw->info.xrefList, TRUE);
  902.      XawListChange(iw->info.xrefList, lst, lst_size, 0, TRUE);
  903.  
  904.      /* Show the new text */
  905.      i = 0;
  906.      if (iw->info.tempfile) {
  907.       extern int unlink();
  908.  
  909.       unlink(iw->info.tempfile);
  910.       iw->info.tempfile = NULL;
  911.      }
  912.      if (I_START(n->text)) {
  913.       FILE *fp;
  914.  
  915.       iw->info.tempfile = tmpnam(tmpfile);
  916.       if ((fp = fopen(iw->info.tempfile, "w")) == NULL) {
  917.            XtError("info: Can't open temporary file");
  918.            return;
  919.       }
  920.       else {
  921.            fwrite(START(iw) + I_START(n->text), 1, I_LEN(n->text), fp);
  922.            fclose(fp);
  923.       }
  924.       XtSetArg(args[i], XtNstring, iw->info.tempfile);        i++;
  925.      }
  926.      else {
  927.       iw->info.tempfile = NULL;
  928.       XtSetArg(args[i], XtNstring, iw->info.file);            i++;
  929.      }
  930.      XtSetValues(iw->info.nodeText, args, i);
  931. }
  932.  
  933. /* display the header information */
  934. Local void displayHeader(iw, n)
  935. InfoWidget iw;
  936. NodeInfo *n;
  937. {
  938.      Arg args[5];
  939.      Cardinal i;
  940.      String tmp;
  941.      int sensitive;
  942.  
  943.      /* set the file name */
  944.      tmp = strconcat("File: ", file_name(iw->info.file));
  945.      i = 0;
  946.      XtSetArg(args[i], XtNlabel, tmp);                    i++;
  947.      XtSetValues(iw->info.fileLabel, args, i);
  948.  
  949.      /* set the node name */
  950.      i = 0;
  951.      if ((tmp = offsetToString(iw, n->name)) != NULL)
  952.       sensitive = TRUE;
  953.      else
  954.       sensitive = FALSE;
  955.      XtSetArg(args[i], XtNlabel, strconcat("Node: ", tmp));        i++;
  956.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  957.      XtSetValues(iw->info.nodeLabel, args, i);
  958.  
  959.      /* set the prev */
  960.      i = 0;
  961.      if ((tmp = offsetToString(iw, n->prev)) != NULL)
  962.       sensitive = TRUE;
  963.      else
  964.       sensitive = FALSE;
  965.      XtSetArg(args[i], XtNlabel, strconcat("Prev: ", tmp));        i++;
  966.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  967.      XtSetValues(iw->info.prevCmd, args, i);
  968.  
  969.      /* set the up */
  970.      i = 0;
  971.      if ((tmp = offsetToString(iw, n->up)) != NULL)
  972.       sensitive = TRUE;
  973.      else
  974.       sensitive = FALSE;
  975.      XtSetArg(args[i], XtNlabel, strconcat("Up: ", tmp));        i++;
  976.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  977.      XtSetValues(iw->info.upCmd, args, i);
  978.  
  979.      /* set the next */
  980.      i = 0;
  981.      if ((tmp = offsetToString(iw, n->next)) != NULL)
  982.       sensitive = TRUE;
  983.      else
  984.       sensitive = FALSE;
  985.      XtSetArg(args[i], XtNlabel, strconcat("Next: ", tmp));        i++;
  986.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  987.      XtSetValues(iw->info.nextCmd, args, i);
  988. }
  989.  
  990. /*
  991.  * Look for tag table information in the current buffer. If tag table
  992.  * is indirect, return TRUE, else return false.
  993.  */
  994. Local Boolean parseTags(iw)
  995. InfoWidget iw;
  996. {
  997.      String start, s1;
  998.      char tmp[MAXSTR];
  999.      Boolean indirect = FALSE;
  1000.      int i;
  1001.  
  1002.      /*
  1003.       * go back about 8 lines. I don't know if this will always back up
  1004.       * past the end marker, but Emacs info seems to think so.
  1005.       */
  1006.      start = END(iw);
  1007.      i = 0;
  1008.      while (i < 8)
  1009.       if (*(--start) == '\n')
  1010.            i++;
  1011.  
  1012.      start = search(iw, start, END(iw), TAGEND_TOKEN, TRUE);
  1013.      if (start && (start = search_back(iw, start, START(iw),
  1014.                        TAGTABLE_TOKEN, TRUE))) {
  1015.       ALLOC_TABLE(TAGTABLE(iw));
  1016.       /* we were searching backward so move over the token */
  1017.       start += strlen(TAGTABLE_TOKEN);
  1018.       if ((s1 = search(iw, start, start + strlen(ITAGTABLE_TOKEN) + 10,
  1019.                ITAGTABLE_TOKEN, TRUE)) != NULL) {
  1020.            indirect = TRUE;
  1021.            start = s1;
  1022.       }
  1023.       while ((start = search(iw, start, END(iw), NODE_TOKEN, FALSE))
  1024.          != NULL) {
  1025.            MAYBE_BUMP_TABLE(TAGTABLE(iw));
  1026.            strccpy(tmp, start, DEL_CHAR);
  1027.            I_NAME(TPOS(TAGTABLE(iw))) = XtNewString(tmp);
  1028.            start += strlen(tmp) + 1;
  1029.            sscanf(start, "%d", &I_OFFSET(TPOS(TAGTABLE(iw))));
  1030.            INCP(TAGTABLE(iw));
  1031.       }
  1032.       ROUND_TABLE(TAGTABLE(iw));
  1033.      }
  1034.      else if (TAGTABLE(iw).table)
  1035.       FREE_TAG_TABLE(TAGTABLE(iw));
  1036.      return(indirect);
  1037. }
  1038.  
  1039. /* Look for indirect file information in the current buffer */
  1040. Local void parseIndirect(iw, needIndirect)
  1041. InfoWidget iw;
  1042. Boolean needIndirect;
  1043. {
  1044.      String start;
  1045.      char tmp[MAXSTR], *s1;
  1046.  
  1047.      if (start = search(iw, START(iw), END(iw), INDIRECT_TOKEN, TRUE)) {
  1048.       /* move backwards looking for the INFO_CHAR */
  1049.       for (s1 = start; s1 >= START(iw) && *s1 != INFO_CHAR; s1--);
  1050.       if (s1 < START(iw)) {
  1051.            message(iw, "?Invalid indirect table for %s!", iw->info.file);
  1052.            return;
  1053.       }
  1054.       else
  1055.            HDRSIZE(iw) = INTOFF(START(iw), s1);
  1056.       ALLOC_TABLE(INDIRECT(iw));
  1057.       for (IDX(INDIRECT(iw)) = 0; *start != INFO_CHAR; INCP(INDIRECT(iw))){
  1058.            MAYBE_BUMP_TABLE(INDIRECT(iw));
  1059.            strccpy(tmp, start, ':');
  1060.            I_NAME(TPOS(INDIRECT(iw))) = XtNewString(tmp);
  1061.            start += strlen(tmp) + 1;
  1062.            sscanf(start, "%d", &I_OFFSET(TPOS(INDIRECT(iw))));
  1063.            start = index(start, '\n') + 1;
  1064.       }
  1065.       ROUND_TABLE(INDIRECT(iw));
  1066.      }
  1067.      else if (needIndirect)
  1068.       message(iw, "?Indirect table not found for %s! Hilfe!",
  1069.           iw->info.file);
  1070.      else if (INDIRECT(iw).table)
  1071.       FREE_TAG_TABLE(INDIRECT(iw));
  1072. }
  1073.  
  1074. /*****************************************************************************
  1075.  * Text display functions.                                                   *
  1076.  *****************************************************************************/
  1077.  
  1078. /* display a message in the message area */
  1079. Local void message(iw, s, p1, p2, p3)
  1080. InfoWidget iw;
  1081. String s;
  1082. caddr_t p1, p2, p3;
  1083. {
  1084.      char msgbuf[MAXSTR];
  1085.      Arg args[5];
  1086.      Cardinal i;
  1087.  
  1088.      i = 0;
  1089.      if (s) {
  1090.       sprintf(msgbuf, s, p1, p2, p3);
  1091.       XtSetArg(args[i], XtNlabel, msgbuf);    i++;
  1092.       XtSetValues(iw->info.messageLabel, args, i);
  1093.       XBell(XtDisplay(iw), 50);
  1094.       if (*s == '?')    /* a dire warning */
  1095.            XtWarning(msgbuf);
  1096.      }
  1097.      else {    /* clear the message area */
  1098.       XtSetArg(args[i], XtNlabel, " ");    i++;
  1099.       XtSetValues(iw->info.messageLabel, args, i);
  1100.      }
  1101. }
  1102.  
  1103. /* display the current node/file */
  1104. Local void showStatus(iw, n)
  1105. InfoWidget iw;
  1106. NodeInfo *n;
  1107. {
  1108.      char statbuf[MAXSTR];
  1109.      Arg args[5];
  1110.      Cardinal i;
  1111.      String sub = iw->info.subFile;
  1112.  
  1113.      sprintf(statbuf, "(%s)%s, %d characters%s", file_name(iw->info.file),
  1114.          iw->info.node, n->length,
  1115.          sub ? strconcat(", subfile: ", sub) : ".");
  1116.      i = 0;
  1117.      XtSetArg(args[i], XtNlabel, statbuf);    i++;
  1118.      XtSetValues(iw->info.statusLabel, args, i);
  1119. }
  1120.  
  1121. /*****************************************************************************
  1122.  * Various callback/action routines                                          *
  1123.  *****************************************************************************/
  1124.  
  1125. Local void NodeDir(w, event, params, num_params)
  1126. Widget   w;
  1127. XEvent   *event;
  1128. String   *params;
  1129. Cardinal *num_params;
  1130. {
  1131.      InfoWidget iw = TOP_WIDGET(w);
  1132.  
  1133.      if (getNode(iw, "dir", "Top", NULL) == FALSE)
  1134.       message(iw, "?Yow! The directory seems to have disappeared!\n");
  1135. }
  1136.  
  1137. Local void NodeNext(w, event, params, num_params)
  1138. Widget   w;
  1139. XEvent   *event;
  1140. String   *params;
  1141. Cardinal *num_params;
  1142. {
  1143.      InfoWidget iw = TOP_WIDGET(w);
  1144.  
  1145.      do_next(NULL, iw, NULL);
  1146. }
  1147.  
  1148. Local void NodePrev(w, event, params, num_params)
  1149. Widget   w;
  1150. XEvent   *event;
  1151. String   *params;
  1152. Cardinal *num_params;
  1153. {
  1154.      InfoWidget iw = TOP_WIDGET(w);
  1155.  
  1156.      do_prev(NULL, iw, NULL);
  1157. }
  1158.  
  1159.  
  1160. Local void NodeUp(w, event, params, num_params)
  1161. Widget   w;
  1162. XEvent   *event;
  1163. String   *params;
  1164. Cardinal *num_params;
  1165. {
  1166.      InfoWidget iw = TOP_WIDGET(w);
  1167.  
  1168.      do_up(NULL, iw, NULL);
  1169. }
  1170.  
  1171. Local void NodeTop(w, event, params, num_params)
  1172. Widget   w;
  1173. XEvent   *event;
  1174. String   *params;
  1175. Cardinal *num_params;
  1176. {
  1177.      InfoWidget iw = TOP_WIDGET(w);
  1178.  
  1179.      if (getNode(iw, NULL, "Top", NULL) == FALSE)
  1180.       message(iw, "?This node has no top! Bad joss!");
  1181. }
  1182.  
  1183. Local void NodeLast(w, event, params, num_params)
  1184. Widget   w;
  1185. XEvent   *event;
  1186. String   *params;
  1187. Cardinal *num_params;
  1188. {
  1189.      NodeInfo *tmp;
  1190.      InfoWidget iw = TOP_WIDGET(w);
  1191.      
  1192.      if ((tmp = popNode(iw)) != NULL) {
  1193.       if (getNode(iw, tmp->file, tmp->node, tmp) == FALSE)
  1194.            message(iw, "?Can't pop back to last node! We're hosed!");
  1195.      }
  1196.      else
  1197.       message(iw, "No further history.");
  1198. }
  1199.  
  1200. Local void NodeXRef(w, event, params, num_params)
  1201. Widget   w;
  1202. XEvent   *event;
  1203. String   *params;
  1204. Cardinal *num_params;
  1205. {
  1206.      InfoWidget iw = TOP_WIDGET(w);
  1207.  
  1208.      do_xref(NULL, iw, NULL);
  1209. }
  1210.  
  1211. Local void NodeGoto(w, event, params, num_params)
  1212. Widget   w;
  1213. XEvent   *event;
  1214. String   *params;
  1215. Cardinal *num_params;
  1216. {
  1217.      InfoWidget iw = TOP_WIDGET(w);
  1218.  
  1219.      do_goto(NULL, iw, NULL);
  1220. }
  1221.  
  1222. Local void NodeSearch(w, event, params, num_params)
  1223. Widget   w;
  1224. XEvent   *event;
  1225. String   *params;
  1226. Cardinal *num_params;
  1227. {
  1228.      InfoWidget iw = TOP_WIDGET(w);
  1229.  
  1230.      do_search(NULL, iw, NULL);
  1231. }
  1232.  
  1233. Local void NodeQuit(w, event, params, num_params)
  1234. Widget   w;
  1235. XEvent   *event;
  1236. String   *params;
  1237. Cardinal *num_params;
  1238. {
  1239.      InfoWidget iw = TOP_WIDGET(w);
  1240.  
  1241.      if (XtHasCallbacks(iw, XtNcallback) != XtCallbackHasSome)
  1242.       message(iw, "Sorry, I just don't know how to quit.");
  1243.      else
  1244.       XtCallCallbacks(iw, XtNcallback, NULL);
  1245. }
  1246.  
  1247. Local void NodeTutorial(w, event, params, num_params)
  1248. Widget   w;
  1249. XEvent   *event;
  1250. String   *params;
  1251. Cardinal *num_params;
  1252. {
  1253.      InfoWidget iw = TOP_WIDGET(w);
  1254.  
  1255.      if (getNode(iw, "info", "Help", NULL) == FALSE)
  1256.       message(iw, "?Hmmm. I can't seem to find the info tutorial!");
  1257. }
  1258.  
  1259. /*
  1260.  * Seems there should be a better way of doing this. Methinks the
  1261.  * XtCallbackPopdown() stuff isn't general enough. Should be a way of
  1262.  * doing this (and only this).
  1263.  */
  1264. Local void popdown(w, client_data, call_data)
  1265. Widget w;
  1266. caddr_t client_data;
  1267. caddr_t call_data;
  1268. {
  1269.      XtPopdown((Widget)client_data);
  1270. }
  1271.  
  1272. Local void NodeHelp(w, event, params, num_params)
  1273. Widget   w;
  1274. XEvent   *event;
  1275. String   *params;
  1276. Cardinal *num_params;
  1277. {
  1278.      Cardinal i;
  1279.      Arg args[5];
  1280.      InfoWidget iw = TOP_WIDGET(w);
  1281.      
  1282.      if (!iw->info.helpPopup) {
  1283.       Widget hpane, htext;
  1284.       Local XtCallbackRec cb[2];
  1285.       Local char pophelp[] = "None<Key>q:    MenuPopdown(help)\n";
  1286.  
  1287.       /* create the help popup */
  1288.       i = 0;
  1289.       XtSetArg(args[i], XtNheight, 300);            i++;
  1290.       XtSetArg(args[i], XtNwidth, 400);            i++; 
  1291.       iw->info.helpPopup = XtCreatePopupShell("help",
  1292.                           topLevelShellWidgetClass,
  1293.                           iw, args, i);
  1294.       i = 0;
  1295.       hpane = XtCreateManagedWidget("pane", panedWidgetClass,
  1296.                     iw->info.helpPopup, args, i);
  1297.       i = 0;
  1298.       cb[0].callback = popdown;
  1299.       cb[0].closure = (caddr_t)iw->info.helpPopup;
  1300.       XtSetArg(args[i], XtNcallback, cb);            i++;
  1301.       XtCreateManagedWidget("Close", commandWidgetClass,
  1302.                 hpane, args, i);
  1303.       i = 0;
  1304.       XtSetArg(args[i], XtNtype, XawAsciiFile);        i++;
  1305.       XtSetArg(args[i], XtNstring, iw->info.helpFile);    i++;
  1306.       XtSetArg(args[i], XtNeditType, XawtextRead);        i++;
  1307.       XtSetArg(args[i], XtNscrollVertical, XawtextScrollWhenNeeded); i++;
  1308.       XtSetArg(args[i], XtNwrap, XawtextWrapWord);        i++;
  1309.       htext = XtCreateManagedWidget("text", asciiTextWidgetClass,
  1310.                     hpane, args, i);
  1311.       XtOverrideTranslations(htext, XtParseTranslationTable(textTrans));
  1312.       XtOverrideTranslations(htext, XtParseTranslationTable(pophelp));
  1313.      }
  1314.      XtPopup(iw->info.helpPopup, XtGrabNonexclusive);
  1315. }
  1316.  
  1317. Local void NodeMenuSelectByNumber(w, event, params, num_params)
  1318. Widget   w;
  1319. XEvent   *event;
  1320. String   *params;
  1321. Cardinal *num_params;
  1322. {
  1323.      Import int atoi();
  1324.      int menunum;
  1325.      int nitems;
  1326.      InfoWidget iw = TOP_WIDGET(w);
  1327.  
  1328.      nitems = IDX(CURNODE(iw)->menu.t);
  1329.      menunum = atoi(*params);
  1330.      /* menu number of zero means get menu from arg area */
  1331.      if (!menunum)
  1332.       do_menu(NULL, iw, NULL);
  1333.      else if (!nitems)
  1334.       message(iw, "No menu for this node.");
  1335.      else if (menunum > nitems)
  1336.       message(iw, "There are only %d menu items.", nitems);
  1337.      else {
  1338.       XawListHighlight(iw->info.menuList, menunum - 1);
  1339.       if (getNode(iw, NULL,
  1340.               offsetToString(iw,CURNODE(iw)->menu.t.table[menunum-1]),
  1341.               NULL) == FALSE)
  1342.            message(iw, "?Can't find node for menu item #%s", *params);
  1343.      }
  1344. }
  1345.  
  1346. Local void NodePrint(w, event, params, num_params)
  1347. Widget   w;
  1348. XEvent   *event;
  1349. String   *params;
  1350. Cardinal *num_params;
  1351. {
  1352.      Import int unlink();
  1353.      String tmp;
  1354.      FILE *out;
  1355.      InfoWidget iw = TOP_WIDGET(w);
  1356.  
  1357.      /* if you don't have this routine in your stdlib, make one up */
  1358.      tmp = tmpnam(NULL);
  1359.  
  1360.      if (!CURNODE(iw))
  1361.       message(iw, "?No current node?");
  1362.      else if ((out = fopen(tmp, "w")) == NULL)
  1363.       message(iw, "?Can't open temporary file '%s'.", tmp);
  1364.      else {
  1365.       String s1 = NSTART(iw, CURNODE(iw));
  1366.       String s2 = NEND(iw, CURNODE(iw));
  1367.       char syscmd[MAXSTR];
  1368.       int stat;
  1369.  
  1370.       message(iw, "Sending '%s' to the printer, please wait..",
  1371.           iw->info.node);
  1372.  
  1373.       while (s1 < s2) {
  1374.            fputc(*s1, out);
  1375.            s1++;
  1376.       }
  1377.       fclose(out);
  1378.       sprintf(syscmd, "%s %s", iw->info.printCmd, tmp);
  1379.       if ((stat = system(syscmd)) != 0)
  1380.            message(iw, "?'%s' failed with exit status %d. Help!",
  1381.                syscmd, stat);
  1382.       else
  1383.            message(iw, "Finished printing.");
  1384.       unlink(tmp);
  1385.      }
  1386.        
  1387.      
  1388. }
  1389.  
  1390. Local void do_prev(w, client_data, call_data)
  1391. Widget w;
  1392. caddr_t client_data;
  1393. caddr_t call_data;
  1394. {
  1395.      InfoWidget iw = (InfoWidget)client_data;
  1396.      String tmp;
  1397.  
  1398.      if ((tmp = offsetToString(iw, CURNODE(iw)->prev))) {
  1399.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1400.            message(iw, "?Can't find the previous (%s) for this node.",
  1401.                tmp);
  1402.      }
  1403.      else
  1404.       message(iw, "Node has no previous");
  1405. }
  1406.  
  1407. Local void do_up(w, client_data, call_data)
  1408. Widget w;
  1409. caddr_t client_data;
  1410. caddr_t call_data;
  1411. {
  1412.      InfoWidget iw = (InfoWidget)client_data;
  1413.      String tmp;
  1414.  
  1415.      if ((tmp = offsetToString(iw, CURNODE(iw)->up))) {
  1416.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1417.            message(iw, "?Can't find the up (%s) for this node.", tmp);
  1418.      }
  1419.      else
  1420.       message(iw, "Node has no up");
  1421. }
  1422.  
  1423. Local void do_next(w, client_data, call_data)
  1424. Widget w;
  1425. caddr_t client_data;
  1426. caddr_t call_data;
  1427. {
  1428.      InfoWidget iw = (InfoWidget)client_data;
  1429.      String tmp;
  1430.  
  1431.      if ((tmp = offsetToString(iw, CURNODE(iw)->next))) {
  1432.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1433.            message(iw, "?Can't find the next (%s) for this node.", tmp);
  1434.      }
  1435.      else
  1436.       message(iw, "Node has no next");
  1437. }
  1438.  
  1439. Local void do_xref(w, client_data, call_data)
  1440. Widget w;
  1441. caddr_t client_data;
  1442. caddr_t call_data;
  1443. {
  1444.      InfoWidget iw = (InfoWidget)client_data;
  1445.      String tmp;
  1446.  
  1447.      if ((tmp = get_arg(iw)) != NULL) {
  1448.       if ((tmp = trueName(iw, CURNODE(iw)->xref, tmp)) == NULL)
  1449.            message(iw, "No cross reference entry named '%s' in this node.",
  1450.                get_arg(iw));
  1451.       else if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1452.            message(iw, "?Can't find node for xref item '%s'!",
  1453.                get_arg(iw));
  1454.      }
  1455.      else
  1456.       message(iw, "You must supply the name of a cross-reference.");
  1457. }
  1458.  
  1459. Local void do_menu(w, client_data, call_data)
  1460. Widget w;
  1461. caddr_t client_data;
  1462. caddr_t call_data;
  1463. {
  1464.      InfoWidget iw = (InfoWidget)client_data;
  1465.      String tmp;
  1466.  
  1467.      if ((tmp = get_arg(iw)) != NULL) {
  1468.       if ((tmp = trueName(iw, CURNODE(iw)->menu, tmp)) == NULL)
  1469.            message(iw, "No menu entry named '%s' in this node.",
  1470.                get_arg(iw));
  1471.       else if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1472.            message(iw, "?Can't find node for menu item '%s'",
  1473.                get_arg(iw));
  1474.      }
  1475.      else
  1476.       message(iw, "You must supply a menu entry name.");
  1477. }
  1478.  
  1479. Local void do_goto(w, client_data, call_data)
  1480. Widget w;
  1481. caddr_t client_data;
  1482. caddr_t call_data;
  1483. {
  1484.      InfoWidget iw = (InfoWidget)client_data;
  1485.      String tmp;
  1486.  
  1487.      if ((tmp = get_arg(iw)) != NULL) {
  1488.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1489.            message(iw, "Can't find a node named %s", tmp);
  1490.      }
  1491.      else
  1492.       message(iw, "You must supply the name of a node to go to.");
  1493. }
  1494.  
  1495. /*
  1496.  * Implement a somewhat simplistic search strategy. If file has an indirect
  1497.  * list, look for a match in the tag table (since just looking in the current
  1498.  * file probably wouldn't be very useful). If not, then search the current
  1499.  * file. If we're successful in either case, record the position (in the
  1500.  * tags table or the file) so that we don't hit it again right away.
  1501.  */
  1502. Local void do_search(w, client_data, call_data)
  1503. Widget w;
  1504. caddr_t client_data;
  1505. caddr_t call_data;
  1506. {
  1507.      InfoWidget iw = (InfoWidget)client_data;
  1508.      String tmp, s;
  1509.      char name[MAXSTR];
  1510.      Local struct {
  1511.       String file;
  1512.       caddr_t pos;
  1513.      } oldPos;
  1514.  
  1515.      if ((tmp = get_arg(iw)) != NULL) {
  1516.       /* if remembered position is invalid, reset it */
  1517.       if (strcomp(oldPos.file, iw->info.file)) {
  1518.            oldPos.file = iw->info.file;
  1519.            oldPos.pos = NULL;
  1520.       }
  1521.       if (INDIRECT(iw).table) {
  1522.            ID_P i;
  1523.            int len = strlen(tmp);
  1524.  
  1525.            if (oldPos.pos)
  1526.             i = (ID_P)oldPos.pos;
  1527.            else
  1528.             i = TAGTABLE(iw).table;
  1529.            /* do a tags search */
  1530.            while (I_NAME(*i)) {
  1531.             if (!strncomp(I_NAME(*i), tmp, len))
  1532.              break;
  1533.             i++;
  1534.            }
  1535.            /* success? */
  1536.            if (I_NAME(*i)) {
  1537.             oldPos.pos = (caddr_t)(i + 1);
  1538.             if (getNode(iw, iw->info.file, I_NAME(*i), NULL) == FALSE)
  1539.              message(iw, "?Can't find node for tag %s!",
  1540.                  I_NAME(*i));
  1541.            }
  1542.            else {
  1543.             message(iw, "Tag search for '%s' failed.", tmp);
  1544.             oldPos.pos = NULL;
  1545.            }
  1546.       }
  1547.       else {
  1548.            if (oldPos.pos)
  1549.             s = (String)oldPos.pos;
  1550.            else
  1551.             s = START(iw);
  1552.            if ((s = search(iw, s, END(iw), 
  1553.                    strconcat(NODE_TOKEN, tmp),
  1554.                    TRUE)) != NULL) {
  1555.             int i;
  1556.  
  1557.             oldPos.pos = (caddr_t)s;
  1558.             strcpy(name, tmp);
  1559.             i = strlen(name);
  1560.             while (!index(NAME, *s))
  1561.              name[i++] = *s++;
  1562.             name[i] = '\0';
  1563.             if (getNode(iw, iw->info.file, name, NULL) == FALSE)
  1564.              message(iw, "?Can't find node name in search!");
  1565.            }
  1566.            else {
  1567.             message(iw, "Search for '%s' failed.", tmp);
  1568.             oldPos.pos = NULL;
  1569.            }
  1570.       }
  1571.      }
  1572.      else
  1573.       message(iw, "You must supply a search string.");
  1574. }
  1575.  
  1576. /* These two handle selections from the menu and xref lists */
  1577.  
  1578. Local void do_menu_sel(w, client_data, call_data)
  1579. Widget w;
  1580. caddr_t client_data;
  1581. caddr_t call_data;
  1582. {
  1583.      InfoWidget iw = (InfoWidget)client_data;
  1584.      XawListReturnStruct *rs = (XawListReturnStruct *)call_data;
  1585.  
  1586.      if (getNode(iw, NULL, trueName(iw, CURNODE(iw)->menu, rs->string),
  1587.          NULL) == FALSE)
  1588.       message(iw, "?Can't find node for menu item '%s'", rs->string);
  1589. }
  1590.  
  1591. Local void do_xref_sel(w, client_data, call_data)
  1592. Widget w;
  1593. caddr_t client_data;
  1594. caddr_t call_data;
  1595. {
  1596.      InfoWidget iw = (InfoWidget)client_data;
  1597.      XawListReturnStruct *rs = (XawListReturnStruct *)call_data;
  1598.  
  1599.      if (getNode(iw, NULL, trueName(iw, CURNODE(iw)->xref, rs->string),
  1600.          NULL) == FALSE)
  1601.       message(iw, "?Can't find node for cross reference '%s'", rs->string);
  1602. }
  1603.  
  1604. /*****************************************************************************
  1605.  * Utility functions.                                                        *
  1606.  *****************************************************************************/
  1607.  
  1608. /*
  1609.  * Search for a file along a path, returning the complete path name if found.
  1610.  * This routine uses fopen() rather than access() to determine whether a file
  1611.  * exists (and is readable) because access()'s argument macros [X_OK, R_OK, ..]
  1612.  * tend to be in different places on different unix's and it's a pain to find
  1613.  * them reliably.
  1614.  */
  1615. Local String find_file(path, name)
  1616. String path, name;
  1617. {
  1618.      FILE *tmp = NULL;
  1619.      String cp = path;
  1620.      Boolean more_path = TRUE;
  1621.      Local char dir[MAXPATHLEN];
  1622.  
  1623.      dir[0] = '\0';
  1624.  
  1625.      /* absolute path name? */
  1626.      if (name[0] == '/') {
  1627.       if ((tmp = fopen(name, "r")) != NULL) {
  1628.            fclose(tmp);
  1629.            return(name);
  1630.       }
  1631.       else
  1632.            name = file_name(name);
  1633.      }
  1634.      while (!tmp && more_path) {
  1635.           if ((cp = index(path, ':')) != NULL) {
  1636.                strncpy(dir, path, cp - path);
  1637.            dir[cp - path] = '\0';
  1638.                strcat(dir, "/");
  1639.                path = cp + 1;
  1640.           }
  1641.           else {
  1642.                strcpy(dir, path);
  1643.                strcat(dir, "/");
  1644.                more_path = FALSE;
  1645.           }
  1646.           strcat(dir, name);
  1647.           tmp = fopen(dir, "r");
  1648.       if (!tmp)    /* if we failed, try again in lower case */
  1649.            tmp = fopen(downcase(dir), "r");
  1650.      }
  1651.      if (tmp)
  1652.           fclose(tmp);
  1653.      if (dir[0])
  1654.           return(dir);
  1655.      else
  1656.           return(NULL);
  1657. }
  1658.  
  1659. /* return the file part of a path name */
  1660. Local Inline String file_name(s)
  1661. register String s;
  1662. {
  1663.      register int i = strlen(s);
  1664.  
  1665.      while (i) {
  1666.       if (s[i - 1] == '/')
  1667.            return(s + i);
  1668.       i--;
  1669.      }
  1670.      return(s);
  1671. }
  1672.  
  1673. /* strip evil tab/formfeed/newline chars from a string (replacing w/blanks) */
  1674. Local Inline String strip_evil(s)
  1675. register String s;
  1676. {
  1677.      if (s) {
  1678.       while (*s) {
  1679.            if (isspace(*s))
  1680.             *s = ' ';
  1681.            s++;
  1682.       }
  1683.      }
  1684.      return(s);
  1685. }
  1686.  
  1687. /* Convert from an offset ID to a string. */
  1688. Local Inline String offsetToString(iw, blk)
  1689. InfoWidget iw;
  1690. ID blk;
  1691. {
  1692.      Local char ret[MAXSTR];
  1693.  
  1694.      if (I_LEN(blk) != 0) {
  1695.       strncpy(ret, START(iw) + I_START(blk), I_LEN(blk));
  1696.       ret[I_LEN(blk)] = '\0';
  1697.       return(ret);
  1698.      }
  1699.      else
  1700.       return(NULL);
  1701. }
  1702.  
  1703. /* chew through white space */
  1704. Local Inline String eat_whitespace(s)
  1705. register String s;
  1706. {
  1707.      while (*s && isspace(*s))
  1708.       s++;
  1709.      return(s);
  1710. }
  1711.  
  1712. /* Get the argument area as a string */
  1713. Local String get_arg(iw)
  1714. InfoWidget iw;
  1715. {
  1716.      String str;
  1717.      Arg args[1];
  1718.  
  1719.      XtSetArg(args[0], XtNstring, &str);
  1720.      XtGetValues(iw->info.argText, args, 1);
  1721.      if (*str)
  1722.       return(str);
  1723.      else
  1724.       return(NULL);
  1725. }
  1726.  
  1727. /* look up the actual name of a list item */
  1728. Local String trueName(iw, lst, name)
  1729. InfoWidget iw;
  1730. IDList lst;
  1731. String name;
  1732. {
  1733.      register int i;
  1734.  
  1735.      for (i = 0; i < lst.t.idx; i++)
  1736.       if (!strcomp(lst.l[i], name))
  1737.            return(offsetToString(iw, lst.t.table[i]));
  1738.      return(NULL);
  1739. }
  1740.               
  1741. /* Search for a string. */
  1742. Local String search(iw, start, end, str, igncase)
  1743. InfoWidget iw;
  1744. register String start, end, str;
  1745. Boolean igncase;
  1746. {
  1747.      register String ind = str;
  1748.      register String stop = str + strlen(str);
  1749.      register int comp;
  1750.  
  1751.      while (start < end) {
  1752.       if (!igncase)
  1753.            comp = (*start == *ind);
  1754.       else
  1755.            comp = (tolower(*start) == tolower(*ind));
  1756.       if (!comp) {
  1757.            if (ind != str)
  1758.             ind = str;
  1759.            else
  1760.             start++;
  1761.       }
  1762.       else {
  1763.            if (++start <= end && ++ind == stop)
  1764.             return(start);
  1765.       }
  1766.      }
  1767.      return(NULL);
  1768. }
  1769.  
  1770. /*
  1771.  * Like search(), but in the reverse direction.
  1772.  */
  1773. Local String search_back(iw, start, end, str, igncase)
  1774. InfoWidget iw;
  1775. register String start, end, str;
  1776. Boolean igncase;
  1777. {
  1778.      register String ind;
  1779.      register String stop;
  1780.      register int comp;
  1781.  
  1782.      ind = str = reverse(str);
  1783.      stop = ind + strlen(ind);
  1784.  
  1785.      while (start > end) {
  1786.       if (!igncase)
  1787.            comp = (*start == *ind);
  1788.       else
  1789.            comp = (tolower(*start) == tolower(*ind));
  1790.       if (!comp) {
  1791.            if (ind != str)
  1792.             ind = str;
  1793.            else
  1794.             start--;
  1795.       }
  1796.       else {
  1797.            start--;
  1798.            if (++ind == stop)
  1799.             return(start);
  1800.       }
  1801.      }
  1802.      return(NULL);
  1803. }
  1804.  
  1805. /*
  1806.  * Safe and sane strcmp. Deals with null pointer for either arg and ignores
  1807.  * case.
  1808.  */
  1809. Local Inline int strcomp(s1, s2)
  1810. register String s1, s2;
  1811. {
  1812.      if (s1 && s2) {
  1813.       if (strlen(s1) != strlen(s2))
  1814.           return(-1);
  1815.  
  1816.       while (*s1 && *s2 && (tolower(*s1) == tolower(*s2)))
  1817.            s1++, s2++;
  1818.       if (!*s1 && !*s2)
  1819.            return(0);
  1820.       else if (*s1 < *s2)
  1821.            return (-1);
  1822.       else
  1823.            return(1);
  1824.      }
  1825.      else if (!s1 && !s2)
  1826.           return(0);
  1827.      else if (!s1 && s2)
  1828.           return(-1);
  1829.      else
  1830.           return(1);
  1831. }
  1832.  
  1833. /* like above, but stops after n characters */
  1834. Local Inline int strncomp(s1, s2, n)
  1835. register String s1, s2;
  1836. int n;
  1837. {
  1838.      register String s3 = s2 + n;
  1839.  
  1840.      if (s1 && s2) {
  1841.       while (*s1 && *s2 && (tolower(*s1) == tolower(*s2)) && (s2 < s3))
  1842.            s1++, s2++;
  1843.       if (!*s1 && !*s2 || s2 == s3)
  1844.            return(0);
  1845.       else if (*s1 < *s2)
  1846.            return (-1);
  1847.       else
  1848.            return(1);
  1849.      }
  1850.      else if (!s1 && !s2)
  1851.           return(0);
  1852.      else if (!s1 && s2)
  1853.           return(-1);
  1854.      else
  1855.           return(1);
  1856. }
  1857.  
  1858. /* Copy s2 to s1 up to (but not including) character c */
  1859. Local Inline void strccpy(s1, s2, c)
  1860. register String s1, s2;
  1861. register char c;
  1862. {
  1863.      while (*s2 && *s2 != c)
  1864.       *(s1++) = *(s2++);
  1865.      *s1 = '\0';
  1866. }
  1867.  
  1868. /*
  1869.  * Return integer subscript of character 'c' in string 's'.
  1870.  * (why doesn't this already exist in a library somewhere?).
  1871.  */
  1872. Local Inline int iindex(s, c)
  1873. register char *s, c;
  1874. {
  1875.      register char *cp;
  1876.  
  1877.      if (!s)
  1878.           return(-1);
  1879.      cp = index(s, c);
  1880.      if (cp)
  1881.           return(cp - s);
  1882.      else
  1883.           return(-1);
  1884. }
  1885.  
  1886. Local String substr(s, p1, p2)
  1887. register String s;
  1888. register int p1, p2;
  1889. {
  1890.      Local char ret[MAXSTR];
  1891.      register int i = 0;
  1892.  
  1893.      if (p1 > p2) {
  1894.       sprintf(ret, "substr: start %d, end %d. start must be <= end",
  1895.           p1, p2);
  1896.       XtWarning(ret);
  1897.           return(NULL);
  1898.      }
  1899.      if (p2 - p1 > MAXSTR) {
  1900.           sprintf(ret, "substr: end - start is > max len of %d", MAXSTR);
  1901.       XtWarning(ret);
  1902.           return(NULL);
  1903.      }
  1904.      while (p1 <= p2)
  1905.           ret[i++] = s[p1++];
  1906.      ret[i] = '\0';
  1907.      return(ret);
  1908. }
  1909.  
  1910. /*
  1911.  * Safely concatenate two strings into static area, returning pointer to
  1912.  * result.
  1913.  */
  1914. Local String strconcat(s1, s2)
  1915. register String s1, s2;
  1916. {
  1917.      Local char ret[MAXSTR];
  1918.      int len1;
  1919.  
  1920.      if (s1) {
  1921.       if ((len1 = strlen(s1)) >= MAXSTR) {
  1922.            sprintf(ret, "strconcat: length of s1 > MAX (%d)", MAXSTR);
  1923.            XtWarning(ret);
  1924.            return(NULL);
  1925.       }
  1926.       else
  1927.            strcpy(ret, s1);
  1928.       if (s2) {
  1929.            if (len1 + strlen(s2) > MAXSTR) {
  1930.             sprintf(ret, "strconcat: length of s1 + s2 is > MAX (%d)",
  1931.                 MAXSTR);
  1932.             XtWarning(ret);
  1933.            }
  1934.            else
  1935.             strcat(ret, s2);
  1936.       }
  1937.       return(ret);
  1938.      }
  1939.      else
  1940.       return(NULL);
  1941. }
  1942.  
  1943. /* reverse a string so that a simple reverse search may be done on it */
  1944. Local String reverse(s)
  1945. register String s;
  1946. {
  1947.      Local char ret[MAXSTR];
  1948.      register int i, len;
  1949.  
  1950.      if ((len = strlen(s)) > MAXSTR) {
  1951.       sprintf(ret, "reverse: string too long to reverse. MAX is %d",
  1952.           MAXSTR);
  1953.       XtWarning(ret);
  1954.       return(NULL);
  1955.      }
  1956.      else {
  1957.       i = 0;
  1958.       while (len)
  1959.            ret[i++] = s[--len];
  1960.       ret[i] = '\0';
  1961.       return(ret);
  1962.      }
  1963. }
  1964.  
  1965. /* convert a string to lower case */
  1966. Local Inline String downcase(s)
  1967. register String s;
  1968. {
  1969.      String orig = s;
  1970.  
  1971.      if (s)
  1972.       while (*s) {
  1973.            *s = tolower(*s);
  1974.            s++;
  1975.       }
  1976.      return(orig);
  1977. }
  1978.