home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / x / volume10 / infow / part02 / Info.c
Encoding:
C/C++ Source or Header  |  1993-04-28  |  59.5 KB  |  2,289 lines

  1. #ifndef lint
  2. static char *rcsid = "$Header: /usr3/xinfo/RCS/Info.c,v 1.8 90/11/12 18:06:46 jkh Exp $";
  3. #endif
  4.  
  5. #include "InfoP.h"
  6.  
  7. #include <X11/Shell.h>
  8. #include <X11/StringDefs.h>
  9. #include <X11/Xaw/AsciiText.h>
  10. #include <X11/Xaw/Box.h>
  11. #include <X11/Xaw/Command.h>
  12. #include <X11/Xaw/Dialog.h>
  13. #include <X11/Xaw/Label.h>
  14. #include <X11/Xaw/List.h>
  15. #include <X11/Xaw/Paned.h>
  16. #include <X11/Xaw/Viewport.h>
  17.  
  18. #include <sys/stat.h>
  19. #include <stdio.h>
  20. #include <ctype.h>
  21. #include <pwd.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.  * $Log:    Info.c,v $
  53.  * Revision 1.8  90/11/12  18:06:46  jkh
  54.  * Removed aggregate initializations. GCC likes them, noone else does.
  55.  * 
  56.  * Revision 1.7  90/11/12  13:46:11  jkh
  57.  * Fixed bug with bell_volume resource
  58.  * 
  59.  * Revision 1.6  90/11/11  23:22:59  jkh
  60.  * Last minute fixes.
  61.  * 
  62.  * Revision 1.5  90/11/11  22:24:05  jkh
  63.  * Added option to enable/disable retention of arg text.
  64.  * 
  65.  * Revision 1.4  90/11/11  21:19:39  jkh
  66.  * Release 1.01
  67.  * 
  68.  * Revision 1.3  90/11/07  01:28:30  jkh
  69.  * Tweaked dialog popup to accept <return> as fast confirm.
  70.  * 
  71.  * Revision 1.2  90/11/06  15:12:47  jkh
  72.  * Fixed memory leaks
  73.  * 
  74.  * Revision 1.1  90/11/06  01:47:28  jkh
  75.  * Initial revision
  76.  * 
  77.  */
  78.  
  79. #define offset(name)    XtOffset(InfoWidget, info.name)
  80.  
  81. Local XtResource resources[] = {
  82.      { XpNinfoPath, XpCInfoPath, XtRString, sizeof(String),
  83.         offset(path), XtRString, XpDefaultInfoPath            },
  84.      { XpNinfoFile, XpCInfoFile, XtRString, sizeof(String),
  85.         offset(file), XtRString, XpDefaultInfoFile            },
  86.      { XpNinfoNode, XpCInfoNode, XtRString, sizeof(String),
  87.         offset(node), XtRString, XpDefaultInfoNode            },
  88.      { XpNbellVolume, XpCBellVolume, XtRInt, sizeof(int),
  89.         offset(bell_volume), XtRInt, (caddr_t)XpDefaultBellVolume    },
  90.      { XpNretainArg, XpCRetainArg, XtRBoolean, sizeof(Boolean),
  91.         offset(retain_arg), XtRBoolean, FALSE            },
  92.      { XtNcallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
  93.         offset(callback), XtRCallback, NULL                },
  94.      { XpNprintCommand, XpCPrintCommand, XtRString, sizeof(String),
  95.         offset(printCmd), XtRString, XpDefaultPrintCommand        },
  96. };
  97.  
  98. #undef offset
  99.  
  100. Local Boolean SetValues();
  101. Local XtGeometryResult GeometryManager();
  102. Local void Destroy();
  103. Local void Initialize();
  104. Local void Realize();
  105. Local void Resize();
  106.  
  107. /* Routines called directly by actions */
  108. Local void Abort();
  109. Local void ButtonSelection();
  110. Local void Confirm();
  111. Local void NodeDir();
  112. Local void NodeGoto();
  113. Local void NodeHelp();
  114. Local void NodeLast();
  115. Local void NodeMenuSelectByNumber();
  116. Local void NodeNext();
  117. Local void NodePrev();
  118. Local void NodePrint();
  119. Local void NodeQuit();
  120. Local void NodeSearch();
  121. Local void NodeTop();
  122. Local void NodeTutorial();
  123. Local void NodeUp();
  124. Local void NodeXRef();
  125.  
  126. /* Routines called directly from callbacks or indirectly by actions */
  127. Local void do_dialog_abort();
  128. Local void do_dialog_confirm();
  129. Local void do_goto();
  130. Local void do_menu();
  131. Local void do_menu_sel();
  132. Local void do_next();
  133. Local void do_popdown();
  134. Local void do_prev();
  135. Local void do_quit();
  136. Local void do_search();
  137. Local void do_up();
  138. Local void do_xref();
  139. Local void do_xref_sel();
  140.  
  141. /* Utility routines */
  142. Local Boolean getNode();
  143. Local Boolean parseTags();
  144. Local InfoWidget find_top();
  145. Local NodeInfo *popNode();
  146. Local NodeInfo *pushNode();
  147. Local String downcase();
  148. Local String eat_whitespace();
  149. Local String file_name();
  150. Local String find_file();
  151. Local String getFile();
  152. Local String get_arg();
  153. Local String normalize_whitespace();
  154. Local String offsetToString();
  155. Local String reverse();
  156. Local String search();
  157. Local String search_back();
  158. Local String strconcat();
  159. Local String substr();
  160. Local String trueName();
  161. Local int findNode();
  162. Local int iindex();
  163. Local int strcomp();
  164. Local int strncomp();
  165. Local void clear_arg();
  166. Local void dialog();
  167. Local void displayHeader();
  168. Local void displayNode();
  169. Local void feep();
  170. Local void getXY();
  171. Local void message();
  172. Local void parseHeader();
  173. Local void parseIndirect();
  174. Local void parseMenu();
  175. Local void parseNode();
  176. Local void parseXRefs();
  177. Local void showStatus();
  178. Local void strccpy();
  179.  
  180. Local XtActionsRec actionTable[] =
  181. {
  182.      {    "abort",        Abort            },
  183.      {    "confirm",        Confirm            },
  184.      {    "info_click",        ButtonSelection        },
  185.      {    "info_dir",        NodeDir            },
  186.      {    "info_goto",        NodeGoto        },
  187.      {    "info_last",        NodeLast        },
  188.      {    "info_menusel",        NodeMenuSelectByNumber    },
  189.      {    "info_next",        NodeNext        },
  190.      {    "info_nodeSearch",    NodeSearch        },
  191.      {    "info_popupHelp",    NodeHelp        },
  192.      {    "info_prev",        NodePrev        },
  193.      {    "info_print",        NodePrint        },
  194.      {    "info_quit",        NodeQuit        },
  195.      {    "info_top",        NodeTop            },
  196.      {    "info_tutorial",    NodeTutorial        },
  197.      {    "info_up",        NodeUp            },
  198.      {    "info_xref",        NodeXRef        },
  199.      {    NULL,            NULL            }
  200. };
  201.  
  202. Export InfoClassRec infoClassRec = {
  203.   { /* core fields */
  204.     /* superclass        */    (WidgetClass)&compositeClassRec,
  205.     /* class_name        */    "Info",
  206.     /* widget_size        */    sizeof(InfoRec),
  207.     /* class_initialize        */    NULL,
  208.     /* class_part_initialize    */    NULL,
  209.     /* class_inited        */    FALSE,
  210.     /* initialize        */    Initialize,
  211.     /* initialize_hook        */    NULL,
  212.     /* realize            */    Realize,
  213.     /* actions            */    actionTable,
  214.     /* num_actions        */    XtNumber(actionTable),
  215.     /* resources        */    resources,
  216.     /* num_resources        */    XtNumber(resources),
  217.     /* xrm_class        */    NULLQUARK,
  218.     /* compress_motion        */    TRUE,
  219.     /* compress_exposure    */    TRUE,
  220.     /* compress_enterleave    */    TRUE,
  221.     /* visible_interest        */    FALSE,
  222.     /* destroy            */    Destroy,
  223.     /* resize            */    Resize,
  224.     /* expose            */    XtInheritExpose,
  225.     /* set_values        */    SetValues,
  226.     /* set_values_hook        */    NULL,
  227.     /* set_values_almost    */    XtInheritSetValuesAlmost,
  228.     /* get_values_hook        */    NULL,
  229.     /* accept_focus        */    XtInheritAcceptFocus,
  230.     /* version            */    XtVersion,
  231.     /* callback_private        */    NULL,
  232.     /* tm_table            */    NULL,
  233.     /* query_geometry        */    XtInheritQueryGeometry,
  234.     /* display_accelerator    */    XtInheritDisplayAccelerator,
  235.     /* extension        */    NULL
  236.   },
  237.   { /* composite fields         */
  238.     /* geometry_manager         */      GeometryManager,
  239.     /* change_managed           */      NULL,
  240.     /* insert_child             */      XtInheritInsertChild,
  241.     /* delete_child             */      XtInheritDeleteChild,
  242.     /* extension                */      NULL,
  243.   },
  244.   { /* info fields        */
  245.     /* empty            */    0
  246.   }
  247. };
  248.  
  249. Export WidgetClass infoWidgetClass = (WidgetClass)&infoClassRec;
  250.  
  251. #ifndef tolower
  252. Import char tolower();
  253. #define TOLOWER(c) (tolower(c))
  254. #else
  255. #define TOLOWER(c) (isupper(c) ? tolower(c) : (c))
  256. #endif
  257.  
  258. Local XtCallbackRec cb[2];
  259. #define XtSetCbk(argarray, rtn, arg) \
  260.      cb[0].callback = rtn; \
  261.      cb[0].closure = (caddr_t)arg; \
  262.      XtSetArg(argarray, XtNcallback, cb)
  263.  
  264. /*****************************************************************************
  265.  * Widget manipulation routines.                                             *
  266.  *****************************************************************************/
  267.  
  268. Local Boolean SetValues(current, request, new)
  269. Widget current, request, new;
  270. {
  271.      InfoWidget cw = (InfoWidget)current;
  272.      InfoWidget nw = (InfoWidget)new;
  273.  
  274.      if (cw->info.file != nw->info.file
  275.      || strcomp(cw->info.file, nw->info.file)
  276.      || cw->info.node != nw->info.node
  277.      || strcomp(cw->info.node, nw->info.node)) {
  278.       XtFree(cw->info.file);
  279.       XtFree(cw->info.node);
  280.       getNode(nw, nw->info.file, nw->info.node, NULL);
  281.      }
  282.      /* getNode() does the redisplay implicitly */
  283.      return FALSE;
  284. }
  285.  
  286. /* We only manage one widget (the pane) directly */
  287. Local XtGeometryResult GeometryManager(w, request, reply)
  288. InfoWidget w;
  289. XtWidgetGeometry *request;
  290. XtWidgetGeometry *reply; /* RETURN */
  291. {
  292.      XtGeometryResult res;
  293.      Dimension width, height;
  294.  
  295.      width = w->core.width;
  296.      height = w->core.height;
  297.  
  298.      /* We don't really care; see what daddy says */
  299.      res = XtMakeGeometryRequest(XtParent(w), request, reply);
  300.      if (res == XtGeometryNo)
  301.       return res;
  302.      else if (res == XtGeometryAlmost) {
  303.       if (reply->request_mode & CWWidth)
  304.            width = reply->width;
  305.       if (reply->request_mode & CWHeight)
  306.            height = reply->height;
  307.      }
  308.      else { /* Has to be XtGeometryYes */
  309.       if (request->request_mode & CWWidth)
  310.            width = request->width;
  311.       if (request->request_mode & CWHeight)
  312.            height = request->height;
  313.      }
  314.      XtResizeWidget(w, width, height, w->core.border_width);
  315. }
  316.  
  317. Local void Destroy(w)
  318. Widget w;
  319. {
  320.      InfoWidget iw = (InfoWidget)w;
  321.  
  322.      if (INDIRECT(iw).table)
  323.       FREE_TAG_TABLE(INDIRECT(iw));
  324.      if (TAGTABLE(iw).table)
  325.       FREE_TAG_TABLE(TAGTABLE(iw));
  326.      while (popNode(iw));    /* popNode will free all but last */
  327.      /* now free the last one */
  328.      if (CURNODE(iw)) {
  329.       XtFree(CURNODE(iw)->file);
  330.       XtFree(CURNODE(iw)->node);
  331.       XtFree(CURNODE(iw));
  332.      }
  333.      XtFree(iw->info.file);
  334.      XtFree(iw->info.node);
  335. }
  336.  
  337. Local void Initialize(request, new)
  338. Widget request;
  339. Widget new;
  340. {
  341.      Arg args[15];
  342.      Cardinal i;
  343.      InfoWidget iw = (InfoWidget)new;
  344.      Widget top, box1, box2, vport, vport2;
  345.      char blanks[MAXSTR], *cp;
  346.      Import char *bzero();
  347.  
  348.      /* create a blank filled string as a placeholder for certain labels */
  349.      for (i = 0; i < MAXSTR - 1; i++)
  350.       blanks[i] = ' ';
  351.      blanks[i] = '\0';
  352.  
  353.      /* Pick some desperation defaults */
  354.      if (new->core.width == 0)
  355.       new->core.width = 100;
  356.      if (new->core.height == 0)
  357.       new->core.height = 50;
  358.  
  359.      /* Prevent later confusion */
  360.      iw->info.arg[0] = '\0';
  361.  
  362.      /* Create outer pane */
  363.      i = 0;
  364.      top = XtCreateManagedWidget("pane1", panedWidgetClass, new, args, i);
  365.  
  366.      /* Create top row of "main control" buttons and labels. */
  367.      i = 0;
  368.      box1 = XtCreateManagedWidget("box1", boxWidgetClass, top, args, i);
  369.  
  370.      if (iw->info.callback) {
  371.       Widget q;
  372.  
  373.       i = 0;
  374.       q = XtCreateManagedWidget("quit", commandWidgetClass,
  375.                 box1, args, i);
  376.       XtAddCallback(q, XtNcallback, do_quit, iw);
  377.      }
  378.      i = 0;
  379.      XtSetArg(args[i], XtNlabel, "File: ");                i++;
  380.      iw->info.fileLabel = XtCreateManagedWidget("file", labelWidgetClass,
  381.                         box1, args, i);
  382.      i = 0;
  383.      XtSetArg(args[i], XtNlabel, "Node: ");                i++;
  384.      iw->info.nodeLabel = XtCreateManagedWidget("node", labelWidgetClass,
  385.                         box1, args, i);
  386.      i = 0;
  387.      XtSetArg(args[i], XtNlabel, "Prev: ");                i++;
  388.      XtSetCbk(args[i], do_prev, iw);                    i++;
  389.      iw->info.prevCmd = XtCreateManagedWidget("prev", commandWidgetClass,
  390.                           box1, args, i);
  391.      i = 0;
  392.      XtSetArg(args[i], XtNlabel, "Up: ");                i++;
  393.      XtSetCbk(args[i], do_up, iw);                    i++;
  394.      iw->info.upCmd = XtCreateManagedWidget("up", commandWidgetClass,
  395.                         box1, args, i);
  396.      i = 0;
  397.      XtSetArg(args[i], XtNlabel, "Next: ");                i++;
  398.      XtSetCbk(args[i], do_next, iw);                    i++;
  399.      iw->info.nextCmd = XtCreateManagedWidget("next", commandWidgetClass,
  400.                           box1, args, i);
  401.  
  402.      /* Create the menu pane */
  403.      i = 0;
  404.      XtSetArg(args[i], XtNallowVert, TRUE);                i++;
  405.      vport = XtCreateManagedWidget("vport1", viewportWidgetClass,
  406.                    top, args, i);
  407.  
  408.      i = 0;
  409.      XtSetCbk(args[i], do_menu_sel, iw);                i++;
  410.      iw->info.menuList = XtCreateManagedWidget("menu", listWidgetClass,
  411.                            vport, args, i);
  412.      /*
  413.       * Create the text area for displaying node contents.
  414.       */
  415.      i = 0;
  416.      XtSetArg(args[i], XtNstring, blanks);                i++;
  417.      XtSetArg(args[i], XtNlength, MAXSTR);                i++;
  418.      XtSetArg(args[i], XtNeditType, XawtextRead);            i++;
  419.      XtSetArg(args[i], XtNuseStringInPlace, TRUE);            i++;
  420.      XtSetArg(args[i], XtNtype, XawAsciiString);            i++;
  421.      iw->info.nodeText = XtCreateManagedWidget("nodeText",
  422.                            asciiTextWidgetClass,
  423.                            top, args, i);
  424.      i = 0;
  425.      XtSetArg(args[i], XtNallowVert, TRUE);                i++;
  426.      vport2 = XtCreateManagedWidget("vport2", viewportWidgetClass,
  427.                     top, args, i);
  428.  
  429.      /* Create the xref pane */
  430.      i = 0;
  431.      XtSetCbk(args[i], do_xref_sel, iw);                i++;
  432.      iw->info.xrefList = XtCreateManagedWidget("xref", listWidgetClass,
  433.                            vport2, args, i);
  434.  
  435.      /*
  436.       * Create the bottom "auxilliary" command button group.
  437.       */
  438.      i = 0;
  439.      box2 = XtCreateManagedWidget("box2", boxWidgetClass,
  440.                   top, args, i);
  441.      i = 0;
  442.      XtSetCbk(args[i], do_menu, iw);                    i++;
  443.      iw->info.xrefCmd = XtCreateManagedWidget("menu", commandWidgetClass,
  444.                           box2, args, i);
  445.      i = 0;
  446.      XtSetCbk(args[i], do_xref, iw);                    i++;
  447.      iw->info.xrefCmd = XtCreateManagedWidget("xref", commandWidgetClass,
  448.                           box2, args, i);
  449.      i = 0;
  450.      XtSetCbk(args[i], do_goto, iw);                    i++;
  451.      iw->info.gotoCmd = XtCreateManagedWidget("goto", commandWidgetClass,
  452.                           box2, args, i);
  453.      i = 0;
  454.      XtSetCbk(args[i], do_search, iw);                    i++;
  455.      iw->info.searchCmd = XtCreateManagedWidget("search", commandWidgetClass,
  456.                         box2, args, i);
  457.      i = 0;
  458.      bzero(iw->info.arg, ARGLEN);
  459.      XtSetArg(args[i], XtNstring, iw->info.arg);            i++;
  460.      XtSetArg(args[i], XtNlength, ARGLEN);                i++;
  461.      XtSetArg(args[i], XtNuseStringInPlace, TRUE);            i++;
  462.      XtSetArg(args[i], XtNeditType, XawtextEdit);            i++;
  463.      iw->info.argText = XtCreateManagedWidget("arg", asciiTextWidgetClass,
  464.                           box2, args, i);
  465.  
  466.      /*
  467.       * Create the status and message area labels.
  468.       */
  469.      i = 0;
  470.      XtSetArg(args[i], XtNresize, FALSE);                i++;
  471.      XtSetArg(args[i], XtNlabel, blanks);                i++;
  472.      XtSetArg(args[i], XtNborderWidth, 0);                i++;
  473.      iw->info.statusLabel = XtCreateManagedWidget("status", labelWidgetClass,
  474.                           top, args, i);
  475.      i = 0;
  476.      XtSetArg(args[i], XtNresize, FALSE);                i++;
  477.      XtSetArg(args[i], XtNlabel, blanks);                i++;
  478.      XtSetArg(args[i], XtNborderWidth, 0);                i++;
  479.      iw->info.messageLabel = XtCreateManagedWidget("message", labelWidgetClass,
  480.                            top, args, i);
  481.  
  482.      /* set the initial node information */
  483.      ZERO_TABLE(INDIRECT(iw));
  484.      ZERO_TABLE(TAGTABLE(iw));
  485.      DATA(iw) = NULL;
  486.      CURNODE(iw) = NULL;
  487.  
  488.      iw->info.file = XtNewString(iw->info.file);
  489.      iw->info.node = XtNewString(iw->info.node);
  490.  
  491.      if (getNode(iw, iw->info.file, iw->info.node, NULL) == FALSE)
  492.       message(iw, "?Can't find initial file/node.");
  493. }
  494.  
  495. Local void Realize(w, value_mask, attributes)
  496. InfoWidget w;
  497. Mask *value_mask;
  498. XSetWindowAttributes *attributes;
  499. {
  500.      if (w->composite.num_children < 1)
  501.       XtError("No children?!?");
  502.      else {
  503.       /* Create window with which to manage child */
  504.       XtCreateWindow(w, (unsigned int)InputOutput,
  505.              (Visual *)CopyFromParent, *value_mask, attributes);
  506.       XtResizeWidget(w->composite.children[0], w->core.width,
  507.              w->core.height, 0);
  508.       /*
  509.        * Install accelerators onto widgets we know will need them.
  510.        * Note that Volume 4 of the O'Reilly "X Toolkit Intrinsics
  511.        * Programming Manual" (page 204, paragraph 5) says that widgets
  512.        * should never do this. I disagree; here's a case in point.
  513.        */
  514.       XtInstallAllAccelerators(w, w);
  515.       XtInstallAccelerators(w->info.nodeText, w);
  516.      }
  517. }
  518.  
  519. Local void Resize(w)
  520. InfoWidget w;
  521. {
  522.      XtResizeWidget(w->composite.children[0], w->core.width, w->core.height,
  523.             0);
  524. }
  525.  
  526. /*****************************************************************************
  527.  * Info file manipulation routines.                                          *
  528.  *****************************************************************************/
  529.  
  530. /* Here is the main guy. Handles all navigation within the info tree. */
  531. Local Boolean getNode(iw, file, node, pushTo)
  532. InfoWidget iw;
  533. String file, node;
  534. NodeInfo *pushTo;
  535. {
  536.      NodeInfo *cur;
  537.      int offset;
  538.      Boolean status = FALSE, needfile;
  539.  
  540.      if (node && index(node, '(') && index(node, ')')) {
  541.       file = substr(node, iindex(node, '(') + 1,
  542.             iindex(node, ')') - 1);
  543.       node = index(node, ')') + 1;
  544.      }
  545.      if (!node || !*node)
  546.       node = "Top";
  547.  
  548.      if (!file) {
  549.       file = iw->info.file;
  550.       needfile = !DATA(iw);
  551.      }
  552.      else
  553.       needfile = !DATA(iw) ||
  554.            strcomp(file_name(file), file_name(iw->info.file));
  555.      if (needfile) {
  556.       /* get a new file */
  557.       if ((file = getFile(iw, file, FALSE)) != NULL) {
  558.            if (file && iw->info.file != file) {
  559.             XtFree(iw->info.file);
  560.             iw->info.file = XtNewString(file);
  561.            }
  562.            iw->info.subFile = NULL;
  563.       }
  564.      }
  565.      else if (!strcomp(node, iw->info.node))
  566.       return TRUE;    /* we're already there */
  567.      else {
  568.       XtFree(iw->info.node);
  569.       iw->info.node = XtNewString(node);
  570.      }
  571.      if (file && (offset = findNode(iw, node)) >= 0) {
  572.       if (!pushTo) {
  573.            cur = pushNode(iw, iw->info.file, iw->info.node, offset);
  574.            parseNode(iw, cur, offset);
  575.       }
  576.       else
  577.            cur = pushTo;
  578.       displayNode(iw, cur);
  579.       message(iw, NULL);
  580.       showStatus(iw, cur);
  581.       if (!iw->info.retain_arg)
  582.            clear_arg(iw);
  583.       status = TRUE;
  584.      }
  585.      else {
  586.       /* Failed to get the new node, go back (but only once) */
  587.       if (!pushTo && CURNODE(iw))
  588.            getNode(iw, CURNODE(iw)->file, CURNODE(iw)->node, CURNODE(iw));
  589.      }
  590.      return status;
  591. }
  592.  
  593. /* Loads in file "name" and tag/indirect info, if any. */
  594. Local String getFile(iw, name, subfilep)
  595. InfoWidget iw;
  596. String name;
  597. Boolean subfilep;
  598. {
  599.      String ret;
  600.  
  601.      FILE *fp;
  602.  
  603.      ret = find_file(iw->info.path, name);
  604.      if (ret) {
  605.       Import int stat();
  606.       struct stat sb;
  607.  
  608.       if (!stat(ret, &sb) && (fp = fopen(ret, "r"))) {
  609.            XtFree(DATA(iw));
  610.            DATA(iw) = XtMalloc(sb.st_size + 1);
  611.            /* V.4 users will want to replace with an mmap() call */
  612.            if (fread(DATA(iw), 1, sb.st_size, fp) == sb.st_size) {
  613.             fclose(fp);
  614.             DATA(iw)[DATASIZE(iw) = sb.st_size] = '\0';
  615.             if (!subfilep) {
  616.              Boolean needIndirect;
  617.  
  618.              needIndirect = parseTags(iw);
  619.              parseIndirect(iw, needIndirect);
  620.             }
  621.            }
  622.            else {
  623.             message(iw, "?Read error on %s.", name);
  624.             XtFree(DATA(iw));
  625.             ret = NULL;
  626.            }
  627.       }
  628.       else
  629.            ret = NULL;
  630.      }
  631.      return ret;
  632. }
  633.  
  634. /* Look through tag table (and/or current buffer) for a node */
  635. Local int findNode(iw, name)
  636. InfoWidget iw;
  637. String name;
  638. {
  639.      ID_P i;
  640.      int offset = -1;
  641.      String s, srch;
  642.  
  643.      /* A node name of "*" means the whole file */
  644.      if (!strcomp(name, "*"))
  645.       return 0;
  646.  
  647.      if (TAGTABLE(iw).table) {
  648.       for (i = TAGTABLE(iw).table; I_NAME(*i); i++) {
  649.            if (!strcomp(I_NAME(*i), name)) {
  650.             offset = I_OFFSET(*i);
  651.             break;
  652.            }
  653.       }
  654.       /* if we found the tag and there's an indirect table, adjust */
  655.       if (offset > 0 && INDIRECT(iw).table) {
  656.            String sub;
  657.  
  658.            for (i = INDIRECT(iw).table; I_NAME(*i); i++) {
  659.             if (I_OFFSET(*i) > offset)    /* got it */
  660.              break;
  661.            }
  662.            sub = I_NAME(*(--i));
  663.            if (strcomp(sub, iw->info.subFile)) {
  664.             if (!getFile(iw, sub, TRUE))
  665.              return 0;
  666.             else
  667.              iw->info.subFile = sub;
  668.            }
  669.            offset -= I_OFFSET(*i);
  670.            /* compensate for header */
  671.            offset += HDRSIZE(iw);
  672.       }
  673.      }
  674.      /*
  675.       * Now search forward for the node name. Note that this will
  676.       * work whether or not we found the tag in the tag table. Having
  677.       * found the tag only insures that we search a little less.
  678.       */
  679.      s = START(iw);
  680.      if (offset > 0)
  681.       s += offset;
  682.  
  683.      /*
  684.       * since bogus tags can leave us *after* the node start as well as
  685.       * before it, we risk a little extra searching and back up to the
  686.       * closest node marker above. Es tut mir leid, but this is what you
  687.       * get with out-of-date tags!
  688.       */
  689.      while (s > START(iw) && !INFO_CHAR(*s))
  690.       --s;
  691.      srch = strconcat(NODE_TOKEN, name);
  692.      while (s) {
  693.       if ((s = search(iw, s, END(iw), srch, TRUE)) != NULL) {
  694.            /* If not an exact match, keep looking */
  695.            if (!index(NAME, *s))
  696.             continue;
  697.            offset = INTOFF(START(iw), s);
  698.            /* found it, move to the beginning */ 
  699.            while(!INFO_CHAR(START(iw)[offset - 1]))
  700.             offset--;
  701.            s = NULL;
  702.       }
  703.       else
  704.            offset = -1;
  705.      }
  706.      return offset;
  707. }
  708.      
  709. /* Push a node onto the history list */
  710. Local NodeInfo *pushNode(iw, file, node, offset)
  711. InfoWidget iw;
  712. String file, node;
  713. int offset;
  714. {
  715.      NodeInfo *tmp;
  716.  
  717.      tmp = XtNew(NodeInfo);
  718.      bzero(tmp, sizeof(NodeInfo));
  719.      tmp->file = XtNewString(file);
  720.      tmp->node = XtNewString(node);
  721.      tmp->start = offset;
  722.      tmp->nextNode = CURNODE(iw);
  723.      CURNODE(iw) = tmp;
  724.      return tmp;
  725. }
  726.  
  727. /* Pop a node off the history list */
  728. Local NodeInfo *popNode(iw)
  729. InfoWidget iw;
  730. {
  731.      NodeInfo *tmp = NULL;
  732.  
  733.      if (CURNODE(iw) && CURNODE(iw)->nextNode) {
  734.       tmp = CURNODE(iw)->nextNode;
  735.       XtFree(CURNODE(iw)->file);
  736.       XtFree(CURNODE(iw)->node);
  737.       FREE_LIST(CURNODE(iw)->menu);
  738.       FREE_LIST(CURNODE(iw)->xref);
  739.       XtFree(CURNODE(iw));
  740.       CURNODE(iw) = tmp;
  741.      }
  742.      return tmp;
  743. }
  744.  
  745. /* Parse out all the header/menu/xref information for a node. */
  746. Local void parseNode(iw, n, offset)
  747. InfoWidget iw;
  748. NodeInfo *n;
  749. int offset;
  750. {
  751.      register String start = START(iw) + offset;
  752.  
  753.      /* was the whole file ("*") selected? */
  754.      if (offset == 0) {
  755.       n->length = DATASIZE(iw);
  756.       I_START(n->name) = I_LEN(n->name) = 0;
  757.       I_START(n->prev) = I_LEN(n->prev) = 0;
  758.       I_START(n->up) = I_LEN(n->up) = 0;
  759.       I_START(n->next) = I_LEN(n->next) = 0;
  760.       I_START(n->text) = 0;
  761.       I_LEN(n->text) = n->length;
  762.      }
  763.      else {
  764.       /* find the end of the node */
  765.       n->length = 0;
  766.       start = START(iw) + offset;
  767.       while (start < END(iw) && !INFO_CHAR(*start)) {
  768.            n->length++;
  769.            start++;
  770.       }
  771.      }
  772.      /* get the header */
  773.      parseHeader(iw, n);
  774.      /* get the menu items */
  775.      parseMenu(iw, n);
  776.      /* get the cross reference entries */
  777.      parseXRefs(iw, n);
  778. }
  779.  
  780. Local void parseHeader(iw, n)
  781. InfoWidget iw;
  782. NodeInfo *n;
  783. {
  784.      String strpbrk(), tmp;
  785.  
  786.      /* first, get the node name offset */
  787.      I_START(n->name) = INTOFF(START(iw), NSEARCH(iw, n, NODE_TOKEN));
  788.      I_LEN(n->name) = INTOFF(START(iw), strpbrk(START(iw) + I_START(n->name),
  789.                            NAME_END_TOKEN)) -
  790.                             I_START(n->name);
  791.  
  792.      /* now the prev, if any */
  793.      if ((I_START(n->prev) = INTOFF(START(iw),
  794.                     NSEARCH(iw, n, PREV_TOKEN))) > 0)
  795.       I_LEN(n->prev) = INTOFF(START(iw),
  796.                   strpbrk(START(iw) + I_START(n->prev),
  797.                             NAME_END_TOKEN)) -
  798.                              I_START(n->prev);
  799.      else
  800.       I_LEN(n->prev) = I_START(n->prev) = 0;
  801.  
  802.      /* and the up, if any */
  803.      if ((I_START(n->up) = INTOFF(START(iw),
  804.                   NSEARCH(iw, n, UP_TOKEN))) > 0)
  805.       I_LEN(n->up) = INTOFF(START(iw), strpbrk(START(iw) + I_START(n->up),
  806.                           NAME_END_TOKEN)) -
  807.                                I_START(n->up);
  808.      else
  809.       I_LEN(n->up) = I_START(n->up) = 0;
  810.  
  811.      /* the next, if any */
  812.      if ((I_START(n->next) = INTOFF(START(iw),
  813.                     NSEARCH(iw, n, NEXT_TOKEN))) > 0)
  814.       I_LEN(n->next) = INTOFF(START(iw),
  815.                   strpbrk(START(iw) + I_START(n->next),
  816.                             NAME_END_TOKEN)) -
  817.                              I_START(n->next);
  818.      else
  819.       I_LEN(n->next) = I_START(n->next) = 0;
  820.  
  821.      /* And finally skip over the header and set the text offset there */
  822.      tmp = START(iw) + I_START(n->name);
  823.      while (*tmp != '\n')
  824.       tmp++;
  825.      I_START(n->text) = INTOFF(START(iw), tmp + 1);
  826.      I_LEN(n->text) = n->length - (I_START(n->text) - n->start);
  827. }
  828.  
  829. Local void parseMenu(iw, n)
  830. InfoWidget iw;
  831. NodeInfo *n;
  832. {
  833.      register String mstart;
  834.      String strpbrk();
  835.  
  836.      /* start clean */
  837.      ZERO_LIST(n->menu);
  838.  
  839.      /* Does node have a menu? */
  840.      if ((mstart = NSEARCH(iw, n, MENU_TOKEN)) != NULL) {
  841.       /* Initialize table and string list */
  842.       ALLOC_LIST(n->menu);
  843.  
  844.       /* go looking for menu items */
  845.       while (mstart = search(iw, mstart, NEND(iw, n), MENU_SEP_TOKEN,
  846.                  FALSE)) {
  847.            MAYBE_BUMP_LIST(n->menu);
  848.            I_LEN(TPOS(n->menu.t)) = 0;
  849.            I_START(TPOS(n->menu.t)) = INTOFF(START(iw), mstart);
  850.            while (*(mstart++) != ':')
  851.             I_LEN(TPOS(n->menu.t))++;
  852.            /* save the menu name as a string */
  853.            LPOS(n->menu) = XtMalloc(I_LEN(TPOS(n->menu.t)) + 1);
  854.            strncpy(LPOS(n->menu), START(iw) + I_START(TPOS(n->menu.t)),
  855.                I_LEN(TPOS(n->menu.t)));
  856.            LPOS(n->menu)[I_LEN(TPOS(n->menu.t))] = '\0';
  857.            normalize_whitespace(LPOS(n->menu));
  858.            /* Is the menu name not the node name? */
  859.            if (*mstart != ':') {
  860.             int plev = 0;
  861.  
  862.             mstart = eat_whitespace(mstart);
  863.             I_START(TPOS(n->menu.t)) = INTOFF(START(iw), mstart);
  864.             while (*mstart != '\0' && !(plev == 0
  865.                && index(NAME_END_TOKEN, *mstart) != NULL)) {
  866.               if (*mstart == '(')
  867.                    ++plev;
  868.               else if (*mstart == ')')
  869.                    --plev;
  870.               mstart++;
  871.              }
  872.             I_LEN(TPOS(n->menu.t)) =
  873.              INTOFF(START(iw), mstart) - I_START(TPOS(n->menu.t));
  874.            }
  875.            INCP(n->menu.t);
  876.       }
  877.       ROUND_LIST(n->menu);
  878.      }
  879. }
  880.       
  881. Local void parseXRefs(iw, n)
  882. InfoWidget iw;
  883. NodeInfo *n;
  884. {
  885.      register String nstart;
  886.      String strpbrk();
  887.  
  888.      /* start clean */
  889.      ZERO_LIST(n->xref);
  890.  
  891.      /* Do we have any cross-reference entries? */
  892.      if ((nstart = search(iw, NSTART(iw, n), NEND(iw, n), NOTE_TOKEN, TRUE))
  893.      != NULL) {
  894.       ALLOC_LIST(n->xref);
  895.       nstart = NSTART(iw, n);
  896.  
  897.       /*
  898.        * Go looking for cross-references (including the one we just
  899.        * found; wasteful, but avoiding it would make for grotty code).
  900.         */
  901.       while (nstart = search(iw, nstart, NEND(iw, n), NOTE_TOKEN, TRUE)) {
  902.            /* skip over whitespace */
  903.            nstart = eat_whitespace(nstart);
  904.            MAYBE_BUMP_LIST(n->xref);
  905.            I_LEN(TPOS(n->xref.t)) = 0;
  906.            I_START(TPOS(n->xref.t)) = INTOFF(START(iw), nstart);
  907.            while (*(nstart++) != ':')
  908.             I_LEN(TPOS(n->xref.t))++;
  909.            /* save the note name as a string */
  910.            LPOS(n->xref) = XtMalloc(I_LEN(TPOS(n->xref.t)) + 1);
  911.            strncpy(LPOS(n->xref), START(iw) + I_START(TPOS(n->xref.t)),
  912.                I_LEN(TPOS(n->xref.t)));
  913.            LPOS(n->xref)[I_LEN(TPOS(n->xref.t))] = '\0';
  914.            normalize_whitespace(LPOS(n->xref));
  915.            /* Is the note name not the first part? */
  916.            if (*nstart != ':') {
  917.             nstart = eat_whitespace(nstart + 1);
  918.             I_START(TPOS(n->xref.t)) = INTOFF(START(iw), nstart);
  919.             I_LEN(TPOS(n->xref.t)) =
  920.              INTOFF(START(iw), strpbrk(nstart, NAME_END_TOKEN)) -
  921.                   I_START(TPOS(n->xref.t));
  922.            }
  923.            INCP(n->xref.t);
  924.       }
  925.       ROUND_LIST(n->xref);
  926.      }
  927. }           
  928.  
  929. /* Put the node information on the screen */
  930. Local void displayNode(iw, n)
  931. InfoWidget iw;
  932. NodeInfo *n;
  933. {
  934.      Arg args[5];
  935.      Cardinal i, lst_size;
  936.      String *lst;
  937.      Local char *nolist[] = { "", NULL };     /* make the list widget happy */
  938.      Local char tmpfile[256];
  939.  
  940.      /* Make sure it doesn't try anything cute until we're ready */
  941.      XawTextDisableRedisplay(iw->info.nodeText);
  942.  
  943.      /*
  944.       * There exists a strange bug in the text widget that causes text
  945.       * to be erroneously selected when we're mousing selections. Since
  946.       * we don't want to keep things selected while we're navigating
  947.       * anyway, this is a satisfactory workaround.
  948.       */
  949.      XawTextUnsetSelection(iw->info.nodeText);
  950.      
  951.      /* show the header */
  952.      displayHeader(iw, n);
  953.  
  954.      /* show the menu */
  955.      if (!n->menu.l) {
  956.       lst = nolist;
  957.       lst_size = 1;
  958.      }
  959.      else {
  960.       lst = n->menu.l;
  961.       lst_size = IDX(n->menu.t);
  962.      }
  963.      XawListChange(iw->info.menuList, lst, lst_size, 0, TRUE);
  964.  
  965.      /* change the xref list */
  966.      if (!n->xref.l) {
  967.       lst = nolist;
  968.       lst_size = 1;
  969.      }
  970.      else {
  971.       lst = n->xref.l;
  972.       lst_size = IDX(n->xref.t);
  973.      }
  974.      XawListChange(iw->info.xrefList, lst, lst_size, 0, TRUE);
  975.  
  976.      /* Show the new text */
  977.      i = 0;
  978.      if (I_START(n->text)) {
  979.       char *addr = (START(iw) + I_START(n->text) + I_LEN(n->text));
  980.  
  981.       XtSetArg(args[i], XtNstring, START(iw) + I_START(n->text));    i++;
  982.       XtSetArg(args[i], XtNlength, I_LEN(n->text));            i++;
  983.       if (INFO_CHAR(*addr))
  984.            *addr = '\0';
  985.       else {
  986.            char msg[256];
  987.  
  988.            sprintf(msg, "Encountered bad terminator (%d) for node '%s'",
  989.                *addr, n->name);
  990.            XtWarning(msg);
  991.       }
  992.      }
  993.      else {
  994.       XtSetArg(args[i], XtNstring, START(iw));            i++;
  995.       XtSetArg(args[i], XtNlength, DATASIZE(iw));            i++;
  996.      }
  997.      XtSetValues(iw->info.nodeText, args, i);
  998.  
  999.      /* Go for redisplay */
  1000.      XawTextEnableRedisplay(iw->info.nodeText);
  1001.  
  1002.      /* Stick the insertion marker at the top where it's out of the way */
  1003.      XawTextSetInsertionPoint(iw->info.nodeText, 0);
  1004. }
  1005.  
  1006. /* display the header information */
  1007. Local void displayHeader(iw, n)
  1008. InfoWidget iw;
  1009. NodeInfo *n;
  1010. {
  1011.      Arg args[5];
  1012.      Cardinal i;
  1013.      String tmp;
  1014.      int sensitive;
  1015.  
  1016.      /* set the file name */
  1017.      tmp = strconcat("File: ", file_name(iw->info.file));
  1018.      i = 0;
  1019.      XtSetArg(args[i], XtNlabel, tmp);                    i++;
  1020.      XtSetValues(iw->info.fileLabel, args, i);
  1021.  
  1022.      /* set the node name */
  1023.      i = 0;
  1024.      if ((tmp = offsetToString(iw, n->name)) != NULL)
  1025.       sensitive = TRUE;
  1026.      else
  1027.       sensitive = FALSE;
  1028.      XtSetArg(args[i], XtNlabel, strconcat("Node: ", tmp));        i++;
  1029.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  1030.      XtSetValues(iw->info.nodeLabel, args, i);
  1031.  
  1032.      /* set the prev */
  1033.      i = 0;
  1034.      if ((tmp = offsetToString(iw, n->prev)) != NULL)
  1035.       sensitive = TRUE;
  1036.      else
  1037.       sensitive = FALSE;
  1038.      XtSetArg(args[i], XtNlabel, strconcat("Prev: ", tmp));        i++;
  1039.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  1040.      XtSetValues(iw->info.prevCmd, args, i);
  1041.  
  1042.      /* set the up */
  1043.      i = 0;
  1044.      if ((tmp = offsetToString(iw, n->up)) != NULL)
  1045.       sensitive = TRUE;
  1046.      else
  1047.       sensitive = FALSE;
  1048.      XtSetArg(args[i], XtNlabel, strconcat("Up: ", tmp));        i++;
  1049.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  1050.      XtSetValues(iw->info.upCmd, args, i);
  1051.  
  1052.      /* set the next */
  1053.      i = 0;
  1054.      if ((tmp = offsetToString(iw, n->next)) != NULL)
  1055.       sensitive = TRUE;
  1056.      else
  1057.       sensitive = FALSE;
  1058.      XtSetArg(args[i], XtNlabel, strconcat("Next: ", tmp));        i++;
  1059.      XtSetArg(args[i], XtNsensitive, sensitive);            i++;
  1060.      XtSetValues(iw->info.nextCmd, args, i);
  1061. }
  1062.  
  1063. /*
  1064.  * Look for tag table information in the current buffer. If tag table
  1065.  * is indirect, return TRUE, else return false.
  1066.  */
  1067. Local Boolean parseTags(iw)
  1068. InfoWidget iw;
  1069. {
  1070.      String start, s1;
  1071.      char tmp[MAXSTR];
  1072.      Boolean indirect = FALSE;
  1073.      int i;
  1074.  
  1075.      /*
  1076.       * go back about 8 lines. I don't know if this will always back up
  1077.       * past the end marker, but Emacs info seems to think so.
  1078.       */
  1079.      start = END(iw);
  1080.      i = 0;
  1081.      while (i < 8)
  1082.       if (*(--start) == '\n')
  1083.            i++;
  1084.  
  1085.      start = search(iw, start, END(iw), TAGEND_TOKEN, TRUE);
  1086.      if (start && (start = search_back(iw, start, START(iw),
  1087.                        TAGTABLE_TOKEN, TRUE))) {
  1088.       ALLOC_TABLE(TAGTABLE(iw));
  1089.       /* we were searching backward so move over the token */
  1090.       start += strlen(TAGTABLE_TOKEN);
  1091.       if ((s1 = search(iw, start, start + strlen(ITAGTABLE_TOKEN) + 10,
  1092.                ITAGTABLE_TOKEN, TRUE)) != NULL) {
  1093.            indirect = TRUE;
  1094.            start = s1;
  1095.       }
  1096.       while ((start = search(iw, start, END(iw), NODE_TOKEN, FALSE))
  1097.          != NULL) {
  1098.            MAYBE_BUMP_TABLE(TAGTABLE(iw));
  1099.            strccpy(tmp, start, DEL_CHAR);
  1100.            I_NAME(TPOS(TAGTABLE(iw))) = XtNewString(tmp);
  1101.            start += strlen(tmp) + 1;
  1102.            sscanf(start, "%d", &I_OFFSET(TPOS(TAGTABLE(iw))));
  1103.            INCP(TAGTABLE(iw));
  1104.       }
  1105.       ROUND_TABLE(TAGTABLE(iw));
  1106.      }
  1107.      else if (TAGTABLE(iw).table)
  1108.       FREE_TAG_TABLE(TAGTABLE(iw));
  1109.      return indirect;
  1110. }
  1111.  
  1112. /* Look for indirect file information in the current buffer */
  1113. Local void parseIndirect(iw, needIndirect)
  1114. InfoWidget iw;
  1115. Boolean needIndirect;
  1116. {
  1117.      String start;
  1118.      char tmp[MAXSTR], *s1;
  1119.  
  1120.      if (start = search(iw, START(iw), END(iw), INDIRECT_TOKEN, TRUE)) {
  1121.       /* move backwards looking for the INFO_CHAR */
  1122.       for (s1 = start; s1 >= START(iw) && !INFO_CHAR(*s1); s1--);
  1123.       if (s1 < START(iw)) {
  1124.            message(iw, "?Invalid indirect table for %s!", iw->info.file);
  1125.            return;
  1126.       }
  1127.       else
  1128.            HDRSIZE(iw) = INTOFF(START(iw), s1);
  1129.       ALLOC_TABLE(INDIRECT(iw));
  1130.       for (IDX(INDIRECT(iw)) = 0; !INFO_CHAR(*start); INCP(INDIRECT(iw))){
  1131.            MAYBE_BUMP_TABLE(INDIRECT(iw));
  1132.            strccpy(tmp, start, ':');
  1133.            I_NAME(TPOS(INDIRECT(iw))) = XtNewString(tmp);
  1134.            start += strlen(tmp) + 1;
  1135.            sscanf(start, "%d", &I_OFFSET(TPOS(INDIRECT(iw))));
  1136.            start = index(start, '\n') + 1;
  1137.       }
  1138.       ROUND_TABLE(INDIRECT(iw));
  1139.      }
  1140.      else if (needIndirect)
  1141.       message(iw, "?Indirect table not found for %s! Hilfe!",
  1142.           iw->info.file);
  1143.      else if (INDIRECT(iw).table)
  1144.       FREE_TAG_TABLE(INDIRECT(iw));
  1145. }
  1146.  
  1147. /*****************************************************************************
  1148.  * Text display functions.                                                   *
  1149.  *****************************************************************************/
  1150.  
  1151. /* display a message in the message area */
  1152. Local void message(iw, s, p1, p2, p3)
  1153. InfoWidget iw;
  1154. String s;
  1155. caddr_t p1, p2, p3;
  1156. {
  1157.      char msgbuf[MAXSTR];
  1158.      Arg args[5];
  1159.      Cardinal i;
  1160.  
  1161.      i = 0;
  1162.      if (s) {
  1163.       sprintf(msgbuf, s, p1, p2, p3);
  1164.       XtSetArg(args[i], XtNlabel, msgbuf);    i++;
  1165.       XtSetValues(iw->info.messageLabel, args, i);
  1166.       feep(iw);
  1167.       if (*s == '?')    /* a dire warning */
  1168.            XtWarning(msgbuf);
  1169.      }
  1170.      else {    /* clear the message area */
  1171.       XtSetArg(args[i], XtNlabel, " ");    i++;
  1172.       XtSetValues(iw->info.messageLabel, args, i);
  1173.      }
  1174. }
  1175.  
  1176. /* display the current node/file */
  1177. Local void showStatus(iw, n)
  1178. InfoWidget iw;
  1179. NodeInfo *n;
  1180. {
  1181.      char statbuf[MAXSTR];
  1182.      Arg args[5];
  1183.      Cardinal i;
  1184.      String sub = iw->info.subFile;
  1185.  
  1186.      sprintf(statbuf, "(%s)%s, %d characters%s", file_name(iw->info.file),
  1187.          iw->info.node, n->length,
  1188.          sub ? strconcat(", subfile: ", sub) : ".");
  1189.      i = 0;
  1190.      XtSetArg(args[i], XtNlabel, statbuf);    i++;
  1191.      XtSetValues(iw->info.statusLabel, args, i);
  1192. }
  1193.  
  1194. /*****************************************************************************
  1195.  * Functions used by actions                                                 *
  1196.  *****************************************************************************/
  1197.  
  1198. Local void Abort(w, event, params, num_params)
  1199. Widget   w;
  1200. XEvent   *event;
  1201. String   *params;
  1202. Cardinal *num_params;
  1203. {
  1204.      InfoWidget iw = find_top(w);
  1205.  
  1206.      feep(iw);
  1207.      do_dialog_abort(w, iw, NULL);
  1208. }
  1209.  
  1210. Local void Confirm(w, event, params, num_params)
  1211. Widget   w;
  1212. XEvent   *event;
  1213. String   *params;
  1214. Cardinal *num_params;
  1215. {
  1216.      InfoWidget iw = find_top(w);
  1217.  
  1218.      if (w == iw->info.argText)
  1219.       (*(iw->info.requester))(w, iw, NULL);
  1220.      else
  1221.       do_dialog_confirm(w, iw, NULL);
  1222. }
  1223.  
  1224. Local void NodeDir(w, event, params, num_params)
  1225. Widget   w;
  1226. XEvent   *event;
  1227. String   *params;
  1228. Cardinal *num_params;
  1229. {
  1230.      InfoWidget iw = find_top(w);
  1231.  
  1232.      if (getNode(iw, "dir", "Top", NULL) == FALSE)
  1233.       message(iw, "?Yow! The directory seems to have disappeared!\n");
  1234. }
  1235.  
  1236. Local void NodeNext(w, event, params, num_params)
  1237. Widget   w;
  1238. XEvent   *event;
  1239. String   *params;
  1240. Cardinal *num_params;
  1241. {
  1242.      do_next(NULL, find_top(w), NULL);
  1243. }
  1244.  
  1245. Local void NodePrev(w, event, params, num_params)
  1246. Widget   w;
  1247. XEvent   *event;
  1248. String   *params;
  1249. Cardinal *num_params;
  1250. {
  1251.      do_prev(NULL, find_top(w), NULL);
  1252. }
  1253.  
  1254.  
  1255. Local void NodeUp(w, event, params, num_params)
  1256. Widget   w;
  1257. XEvent   *event;
  1258. String   *params;
  1259. Cardinal *num_params;
  1260. {
  1261.      do_up(NULL, find_top(w), NULL);
  1262. }
  1263.  
  1264. Local void NodeTop(w, event, params, num_params)
  1265. Widget   w;
  1266. XEvent   *event;
  1267. String   *params;
  1268. Cardinal *num_params;
  1269. {
  1270.      InfoWidget iw = find_top(w);
  1271.  
  1272.      if (getNode(iw, NULL, "Top", NULL) == FALSE)
  1273.       message(iw, "?This node has no top! Bad joss!");
  1274. }
  1275.  
  1276. Local void NodeLast(w, event, params, num_params)
  1277. Widget   w;
  1278. XEvent   *event;
  1279. String   *params;
  1280. Cardinal *num_params;
  1281. {
  1282.      NodeInfo *tmp;
  1283.      InfoWidget iw = find_top(w);
  1284.      
  1285.      if ((tmp = popNode(iw)) != NULL) {
  1286.       if (getNode(iw, tmp->file, tmp->node, tmp) == FALSE)
  1287.            message(iw, "?Can't pop back to node (%s)%s! We're hosed!",
  1288.                tmp->file, tmp->node);
  1289.      }
  1290.      else
  1291.       message(iw, "No further history.");
  1292. }
  1293.  
  1294. Local void NodeXRef(w, event, params, num_params)
  1295. Widget   w;
  1296. XEvent   *event;
  1297. String   *params;
  1298. Cardinal *num_params;
  1299. {
  1300.      do_xref(NULL, find_top(w), NULL);
  1301. }
  1302.  
  1303. Local void NodeGoto(w, event, params, num_params)
  1304. Widget   w;
  1305. XEvent   *event;
  1306. String   *params;
  1307. Cardinal *num_params;
  1308. {
  1309.      do_goto(NULL, find_top(w), NULL);
  1310. }
  1311.  
  1312. Local void NodeSearch(w, event, params, num_params)
  1313. Widget   w;
  1314. XEvent   *event;
  1315. String   *params;
  1316. Cardinal *num_params;
  1317. {
  1318.      do_search(NULL, find_top(w), NULL);
  1319. }
  1320.  
  1321. Local void NodeQuit(w, event, params, num_params)
  1322. Widget   w;
  1323. XEvent   *event;
  1324. String   *params;
  1325. Cardinal *num_params;
  1326. {
  1327.      do_quit(NULL, find_top(w), NULL);
  1328. }
  1329.  
  1330. Local void NodeTutorial(w, event, params, num_params)
  1331. Widget   w;
  1332. XEvent   *event;
  1333. String   *params;
  1334. Cardinal *num_params;
  1335. {
  1336.      InfoWidget iw = find_top(w);
  1337.  
  1338.      if (getNode(iw, "info", "Help", NULL) == FALSE)
  1339.       message(iw, "?Hmmm. I can't seem to find the info tutorial!");
  1340. }
  1341.  
  1342. Local void NodeHelp(w, event, params, num_params)
  1343. Widget   w;
  1344. XEvent   *event;
  1345. String   *params;
  1346. Cardinal *num_params;
  1347. {
  1348.      Cardinal i;
  1349.      Arg args[10];
  1350.      InfoWidget iw = find_top(w);
  1351.      
  1352.      if (!iw->info.helpPopup) {
  1353.       Widget hpane, htext;
  1354.       Local XtCallbackRec cb[2];
  1355.  
  1356.       /* create the help popup */
  1357.       i = 0;
  1358.       iw->info.helpPopup = XtCreatePopupShell("help",
  1359.                           transientShellWidgetClass,
  1360.                           iw, args, i);
  1361.       i = 0;
  1362.       hpane = XtCreateManagedWidget("pane", panedWidgetClass,
  1363.                     iw->info.helpPopup, args, i);
  1364.       i = 0;
  1365.       cb[0].callback = do_popdown;
  1366.       cb[0].closure = (caddr_t)iw->info.helpPopup;
  1367.       XtSetArg(args[i], XtNcallback, cb);            i++;
  1368.       XtCreateManagedWidget("Close", commandWidgetClass,
  1369.                 hpane, args, i);
  1370.       i = 0;
  1371.       XtSetArg(args[i], XtNtype, XawAsciiString);        i++;
  1372.       XtSetArg(args[i], XtNeditType, XawtextRead);        i++;
  1373.       htext = XtCreateManagedWidget("text", asciiTextWidgetClass,
  1374.                     hpane, args, i);
  1375.      }
  1376.  
  1377.      i = 0;
  1378.      XtSetArg(args[i], XtNx, event->xbutton.x);            i++;
  1379.      XtSetArg(args[i], XtNy, event->xbutton.y);            i++;
  1380.      XtSetValues(iw->info.helpPopup, args, i);
  1381.  
  1382.      XtPopup(iw->info.helpPopup, XtGrabNonexclusive);
  1383. }
  1384.  
  1385. Local void ButtonSelection(w, event, params, num_params)
  1386. Widget   w;
  1387. XEvent   *event;
  1388. String   *params;
  1389. Cardinal *num_params;
  1390. {
  1391.      char tmp[512], *idx;
  1392.      XawTextPosition beg, end, nlen;
  1393.      XawTextBlock ret, asterisk, colon;
  1394.      InfoWidget iw = find_top(w);
  1395.  
  1396.      SET_BLOCK(asterisk, 0, 1, "*");
  1397.      SET_BLOCK(colon, 0, 1, ":");
  1398.  
  1399.      /* Next, try and get a complete "item" selected */
  1400.      if ((beg = XawTextSearch(w, XawsdLeft, &asterisk)) != XawTextSearchError)
  1401.       XawTextSetInsertionPoint(w, beg);
  1402.      else
  1403.       return;    /* Bomb out */
  1404.      if ((end = XawTextSearch(w, XawsdRight, &colon)) != XawTextSearchError &&
  1405.      end > beg) {
  1406.       long len = end - beg;
  1407.  
  1408.       /* Victory! Now try and figure out what it is */
  1409.       if (!XawTextSourceRead(XawTextGetSource(w), beg, &ret, len))
  1410.            return;    /* If can't read, forget it */
  1411.       else while (ret.length && *(ret.ptr) == '*' || isspace(*(ret.ptr)))
  1412.            --ret.length, ++ret.ptr;
  1413.       if (!ret.length)
  1414.            return;    /* Nothing left, forget it */
  1415.       else {
  1416.            strncpy(tmp, ret.ptr, ret.length);
  1417.            tmp[ret.length] = '\0';
  1418.            normalize_whitespace(tmp);
  1419.            if (!strncomp(tmp, "note ", 5)) {
  1420.             if (!(idx = trueName(iw, CURNODE(iw)->xref, tmp + 5)))
  1421.              feep(iw);
  1422.             else if (getNode(iw, NULL, idx, NULL) == FALSE)
  1423.              message(iw, "?Can't find cross reference for '%s'!",
  1424.                  idx);
  1425.            }
  1426.            else {
  1427.             if (!(idx = trueName(iw, CURNODE(iw)->menu, tmp)))
  1428.              feep(iw);
  1429.             else if (getNode(iw, NULL, idx, NULL) == FALSE)
  1430.              message(iw, "?Can't find menu entry for '%s'!",
  1431.                  idx);
  1432.            }
  1433.       }            
  1434.      }
  1435. }
  1436.  
  1437. Local void NodeMenuSelectByNumber(w, event, params, num_params)
  1438. Widget   w;
  1439. XEvent   *event;
  1440. String   *params;
  1441. Cardinal *num_params;
  1442. {
  1443.      Import int atoi();
  1444.      int menunum;
  1445.      int nitems;
  1446.      InfoWidget iw = find_top(w);
  1447.  
  1448.      nitems = IDX(CURNODE(iw)->menu.t);
  1449.      menunum = atoi(*params);
  1450.      /* menu number of zero means get menu from arg area */
  1451.      if (!menunum)
  1452.       do_menu(NULL, iw, NULL);
  1453.      else if (!nitems)
  1454.       message(iw, "No menu for this node.");
  1455.      else if (menunum > nitems)
  1456.       message(iw, "There are only %d menu items.", nitems);
  1457.      else {
  1458.       XawListHighlight(iw->info.menuList, menunum - 1);
  1459.       if (getNode(iw, NULL,
  1460.               offsetToString(iw,CURNODE(iw)->menu.t.table[menunum-1]),
  1461.               NULL) == FALSE)
  1462.            message(iw, "?Can't find node for menu item #%s", *params);
  1463.      }
  1464. }
  1465.  
  1466. Local void NodePrint(w, event, params, num_params)
  1467. Widget   w;
  1468. XEvent   *event;
  1469. String   *params;
  1470. Cardinal *num_params;
  1471. {
  1472.      Import int unlink();
  1473.      String tmp;
  1474.      FILE *out;
  1475.      InfoWidget iw = find_top(w);
  1476.  
  1477.      /* if you don't have this routine in your stdlib, make one up */
  1478.      tmp = tmpnam(NULL);
  1479.  
  1480.      if (!CURNODE(iw))
  1481.       message(iw, "?No current node?");
  1482.      else if ((out = fopen(tmp, "w")) == NULL)
  1483.       message(iw, "?Can't open temporary file '%s'.", tmp);
  1484.      else {
  1485.       String s1 = NSTART(iw, CURNODE(iw));
  1486.       String s2 = NEND(iw, CURNODE(iw));
  1487.       char syscmd[MAXSTR];
  1488.       int stat;
  1489.  
  1490.       fwrite(s1, s2 - s1, 1, out);
  1491.       fclose(out);
  1492.  
  1493.       message(iw, "Sending '%s' to the printer, please wait..",
  1494.           iw->info.node);
  1495.  
  1496.       sprintf(syscmd, "%s %s", iw->info.printCmd, tmp);
  1497.       if ((stat = system(syscmd)) != 0)
  1498.            message(iw, "?'%s' failed with exit status %d. Help!",
  1499.                syscmd, stat);
  1500.       else
  1501.            message(iw, "Finished printing.");
  1502.       unlink(tmp);
  1503.      }
  1504.        
  1505.      
  1506. }
  1507.  
  1508. /*****************************************************************************
  1509.  * Functions used from callback lists.                                       *
  1510.  *****************************************************************************/
  1511.  
  1512. /* Abort the dialog operation */
  1513. Local void do_dialog_abort(w, client_data, call_data)
  1514. Widget w;
  1515. caddr_t client_data;
  1516. caddr_t call_data;
  1517. {
  1518.      InfoWidget iw = (InfoWidget)client_data;
  1519.  
  1520.      if (w == iw->info.argText)
  1521.       clear_arg(iw);
  1522.      else
  1523.       XtDestroyWidget(iw->info.argPopup);
  1524. }
  1525.  
  1526. /* Confirm the dialog operation */
  1527. Local void do_dialog_confirm(w, client_data, call_data)
  1528. Widget w;
  1529. caddr_t client_data;
  1530. caddr_t call_data;
  1531. {
  1532.      InfoWidget iw = (InfoWidget)client_data;
  1533.      XawTextBlock blk;
  1534.  
  1535.      XtDestroyWidget(iw->info.argPopup);
  1536.  
  1537.      SET_BLOCK(blk, 0, 0, NULL);
  1538.      if ((blk.ptr = XawDialogGetValueString(XtParent(w))) &&
  1539.      (blk.length = strlen(blk.ptr))) {
  1540.       if (blk.length > ARGLEN)    /* truncate if necessary */
  1541.            blk.ptr[blk.length = ARGLEN] = '\0';
  1542.       XawTextReplace(iw->info.argText, 0, blk.length, &blk);
  1543.       (*(iw->info.requester))(w, iw, NULL);
  1544.      }
  1545. }
  1546.  
  1547. /*
  1548.  * Seems there should be a better way of doing this. Methinks the
  1549.  * XtCallbackPopdown() stuff isn't general enough. Should be a way of
  1550.  * doing this (and only this).
  1551.  */
  1552. Local void do_popdown(w, client_data, call_data)
  1553. Widget w;
  1554. caddr_t client_data;
  1555. caddr_t call_data;
  1556. {
  1557.      XtPopdown((Widget)client_data);
  1558. }
  1559.  
  1560. Local void do_prev(w, client_data, call_data)
  1561. Widget w;
  1562. caddr_t client_data;
  1563. caddr_t call_data;
  1564. {
  1565.      InfoWidget iw = (InfoWidget)client_data;
  1566.      String tmp;
  1567.  
  1568.      if ((tmp = offsetToString(iw, CURNODE(iw)->prev))) {
  1569.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1570.            message(iw, "?Can't find the previous (%s) for this node.",
  1571.                tmp);
  1572.      }
  1573.      else
  1574.       message(iw, "Node has no previous");
  1575. }
  1576.  
  1577. Local void do_quit(w, client_data, call_data)
  1578. Widget w;
  1579. caddr_t client_data;
  1580. caddr_t call_data;
  1581. {
  1582.      InfoWidget iw = (InfoWidget)client_data;
  1583.  
  1584.      if (XtHasCallbacks(iw, XtNcallback) != XtCallbackHasSome)
  1585.       message(iw, "Sorry, I just don't know how to quit.");
  1586.      else
  1587.       XtCallCallbacks(iw, XtNcallback, NULL);
  1588. }
  1589.  
  1590. Local void do_up(w, client_data, call_data)
  1591. Widget w;
  1592. caddr_t client_data;
  1593. caddr_t call_data;
  1594. {
  1595.      InfoWidget iw = (InfoWidget)client_data;
  1596.      String tmp;
  1597.  
  1598.      if ((tmp = offsetToString(iw, CURNODE(iw)->up))) {
  1599.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1600.            message(iw, "?Can't find the up (%s) for this node.", tmp);
  1601.      }
  1602.      else
  1603.       message(iw, "Node has no up");
  1604. }
  1605.  
  1606. Local void do_next(w, client_data, call_data)
  1607. Widget w;
  1608. caddr_t client_data;
  1609. caddr_t call_data;
  1610. {
  1611.      InfoWidget iw = (InfoWidget)client_data;
  1612.      String tmp;
  1613.  
  1614.      if ((tmp = offsetToString(iw, CURNODE(iw)->next))) {
  1615.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1616.            message(iw, "?Can't find the next (%s) for this node.", tmp);
  1617.      }
  1618.      else
  1619.       message(iw, "Node has no next");
  1620. }
  1621.  
  1622. Local void do_xref(w, client_data, call_data)
  1623. Widget w;
  1624. caddr_t client_data;
  1625. caddr_t call_data;
  1626. {
  1627.      InfoWidget iw = (InfoWidget)client_data;
  1628.      String tmp;
  1629.  
  1630.      if ((tmp = get_arg(iw)) != NULL) {
  1631.       if ((tmp = trueName(iw, CURNODE(iw)->xref, tmp)) == NULL)
  1632.            message(iw, "No cross reference entry named '%s' in this node.",
  1633.                get_arg(iw));
  1634.       else if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1635.            message(iw, "?Can't find node for xref item '%s'!",
  1636.                get_arg(iw));
  1637.      }
  1638.      else
  1639.       dialog(iw, "Please specify a cross reference:", do_xref);
  1640. }
  1641.  
  1642. Local void do_menu(w, client_data, call_data)
  1643. Widget w;
  1644. caddr_t client_data;
  1645. caddr_t call_data;
  1646. {
  1647.      InfoWidget iw = (InfoWidget)client_data;
  1648.      String tmp;
  1649.  
  1650.      if ((tmp = get_arg(iw)) != NULL) {
  1651.       if ((tmp = trueName(iw, CURNODE(iw)->menu, tmp)) == NULL)
  1652.            message(iw, "No menu entry named '%s' in this node.",
  1653.                get_arg(iw));
  1654.       else if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1655.            message(iw, "?Can't find node for menu item '%s'",
  1656.                get_arg(iw));
  1657.      }
  1658.      else
  1659.       dialog(iw, "Please specify a menu entry:", do_menu);
  1660. }
  1661.  
  1662. Local void do_goto(w, client_data, call_data)
  1663. Widget w;
  1664. caddr_t client_data;
  1665. caddr_t call_data;
  1666. {
  1667.      InfoWidget iw = (InfoWidget)client_data;
  1668.      String tmp;
  1669.  
  1670.      if ((tmp = get_arg(iw)) != NULL) {
  1671.       if (getNode(iw, NULL, tmp, NULL) == FALSE)
  1672.            message(iw, "Can't find a node named %s", tmp);
  1673.      }
  1674.      else
  1675.       dialog(iw, "Please specify the name of a node go to:", do_goto);
  1676. }
  1677.  
  1678. /*
  1679.  * Implement a somewhat simplistic search strategy. If file has an indirect
  1680.  * list, look for a match in the tag table (since just looking in the current
  1681.  * file probably wouldn't be very useful). If not, then search the current
  1682.  * file. If we're successful in either case, record the position (in the
  1683.  * tags table or the file) so that we don't hit it again right away.
  1684.  */
  1685. Local void do_search(w, client_data, call_data)
  1686. Widget w;
  1687. caddr_t client_data;
  1688. caddr_t call_data;
  1689. {
  1690.      InfoWidget iw = (InfoWidget)client_data;
  1691.      String tmp, s;
  1692.      char name[MAXSTR];
  1693.      Local struct {
  1694.       String file;
  1695.       caddr_t pos;
  1696.      } oldPos;
  1697.  
  1698.      if ((tmp = get_arg(iw)) != NULL) {
  1699.       /* if remembered position is invalid, reset it */
  1700.       if (strcomp(oldPos.file, iw->info.file)) {
  1701.            oldPos.file = iw->info.file;
  1702.            oldPos.pos = NULL;
  1703.       }
  1704.       if (INDIRECT(iw).table) {
  1705.            ID_P i;
  1706.            int len = strlen(tmp);
  1707.  
  1708.            if (oldPos.pos)
  1709.             i = (ID_P)oldPos.pos;
  1710.            else
  1711.             i = TAGTABLE(iw).table;
  1712.            /* do a tags search */
  1713.            while (I_NAME(*i)) {
  1714.             if (!strncomp(I_NAME(*i), tmp, len))
  1715.              break;
  1716.             i++;
  1717.            }
  1718.            /* success? */
  1719.            if (I_NAME(*i)) {
  1720.             oldPos.pos = (caddr_t)(i + 1);
  1721.             if (getNode(iw, iw->info.file, I_NAME(*i), NULL) == FALSE)
  1722.              message(iw, "?Can't find node for tag %s!",
  1723.                  I_NAME(*i));
  1724.            }
  1725.            else {
  1726.             message(iw, "Tag search for '%s' failed.", tmp);
  1727.             oldPos.pos = NULL;
  1728.            }
  1729.       }
  1730.       else {
  1731.            if (oldPos.pos)
  1732.             s = (String)oldPos.pos;
  1733.            else
  1734.             s = START(iw);
  1735.            if ((s = search(iw, s, END(iw), 
  1736.                    strconcat(NODE_TOKEN, tmp),
  1737.                    TRUE)) != NULL) {
  1738.             int i;
  1739.  
  1740.             oldPos.pos = (caddr_t)s;
  1741.             strcpy(name, tmp);
  1742.             i = strlen(name);
  1743.             while (!index(NAME, *s))
  1744.              name[i++] = *s++;
  1745.             name[i] = '\0';
  1746.             if (getNode(iw, iw->info.file, name, NULL) == FALSE)
  1747.              message(iw, "?Can't find node name in search!");
  1748.            }
  1749.            else {
  1750.             message(iw, "Search for '%s' failed.", tmp);
  1751.             oldPos.pos = NULL;
  1752.            }
  1753.       }
  1754.      }
  1755.      else
  1756.       dialog(iw, "Please enter a string to search for:", do_search);
  1757. }
  1758.  
  1759. /* These two handle selections from the menu and xref lists */
  1760.  
  1761. Local void do_menu_sel(w, client_data, call_data)
  1762. Widget w;
  1763. caddr_t client_data;
  1764. caddr_t call_data;
  1765. {
  1766.      InfoWidget iw = (InfoWidget)client_data;
  1767.      XawListReturnStruct *rs = (XawListReturnStruct *)call_data;
  1768.  
  1769.      if (getNode(iw, NULL, trueName(iw, CURNODE(iw)->menu, rs->string),
  1770.          NULL) == FALSE)
  1771.       message(iw, "?Can't find node for menu item '%s'", rs->string);
  1772. }
  1773.  
  1774. Local void do_xref_sel(w, client_data, call_data)
  1775. Widget w;
  1776. caddr_t client_data;
  1777. caddr_t call_data;
  1778. {
  1779.      InfoWidget iw = (InfoWidget)client_data;
  1780.      XawListReturnStruct *rs = (XawListReturnStruct *)call_data;
  1781.  
  1782.      if (getNode(iw, NULL, trueName(iw, CURNODE(iw)->xref, rs->string),
  1783.          NULL) == FALSE)
  1784.       message(iw, "?Can't find node for cross reference '%s'", rs->string);
  1785. }
  1786.  
  1787. /*****************************************************************************
  1788.  * Xlib and toolkit utility functions.                                       *
  1789.  *****************************************************************************/
  1790.  
  1791. /* Clear the argument text */
  1792. Local void clear_arg(iw)
  1793. InfoWidget iw;
  1794. {
  1795.      XawTextBlock blk;
  1796.  
  1797.      SET_BLOCK(blk, 0, 0, "");
  1798.      XawTextReplace(iw->info.argText, 0, strlen(iw->info.arg), &blk);
  1799. }
  1800.  
  1801. /* Put up a dialog to get necessary information */
  1802. Local void dialog(iw, msg, callback)
  1803. InfoWidget iw;
  1804. String msg;
  1805. void (*callback)();
  1806. {
  1807.      Arg args[10];
  1808.      Cardinal i;
  1809.      int x, y;
  1810.      Widget dg, abort, confirm;
  1811.  
  1812.      /*
  1813.       * We create the dialog everytime (rather than just once, followed
  1814.       * by Popup/Popdown requests) so that it will be made the proper size
  1815.       * for the label each time. Can't seem to get it to resize dynamically,
  1816.       * so I don't see any other way.
  1817.       */
  1818.      iw->info.requester = callback;
  1819.  
  1820.      /* Don't see any other way of doing this. It seems there should be. */
  1821.      getXY(iw, &x, &y);
  1822.  
  1823.      /* Position nicely */
  1824.      i = 0;
  1825.      XtSetArg(args[i], XtNx, x - 30 > 0 ? x - 30 : 0);    i++;
  1826.      XtSetArg(args[i], XtNy, y - 30 > 0 ? y - 30 : 0);    i++;
  1827.      XtSetArg(args[i], XtNallowShellResize, TRUE);    i++;
  1828.      iw->info.argPopup = XtCreatePopupShell("need_argument",
  1829.                        transientShellWidgetClass,
  1830.                        iw, args, i);
  1831.      i = 0;
  1832.      XtSetArg(args[i], XtNvalue, iw->info.arg);        i++;
  1833.      XtSetArg(args[i], XtNlabel, msg);            i++;
  1834.      dg = XtCreateManagedWidget("dialog", dialogWidgetClass,
  1835.                 iw->info.argPopup, args, i);
  1836.  
  1837.      i = 0;
  1838.      abort = XtCreateManagedWidget("abort", commandWidgetClass,
  1839.                    dg, args, i);
  1840.      XtAddCallback(abort, XtNcallback, do_dialog_abort, iw);
  1841.  
  1842.      i = 0;
  1843.      confirm = XtCreateManagedWidget("confirm", commandWidgetClass,
  1844.                      dg, args, i);
  1845.      XtAddCallback(confirm, XtNcallback, do_dialog_confirm, iw);
  1846.  
  1847.      XtPopup(iw->info.argPopup, XtGrabExclusive);
  1848. }
  1849.  
  1850. /* Toot the horn */
  1851. Local void feep(iw)
  1852. InfoWidget iw;
  1853. {
  1854.      XBell(XtDisplay(iw), iw->info.bell_volume);
  1855. }
  1856.  
  1857. /* Find the info widget in a hierarchy */
  1858. Local Inline InfoWidget find_top(w)
  1859. Widget w;
  1860. {
  1861.      register Widget tmp = w;
  1862.  
  1863.      while (tmp) {
  1864.       if (XtClass(tmp) == infoWidgetClass)
  1865.            return (InfoWidget)tmp;
  1866.       else
  1867.            tmp = XtParent(tmp);
  1868.      }
  1869.      if (!tmp)
  1870.       XtError("Walked off end of widget hierarchy!");
  1871.      return (InfoWidget)NULL;
  1872. }
  1873.  
  1874. /* Return the arg contents if set, else NULL */
  1875. Local String get_arg(w)
  1876. InfoWidget w;
  1877. {
  1878.      if (strlen(w->info.arg))
  1879.       return w->info.arg;
  1880.      else
  1881.       return NULL;
  1882. }
  1883.  
  1884. /* Return the root XY coords of the pointer */
  1885. Local void getXY(w, xp, yp)
  1886. Widget w;
  1887. int *xp, *yp;
  1888. {
  1889.      Window junkr, junkc;
  1890.      int junkx, junky;
  1891.      unsigned int mask;
  1892.  
  1893.      (void) XQueryPointer(XtDisplay(w), XtWindow(w), &junkr, &junkc,
  1894.               xp, yp, &junkx, &junky, &mask);
  1895. }
  1896.  
  1897. /*****************************************************************************
  1898.  * Unix and string utility functions.                                        *
  1899.  *****************************************************************************/
  1900.  
  1901. /* Search for a file along a path, returning the complete path name if found */
  1902. Local String find_file(path, name)
  1903. String path, name;
  1904. {
  1905.      String cp = path;
  1906.      Boolean more_path = TRUE;
  1907.      Local char dir[MAXPATHLEN];
  1908.      int status = -1;
  1909.  
  1910.      dir[0] = '\0';
  1911.  
  1912.      /* absolute path name? */
  1913.      if (name[0] == '/') {
  1914.       if (!access(name, R_OK))
  1915.            return name;
  1916.       else
  1917.            name = file_name(name);
  1918.      }
  1919.      while (status && more_path) {
  1920.           if ((cp = index(path, ':')) != NULL) {
  1921.                strncpy(dir, path, cp - path);
  1922.            dir[cp - path] = '\0';
  1923.                strcat(dir, "/");
  1924.                path = cp + 1;
  1925.           }
  1926.           else {
  1927.                strcpy(dir, path);
  1928.                strcat(dir, "/");
  1929.                more_path = FALSE;
  1930.           }
  1931.           strcat(dir, name);
  1932.       /* if we failed, try again in lower case */
  1933.           if (status = access(dir, R_OK))
  1934.            status = access(downcase(dir), R_OK);
  1935.      }
  1936.      if (dir[0])
  1937.           return dir;
  1938.      else
  1939.           return NULL;
  1940. }
  1941.  
  1942. /* return the file part of a path name */
  1943. Local Inline String file_name(s)
  1944. register String s;
  1945. {
  1946.      register int i = strlen(s);
  1947.  
  1948.      while (i) {
  1949.       if (s[i - 1] == '/')
  1950.            return s + i;
  1951.       i--;
  1952.      }
  1953.      return s;
  1954. }
  1955.  
  1956. /* strip evil tab/formfeed/newline chars from a string (replacing w/blanks) */
  1957. Local Inline String normalize_whitespace(s)
  1958. String s;
  1959. {
  1960.      register String tmp;
  1961.  
  1962.      if (tmp = s) {
  1963.       while (*tmp) {
  1964.            if (isspace(*tmp))
  1965.             *tmp = ' ';
  1966.            ++tmp;
  1967.       }
  1968.      }
  1969.      return s;
  1970. }
  1971.  
  1972. /* Convert from an offset ID to a string. */
  1973. Local Inline String offsetToString(iw, blk)
  1974. InfoWidget iw;
  1975. ID blk;
  1976. {
  1977.      Local char ret[MAXSTR];
  1978.  
  1979.      if (I_LEN(blk) != 0) {
  1980.       strncpy(ret, START(iw) + I_START(blk), I_LEN(blk));
  1981.       ret[I_LEN(blk)] = '\0';
  1982.       return normalize_whitespace(ret);
  1983.      }
  1984.      else
  1985.       return NULL;
  1986. }
  1987.  
  1988. /* chew through white space */
  1989. Local Inline String eat_whitespace(s)
  1990. register String s;
  1991. {
  1992.      while (*s && isspace(*s))
  1993.       s++;
  1994.      return s;
  1995. }
  1996.  
  1997. /* look up the actual name of a list item */
  1998. Local String trueName(iw, lst, name)
  1999. InfoWidget iw;
  2000. IDList lst;
  2001. String name;
  2002. {
  2003.      register int i;
  2004.  
  2005.      for (i = 0; i < lst.t.idx; i++)
  2006.       if (!strcomp(lst.l[i], name))
  2007.            return offsetToString(iw, lst.t.table[i]);
  2008.      return NULL;
  2009. }
  2010.               
  2011. /* Search for a string */
  2012. Local String search(iw, start, end, str, igncase)
  2013. InfoWidget iw;
  2014. register String start, end, str;
  2015. Boolean igncase;
  2016. {
  2017.      register String ind = str;
  2018.      register String stop = str + strlen(str);
  2019.      register int comp;
  2020.  
  2021.      while (start < end) {
  2022.       if (!igncase)
  2023.            comp = (*start == *ind);
  2024.       else
  2025.            comp = (TOLOWER(*start) == TOLOWER(*ind));
  2026.       if (!comp) {
  2027.            if (ind != str)
  2028.             ind = str;
  2029.            else
  2030.             start++;
  2031.       }
  2032.       else {
  2033.            if (++start <= end && ++ind == stop)
  2034.             return start;
  2035.       }
  2036.      }
  2037.      return NULL;
  2038. }
  2039.  
  2040. /* Like search(), but in the reverse direction */
  2041. Local String search_back(iw, start, end, str, igncase)
  2042. InfoWidget iw;
  2043. register String start, end, str;
  2044. Boolean igncase;
  2045. {
  2046.      register String ind;
  2047.      register String stop;
  2048.      register int comp;
  2049.  
  2050.      ind = str = reverse(str);
  2051.      stop = ind + strlen(ind);
  2052.  
  2053.      while (start > end) {
  2054.       if (!igncase)
  2055.            comp = (*start == *ind);
  2056.       else
  2057.            comp = (TOLOWER(*start) == TOLOWER(*ind));
  2058.       if (!comp) {
  2059.            if (ind != str)
  2060.             ind = str;
  2061.            else
  2062.             start--;
  2063.       }
  2064.       else {
  2065.            start--;
  2066.            if (++ind == stop)
  2067.             return start;
  2068.       }
  2069.      }
  2070.      return NULL;
  2071. }
  2072.  
  2073. /*
  2074.  * Safe and sane strcmp. Deals with null pointer for either arg and ignores
  2075.  * case. All whitespace is considered equivalent.
  2076.  */
  2077. Local Inline int strcomp(s1, s2)
  2078. register String s1, s2;
  2079. {
  2080.      if (s1 && s2) {
  2081.       if (strlen(s1) != strlen(s2))
  2082.           return -1;
  2083.  
  2084.       while (*s1 && *s2 && (TOLOWER(*s1) == TOLOWER(*s2)))
  2085.            ++s1, ++s2;
  2086.       if (!*s1 && !*s2)
  2087.            return 0;
  2088.       else if (*s1 < *s2)
  2089.            return -1;
  2090.       else
  2091.            return 1;
  2092.      }
  2093.      else if (!s1 && !s2)
  2094.           return 0;
  2095.      else if (!s1 && s2)
  2096.           return -1;
  2097.      else
  2098.           return 1;
  2099. }
  2100.  
  2101. /* like above, but stops after n characters */
  2102. Local Inline int strncomp(s1, s2, n)
  2103. register String s1, s2;
  2104. int n;
  2105. {
  2106.      register String s3 = s2 + n;
  2107.  
  2108.      if (s1 && s2) {
  2109.       while (s2 < s3 && *s1 && *s2 && (TOLOWER(*s1) == TOLOWER(*s2)))
  2110.            ++s1, ++s2;
  2111.       if (!*s1 && !*s2 || s2 == s3)
  2112.            return 0;
  2113.       else if (*s1 < *s2)
  2114.            return -1;
  2115.       else
  2116.            return 1;
  2117.      }
  2118.      else if (!s1 && !s2)
  2119.           return 0;
  2120.      else if (!s1 && s2)
  2121.           return -1;
  2122.      else
  2123.           return 1;
  2124. }
  2125.  
  2126. /* Copy s2 to s1 up to (but not including) character c */
  2127. Local Inline void strccpy(s1, s2, c)
  2128. register String s1, s2;
  2129. register char c;
  2130. {
  2131.      while (*s2 && *s2 != c)
  2132.       *(s1++) = *(s2++);
  2133.      *s1 = '\0';
  2134. }
  2135.  
  2136. /*
  2137.  * Return integer subscript of character 'c' in string 's'.
  2138.  * (why doesn't this already exist in a library somewhere?).
  2139.  */
  2140. Local Inline int iindex(s, c)
  2141. register char *s, c;
  2142. {
  2143.      register char *cp;
  2144.  
  2145.      if (!s)
  2146.           return -1;
  2147.      cp = index(s, c);
  2148.      if (cp)
  2149.           return cp - s;
  2150.      else
  2151.           return -1;
  2152. }
  2153.  
  2154. Local String substr(s, p1, p2)
  2155. register String s;
  2156. register int p1, p2;
  2157. {
  2158.      Local char ret[MAXSTR];
  2159.      register int i = 0;
  2160.  
  2161.      if (p1 > p2) {
  2162.       sprintf(ret, "substr: start %d, end %d. start must be <= end",
  2163.           p1, p2);
  2164.       XtWarning(ret);
  2165.           return NULL;
  2166.      }
  2167.      if (p2 - p1 > MAXSTR) {
  2168.           sprintf(ret, "substr: end - start is > max len of %d", MAXSTR);
  2169.       XtWarning(ret);
  2170.           return NULL;
  2171.      }
  2172.      while (p1 <= p2)
  2173.           ret[i++] = s[p1++];
  2174.      ret[i] = '\0';
  2175.      return ret;
  2176. }
  2177.  
  2178. /*
  2179.  * Safely concatenate two strings into static area, returning pointer to
  2180.  * result.
  2181.  */
  2182. Local String strconcat(s1, s2)
  2183. register String s1, s2;
  2184. {
  2185.      Local char ret[MAXSTR];
  2186.      int len1;
  2187.  
  2188.      if (s1) {
  2189.       if ((len1 = strlen(s1)) >= MAXSTR) {
  2190.            sprintf(ret, "strconcat: length of s1 > MAX (%d)", MAXSTR);
  2191.            XtWarning(ret);
  2192.            return NULL;
  2193.       }
  2194.       else
  2195.            strcpy(ret, s1);
  2196.       if (s2) {
  2197.            if (len1 + strlen(s2) > MAXSTR) {
  2198.             sprintf(ret, "strconcat: length of s1 + s2 is > MAX (%d)",
  2199.                 MAXSTR);
  2200.             XtWarning(ret);
  2201.            }
  2202.            else
  2203.             strcat(ret, s2);
  2204.       }
  2205.       return ret;
  2206.      }
  2207.      else
  2208.       return NULL;
  2209. }
  2210.  
  2211. /* reverse a string so that a simple reverse search may be done on it */
  2212. Local String reverse(s)
  2213. register String s;
  2214. {
  2215.      Local char ret[MAXSTR];
  2216.      register int i, len;
  2217.  
  2218.      if ((len = strlen(s)) > MAXSTR) {
  2219.       sprintf(ret, "reverse: string too long to reverse. MAX is %d",
  2220.           MAXSTR);
  2221.       XtWarning(ret);
  2222.       return NULL;
  2223.      }
  2224.      else {
  2225.       i = 0;
  2226.       while (len)
  2227.            ret[i++] = s[--len];
  2228.       ret[i] = '\0';
  2229.       return ret;
  2230.      }
  2231. }
  2232.  
  2233. /* convert a string to lower case */
  2234. Local Inline String downcase(s)
  2235. register String s;
  2236. {
  2237.      String orig = s;
  2238.  
  2239.      if (s)
  2240.       while (*s) {
  2241.            *s = TOLOWER(*s);
  2242.            s++;
  2243.       }
  2244.      return orig;
  2245. }
  2246.  
  2247. #ifdef BSD
  2248. /* BSD users don't have strpbrk() */
  2249. /* Routines borrowed from PD libc written by Richard A. O'Keefe. */
  2250.  
  2251. #if     CharsAreSigned
  2252. #define MaxPosChar      127
  2253. #else  ~CharsAreSigned
  2254. #define MaxPosChar      255
  2255. #endif  CharsAreSigned
  2256. #ifndef _AlphabetSize
  2257. #define _AlphabetSize   128
  2258. #endif
  2259.  
  2260. static int  _set_ctr = MaxPosChar;
  2261. static char _set_vec[_AlphabetSize];
  2262.  
  2263. void _str2set(set)
  2264. register String set;
  2265. {
  2266.      if (set == NULL)
  2267.       return;
  2268.      if (++_set_ctr == MaxPosChar+1) {
  2269.       register char *w = &_set_vec[_AlphabetSize];
  2270.       do
  2271.            *--w = '\0';
  2272.       while (w != &_set_vec[0]);
  2273.           _set_ctr = 1;
  2274.      }
  2275.      while (*set)
  2276.       _set_vec[*set++] = _set_ctr;
  2277. }
  2278.  
  2279. String strpbrk(s1, s2)
  2280. register String s1, s2;
  2281. {
  2282.      _str2set(set);
  2283.      while (_set_vec[*str] != _set_ctr)
  2284.           if (!*str++)
  2285.            return NULL;
  2286.      return str;
  2287. }
  2288. #endif /* BSD */
  2289.