home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Professional / OS2PRO194.ISO / os2 / editor / pminfo / pminfo.cc < prev    next >
C/C++ Source or Header  |  1994-01-31  |  28KB  |  1,073 lines

  1.  
  2. /*
  3.      Program PM-Info: a program for viewing GNU-style hypertext info
  4.      documentation files.
  5.      Copyright (C) 1992  Colin Jensen
  6.      
  7.      This program is free software; you can redistribute it and/or modify
  8.      it under the terms of the GNU General Public License as published by
  9.      the Free Software Foundation; either version 2 of the License, or
  10.      (at your option) any later version.
  11.      
  12.      This program is distributed in the hope that it will be useful,
  13.      but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.      GNU General Public License for more details.
  16.      
  17.      You should have received a copy of the GNU General Public License
  18.      along with this program; if not, write to the Free Software
  19.      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20.  
  21.      Contact addresses:
  22.     Colin Jensen
  23.     email: cjensen@ampex.com or ljensen@netcom.netcom.com
  24.     US mail: 4902 Esguerra Terrace, Fremont CA, 94555
  25. */
  26.  
  27.  
  28.  
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #include <string.h>
  32. #include <ctype.h>
  33.  
  34. #ifdef DEBUG
  35. #define BUGME(exp) exp
  36. #else
  37. #define BUGME(exp)
  38. #endif
  39.  
  40. #include "pminfo.h"
  41. extern char *embedded_text_COPYING[], *embedded_text_Copyrght[];
  42. extern int embedded_text_COPYING_count, embedded_text_Copyrght_count;
  43.  
  44. BUGME(FILE *fnote;)
  45. #ifdef DEBUG
  46. #define NOTE(id) \
  47. do { fprintf(fnote, #id " is %d\n", id); fflush(fnote); } while(0)
  48. #else
  49. #define NOTE(id)
  50. #endif /* DEBUG */
  51. #define max(a,b) (((a) > (b)) ? (a) : (b))
  52. #define min(a,b) (((a) < (b)) ? (a) : (b))
  53. #define LIMIT(v,a,b) (v) = max((a),min((b),(v)))
  54.  
  55. extern "C" {
  56. #define INCL_WIN
  57. #define INCL_GPI
  58. #include <os2.h>
  59. }
  60.  
  61. class StdWin
  62. {
  63. private:
  64.     static StdWin *stdwinlist;
  65.     StdWin *stdwinlist_next;
  66.     static MRESULT ClientWndProcWhack(HWND, USHORT, MPARAM, MPARAM);
  67.     MRESULT ClientWndProc(HWND, USHORT, MPARAM, MPARAM);
  68.     HWND frame;
  69. public:
  70.     HWND window;
  71.  
  72.     virtual MRESULT MsgSize(HWND, USHORT, MPARAM, MPARAM, int *);
  73.     virtual MRESULT MsgPaint(HWND, USHORT, MPARAM, MPARAM, int *);
  74.     virtual MRESULT MsgCreate(HWND, USHORT, MPARAM, MPARAM, int *);
  75.     virtual MRESULT MsgVscroll(HWND, USHORT, MPARAM, MPARAM, int *);
  76.     virtual MRESULT MsgHscroll(HWND, USHORT, MPARAM, MPARAM, int *);
  77.     virtual MRESULT MsgButton1Down(HWND, USHORT, MPARAM, MPARAM, int *);
  78.     virtual MRESULT MsgChar(HWND, USHORT, MPARAM, MPARAM, int *);
  79.     virtual MRESULT MsgCommand(HWND, USHORT, MPARAM, MPARAM, int *);
  80.     StdWin(void);
  81.     void Init(HAB, const char *classname, ULONG message_flags, 
  82.     ULONG frame_flags);
  83.     void Destroy(void);
  84.  
  85.     static void StdStartup(HAB *hab, HMQ *hmq);
  86.     static void StdMainLoop(HAB hab);
  87.     static void StdExit(HAB hab, HMQ hmq);
  88. };
  89.  
  90. StdWin *StdWin::stdwinlist = NULL;
  91.  
  92. MRESULT StdWin::MsgCreate(HWND, USHORT, MPARAM, MPARAM, int *)
  93. {
  94.     BUGME(fprintf(fnote, "Well, how did I get here?\n");)
  95.     return 0;
  96. }
  97.  
  98. MRESULT StdWin::MsgSize(HWND, USHORT, MPARAM, MPARAM, int *dodefault)
  99. {
  100.     *dodefault = 1;
  101.     return 0;
  102. }
  103.  
  104. MRESULT StdWin::MsgPaint(HWND hwnd, USHORT, MPARAM, MPARAM, int *)
  105. {
  106.     // By default, draw a blank window
  107.     HPS hps = WinBeginPaint(hwnd, NULL, NULL);
  108.     GpiErase(hps);
  109.     WinEndPaint(hps);
  110.     return 0;
  111. }
  112.  
  113. MRESULT StdWin::MsgVscroll(HWND, USHORT, MPARAM, MPARAM, int *dodefault)
  114. {
  115.     *dodefault = 1;
  116.     return 0;
  117. }
  118.  
  119. MRESULT StdWin::MsgHscroll(HWND, USHORT, MPARAM, MPARAM, int *dodefault)
  120. {
  121.     *dodefault = 1;
  122.     return 0;
  123. }
  124.  
  125. MRESULT StdWin::MsgChar(HWND, USHORT, MPARAM, MPARAM, int *dodefault)
  126. {
  127.     *dodefault = 1;
  128.     return 0;
  129. }
  130.  
  131. MRESULT StdWin::MsgButton1Down(HWND, USHORT, MPARAM, MPARAM, int *dodefault)
  132. {
  133.     *dodefault = 1;
  134.     return 0;
  135. }
  136.  
  137. MRESULT StdWin::MsgCommand(HWND, USHORT, MPARAM, MPARAM, int *dodefault)
  138. {
  139.     *dodefault = 1;
  140.     return 0;
  141. }
  142.  
  143. StdWin::StdWin(void)
  144. {
  145. }
  146.  
  147. void StdWin::Init(HAB hab, const char *classname, ULONG message_flags,
  148.     ULONG frame_flags)
  149. {
  150.     // Insert self into list of event windows
  151.     stdwinlist_next = stdwinlist;
  152.     stdwinlist = this;
  153.  
  154.     // Register the class with PM
  155.     static int is_registered = 0;
  156.     if (!is_registered) {
  157.     is_registered = 1;
  158.     WinRegisterClass(hab, (PSZ)classname, ClientWndProcWhack,
  159.         message_flags, 0);
  160.     }
  161.  
  162.     // Then instantiate ourself
  163.     frame = WinCreateStdWindow(HWND_DESKTOP, WS_VISIBLE, &frame_flags,
  164.     (PSZ)classname, NULL, 0L, 0, ID_RESOURCE, &window);
  165.  
  166.     WinSendMsg(frame, WM_SETICON,
  167.     WinQuerySysPointer(HWND_DESKTOP, SPTR_APPICON, FALSE),
  168.     NULL);
  169. }
  170.  
  171. void StdWin::Destroy(void)
  172. {
  173.     WinDestroyWindow(frame);
  174. }
  175.  
  176. MRESULT StdWin::ClientWndProcWhack(HWND window, USHORT msg, MPARAM mp1, 
  177.     MPARAM mp2)
  178. {
  179.     StdWin *target_window;
  180.     // Locate ourself in the list of existing windows, and exec ourself
  181.     for (target_window = stdwinlist; target_window != NULL; 
  182.     target_window = target_window->stdwinlist_next) {
  183.     if (target_window->window == window) 
  184.         return target_window->ClientWndProc(window, msg, mp1, mp2);
  185.     }
  186.     // We really should never fall thru this loop, but just in case
  187.     return WinDefWindowProc(window, msg, mp1, mp2);
  188. }
  189.  
  190. MRESULT StdWin::ClientWndProc(HWND hwnd, USHORT msg, MPARAM mp1, MPARAM mp2)
  191. {
  192.     int dodefault = 0;
  193.     MRESULT result;
  194.     switch(msg) {
  195.     case WM_CREATE:
  196.     BUGME(fprintf(fnote, "Client Proc: CREATE\n");)
  197.     result = MsgCreate(hwnd, msg, mp1, mp2, &dodefault);
  198.     break;
  199.     case WM_SIZE:
  200.     BUGME(fprintf(fnote, "Client Proc: SIZE\n");)
  201.     result = MsgSize(hwnd, msg, mp1, mp2, &dodefault);
  202.     break;
  203.     case WM_PAINT:
  204.     BUGME(fprintf(fnote, "Client Proc: PAINT\n");)
  205.     result = MsgPaint(hwnd, msg, mp1, mp2, &dodefault);
  206.     break;
  207.     case WM_VSCROLL:
  208.     BUGME(fprintf(fnote, "Client Proc: VSCROLL\n");)
  209.     result = MsgVscroll(hwnd, msg, mp1, mp2, &dodefault);
  210.     break;
  211.     case WM_HSCROLL:
  212.     BUGME(fprintf(fnote, "Client Proc: HSCROLL\n");)
  213.     result = MsgHscroll(hwnd, msg, mp1, mp2, &dodefault);
  214.     break;
  215.     case WM_BUTTON1DOWN:
  216.     BUGME(fprintf(fnote, "Client Proc: Button 1 Down\n");)
  217.     result = MsgButton1Down(hwnd, msg, mp1, mp2, &dodefault);
  218.     break;
  219.     case WM_CHAR:
  220.     BUGME(fprintf(fnote, "Client Proc: Char\n");)
  221.     result = MsgChar(hwnd, msg, mp1, mp2, &dodefault);
  222.     break;
  223.     case WM_COMMAND:
  224.     BUGME(fprintf(fnote, "Client Proc: Command\n");)
  225.     result = MsgCommand(hwnd, msg, mp1, mp2, &dodefault);
  226.     break;
  227.     default:
  228.     //BUGME(fprintf(fnote, "Client Proc: MSG 0x%04X\n", msg);)
  229.     dodefault = 1;
  230.     }
  231.     return (dodefault) ? WinDefWindowProc(hwnd, msg, mp1, mp2) : result;
  232. }
  233.  
  234. void StdWin::StdStartup(HAB *hab, HMQ *hmq)
  235. {
  236.     *hab = WinInitialize(0);
  237.     *hmq = WinCreateMsgQueue(*hab, 0);
  238. }
  239.  
  240. void StdWin::StdMainLoop(HAB hab)
  241. {
  242.     QMSG qmsg;
  243.     while (WinGetMsg(hab, &qmsg, NULL, 0, 0)) {
  244.     WinDispatchMsg(hab, &qmsg);
  245.     }
  246. }
  247.  
  248. void StdWin::StdExit(HAB hab, HMQ hmq)
  249. {
  250.     WinDestroyMsgQueue(hmq);
  251.     WinTerminate(hab);
  252. }
  253.  
  254. char *fgetline(FILE *f)
  255. {
  256.     char buf[1024];
  257.     char *p;
  258.     char strcount;
  259.     char *dp, *sp;
  260.  
  261.     // Get a line from the input stream
  262.     p = fgets(buf, sizeof(buf), f);
  263.     if (p == NULL) return NULL;
  264.     // Trim off trailing nl and cr
  265.     p = buf + strlen(buf) - 1;
  266.     while (p >= buf && isspace(*p)) *p-- = '\0';
  267.     // Decide how many chars this is, after tab expansion
  268.     for (strcount = 0, p = buf; *p != '\0'; p++) {
  269.     if (*p == '\t') {
  270.         strcount += 8 - ((p - buf) % 8);
  271.     } else {
  272.         strcount++;
  273.     }
  274.     }
  275.     // Alloc a permanent place for the expanded string
  276.     p = (char *) malloc(strcount+1);
  277.     // Expand the string into its permanent destination
  278.     for (sp = buf, dp = p; *sp != '\0'; sp++) {
  279.     if (*sp == '\t') {
  280.         strcount = 8 - ((sp - buf) % 8);
  281.         while (strcount-- > 0) *dp++ = ' ';
  282.     } else {
  283.         *dp++ = *sp;
  284.     }
  285.     }
  286.     *dp = '\0';
  287.     return p;
  288. }
  289.  
  290. typedef struct {
  291.     char *filename;
  292.     long int position;
  293. } SplitRec;
  294.  
  295. typedef struct splitlist_struct SplitList;
  296. struct splitlist_struct {
  297.     SplitList *next;
  298.     SplitRec split;
  299. };
  300.  
  301. SplitList *splitlist_cons(SplitRec split, SplitList *next)
  302. {
  303.     SplitList *list = (SplitList *) malloc(sizeof(SplitList));
  304.     list->split = split;
  305.     list->next = next;
  306.     return list;
  307. }
  308.  
  309. class HistoryList
  310. {
  311.     char **const history;
  312.     const int size;
  313.     int current_size, current_pos;
  314. public:
  315.     char *pop();
  316.     void push(const char *);
  317.     HistoryList(int asize);
  318. };
  319.  
  320. HistoryList::HistoryList(int asize)
  321.      : size(asize), 
  322.        history(malloc(sizeof(char *) * asize)),
  323.        current_size(0),
  324.        current_pos(0)
  325. {
  326. }
  327.  
  328. char *HistoryList::pop(void)
  329. {
  330.     if (current_size == 0) return NULL;
  331.     current_size--;
  332.     if (current_pos == 0) {
  333.     current_pos = size-1;
  334.     } else {
  335.     current_pos--;
  336.     }
  337.     return history[current_pos];
  338. }
  339.  
  340. void HistoryList::push(const char *pushval)
  341. {
  342.     if (current_size == size) {
  343.     /* free entry before filling */
  344.     free(history[current_pos]);
  345.     } else {
  346.     current_size++;
  347.     }
  348.     history[current_pos++] = strdup(pushval);
  349.     if (current_pos >= size) {
  350.     current_pos = 0;
  351.     }
  352. };
  353.  
  354.  
  355. class InfoFile
  356. {
  357. private:
  358.     int okval;
  359.     int split_file;
  360.     SplitList *splitlist;
  361.     int tags_table_availible;
  362.     SplitList *tags_table;
  363.     FILE *infofile;
  364.     HistoryList history;
  365.     char *most_recent_filename;
  366.     void open(const char *);
  367.     void getnode_attempt(const char *nodename, char ***page, int *pagelen);
  368. public:
  369.     int ok(void) { return okval; }
  370.     InfoFile(const char *filename);
  371.     ~InfoFile(void);
  372.     void getnode(const char *nodename, char ***page, int *pagelen);
  373.     void get_last_node(char ***page, int *pagelen);
  374. };
  375.  
  376.  
  377. void InfoFile::open(const char *filename)
  378. {
  379.     if (most_recent_filename != NULL) free(most_recent_filename);
  380.     most_recent_filename = strdup(filename);
  381.     BUGME(fprintf(fnote, "InfoFile::open(%s)\n", filename);)
  382.     infofile = fopen(filename, "rt");
  383.     if (infofile == NULL) {
  384.     okval = 0;
  385.     BUGME(fprintf(fnote, "failed to open\n");)
  386.     return;
  387.     }
  388.     okval = 1;
  389.     split_file = 0; /* Hopefully... */
  390.     tags_table_availible = 0;
  391.     /* See if we can find an indirect table */
  392.     BUGME(fprintf(fnote, "Looking to see if we have a split file\n");)
  393.     char buf[1024];
  394.     char *p;
  395.     for(;;) {
  396.     p = fgets(buf, sizeof(buf), infofile);
  397.     if (p == NULL) return;
  398.     if (*p != '\x1F') continue;
  399.     p = fgets(buf, sizeof(buf), infofile);
  400.     if (p == NULL) return;
  401.     const char * const indirect = "Indirect:";
  402.     if (strncmp(p, indirect, strlen(indirect))) continue;
  403.     /* Drat! It is a split file */
  404.     split_file = 1;
  405.     break;
  406.     }
  407.     BUGME(fprintf(fnote, "Split file detected\n");)
  408.     char buf2[1024];
  409.     int position;
  410.     int match;
  411.     SplitRec splitrec;
  412.     splitlist = NULL;
  413.     for(;;) {
  414.     p = fgets(buf, sizeof(buf), infofile);
  415.     if (p == NULL || *p == '\x1F') break;
  416.     match = sscanf(buf, "%[^:]: %d", buf2, &position);
  417.     if (match != 2) continue;
  418.     splitrec.filename = strdup(buf2);
  419.     splitrec.position = position;
  420.     splitlist = splitlist_cons(splitrec, splitlist);
  421.     }
  422.  
  423. #ifdef DEBUG
  424.     SplitList *sl;
  425.     for (sl = splitlist; sl != NULL; sl = sl->next) {
  426.     fprintf(fnote, "Subfile ``%s'' at offset %ld\n", sl->split.filename,
  427.         sl->split.position);
  428.     }
  429. #endif
  430.  
  431.     // Now look for a tags table -- We will need it for large files
  432.     BUGME(fprintf(fnote, "Seeking tag table\n");)
  433.     fseek(infofile, 0L, SEEK_SET);
  434.     for(;;) {
  435.     p = fgets(buf, sizeof(buf), infofile);
  436.     if (p == NULL) return;
  437.     if (*p != '\x1F') continue;
  438.     p = fgets(buf, sizeof(buf), infofile);
  439.     if (p == NULL) return;
  440.     const char * const tag_table_name = "Tag Table:";
  441.     if (strncmp(p, tag_table_name, strlen(tag_table_name))) continue;
  442.     // Yes, we have a tag table
  443.     break;
  444.     }
  445.     tags_table_availible = 1;
  446.     BUGME(fprintf(fnote, "Got a tag table\n");)
  447.     tags_table = NULL;
  448.     SplitRec tag;
  449.     for(;;) {
  450.     p = fgets(buf, sizeof(buf), infofile);
  451.     if (p == NULL || *p == '\x1F') break;
  452.     match = sscanf(buf, "Node: %[^\x7F]\x7F %ld", buf2, &position);
  453.     if (match != 2) continue;
  454.     tag.filename = strdup(buf2);
  455.     tag.position = position;
  456.     tags_table = splitlist_cons(tag, tags_table);
  457.     }
  458.  
  459. #ifdef DEBUG
  460.     for (sl = tags_table; sl != NULL; sl = sl->next) {
  461.     fprintf(fnote, "Node ``%s'' at offset %ld\n", sl->split.filename,
  462.         sl->split.position);
  463.     }
  464. #endif
  465. }
  466.  
  467. InfoFile::InfoFile(const char *filename)
  468.      : history(32),
  469.        most_recent_filename(NULL)
  470. {
  471.     open(filename);
  472. }
  473.  
  474. InfoFile::~InfoFile(void)
  475. {
  476.     if (!ok()) return;
  477.     fclose(infofile);
  478. }
  479.  
  480. void InfoFile::getnode_attempt(const char *nodename, char ***page, int *pagelen)
  481. {
  482.     char *p, *pnode;
  483.     int lines_searched = 0;
  484.     char buf[1024];
  485.  
  486.     BUGME(fprintf(fnote, "InfoFile::getnode_attempt(%s, ...)\n", nodename);)
  487.  
  488.     for(;;) {
  489.     // Get a line
  490.     p = fgets(buf, sizeof(buf), infofile);
  491.     if (p == NULL) goto attempt_fail;
  492.     lines_searched++;
  493.  
  494.     // Wait until we have a new page
  495.     if (*p != '\x1F') continue;
  496.  
  497.     // Get the startup line
  498.     p = fgetline(infofile);
  499.     if (p == NULL) goto attempt_fail;
  500.     lines_searched++;
  501.  
  502.     // If it is not a nodeline, try some more
  503.     if (strncmp(p,"File:",5)) {
  504.         free(p);
  505.         continue;
  506.     }
  507.  
  508.     // Get the node name
  509.     pnode = strstr(p, "Node:");
  510.     if (pnode == NULL) {
  511.         free(p);
  512.         continue;
  513.     }
  514.     pnode += 5;
  515.     while (isspace(*pnode)) pnode++;
  516.     
  517.  
  518.     // Give up if wrong name
  519.     if (strncmp(pnode, nodename, strlen(nodename))) {
  520.         free(p);
  521.         continue;
  522.     }
  523.  
  524.     // Otherwise, read the page
  525.     *page = (char **) malloc(64 * sizeof(char *));
  526.     *pagelen = 1;
  527.     int page_alloc = 64;
  528.     (*page)[0] = p;
  529.     for(;;) {
  530.         p = fgetline(infofile);
  531.         if (p == NULL || *p == '\x1F') break;
  532.         if (*pagelen+1 >= page_alloc) {
  533.         page_alloc += 64;
  534.         *page = (char **) realloc(*page, page_alloc * sizeof(char *));
  535.         }
  536.         (*page)[(*pagelen)++] = p;
  537.     }
  538.     return;
  539.     }
  540.     return;
  541. attempt_fail:
  542.     BUGME(fprintf(fnote, "attempt_fail\n");)
  543.     *page = NULL;
  544.     *pagelen = 0;
  545. }
  546.  
  547. void InfoFile::get_last_node(char ***page, int *pagelen)
  548. {
  549.     char *current, *previous;
  550.     *page = NULL;
  551.     *pagelen = 0;
  552.     current = history.pop();
  553.     if (current == NULL) return;
  554.     previous = history.pop();
  555.     if (previous == NULL) {
  556.     history.push(current);
  557.     free(current);
  558.     return;
  559.     }
  560.     getnode(previous, page, pagelen);
  561.     if (*page == NULL || *pagelen == 0) {
  562.     history.push(previous);
  563.     history.push(current);
  564.     }
  565.     free(previous);
  566.     free(current);
  567. }
  568.  
  569. void InfoFile::getnode(const char *anodename, char ***page, int *pagelen)
  570. {
  571.     char cpynodename[strlen(anodename)+1];
  572.     char *nodename = cpynodename;
  573.     while(isspace(*anodename)) anodename++;
  574.     char *cpy = nodename;
  575.     if (*anodename == '(') {
  576.     while (*anodename != '\0' && *anodename != ')') {
  577.         *cpy++ = *anodename++;
  578.     }
  579.     }
  580.     while (*anodename != '\0' && strchr("\t\n,.", *anodename) == NULL) {
  581.     *cpy++ = *anodename++;
  582.     }
  583.     *cpy = '\0';
  584.  
  585.     if (cpynodename[0] == '(') {
  586.     history.push(cpynodename);
  587.     } else {
  588.     char tempbuf[strlen(cpynodename)+strlen(most_recent_filename)+3];
  589.     sprintf(tempbuf, "(%s)%s", most_recent_filename, cpynodename);
  590.     history.push(tempbuf);
  591.     }
  592.  
  593.     BUGME(fprintf(fnote, "InfoFile::getnode(\"%s\",...)\n", nodename);)
  594.     if (*nodename == '(') {
  595.     // Uh-oh.  New file
  596.     char *tail = strchr(nodename, ')');
  597.     if (tail != NULL && tail > nodename + 1) {
  598.         char newfilename[tail-nodename];
  599.         nodename++;
  600.         strncpy(newfilename, nodename, tail-nodename);
  601.         newfilename[tail-nodename] = '\0';
  602.         nodename = tail+1;
  603.         fclose(infofile);
  604.         BUGME(fprintf(fnote, "getnode: Opening ``%s''\n", newfilename);)
  605.         open(newfilename);
  606.     }
  607.     }
  608.     if (!ok()) {
  609. fail:
  610.         BUGME(fprintf(fnote, "getnode: Failed\n");)
  611.     BUGME(fflush(fnote);)
  612.     *page = NULL;
  613.     *pagelen = 0;
  614.     return;
  615.     }
  616.     *page = NULL;
  617.     *pagelen = 0;
  618.  
  619.     if (*nodename == '\0') nodename = "Top";
  620.  
  621.     // Goto start of file
  622.     if (split_file) {
  623.     long min_pos, max_pos;
  624.     if (tags_table_availible) {
  625.         SplitList *tag;
  626.         for (tag = tags_table; tag != NULL; tag = tag->next) {
  627.         if (!strcmp(nodename, tag->split.filename)) break;
  628.         }
  629.         if (tag == NULL) goto fail;
  630.         min_pos = tag->split.position - 1024;
  631.         if (min_pos < 0) min_pos = 0;
  632.         max_pos = tag->split.position + 1024;
  633.     }
  634.     SplitList *sl;
  635.     SplitList *prev = NULL;
  636.     for (sl = splitlist; sl != NULL; sl = sl->next) {
  637.         BUGME(fprintf(fnote, "Trying %s\n", sl->split.filename);)
  638.         if (tags_table_availible) {
  639.         BUGME(fprintf(fnote, "Checking bounds: %ld in [%ld,%ld]\n",
  640.                   sl->split.position, min_pos, max_pos);)
  641.         BUGME(fflush(fnote);)
  642.         if (prev == NULL) {
  643.             if (sl->split.position > max_pos) continue;
  644.         } else if (min_pos > prev->split.position) {
  645.             // No way - we're past the point of possibility
  646.             break;
  647.         } else if (sl->split.position > max_pos) {
  648.             // No way - we haven't gone far enough yet
  649.                 prev = sl;
  650.                 continue;
  651.         } // Otherwise, try this node
  652.         prev = sl;
  653.         }
  654.         if (infofile != NULL) fclose(infofile);
  655.         infofile = fopen(sl->split.filename, "rt");
  656.         if (infofile == NULL) continue;
  657.         fseek(infofile, 0, SEEK_SET);
  658.         getnode_attempt(nodename, page, pagelen);
  659.         if (*page != NULL && *pagelen != 0) break;
  660.     }
  661.     } else {
  662.     fseek(infofile, 0, SEEK_SET);
  663.     getnode_attempt(nodename, page, pagelen);
  664.     }
  665.     if (page == NULL) goto fail;
  666. }
  667.  
  668. class MyWin: public StdWin
  669. {
  670.     int char_width, char_height, char_descender;
  671.     int client_height;
  672.     int client_char_height;
  673.     int scroll_position;
  674.     int max_scroll_position;
  675.     HWND vscrollbar;
  676.     InfoFile info;
  677.     char **view;
  678.     int view_count;
  679.     int view_dynamic;
  680.     static char *error_view[];
  681.     void recalc_max_scroll_position(void)
  682.     {
  683.     max_scroll_position = view_count - client_char_height;
  684.     if (max_scroll_position < 0) max_scroll_position = 0;
  685.     if (scroll_position > max_scroll_position) {
  686.         scroll_position = max_scroll_position;
  687.     }
  688.     NOTE(client_char_height);
  689.     NOTE(view_count);
  690.     NOTE(max_scroll_position);
  691.     NOTE(scroll_position);
  692.     }
  693.     void recalc_scrollbar(void);
  694.     void RuntimeNodeSelect(const char *nodename);
  695.     void HeaderNodeSelect(const char *field);
  696.     void FreeView(void);
  697.     void SelectStaticView(char **static_view, int static_view_count);
  698.     void LastNodeSelect();
  699. public:
  700.     virtual MRESULT MsgPaint(HWND, USHORT, MPARAM, MPARAM, int *);
  701.     virtual MRESULT MsgSize(HWND, USHORT, MPARAM, MPARAM, int *);
  702.     virtual MRESULT MsgCreate(HWND, USHORT, MPARAM, MPARAM, int *);
  703.     virtual MRESULT MsgVscroll(HWND, USHORT, MPARAM, MPARAM, int *);
  704.     virtual MRESULT MsgButton1Down(HWND, USHORT, MPARAM, MPARAM, int *);
  705.     virtual MRESULT MsgChar(HWND, USHORT, MPARAM, MPARAM, int *);
  706.     virtual MRESULT MsgCommand(HWND, USHORT, MPARAM, MPARAM, int *);
  707.     void SelectLine(int line, int xpos);
  708.     void RefreshLine(int line);
  709.     void Init(HAB);
  710.     void NodeSelect(const char *nodename);
  711.     MyWin();
  712. };
  713.  
  714. char *MyWin::error_view[] = { "Error" };
  715.  
  716. MyWin::MyWin()
  717.     : StdWin(),
  718.       info("dir"),
  719.       view(NULL)
  720. {
  721.     NodeSelect("Top");
  722. }
  723.  
  724. void MyWin::SelectStaticView(char **static_view, int static_view_count)
  725. {
  726.     FreeView();
  727.     view = static_view;
  728.     view_count = static_view_count;
  729.     view_dynamic = 0;
  730.     scroll_position = 0;
  731.     recalc_max_scroll_position();
  732.     recalc_scrollbar();
  733.     WinInvalidateRect(window, NULL, FALSE);
  734. }
  735.  
  736.  
  737. void MyWin::FreeView(void)
  738. {
  739.     if (!view_dynamic) {
  740.     view = NULL;
  741.     view_count = 0;
  742.     view_dynamic = 0;
  743.     return;
  744.     }
  745.     if (view != NULL && view != error_view && view_count > 0) {
  746.     for (int i = 0; i < view_count; i++) {
  747.         free(view[i]);
  748.     }
  749.     free(view);
  750.     view = NULL;
  751.     view_count = 0;
  752.     }
  753. }
  754.  
  755. void MyWin::LastNodeSelect()
  756. {
  757.     char **new_view;
  758.     int new_count;
  759.     info.get_last_node(&new_view, &new_count);
  760.     if (new_view == NULL || new_count == 0) return;
  761.     FreeView();
  762.     view_dynamic = 1;
  763.     view = new_view;
  764.     view_count = new_count;
  765.     scroll_position = 0;
  766.     recalc_max_scroll_position();
  767.     recalc_scrollbar();
  768.     WinInvalidateRect(window, NULL, FALSE);
  769. }
  770.  
  771. void MyWin::NodeSelect(const char *name)
  772. {
  773.     BUGME(fprintf(fnote, "MyWin::NodeSelect(\"%s\")\n", name);)
  774.     FreeView();
  775.     info.getnode(name, &view, &view_count);
  776.     if (view == NULL || view_count == 0) {
  777.     view = error_view;
  778.     view_count = 1;
  779.     view_dynamic = 0;
  780.     } else {
  781.     view_dynamic = 1;
  782.     }
  783.     scroll_position = 0;
  784.     recalc_max_scroll_position();
  785. }
  786.  
  787. void MyWin::Init(HAB hab)
  788. {
  789.     StdWin::Init(hab, "StdWin", CS_SIZEREDRAW,
  790.          FCF_TITLEBAR | FCF_SYSMENU | FCF_SIZEBORDER | FCF_MINMAX
  791.          | FCF_SHELLPOSITION | FCF_TASKLIST | FCF_VERTSCROLL | FCF_MENU
  792.          | FCF_ICON);
  793.     scroll_position = 0;
  794. }
  795.  
  796. MRESULT MyWin::MsgChar(HWND, USHORT, MPARAM mp1, MPARAM mp2, int *)
  797. {
  798.     if (SHORT1FROMMP(mp1) & KC_CHAR) {
  799.     switch(SHORT1FROMMP(mp2)) {
  800.     case 't':
  801.     case 'T':
  802.         RuntimeNodeSelect("Top");
  803.         break;
  804.     case 'd':
  805.     case 'D':
  806.         RuntimeNodeSelect("(dir)");
  807.         break;
  808.     case 'u':
  809.     case 'U':
  810.         HeaderNodeSelect("Up");
  811.         break;
  812.     case 'n':
  813.     case 'N':
  814.         HeaderNodeSelect("Next");
  815.         break;
  816.     case 'p':
  817.     case 'P':
  818.         HeaderNodeSelect("Prev");
  819.         break;
  820.     case 'l':
  821.     case 'L':
  822.         LastNodeSelect();
  823.         break;
  824.     }
  825.     }
  826.     return 0;
  827. }
  828.  
  829. MRESULT MyWin::MsgCommand(HWND, USHORT, MPARAM mp1, MPARAM, int *dodefault)
  830. {
  831.     switch(SHORT1FROMMP(mp1)) {
  832.     case IDM_NODE_DIR:
  833.     RuntimeNodeSelect("(dir)");
  834.     break;
  835.     case IDM_NODE_NEXT:
  836.     HeaderNodeSelect("Next");
  837.     break;
  838.     case IDM_NODE_PREVIOUS:
  839.     HeaderNodeSelect("Prev");
  840.     break;
  841.     case IDM_NODE_UP:
  842.     HeaderNodeSelect("Up");
  843.     break;
  844.     case IDM_NODE_LAST:
  845.     LastNodeSelect();
  846.     break;
  847.     case IDM_ABOUT_ABOUT:
  848.     SelectStaticView(embedded_text_Copyrght, embedded_text_Copyrght_count);
  849.     break;
  850.     case IDM_ABOUT_COPYING:
  851.     SelectStaticView(embedded_text_COPYING, embedded_text_COPYING_count);
  852.     break;
  853.     case IDM_ABOUT_WARRANTY:
  854.     SelectStaticView(embedded_text_COPYING, embedded_text_COPYING_count);
  855.     scroll_position = 277; /* Start of Warantee section */
  856.     recalc_max_scroll_position();
  857.     recalc_scrollbar();
  858.     WinInvalidateRect(window, NULL, FALSE);
  859.     break;
  860.     default:
  861.     *dodefault = 1; /* Just in case */
  862.     }
  863.     return 0;
  864. }
  865.  
  866. void MyWin::HeaderNodeSelect(const char *field)
  867. {
  868.     if (view == NULL || view_count == 0) return;
  869.     char search[strlen(field)+2];
  870.     sprintf(search, "%s:", field);
  871.     char *nodename = strstr(view[0], search);
  872.     if (nodename == NULL) {
  873.     // We don't have that particular header in the current node
  874.     return;
  875.     }
  876.     nodename += strlen(search);
  877.     while (isspace(*nodename)) nodename++;
  878.     RuntimeNodeSelect(nodename);
  879. }
  880.  
  881.  
  882. MRESULT MyWin::MsgButton1Down(HWND, USHORT, MPARAM mp1, MPARAM, int *)
  883. {
  884.     int x,y;
  885.     x = SHORT1FROMMP(mp1);
  886.     y = SHORT2FROMMP(mp1);
  887.     y = (client_height - y) / char_height;
  888.     SelectLine(y + scroll_position, x);
  889.     return 0;
  890. }
  891.  
  892. void MyWin::RefreshLine(int line)
  893. {
  894.     WinInvalidateRect(window, NULL, FALSE);
  895. }
  896.  
  897. void MyWin::SelectLine(int line, int)
  898. {
  899.     if (line < 0 || view_count <= line) return;
  900.     char *p = view[line];
  901.     while (isspace(*p)) p++;
  902.     if (*p != '*') return;
  903.     p++;
  904.     while (isspace(*p)) p++;
  905.     if (*p == ':') return;
  906.     char nodename[strlen(p)+1+3];
  907.     char *semicolon = strchr(p, ':');
  908.     if (semicolon == NULL) return;
  909.     if (semicolon[1] == ':') {
  910.     // menu entry *is* nodename
  911.     semicolon--;
  912.     while (isspace(*semicolon)) semicolon--;
  913.     strncpy(nodename, p, semicolon - p + 1);
  914.     nodename[semicolon - p + 1] = '\0';
  915.     } else {
  916.     p = semicolon+1;
  917.     while (isspace(*p)) p++;
  918.     char *target = nodename;
  919.     // Accept any letters in a filename
  920.     if (*p == '(') while (*p != ')' && *p != '\0') {
  921.         *target++ = *p++;
  922.     }
  923.     // Then process the nodename
  924.     while (*p != '\0' && strchr("\t\n,.", *p) == NULL) {
  925.         *target++ = *p++;
  926.     }
  927.     *target = '\0';
  928.     if (*nodename == '\0') return;
  929.     }
  930.     BUGME(fprintf(fnote, "Select: Nodename is ``%s''\n", nodename);)
  931.     RuntimeNodeSelect(nodename);
  932. }
  933.  
  934. void MyWin::RuntimeNodeSelect(const char *nodename)
  935. {
  936.     NodeSelect(nodename);
  937.     recalc_scrollbar();
  938.     WinInvalidateRect(window, NULL, FALSE);
  939. }
  940.  
  941. MRESULT MyWin::MsgVscroll(HWND window, USHORT, MPARAM, MPARAM mp2, int *)
  942. {
  943.     SHORT new_scroll_position = scroll_position;
  944.  
  945.     MsgCreate(window, 0, 0, 0, NULL);
  946.     switch(SHORT2FROMMP(mp2)) {
  947.     case SB_LINEUP:
  948.     new_scroll_position--;
  949.     break;
  950.     case SB_LINEDOWN:
  951.     new_scroll_position++;
  952.     break;
  953.     case SB_PAGEUP:
  954.     new_scroll_position -= max(1,client_char_height);
  955.     break;
  956.     case SB_PAGEDOWN:
  957.     new_scroll_position += max(1,client_char_height);
  958.     break;
  959.     case SB_SLIDERTRACK:
  960.     new_scroll_position = SHORT1FROMMP(mp2);
  961.     break;
  962.     }
  963.     LIMIT(new_scroll_position, 0, max_scroll_position);
  964.     if (new_scroll_position != scroll_position) {
  965.     WinScrollWindow(window, 0, 
  966.         (new_scroll_position - scroll_position) * char_height,
  967.         NULL,NULL,NULL,NULL, SW_INVALIDATERGN);
  968.     scroll_position = new_scroll_position;
  969.     WinSendMsg(vscrollbar, SBM_SETPOS,
  970.         MPFROMSHORT(scroll_position), NULL);
  971.     WinUpdateWindow(window);
  972.     }
  973.     return 0;
  974. }
  975.  
  976. MRESULT MyWin::MsgPaint(HWND window, USHORT msg, MPARAM mp1, MPARAM mp2, int *ip)
  977. {
  978.     MsgCreate(window, msg, mp1, mp2, ip);
  979.     RECTL rect;
  980.     HPS hps = WinBeginPaint(window, NULL, &rect);
  981.     POINTL pt;
  982.     int top,bottom;
  983.     int i;
  984.  
  985.     GpiErase(hps);
  986.     top = (client_height - rect.yTop) / char_height;
  987.     bottom = (client_height - rect.yBottom) / char_height + 1;
  988.     pt.x = 0;
  989.     pt.y = client_height - char_height * (1+top)+ char_descender;
  990.     //fprintf(fnote, "Repaint: vpix %d thru %d\n", rect.yTop, rect.yBottom);
  991.     BUGME(fprintf(fnote, "Repainting lines %d thru %d\n", top, bottom);)
  992.     NOTE(scroll_position);
  993.     top += scroll_position;
  994.     bottom += scroll_position;
  995.     NOTE(top);
  996.     NOTE(bottom);
  997.     LIMIT(top,scroll_position,view_count);
  998.     LIMIT(bottom,scroll_position,view_count);
  999.     for (i = top; i < bottom; i++) {
  1000.     GpiCharStringAt(hps, &pt, strlen(view[i]), (PSZ)view[i]);
  1001.     pt.y -= char_height;
  1002.     }
  1003.     WinEndPaint(hps);
  1004.     return 0;
  1005. }
  1006.  
  1007. void MyWin::recalc_scrollbar(void)
  1008. {
  1009.     WinSendMsg(vscrollbar, SBM_SETSCROLLBAR, MPFROM2SHORT(scroll_position, 0), 
  1010.     MPFROM2SHORT(0, max_scroll_position));
  1011.     WinEnableWindow(vscrollbar, (max_scroll_position == 0) ? FALSE : TRUE);
  1012. }
  1013.  
  1014. MRESULT MyWin::MsgSize(HWND, USHORT, MPARAM, MPARAM mp2, int *)
  1015. {
  1016.     MsgCreate(window, 0, 0, 0, NULL);
  1017.     client_height = SHORT2FROMMP(mp2);
  1018.     client_char_height = client_height / char_height;
  1019.     recalc_max_scroll_position();
  1020.     BUGME(fprintf(fnote, "Size: Scrollbar set to %d of %d\n", scroll_position,
  1021.           max_scroll_position);)
  1022.     recalc_scrollbar();
  1023.     NOTE(client_height);
  1024.     NOTE(client_char_height);
  1025.     NOTE(max_scroll_position);
  1026.     return 0;
  1027. }
  1028.  
  1029. MRESULT MyWin::MsgCreate(HWND window, USHORT, MPARAM, MPARAM, int *)
  1030. {
  1031.     static init = 0;
  1032.     if (init) return 0;
  1033.     init = 1;
  1034.     HPS hps;
  1035.     FONTMETRICS fm;
  1036.  
  1037.     BUGME(fprintf(fnote, "MsgCreate:\n");)
  1038.  
  1039.     hps = WinGetPS(window);
  1040.     GpiQueryFontMetrics(hps, sizeof(fm), &fm);
  1041.  
  1042.     char_width = fm.lEmInc;
  1043.     char_height = fm.lMaxBaselineExt;
  1044.     char_descender = fm.lMaxDescender;
  1045.  
  1046.     vscrollbar = WinWindowFromID(WinQueryWindow(window, QW_PARENT), 
  1047.             FID_VERTSCROLL);
  1048.  
  1049.     NOTE(char_width);
  1050.     NOTE(char_height);
  1051.     NOTE(char_descender);
  1052.  
  1053.     return 0;
  1054. }
  1055.  
  1056.  
  1057. int main(void)
  1058. {
  1059.     HAB hab;
  1060.     HMQ hmq;
  1061.  
  1062.     BUGME(fnote = fopen("note", "wt");)
  1063.     StdWin::StdStartup(&hab, &hmq);
  1064.     MyWin win;
  1065.     win.Init(hab);
  1066.     QMSG qmsg;
  1067.     while (WinGetMsg(hab, &qmsg, NULL, 0, 0)) WinDispatchMsg(hab, &qmsg);
  1068.     win.Destroy();
  1069.     StdWin::StdExit(hab, hmq);
  1070.     BUGME(fclose(fnote);)
  1071.     return 0;
  1072. }
  1073.