jkh@meepmeep.pcs.com (Jordan K. Hubbard) (11/16/90)
Submitted-by: jkh@meepmeep.pcs.com (Jordan K. Hubbard) Posting-number: Volume 10, Issue 75 Archive-name: infow/part02 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of archive 2 (of 2)." # Contents: Info.c # Wrapped by jkh@meepmeep on Mon Nov 12 18:08:34 1990 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'Info.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'Info.c'\" else echo shar: Extracting \"'Info.c'\" \(60891 characters\) sed "s/^X//" >'Info.c' <<'END_OF_FILE' X#ifndef lint static char *rcsid = "$Header: /usr3/xinfo/RCS/Info.c,v 1.8 90/11/12 18:06:46 jkh Exp $"; X#endif X X#include "InfoP.h" X X#include <X11/Shell.h> X#include <X11/StringDefs.h> X#include <X11/Xaw/AsciiText.h> X#include <X11/Xaw/Box.h> X#include <X11/Xaw/Command.h> X#include <X11/Xaw/Dialog.h> X#include <X11/Xaw/Label.h> X#include <X11/Xaw/List.h> X#include <X11/Xaw/Paned.h> X#include <X11/Xaw/Viewport.h> X X#include <sys/stat.h> X#include <stdio.h> X#include <ctype.h> X#include <pwd.h> X X/* X * X * Copyright 1989, 1990 X * Jordan K. Hubbard X * X * PCS Computer Systeme, GmbH. X * Munich, West Germany X * X * X * This file is part of GNU Info widget. X * X * The GNU Info widget is free software; you can redistribute it and/or X * modify it under the terms of the GNU General Public License as published X * by the Free Software Foundation; either version 1, or (at your option) X * any later version. X * X * This software is distributed in the hope that it will be useful, X * but WITHOUT ANY WARRANTY; without even the implied warranty of X * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the X * GNU General Public License for more details. X * X * You should have received a copy of the GNU General Public License X * along with this software; see the file COPYING. If not, write to X * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. X * X * X */ X X/* X * $Log: Info.c,v $ X * Revision 1.8 90/11/12 18:06:46 jkh X * Removed aggregate initializations. GCC likes them, noone else does. X * X * Revision 1.7 90/11/12 13:46:11 jkh X * Fixed bug with bell_volume resource X * X * Revision 1.6 90/11/11 23:22:59 jkh X * Last minute fixes. X * X * Revision 1.5 90/11/11 22:24:05 jkh X * Added option to enable/disable retention of arg text. X * X * Revision 1.4 90/11/11 21:19:39 jkh X * Release 1.01 X * X * Revision 1.3 90/11/07 01:28:30 jkh X * Tweaked dialog popup to accept <return> as fast confirm. X * X * Revision 1.2 90/11/06 15:12:47 jkh X * Fixed memory leaks X * X * Revision 1.1 90/11/06 01:47:28 jkh X * Initial revision X * X */ X X#define offset(name) XtOffset(InfoWidget, info.name) X Local XtResource resources[] = { X { XpNinfoPath, XpCInfoPath, XtRString, sizeof(String), X offset(path), XtRString, XpDefaultInfoPath }, X { XpNinfoFile, XpCInfoFile, XtRString, sizeof(String), X offset(file), XtRString, XpDefaultInfoFile }, X { XpNinfoNode, XpCInfoNode, XtRString, sizeof(String), X offset(node), XtRString, XpDefaultInfoNode }, X { XpNbellVolume, XpCBellVolume, XtRInt, sizeof(int), X offset(bell_volume), XtRInt, (caddr_t)XpDefaultBellVolume }, X { XpNretainArg, XpCRetainArg, XtRBoolean, sizeof(Boolean), X offset(retain_arg), XtRBoolean, FALSE }, X { XtNcallback, XtCCallback, XtRCallback, sizeof(XtCallbackList), X offset(callback), XtRCallback, NULL }, X { XpNprintCommand, XpCPrintCommand, XtRString, sizeof(String), X offset(printCmd), XtRString, XpDefaultPrintCommand }, X}; X X#undef offset X Local Boolean SetValues(); Local XtGeometryResult GeometryManager(); Local void Destroy(); Local void Initialize(); Local void Realize(); Local void Resize(); X X/* Routines called directly by actions */ Local void Abort(); Local void ButtonSelection(); Local void Confirm(); Local void NodeDir(); Local void NodeGoto(); Local void NodeHelp(); Local void NodeLast(); Local void NodeMenuSelectByNumber(); Local void NodeNext(); Local void NodePrev(); Local void NodePrint(); Local void NodeQuit(); Local void NodeSearch(); Local void NodeTop(); Local void NodeTutorial(); Local void NodeUp(); Local void NodeXRef(); X X/* Routines called directly from callbacks or indirectly by actions */ Local void do_dialog_abort(); Local void do_dialog_confirm(); Local void do_goto(); Local void do_menu(); Local void do_menu_sel(); Local void do_next(); Local void do_popdown(); Local void do_prev(); Local void do_quit(); Local void do_search(); Local void do_up(); Local void do_xref(); Local void do_xref_sel(); X X/* Utility routines */ Local Boolean getNode(); Local Boolean parseTags(); Local InfoWidget find_top(); Local NodeInfo *popNode(); Local NodeInfo *pushNode(); Local String downcase(); Local String eat_whitespace(); Local String file_name(); Local String find_file(); Local String getFile(); Local String get_arg(); Local String normalize_whitespace(); Local String offsetToString(); Local String reverse(); Local String search(); Local String search_back(); Local String strconcat(); Local String substr(); Local String trueName(); Local int findNode(); Local int iindex(); Local int strcomp(); Local int strncomp(); Local void clear_arg(); Local void dialog(); Local void displayHeader(); Local void displayNode(); Local void feep(); Local void getXY(); Local void message(); Local void parseHeader(); Local void parseIndirect(); Local void parseMenu(); Local void parseNode(); Local void parseXRefs(); Local void showStatus(); Local void strccpy(); X Local XtActionsRec actionTable[] = X{ X { "abort", Abort }, X { "confirm", Confirm }, X { "info_click", ButtonSelection }, X { "info_dir", NodeDir }, X { "info_goto", NodeGoto }, X { "info_last", NodeLast }, X { "info_menusel", NodeMenuSelectByNumber }, X { "info_next", NodeNext }, X { "info_nodeSearch", NodeSearch }, X { "info_popupHelp", NodeHelp }, X { "info_prev", NodePrev }, X { "info_print", NodePrint }, X { "info_quit", NodeQuit }, X { "info_top", NodeTop }, X { "info_tutorial", NodeTutorial }, X { "info_up", NodeUp }, X { "info_xref", NodeXRef }, X { NULL, NULL } X}; X XExport InfoClassRec infoClassRec = { X { /* core fields */ X /* superclass */ (WidgetClass)&compositeClassRec, X /* class_name */ "Info", X /* widget_size */ sizeof(InfoRec), X /* class_initialize */ NULL, X /* class_part_initialize */ NULL, X /* class_inited */ FALSE, X /* initialize */ Initialize, X /* initialize_hook */ NULL, X /* realize */ Realize, X /* actions */ actionTable, X /* num_actions */ XtNumber(actionTable), X /* resources */ resources, X /* num_resources */ XtNumber(resources), X /* xrm_class */ NULLQUARK, X /* compress_motion */ TRUE, X /* compress_exposure */ TRUE, X /* compress_enterleave */ TRUE, X /* visible_interest */ FALSE, X /* destroy */ Destroy, X /* resize */ Resize, X /* expose */ XtInheritExpose, X /* set_values */ SetValues, X /* set_values_hook */ NULL, X /* set_values_almost */ XtInheritSetValuesAlmost, X /* get_values_hook */ NULL, X /* accept_focus */ XtInheritAcceptFocus, X /* version */ XtVersion, X /* callback_private */ NULL, X /* tm_table */ NULL, X /* query_geometry */ XtInheritQueryGeometry, X /* display_accelerator */ XtInheritDisplayAccelerator, X /* extension */ NULL X }, X { /* composite fields */ X /* geometry_manager */ GeometryManager, X /* change_managed */ NULL, X /* insert_child */ XtInheritInsertChild, X /* delete_child */ XtInheritDeleteChild, X /* extension */ NULL, X }, X { /* info fields */ X /* empty */ 0 X } X}; X XExport WidgetClass infoWidgetClass = (WidgetClass)&infoClassRec; X X#ifndef tolower Import char tolower(); X#define TOLOWER(c) (tolower(c)) X#else X#define TOLOWER(c) (isupper(c) ? tolower(c) : (c)) X#endif X Local XtCallbackRec cb[2]; X#define XtSetCbk(argarray, rtn, arg) \ X cb[0].callback = rtn; \ X cb[0].closure = (caddr_t)arg; \ X XtSetArg(argarray, XtNcallback, cb) X X/***************************************************************************** X * Widget manipulation routines. * X *****************************************************************************/ X Local Boolean SetValues(current, request, new) Widget current, request, new; X{ X InfoWidget cw = (InfoWidget)current; X InfoWidget nw = (InfoWidget)new; X X if (cw->info.file != nw->info.file X || strcomp(cw->info.file, nw->info.file) X || cw->info.node != nw->info.node X || strcomp(cw->info.node, nw->info.node)) { X XtFree(cw->info.file); X XtFree(cw->info.node); X getNode(nw, nw->info.file, nw->info.node, NULL); X } X /* getNode() does the redisplay implicitly */ X return FALSE; X} X X/* We only manage one widget (the pane) directly */ Local XtGeometryResult GeometryManager(w, request, reply) InfoWidget w; XXtWidgetGeometry *request; XXtWidgetGeometry *reply; /* RETURN */ X{ X XtGeometryResult res; X Dimension width, height; X X width = w->core.width; X height = w->core.height; X X /* We don't really care; see what daddy says */ X res = XtMakeGeometryRequest(XtParent(w), request, reply); X if (res == XtGeometryNo) X return res; X else if (res == XtGeometryAlmost) { X if (reply->request_mode & CWWidth) X width = reply->width; X if (reply->request_mode & CWHeight) X height = reply->height; X } X else { /* Has to be XtGeometryYes */ X if (request->request_mode & CWWidth) X width = request->width; X if (request->request_mode & CWHeight) X height = request->height; X } X XtResizeWidget(w, width, height, w->core.border_width); X} X Local void Destroy(w) Widget w; X{ X InfoWidget iw = (InfoWidget)w; X X if (INDIRECT(iw).table) X FREE_TAG_TABLE(INDIRECT(iw)); X if (TAGTABLE(iw).table) X FREE_TAG_TABLE(TAGTABLE(iw)); X while (popNode(iw)); /* popNode will free all but last */ X /* now free the last one */ X if (CURNODE(iw)) { X XtFree(CURNODE(iw)->file); X XtFree(CURNODE(iw)->node); X XtFree(CURNODE(iw)); X } X XtFree(iw->info.file); X XtFree(iw->info.node); X} X Local void Initialize(request, new) Widget request; Widget new; X{ X Arg args[15]; X Cardinal i; X InfoWidget iw = (InfoWidget)new; X Widget top, box1, box2, vport, vport2; X char blanks[MAXSTR], *cp; X Import char *bzero(); X X /* create a blank filled string as a placeholder for certain labels */ X for (i = 0; i < MAXSTR - 1; i++) X blanks[i] = ' '; X blanks[i] = '\0'; X X /* Pick some desperation defaults */ X if (new->core.width == 0) X new->core.width = 100; X if (new->core.height == 0) X new->core.height = 50; X X /* Prevent later confusion */ X iw->info.arg[0] = '\0'; X X /* Create outer pane */ X i = 0; X top = XtCreateManagedWidget("pane1", panedWidgetClass, new, args, i); X X /* Create top row of "main control" buttons and labels. */ X i = 0; X box1 = XtCreateManagedWidget("box1", boxWidgetClass, top, args, i); X X if (iw->info.callback) { X Widget q; X X i = 0; X q = XtCreateManagedWidget("quit", commandWidgetClass, X box1, args, i); X XtAddCallback(q, XtNcallback, do_quit, iw); X } X i = 0; X XtSetArg(args[i], XtNlabel, "File: "); i++; X iw->info.fileLabel = XtCreateManagedWidget("file", labelWidgetClass, X box1, args, i); X i = 0; X XtSetArg(args[i], XtNlabel, "Node: "); i++; X iw->info.nodeLabel = XtCreateManagedWidget("node", labelWidgetClass, X box1, args, i); X i = 0; X XtSetArg(args[i], XtNlabel, "Prev: "); i++; X XtSetCbk(args[i], do_prev, iw); i++; X iw->info.prevCmd = XtCreateManagedWidget("prev", commandWidgetClass, X box1, args, i); X i = 0; X XtSetArg(args[i], XtNlabel, "Up: "); i++; X XtSetCbk(args[i], do_up, iw); i++; X iw->info.upCmd = XtCreateManagedWidget("up", commandWidgetClass, X box1, args, i); X i = 0; X XtSetArg(args[i], XtNlabel, "Next: "); i++; X XtSetCbk(args[i], do_next, iw); i++; X iw->info.nextCmd = XtCreateManagedWidget("next", commandWidgetClass, X box1, args, i); X X /* Create the menu pane */ X i = 0; X XtSetArg(args[i], XtNallowVert, TRUE); i++; X vport = XtCreateManagedWidget("vport1", viewportWidgetClass, X top, args, i); X X i = 0; X XtSetCbk(args[i], do_menu_sel, iw); i++; X iw->info.menuList = XtCreateManagedWidget("menu", listWidgetClass, X vport, args, i); X /* X * Create the text area for displaying node contents. X */ X i = 0; X XtSetArg(args[i], XtNstring, blanks); i++; X XtSetArg(args[i], XtNlength, MAXSTR); i++; X XtSetArg(args[i], XtNeditType, XawtextRead); i++; X XtSetArg(args[i], XtNuseStringInPlace, TRUE); i++; X XtSetArg(args[i], XtNtype, XawAsciiString); i++; X iw->info.nodeText = XtCreateManagedWidget("nodeText", X asciiTextWidgetClass, X top, args, i); X i = 0; X XtSetArg(args[i], XtNallowVert, TRUE); i++; X vport2 = XtCreateManagedWidget("vport2", viewportWidgetClass, X top, args, i); X X /* Create the xref pane */ X i = 0; X XtSetCbk(args[i], do_xref_sel, iw); i++; X iw->info.xrefList = XtCreateManagedWidget("xref", listWidgetClass, X vport2, args, i); X X /* X * Create the bottom "auxilliary" command button group. X */ X i = 0; X box2 = XtCreateManagedWidget("box2", boxWidgetClass, X top, args, i); X i = 0; X XtSetCbk(args[i], do_menu, iw); i++; X iw->info.xrefCmd = XtCreateManagedWidget("menu", commandWidgetClass, X box2, args, i); X i = 0; X XtSetCbk(args[i], do_xref, iw); i++; X iw->info.xrefCmd = XtCreateManagedWidget("xref", commandWidgetClass, X box2, args, i); X i = 0; X XtSetCbk(args[i], do_goto, iw); i++; X iw->info.gotoCmd = XtCreateManagedWidget("goto", commandWidgetClass, X box2, args, i); X i = 0; X XtSetCbk(args[i], do_search, iw); i++; X iw->info.searchCmd = XtCreateManagedWidget("search", commandWidgetClass, X box2, args, i); X i = 0; X bzero(iw->info.arg, ARGLEN); X XtSetArg(args[i], XtNstring, iw->info.arg); i++; X XtSetArg(args[i], XtNlength, ARGLEN); i++; X XtSetArg(args[i], XtNuseStringInPlace, TRUE); i++; X XtSetArg(args[i], XtNeditType, XawtextEdit); i++; X iw->info.argText = XtCreateManagedWidget("arg", asciiTextWidgetClass, X box2, args, i); X X /* X * Create the status and message area labels. X */ X i = 0; X XtSetArg(args[i], XtNresize, FALSE); i++; X XtSetArg(args[i], XtNlabel, blanks); i++; X XtSetArg(args[i], XtNborderWidth, 0); i++; X iw->info.statusLabel = XtCreateManagedWidget("status", labelWidgetClass, X top, args, i); X i = 0; X XtSetArg(args[i], XtNresize, FALSE); i++; X XtSetArg(args[i], XtNlabel, blanks); i++; X XtSetArg(args[i], XtNborderWidth, 0); i++; X iw->info.messageLabel = XtCreateManagedWidget("message", labelWidgetClass, X top, args, i); X X /* set the initial node information */ X ZERO_TABLE(INDIRECT(iw)); X ZERO_TABLE(TAGTABLE(iw)); X DATA(iw) = NULL; X CURNODE(iw) = NULL; X X iw->info.file = XtNewString(iw->info.file); X iw->info.node = XtNewString(iw->info.node); X X if (getNode(iw, iw->info.file, iw->info.node, NULL) == FALSE) X message(iw, "?Can't find initial file/node."); X} X Local void Realize(w, value_mask, attributes) InfoWidget w; Mask *value_mask; XXSetWindowAttributes *attributes; X{ X if (w->composite.num_children < 1) X XtError("No children?!?"); X else { X /* Create window with which to manage child */ X XtCreateWindow(w, (unsigned int)InputOutput, X (Visual *)CopyFromParent, *value_mask, attributes); X XtResizeWidget(w->composite.children[0], w->core.width, X w->core.height, 0); X /* X * Install accelerators onto widgets we know will need them. X * Note that Volume 4 of the O'Reilly "X Toolkit Intrinsics X * Programming Manual" (page 204, paragraph 5) says that widgets X * should never do this. I disagree; here's a case in point. X */ X XtInstallAllAccelerators(w, w); X XtInstallAccelerators(w->info.nodeText, w); X } X} X Local void Resize(w) InfoWidget w; X{ X XtResizeWidget(w->composite.children[0], w->core.width, w->core.height, X 0); X} X X/***************************************************************************** X * Info file manipulation routines. * X *****************************************************************************/ X X/* Here is the main guy. Handles all navigation within the info tree. */ Local Boolean getNode(iw, file, node, pushTo) InfoWidget iw; String file, node; NodeInfo *pushTo; X{ X NodeInfo *cur; X int offset; X Boolean status = FALSE, needfile; X X if (node && index(node, '(') && index(node, ')')) { X file = substr(node, iindex(node, '(') + 1, X iindex(node, ')') - 1); X node = index(node, ')') + 1; X } X if (!node || !*node) X node = "Top"; X X if (!file) { X file = iw->info.file; X needfile = !DATA(iw); X } X else X needfile = !DATA(iw) || X strcomp(file_name(file), file_name(iw->info.file)); X if (needfile) { X /* get a new file */ X if ((file = getFile(iw, file, FALSE)) != NULL) { X if (file && iw->info.file != file) { X XtFree(iw->info.file); X iw->info.file = XtNewString(file); X } X iw->info.subFile = NULL; X } X } X else if (!strcomp(node, iw->info.node)) X return TRUE; /* we're already there */ X else { X XtFree(iw->info.node); X iw->info.node = XtNewString(node); X } X if (file && (offset = findNode(iw, node)) >= 0) { X if (!pushTo) { X cur = pushNode(iw, iw->info.file, iw->info.node, offset); X parseNode(iw, cur, offset); X } X else X cur = pushTo; X displayNode(iw, cur); X message(iw, NULL); X showStatus(iw, cur); X if (!iw->info.retain_arg) X clear_arg(iw); X status = TRUE; X } X else { X /* Failed to get the new node, go back (but only once) */ X if (!pushTo && CURNODE(iw)) X getNode(iw, CURNODE(iw)->file, CURNODE(iw)->node, CURNODE(iw)); X } X return status; X} X X/* Loads in file "name" and tag/indirect info, if any. */ Local String getFile(iw, name, subfilep) InfoWidget iw; String name; Boolean subfilep; X{ X String ret; X X FILE *fp; X X ret = find_file(iw->info.path, name); X if (ret) { X Import int stat(); X struct stat sb; X X if (!stat(ret, &sb) && (fp = fopen(ret, "r"))) { X XtFree(DATA(iw)); X DATA(iw) = XtMalloc(sb.st_size + 1); X /* V.4 users will want to replace with an mmap() call */ X if (fread(DATA(iw), 1, sb.st_size, fp) == sb.st_size) { X fclose(fp); X DATA(iw)[DATASIZE(iw) = sb.st_size] = '\0'; X if (!subfilep) { X Boolean needIndirect; X X needIndirect = parseTags(iw); X parseIndirect(iw, needIndirect); X } X } X else { X message(iw, "?Read error on %s.", name); X XtFree(DATA(iw)); X ret = NULL; X } X } X else X ret = NULL; X } X return ret; X} X X/* Look through tag table (and/or current buffer) for a node */ Local int findNode(iw, name) InfoWidget iw; String name; X{ X ID_P i; X int offset = -1; X String s, srch; X X /* A node name of "*" means the whole file */ X if (!strcomp(name, "*")) X return 0; X X if (TAGTABLE(iw).table) { X for (i = TAGTABLE(iw).table; I_NAME(*i); i++) { X if (!strcomp(I_NAME(*i), name)) { X offset = I_OFFSET(*i); X break; X } X } X /* if we found the tag and there's an indirect table, adjust */ X if (offset > 0 && INDIRECT(iw).table) { X String sub; X X for (i = INDIRECT(iw).table; I_NAME(*i); i++) { X if (I_OFFSET(*i) > offset) /* got it */ X break; X } X sub = I_NAME(*(--i)); X if (strcomp(sub, iw->info.subFile)) { X if (!getFile(iw, sub, TRUE)) X return 0; X else X iw->info.subFile = sub; X } X offset -= I_OFFSET(*i); X /* compensate for header */ X offset += HDRSIZE(iw); X } X } X /* X * Now search forward for the node name. Note that this will X * work whether or not we found the tag in the tag table. Having X * found the tag only insures that we search a little less. X */ X s = START(iw); X if (offset > 0) X s += offset; X X /* X * since bogus tags can leave us *after* the node start as well as X * before it, we risk a little extra searching and back up to the X * closest node marker above. Es tut mir leid, but this is what you X * get with out-of-date tags! X */ X while (s > START(iw) && !INFO_CHAR(*s)) X --s; X srch = strconcat(NODE_TOKEN, name); X while (s) { X if ((s = search(iw, s, END(iw), srch, TRUE)) != NULL) { X /* If not an exact match, keep looking */ X if (!index(NAME, *s)) X continue; X offset = INTOFF(START(iw), s); X /* found it, move to the beginning */ X while(!INFO_CHAR(START(iw)[offset - 1])) X offset--; X s = NULL; X } X else X offset = -1; X } X return offset; X} X X/* Push a node onto the history list */ Local NodeInfo *pushNode(iw, file, node, offset) InfoWidget iw; String file, node; int offset; X{ X NodeInfo *tmp; X X tmp = XtNew(NodeInfo); X bzero(tmp, sizeof(NodeInfo)); X tmp->file = XtNewString(file); X tmp->node = XtNewString(node); X tmp->start = offset; X tmp->nextNode = CURNODE(iw); X CURNODE(iw) = tmp; X return tmp; X} X X/* Pop a node off the history list */ Local NodeInfo *popNode(iw) InfoWidget iw; X{ X NodeInfo *tmp = NULL; X X if (CURNODE(iw) && CURNODE(iw)->nextNode) { X tmp = CURNODE(iw)->nextNode; X XtFree(CURNODE(iw)->file); X XtFree(CURNODE(iw)->node); X FREE_LIST(CURNODE(iw)->menu); X FREE_LIST(CURNODE(iw)->xref); X XtFree(CURNODE(iw)); X CURNODE(iw) = tmp; X } X return tmp; X} X X/* Parse out all the header/menu/xref information for a node. */ Local void parseNode(iw, n, offset) InfoWidget iw; NodeInfo *n; int offset; X{ X register String start = START(iw) + offset; X X /* was the whole file ("*") selected? */ X if (offset == 0) { X n->length = DATASIZE(iw); X I_START(n->name) = I_LEN(n->name) = 0; X I_START(n->prev) = I_LEN(n->prev) = 0; X I_START(n->up) = I_LEN(n->up) = 0; X I_START(n->next) = I_LEN(n->next) = 0; X I_START(n->text) = 0; X I_LEN(n->text) = n->length; X } X else { X /* find the end of the node */ X n->length = 0; X start = START(iw) + offset; X while (start < END(iw) && !INFO_CHAR(*start)) { X n->length++; X start++; X } X } X /* get the header */ X parseHeader(iw, n); X /* get the menu items */ X parseMenu(iw, n); X /* get the cross reference entries */ X parseXRefs(iw, n); X} X Local void parseHeader(iw, n) InfoWidget iw; NodeInfo *n; X{ X String strpbrk(), tmp; X X /* first, get the node name offset */ X I_START(n->name) = INTOFF(START(iw), NSEARCH(iw, n, NODE_TOKEN)); X I_LEN(n->name) = INTOFF(START(iw), strpbrk(START(iw) + I_START(n->name), X NAME_END_TOKEN)) - X I_START(n->name); X X /* now the prev, if any */ X if ((I_START(n->prev) = INTOFF(START(iw), X NSEARCH(iw, n, PREV_TOKEN))) > 0) X I_LEN(n->prev) = INTOFF(START(iw), X strpbrk(START(iw) + I_START(n->prev), X NAME_END_TOKEN)) - X I_START(n->prev); X else X I_LEN(n->prev) = I_START(n->prev) = 0; X X /* and the up, if any */ X if ((I_START(n->up) = INTOFF(START(iw), X NSEARCH(iw, n, UP_TOKEN))) > 0) X I_LEN(n->up) = INTOFF(START(iw), strpbrk(START(iw) + I_START(n->up), X NAME_END_TOKEN)) - X I_START(n->up); X else X I_LEN(n->up) = I_START(n->up) = 0; X X /* the next, if any */ X if ((I_START(n->next) = INTOFF(START(iw), X NSEARCH(iw, n, NEXT_TOKEN))) > 0) X I_LEN(n->next) = INTOFF(START(iw), X strpbrk(START(iw) + I_START(n->next), X NAME_END_TOKEN)) - X I_START(n->next); X else X I_LEN(n->next) = I_START(n->next) = 0; X X /* And finally skip over the header and set the text offset there */ X tmp = START(iw) + I_START(n->name); X while (*tmp != '\n') X tmp++; X I_START(n->text) = INTOFF(START(iw), tmp + 1); X I_LEN(n->text) = n->length - (I_START(n->text) - n->start); X} X Local void parseMenu(iw, n) InfoWidget iw; NodeInfo *n; X{ X register String mstart; X String strpbrk(); X X /* start clean */ X ZERO_LIST(n->menu); X X /* Does node have a menu? */ X if ((mstart = NSEARCH(iw, n, MENU_TOKEN)) != NULL) { X /* Initialize table and string list */ X ALLOC_LIST(n->menu); X X /* go looking for menu items */ X while (mstart = search(iw, mstart, NEND(iw, n), MENU_SEP_TOKEN, X FALSE)) { X MAYBE_BUMP_LIST(n->menu); X I_LEN(TPOS(n->menu.t)) = 0; X I_START(TPOS(n->menu.t)) = INTOFF(START(iw), mstart); X while (*(mstart++) != ':') X I_LEN(TPOS(n->menu.t))++; X /* save the menu name as a string */ X LPOS(n->menu) = XtMalloc(I_LEN(TPOS(n->menu.t)) + 1); X strncpy(LPOS(n->menu), START(iw) + I_START(TPOS(n->menu.t)), X I_LEN(TPOS(n->menu.t))); X LPOS(n->menu)[I_LEN(TPOS(n->menu.t))] = '\0'; X normalize_whitespace(LPOS(n->menu)); X /* Is the menu name not the node name? */ X if (*mstart != ':') { X int plev = 0; X X mstart = eat_whitespace(mstart); X I_START(TPOS(n->menu.t)) = INTOFF(START(iw), mstart); X while (*mstart != '\0' && !(plev == 0 X && index(NAME_END_TOKEN, *mstart) != NULL)) { X if (*mstart == '(') X ++plev; X else if (*mstart == ')') X --plev; X mstart++; X } X I_LEN(TPOS(n->menu.t)) = X INTOFF(START(iw), mstart) - I_START(TPOS(n->menu.t)); X } X INCP(n->menu.t); X } X ROUND_LIST(n->menu); X } X} X Local void parseXRefs(iw, n) InfoWidget iw; NodeInfo *n; X{ X register String nstart; X String strpbrk(); X X /* start clean */ X ZERO_LIST(n->xref); X X /* Do we have any cross-reference entries? */ X if ((nstart = search(iw, NSTART(iw, n), NEND(iw, n), NOTE_TOKEN, TRUE)) X != NULL) { X ALLOC_LIST(n->xref); X nstart = NSTART(iw, n); X X /* X * Go looking for cross-references (including the one we just X * found; wasteful, but avoiding it would make for grotty code). X */ X while (nstart = search(iw, nstart, NEND(iw, n), NOTE_TOKEN, TRUE)) { X /* skip over whitespace */ X nstart = eat_whitespace(nstart); X MAYBE_BUMP_LIST(n->xref); X I_LEN(TPOS(n->xref.t)) = 0; X I_START(TPOS(n->xref.t)) = INTOFF(START(iw), nstart); X while (*(nstart++) != ':') X I_LEN(TPOS(n->xref.t))++; X /* save the note name as a string */ X LPOS(n->xref) = XtMalloc(I_LEN(TPOS(n->xref.t)) + 1); X strncpy(LPOS(n->xref), START(iw) + I_START(TPOS(n->xref.t)), X I_LEN(TPOS(n->xref.t))); X LPOS(n->xref)[I_LEN(TPOS(n->xref.t))] = '\0'; X normalize_whitespace(LPOS(n->xref)); X /* Is the note name not the first part? */ X if (*nstart != ':') { X nstart = eat_whitespace(nstart + 1); X I_START(TPOS(n->xref.t)) = INTOFF(START(iw), nstart); X I_LEN(TPOS(n->xref.t)) = X INTOFF(START(iw), strpbrk(nstart, NAME_END_TOKEN)) - X I_START(TPOS(n->xref.t)); X } X INCP(n->xref.t); X } X ROUND_LIST(n->xref); X } X} X X/* Put the node information on the screen */ Local void displayNode(iw, n) InfoWidget iw; NodeInfo *n; X{ X Arg args[5]; X Cardinal i, lst_size; X String *lst; X Local char *nolist[] = { "", NULL }; /* make the list widget happy */ X Local char tmpfile[256]; X X /* Make sure it doesn't try anything cute until we're ready */ X XawTextDisableRedisplay(iw->info.nodeText); X X /* X * There exists a strange bug in the text widget that causes text X * to be erroneously selected when we're mousing selections. Since X * we don't want to keep things selected while we're navigating X * anyway, this is a satisfactory workaround. X */ X XawTextUnsetSelection(iw->info.nodeText); X X /* show the header */ X displayHeader(iw, n); X X /* show the menu */ X if (!n->menu.l) { X lst = nolist; X lst_size = 1; X } X else { X lst = n->menu.l; X lst_size = IDX(n->menu.t); X } X XawListChange(iw->info.menuList, lst, lst_size, 0, TRUE); X X /* change the xref list */ X if (!n->xref.l) { X lst = nolist; X lst_size = 1; X } X else { X lst = n->xref.l; X lst_size = IDX(n->xref.t); X } X XawListChange(iw->info.xrefList, lst, lst_size, 0, TRUE); X X /* Show the new text */ X i = 0; X if (I_START(n->text)) { X char *addr = (START(iw) + I_START(n->text) + I_LEN(n->text)); X X XtSetArg(args[i], XtNstring, START(iw) + I_START(n->text)); i++; X XtSetArg(args[i], XtNlength, I_LEN(n->text)); i++; X if (INFO_CHAR(*addr)) X *addr = '\0'; X else { X char msg[256]; X X sprintf(msg, "Encountered bad terminator (%d) for node '%s'", X *addr, n->name); X XtWarning(msg); X } X } X else { X XtSetArg(args[i], XtNstring, START(iw)); i++; X XtSetArg(args[i], XtNlength, DATASIZE(iw)); i++; X } X XtSetValues(iw->info.nodeText, args, i); X X /* Go for redisplay */ X XawTextEnableRedisplay(iw->info.nodeText); X X /* Stick the insertion marker at the top where it's out of the way */ X XawTextSetInsertionPoint(iw->info.nodeText, 0); X} X X/* display the header information */ Local void displayHeader(iw, n) InfoWidget iw; NodeInfo *n; X{ X Arg args[5]; X Cardinal i; X String tmp; X int sensitive; X X /* set the file name */ X tmp = strconcat("File: ", file_name(iw->info.file)); X i = 0; X XtSetArg(args[i], XtNlabel, tmp); i++; X XtSetValues(iw->info.fileLabel, args, i); X X /* set the node name */ X i = 0; X if ((tmp = offsetToString(iw, n->name)) != NULL) X sensitive = TRUE; X else X sensitive = FALSE; X XtSetArg(args[i], XtNlabel, strconcat("Node: ", tmp)); i++; X XtSetArg(args[i], XtNsensitive, sensitive); i++; X XtSetValues(iw->info.nodeLabel, args, i); X X /* set the prev */ X i = 0; X if ((tmp = offsetToString(iw, n->prev)) != NULL) X sensitive = TRUE; X else X sensitive = FALSE; X XtSetArg(args[i], XtNlabel, strconcat("Prev: ", tmp)); i++; X XtSetArg(args[i], XtNsensitive, sensitive); i++; X XtSetValues(iw->info.prevCmd, args, i); X X /* set the up */ X i = 0; X if ((tmp = offsetToString(iw, n->up)) != NULL) X sensitive = TRUE; X else X sensitive = FALSE; X XtSetArg(args[i], XtNlabel, strconcat("Up: ", tmp)); i++; X XtSetArg(args[i], XtNsensitive, sensitive); i++; X XtSetValues(iw->info.upCmd, args, i); X X /* set the next */ X i = 0; X if ((tmp = offsetToString(iw, n->next)) != NULL) X sensitive = TRUE; X else X sensitive = FALSE; X XtSetArg(args[i], XtNlabel, strconcat("Next: ", tmp)); i++; X XtSetArg(args[i], XtNsensitive, sensitive); i++; X XtSetValues(iw->info.nextCmd, args, i); X} X X/* X * Look for tag table information in the current buffer. If tag table X * is indirect, return TRUE, else return false. X */ Local Boolean parseTags(iw) InfoWidget iw; X{ X String start, s1; X char tmp[MAXSTR]; X Boolean indirect = FALSE; X int i; X X /* X * go back about 8 lines. I don't know if this will always back up X * past the end marker, but Emacs info seems to think so. X */ X start = END(iw); X i = 0; X while (i < 8) X if (*(--start) == '\n') X i++; X X start = search(iw, start, END(iw), TAGEND_TOKEN, TRUE); X if (start && (start = search_back(iw, start, START(iw), X TAGTABLE_TOKEN, TRUE))) { X ALLOC_TABLE(TAGTABLE(iw)); X /* we were searching backward so move over the token */ X start += strlen(TAGTABLE_TOKEN); X if ((s1 = search(iw, start, start + strlen(ITAGTABLE_TOKEN) + 10, X ITAGTABLE_TOKEN, TRUE)) != NULL) { X indirect = TRUE; X start = s1; X } X while ((start = search(iw, start, END(iw), NODE_TOKEN, FALSE)) X != NULL) { X MAYBE_BUMP_TABLE(TAGTABLE(iw)); X strccpy(tmp, start, DEL_CHAR); X I_NAME(TPOS(TAGTABLE(iw))) = XtNewString(tmp); X start += strlen(tmp) + 1; X sscanf(start, "%d", &I_OFFSET(TPOS(TAGTABLE(iw)))); X INCP(TAGTABLE(iw)); X } X ROUND_TABLE(TAGTABLE(iw)); X } X else if (TAGTABLE(iw).table) X FREE_TAG_TABLE(TAGTABLE(iw)); X return indirect; X} X X/* Look for indirect file information in the current buffer */ Local void parseIndirect(iw, needIndirect) InfoWidget iw; Boolean needIndirect; X{ X String start; X char tmp[MAXSTR], *s1; X X if (start = search(iw, START(iw), END(iw), INDIRECT_TOKEN, TRUE)) { X /* move backwards looking for the INFO_CHAR */ X for (s1 = start; s1 >= START(iw) && !INFO_CHAR(*s1); s1--); X if (s1 < START(iw)) { X message(iw, "?Invalid indirect table for %s!", iw->info.file); X return; X } X else X HDRSIZE(iw) = INTOFF(START(iw), s1); X ALLOC_TABLE(INDIRECT(iw)); X for (IDX(INDIRECT(iw)) = 0; !INFO_CHAR(*start); INCP(INDIRECT(iw))){ X MAYBE_BUMP_TABLE(INDIRECT(iw)); X strccpy(tmp, start, ':'); X I_NAME(TPOS(INDIRECT(iw))) = XtNewString(tmp); X start += strlen(tmp) + 1; X sscanf(start, "%d", &I_OFFSET(TPOS(INDIRECT(iw)))); X start = index(start, '\n') + 1; X } X ROUND_TABLE(INDIRECT(iw)); X } X else if (needIndirect) X message(iw, "?Indirect table not found for %s! Hilfe!", X iw->info.file); X else if (INDIRECT(iw).table) X FREE_TAG_TABLE(INDIRECT(iw)); X} X X/***************************************************************************** X * Text display functions. * X *****************************************************************************/ X X/* display a message in the message area */ Local void message(iw, s, p1, p2, p3) InfoWidget iw; String s; caddr_t p1, p2, p3; X{ X char msgbuf[MAXSTR]; X Arg args[5]; X Cardinal i; X X i = 0; X if (s) { X sprintf(msgbuf, s, p1, p2, p3); X XtSetArg(args[i], XtNlabel, msgbuf); i++; X XtSetValues(iw->info.messageLabel, args, i); X feep(iw); X if (*s == '?') /* a dire warning */ X XtWarning(msgbuf); X } X else { /* clear the message area */ X XtSetArg(args[i], XtNlabel, " "); i++; X XtSetValues(iw->info.messageLabel, args, i); X } X} X X/* display the current node/file */ Local void showStatus(iw, n) InfoWidget iw; NodeInfo *n; X{ X char statbuf[MAXSTR]; X Arg args[5]; X Cardinal i; X String sub = iw->info.subFile; X X sprintf(statbuf, "(%s)%s, %d characters%s", file_name(iw->info.file), X iw->info.node, n->length, X sub ? strconcat(", subfile: ", sub) : "."); X i = 0; X XtSetArg(args[i], XtNlabel, statbuf); i++; X XtSetValues(iw->info.statusLabel, args, i); X} X X/***************************************************************************** X * Functions used by actions * X *****************************************************************************/ X Local void Abort(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X InfoWidget iw = find_top(w); X X feep(iw); X do_dialog_abort(w, iw, NULL); X} X Local void Confirm(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X InfoWidget iw = find_top(w); X X if (w == iw->info.argText) X (*(iw->info.requester))(w, iw, NULL); X else X do_dialog_confirm(w, iw, NULL); X} X Local void NodeDir(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X InfoWidget iw = find_top(w); X X if (getNode(iw, "dir", "Top", NULL) == FALSE) X message(iw, "?Yow! The directory seems to have disappeared!\n"); X} X Local void NodeNext(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_next(NULL, find_top(w), NULL); X} X Local void NodePrev(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_prev(NULL, find_top(w), NULL); X} X X Local void NodeUp(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_up(NULL, find_top(w), NULL); X} X Local void NodeTop(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X InfoWidget iw = find_top(w); X X if (getNode(iw, NULL, "Top", NULL) == FALSE) X message(iw, "?This node has no top! Bad joss!"); X} X Local void NodeLast(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X NodeInfo *tmp; X InfoWidget iw = find_top(w); X X if ((tmp = popNode(iw)) != NULL) { X if (getNode(iw, tmp->file, tmp->node, tmp) == FALSE) X message(iw, "?Can't pop back to node (%s)%s! We're hosed!", X tmp->file, tmp->node); X } X else X message(iw, "No further history."); X} X Local void NodeXRef(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_xref(NULL, find_top(w), NULL); X} X Local void NodeGoto(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_goto(NULL, find_top(w), NULL); X} X Local void NodeSearch(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_search(NULL, find_top(w), NULL); X} X Local void NodeQuit(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X do_quit(NULL, find_top(w), NULL); X} X Local void NodeTutorial(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X InfoWidget iw = find_top(w); X X if (getNode(iw, "info", "Help", NULL) == FALSE) X message(iw, "?Hmmm. I can't seem to find the info tutorial!"); X} X Local void NodeHelp(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X Cardinal i; X Arg args[10]; X InfoWidget iw = find_top(w); X X if (!iw->info.helpPopup) { X Widget hpane, htext; X Local XtCallbackRec cb[2]; X X /* create the help popup */ X i = 0; X iw->info.helpPopup = XtCreatePopupShell("help", X transientShellWidgetClass, X iw, args, i); X i = 0; X hpane = XtCreateManagedWidget("pane", panedWidgetClass, X iw->info.helpPopup, args, i); X i = 0; X cb[0].callback = do_popdown; X cb[0].closure = (caddr_t)iw->info.helpPopup; X XtSetArg(args[i], XtNcallback, cb); i++; X XtCreateManagedWidget("Close", commandWidgetClass, X hpane, args, i); X i = 0; X XtSetArg(args[i], XtNtype, XawAsciiString); i++; X XtSetArg(args[i], XtNeditType, XawtextRead); i++; X htext = XtCreateManagedWidget("text", asciiTextWidgetClass, X hpane, args, i); X } X X i = 0; X XtSetArg(args[i], XtNx, event->xbutton.x); i++; X XtSetArg(args[i], XtNy, event->xbutton.y); i++; X XtSetValues(iw->info.helpPopup, args, i); X X XtPopup(iw->info.helpPopup, XtGrabNonexclusive); X} X Local void ButtonSelection(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X char tmp[512], *idx; X XawTextPosition beg, end, nlen; X XawTextBlock ret, asterisk, colon; X InfoWidget iw = find_top(w); X X SET_BLOCK(asterisk, 0, 1, "*"); X SET_BLOCK(colon, 0, 1, ":"); X X /* Next, try and get a complete "item" selected */ X if ((beg = XawTextSearch(w, XawsdLeft, &asterisk)) != XawTextSearchError) X XawTextSetInsertionPoint(w, beg); X else X return; /* Bomb out */ X if ((end = XawTextSearch(w, XawsdRight, &colon)) != XawTextSearchError && X end > beg) { X long len = end - beg; X X /* Victory! Now try and figure out what it is */ X if (!XawTextSourceRead(XawTextGetSource(w), beg, &ret, len)) X return; /* If can't read, forget it */ X else while (ret.length && *(ret.ptr) == '*' || isspace(*(ret.ptr))) X --ret.length, ++ret.ptr; X if (!ret.length) X return; /* Nothing left, forget it */ X else { X strncpy(tmp, ret.ptr, ret.length); X tmp[ret.length] = '\0'; X normalize_whitespace(tmp); X if (!strncomp(tmp, "note ", 5)) { X if (!(idx = trueName(iw, CURNODE(iw)->xref, tmp + 5))) X feep(iw); X else if (getNode(iw, NULL, idx, NULL) == FALSE) X message(iw, "?Can't find cross reference for '%s'!", X idx); X } X else { X if (!(idx = trueName(iw, CURNODE(iw)->menu, tmp))) X feep(iw); X else if (getNode(iw, NULL, idx, NULL) == FALSE) X message(iw, "?Can't find menu entry for '%s'!", X idx); X } X } X } X} X Local void NodeMenuSelectByNumber(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X Import int atoi(); X int menunum; X int nitems; X InfoWidget iw = find_top(w); X X nitems = IDX(CURNODE(iw)->menu.t); X menunum = atoi(*params); X /* menu number of zero means get menu from arg area */ X if (!menunum) X do_menu(NULL, iw, NULL); X else if (!nitems) X message(iw, "No menu for this node."); X else if (menunum > nitems) X message(iw, "There are only %d menu items.", nitems); X else { X XawListHighlight(iw->info.menuList, menunum - 1); X if (getNode(iw, NULL, X offsetToString(iw,CURNODE(iw)->menu.t.table[menunum-1]), X NULL) == FALSE) X message(iw, "?Can't find node for menu item #%s", *params); X } X} X Local void NodePrint(w, event, params, num_params) Widget w; XXEvent *event; String *params; Cardinal *num_params; X{ X Import int unlink(); X String tmp; X FILE *out; X InfoWidget iw = find_top(w); X X /* if you don't have this routine in your stdlib, make one up */ X tmp = tmpnam(NULL); X X if (!CURNODE(iw)) X message(iw, "?No current node?"); X else if ((out = fopen(tmp, "w")) == NULL) X message(iw, "?Can't open temporary file '%s'.", tmp); X else { X String s1 = NSTART(iw, CURNODE(iw)); X String s2 = NEND(iw, CURNODE(iw)); X char syscmd[MAXSTR]; X int stat; X X fwrite(s1, s2 - s1, 1, out); X fclose(out); X X message(iw, "Sending '%s' to the printer, please wait..", X iw->info.node); X X sprintf(syscmd, "%s %s", iw->info.printCmd, tmp); X if ((stat = system(syscmd)) != 0) X message(iw, "?'%s' failed with exit status %d. Help!", X syscmd, stat); X else X message(iw, "Finished printing."); X unlink(tmp); X } X X X} X X/***************************************************************************** X * Functions used from callback lists. * X *****************************************************************************/ X X/* Abort the dialog operation */ Local void do_dialog_abort(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X X if (w == iw->info.argText) X clear_arg(iw); X else X XtDestroyWidget(iw->info.argPopup); X} X X/* Confirm the dialog operation */ Local void do_dialog_confirm(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X XawTextBlock blk; X X XtDestroyWidget(iw->info.argPopup); X X SET_BLOCK(blk, 0, 0, NULL); X if ((blk.ptr = XawDialogGetValueString(XtParent(w))) && X (blk.length = strlen(blk.ptr))) { X if (blk.length > ARGLEN) /* truncate if necessary */ X blk.ptr[blk.length = ARGLEN] = '\0'; X XawTextReplace(iw->info.argText, 0, blk.length, &blk); X (*(iw->info.requester))(w, iw, NULL); X } X} X X/* X * Seems there should be a better way of doing this. Methinks the X * XtCallbackPopdown() stuff isn't general enough. Should be a way of X * doing this (and only this). X */ Local void do_popdown(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X XtPopdown((Widget)client_data); X} X Local void do_prev(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp; X X if ((tmp = offsetToString(iw, CURNODE(iw)->prev))) { X if (getNode(iw, NULL, tmp, NULL) == FALSE) X message(iw, "?Can't find the previous (%s) for this node.", X tmp); X } X else X message(iw, "Node has no previous"); X} X Local void do_quit(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X X if (XtHasCallbacks(iw, XtNcallback) != XtCallbackHasSome) X message(iw, "Sorry, I just don't know how to quit."); X else X XtCallCallbacks(iw, XtNcallback, NULL); X} X Local void do_up(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp; X X if ((tmp = offsetToString(iw, CURNODE(iw)->up))) { X if (getNode(iw, NULL, tmp, NULL) == FALSE) X message(iw, "?Can't find the up (%s) for this node.", tmp); X } X else X message(iw, "Node has no up"); X} X Local void do_next(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp; X X if ((tmp = offsetToString(iw, CURNODE(iw)->next))) { X if (getNode(iw, NULL, tmp, NULL) == FALSE) X message(iw, "?Can't find the next (%s) for this node.", tmp); X } X else X message(iw, "Node has no next"); X} X Local void do_xref(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp; X X if ((tmp = get_arg(iw)) != NULL) { X if ((tmp = trueName(iw, CURNODE(iw)->xref, tmp)) == NULL) X message(iw, "No cross reference entry named '%s' in this node.", X get_arg(iw)); X else if (getNode(iw, NULL, tmp, NULL) == FALSE) X message(iw, "?Can't find node for xref item '%s'!", X get_arg(iw)); X } X else X dialog(iw, "Please specify a cross reference:", do_xref); X} X Local void do_menu(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp; X X if ((tmp = get_arg(iw)) != NULL) { X if ((tmp = trueName(iw, CURNODE(iw)->menu, tmp)) == NULL) X message(iw, "No menu entry named '%s' in this node.", X get_arg(iw)); X else if (getNode(iw, NULL, tmp, NULL) == FALSE) X message(iw, "?Can't find node for menu item '%s'", X get_arg(iw)); X } X else X dialog(iw, "Please specify a menu entry:", do_menu); X} X Local void do_goto(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp; X X if ((tmp = get_arg(iw)) != NULL) { X if (getNode(iw, NULL, tmp, NULL) == FALSE) X message(iw, "Can't find a node named %s", tmp); X } X else X dialog(iw, "Please specify the name of a node go to:", do_goto); X} X X/* X * Implement a somewhat simplistic search strategy. If file has an indirect X * list, look for a match in the tag table (since just looking in the current X * file probably wouldn't be very useful). If not, then search the current X * file. If we're successful in either case, record the position (in the X * tags table or the file) so that we don't hit it again right away. X */ Local void do_search(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X String tmp, s; X char name[MAXSTR]; X Local struct { X String file; X caddr_t pos; X } oldPos; X X if ((tmp = get_arg(iw)) != NULL) { X /* if remembered position is invalid, reset it */ X if (strcomp(oldPos.file, iw->info.file)) { X oldPos.file = iw->info.file; X oldPos.pos = NULL; X } X if (INDIRECT(iw).table) { X ID_P i; X int len = strlen(tmp); X X if (oldPos.pos) X i = (ID_P)oldPos.pos; X else X i = TAGTABLE(iw).table; X /* do a tags search */ X while (I_NAME(*i)) { X if (!strncomp(I_NAME(*i), tmp, len)) X break; X i++; X } X /* success? */ X if (I_NAME(*i)) { X oldPos.pos = (caddr_t)(i + 1); X if (getNode(iw, iw->info.file, I_NAME(*i), NULL) == FALSE) X message(iw, "?Can't find node for tag %s!", X I_NAME(*i)); X } X else { X message(iw, "Tag search for '%s' failed.", tmp); X oldPos.pos = NULL; X } X } X else { X if (oldPos.pos) X s = (String)oldPos.pos; X else X s = START(iw); X if ((s = search(iw, s, END(iw), X strconcat(NODE_TOKEN, tmp), X TRUE)) != NULL) { X int i; X X oldPos.pos = (caddr_t)s; X strcpy(name, tmp); X i = strlen(name); X while (!index(NAME, *s)) X name[i++] = *s++; X name[i] = '\0'; X if (getNode(iw, iw->info.file, name, NULL) == FALSE) X message(iw, "?Can't find node name in search!"); X } X else { X message(iw, "Search for '%s' failed.", tmp); X oldPos.pos = NULL; X } X } X } X else X dialog(iw, "Please enter a string to search for:", do_search); X} X X/* These two handle selections from the menu and xref lists */ X Local void do_menu_sel(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X XawListReturnStruct *rs = (XawListReturnStruct *)call_data; X X if (getNode(iw, NULL, trueName(iw, CURNODE(iw)->menu, rs->string), X NULL) == FALSE) X message(iw, "?Can't find node for menu item '%s'", rs->string); X} X Local void do_xref_sel(w, client_data, call_data) Widget w; caddr_t client_data; caddr_t call_data; X{ X InfoWidget iw = (InfoWidget)client_data; X XawListReturnStruct *rs = (XawListReturnStruct *)call_data; X X if (getNode(iw, NULL, trueName(iw, CURNODE(iw)->xref, rs->string), X NULL) == FALSE) X message(iw, "?Can't find node for cross reference '%s'", rs->string); X} X X/***************************************************************************** X * Xlib and toolkit utility functions. * X *****************************************************************************/ X X/* Clear the argument text */ Local void clear_arg(iw) InfoWidget iw; X{ X XawTextBlock blk; X X SET_BLOCK(blk, 0, 0, ""); X XawTextReplace(iw->info.argText, 0, strlen(iw->info.arg), &blk); X} X X/* Put up a dialog to get necessary information */ Local void dialog(iw, msg, callback) InfoWidget iw; String msg; void (*callback)(); X{ X Arg args[10]; X Cardinal i; X int x, y; X Widget dg, abort, confirm; X X /* X * We create the dialog everytime (rather than just once, followed X * by Popup/Popdown requests) so that it will be made the proper size X * for the label each time. Can't seem to get it to resize dynamically, X * so I don't see any other way. X */ X iw->info.requester = callback; X X /* Don't see any other way of doing this. It seems there should be. */ X getXY(iw, &x, &y); X X /* Position nicely */ X i = 0; X XtSetArg(args[i], XtNx, x - 30 > 0 ? x - 30 : 0); i++; X XtSetArg(args[i], XtNy, y - 30 > 0 ? y - 30 : 0); i++; X XtSetArg(args[i], XtNallowShellResize, TRUE); i++; X iw->info.argPopup = XtCreatePopupShell("need_argument", X transientShellWidgetClass, X iw, args, i); X i = 0; X XtSetArg(args[i], XtNvalue, iw->info.arg); i++; X XtSetArg(args[i], XtNlabel, msg); i++; X dg = XtCreateManagedWidget("dialog", dialogWidgetClass, X iw->info.argPopup, args, i); X X i = 0; X abort = XtCreateManagedWidget("abort", commandWidgetClass, X dg, args, i); X XtAddCallback(abort, XtNcallback, do_dialog_abort, iw); X X i = 0; X confirm = XtCreateManagedWidget("confirm", commandWidgetClass, X dg, args, i); X XtAddCallback(confirm, XtNcallback, do_dialog_confirm, iw); X X XtPopup(iw->info.argPopup, XtGrabExclusive); X} X X/* Toot the horn */ Local void feep(iw) InfoWidget iw; X{ X XBell(XtDisplay(iw), iw->info.bell_volume); X} X X/* Find the info widget in a hierarchy */ Local Inline InfoWidget find_top(w) Widget w; X{ X register Widget tmp = w; X X while (tmp) { X if (XtClass(tmp) == infoWidgetClass) X return (InfoWidget)tmp; X else X tmp = XtParent(tmp); X } X if (!tmp) X XtError("Walked off end of widget hierarchy!"); X return (InfoWidget)NULL; X} X X/* Return the arg contents if set, else NULL */ Local String get_arg(w) InfoWidget w; X{ X if (strlen(w->info.arg)) X return w->info.arg; X else X return NULL; X} X X/* Return the root XY coords of the pointer */ Local void getXY(w, xp, yp) Widget w; int *xp, *yp; X{ X Window junkr, junkc; X int junkx, junky; X unsigned int mask; X X (void) XQueryPointer(XtDisplay(w), XtWindow(w), &junkr, &junkc, X xp, yp, &junkx, &junky, &mask); X} X X/***************************************************************************** X * Unix and string utility functions. * X *****************************************************************************/ X X/* Search for a file along a path, returning the complete path name if found */ Local String find_file(path, name) String path, name; X{ X String cp = path; X Boolean more_path = TRUE; X Local char dir[MAXPATHLEN]; X int status = -1; X X dir[0] = '\0'; X X /* absolute path name? */ X if (name[0] == '/') { X if (!access(name, R_OK)) X return name; X else X name = file_name(name); X } X while (status && more_path) { X if ((cp = index(path, ':')) != NULL) { X strncpy(dir, path, cp - path); X dir[cp - path] = '\0'; X strcat(dir, "/"); X path = cp + 1; X } X else { X strcpy(dir, path); X strcat(dir, "/"); X more_path = FALSE; X } X strcat(dir, name); X /* if we failed, try again in lower case */ X if (status = access(dir, R_OK)) X status = access(downcase(dir), R_OK); X } X if (dir[0]) X return dir; X else X return NULL; X} X X/* return the file part of a path name */ Local Inline String file_name(s) register String s; X{ X register int i = strlen(s); X X while (i) { X if (s[i - 1] == '/') X return s + i; X i--; X } X return s; X} X X/* strip evil tab/formfeed/newline chars from a string (replacing w/blanks) */ Local Inline String normalize_whitespace(s) String s; X{ X register String tmp; X X if (tmp = s) { X while (*tmp) { X if (isspace(*tmp)) X *tmp = ' '; X ++tmp; X } X } X return s; X} X X/* Convert from an offset ID to a string. */ Local Inline String offsetToString(iw, blk) InfoWidget iw; ID blk; X{ X Local char ret[MAXSTR]; X X if (I_LEN(blk) != 0) { X strncpy(ret, START(iw) + I_START(blk), I_LEN(blk)); X ret[I_LEN(blk)] = '\0'; X return normalize_whitespace(ret); X } X else X return NULL; X} X X/* chew through white space */ Local Inline String eat_whitespace(s) register String s; X{ X while (*s && isspace(*s)) X s++; X return s; X} X X/* look up the actual name of a list item */ Local String trueName(iw, lst, name) InfoWidget iw; IDList lst; String name; X{ X register int i; X X for (i = 0; i < lst.t.idx; i++) X if (!strcomp(lst.l[i], name)) X return offsetToString(iw, lst.t.table[i]); X return NULL; X} X X/* Search for a string */ Local String search(iw, start, end, str, igncase) InfoWidget iw; register String start, end, str; Boolean igncase; X{ X register String ind = str; X register String stop = str + strlen(str); X register int comp; X X while (start < end) { X if (!igncase) X comp = (*start == *ind); X else X comp = (TOLOWER(*start) == TOLOWER(*ind)); X if (!comp) { X if (ind != str) X ind = str; X else X start++; X } X else { X if (++start <= end && ++ind == stop) X return start; X } X } X return NULL; X} X X/* Like search(), but in the reverse direction */ Local String search_back(iw, start, end, str, igncase) InfoWidget iw; register String start, end, str; Boolean igncase; X{ X register String ind; X register String stop; X register int comp; X X ind = str = reverse(str); X stop = ind + strlen(ind); X X while (start > end) { X if (!igncase) X comp = (*start == *ind); X else X comp = (TOLOWER(*start) == TOLOWER(*ind)); X if (!comp) { X if (ind != str) X ind = str; X else X start--; X } X else { X start--; X if (++ind == stop) X return start; X } X } X return NULL; X} X X/* X * Safe and sane strcmp. Deals with null pointer for either arg and ignores X * case. All whitespace is considered equivalent. X */ Local Inline int strcomp(s1, s2) register String s1, s2; X{ X if (s1 && s2) { X if (strlen(s1) != strlen(s2)) X return -1; X X while (*s1 && *s2 && (TOLOWER(*s1) == TOLOWER(*s2))) X ++s1, ++s2; X if (!*s1 && !*s2) X return 0; X else if (*s1 < *s2) X return -1; X else X return 1; X } X else if (!s1 && !s2) X return 0; X else if (!s1 && s2) X return -1; X else X return 1; X} X X/* like above, but stops after n characters */ Local Inline int strncomp(s1, s2, n) register String s1, s2; int n; X{ X register String s3 = s2 + n; X X if (s1 && s2) { X while (s2 < s3 && *s1 && *s2 && (TOLOWER(*s1) == TOLOWER(*s2))) X ++s1, ++s2; X if (!*s1 && !*s2 || s2 == s3) X return 0; X else if (*s1 < *s2) X return -1; X else X return 1; X } X else if (!s1 && !s2) X return 0; X else if (!s1 && s2) X return -1; X else X return 1; X} X X/* Copy s2 to s1 up to (but not including) character c */ Local Inline void strccpy(s1, s2, c) register String s1, s2; register char c; X{ X while (*s2 && *s2 != c) X *(s1++) = *(s2++); X *s1 = '\0'; X} X X/* X * Return integer subscript of character 'c' in string 's'. X * (why doesn't this already exist in a library somewhere?). X */ Local Inline int iindex(s, c) register char *s, c; X{ X register char *cp; X X if (!s) X return -1; X cp = index(s, c); X if (cp) X return cp - s; X else X return -1; X} X Local String substr(s, p1, p2) register String s; register int p1, p2; X{ X Local char ret[MAXSTR]; X register int i = 0; X X if (p1 > p2) { X sprintf(ret, "substr: start %d, end %d. start must be <= end", X p1, p2); X XtWarning(ret); X return NULL; X } X if (p2 - p1 > MAXSTR) { X sprintf(ret, "substr: end - start is > max len of %d", MAXSTR); X XtWarning(ret); X return NULL; X } X while (p1 <= p2) X ret[i++] = s[p1++]; X ret[i] = '\0'; X return ret; X} X X/* X * Safely concatenate two strings into static area, returning pointer to X * result. X */ Local String strconcat(s1, s2) register String s1, s2; X{ X Local char ret[MAXSTR]; X int len1; X X if (s1) { X if ((len1 = strlen(s1)) >= MAXSTR) { X sprintf(ret, "strconcat: length of s1 > MAX (%d)", MAXSTR); X XtWarning(ret); X return NULL; X } X else X strcpy(ret, s1); X if (s2) { X if (len1 + strlen(s2) > MAXSTR) { X sprintf(ret, "strconcat: length of s1 + s2 is > MAX (%d)", X MAXSTR); X XtWarning(ret); X } X else X strcat(ret, s2); X } X return ret; X } X else X return NULL; X} X X/* reverse a string so that a simple reverse search may be done on it */ Local String reverse(s) register String s; X{ X Local char ret[MAXSTR]; X register int i, len; X X if ((len = strlen(s)) > MAXSTR) { X sprintf(ret, "reverse: string too long to reverse. MAX is %d", X MAXSTR); X XtWarning(ret); X return NULL; X } X else { X i = 0; X while (len) X ret[i++] = s[--len]; X ret[i] = '\0'; X return ret; X } X} X X/* convert a string to lower case */ Local Inline String downcase(s) register String s; X{ X String orig = s; X X if (s) X while (*s) { X *s = TOLOWER(*s); X s++; X } X return orig; X} X X#ifdef BSD X/* BSD users don't have strpbrk() */ X/* Routines borrowed from PD libc written by Richard A. O'Keefe. */ X X#if CharsAreSigned X#define MaxPosChar 127 X#else ~CharsAreSigned X#define MaxPosChar 255 X#endif CharsAreSigned X#ifndef _AlphabetSize X#define _AlphabetSize 128 X#endif X static int _set_ctr = MaxPosChar; static char _set_vec[_AlphabetSize]; X void _str2set(set) register String set; X{ X if (set == NULL) X return; X if (++_set_ctr == MaxPosChar+1) { X register char *w = &_set_vec[_AlphabetSize]; X do X *--w = '\0'; X while (w != &_set_vec[0]); X _set_ctr = 1; X } X while (*set) X _set_vec[*set++] = _set_ctr; X} X String strpbrk(s1, s2) register String s1, s2; X{ X _str2set(set); X while (_set_vec[*str] != _set_ctr) X if (!*str++) X return NULL; X return str; X} X#endif /* BSD */ END_OF_FILE if test 60891 -ne `wc -c <'Info.c'`; then echo shar: \"'Info.c'\" unpacked with wrong size! fi # end of 'Info.c' fi echo shar: End of archive 2 \(of 2\). cp /dev/null ark2isdone MISSING="" for I in 1 2 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked both archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0 dan ---------------------------------------------------- O'Reilly && Associates argv@sun.com / argv@ora.com Opinions expressed reflect those of the author only. -- dan ---------------------------------------------------- O'Reilly && Associates argv@sun.com / argv@ora.com Opinions expressed reflect those of the author only.