{ NEWHELP.INC: New Online Help System Routines }

{## todo: Alt+F1 to go back to previous help }
{## todo: add cross-topic search that creates an index (F2?)}
{## todo: keysline at bottom of screen}
{## todo: add help on local search}
{## todo: stop push/pop all the time. Free topic and reload old one on pop}
{## todo: esc=escape, Alt+F1 = pop}
{## todo: improve paragraph formatting by ignoring #255-series }

CONST MIN_HELP_MEMAVAIL = 15000; { 80x43 window costs 4.5Kb! Rest for max.size help topic }

CONST HELPHANDLE_HELPONHELP   : HelpHandleType = $FFFF0000;
      HELPHANDLE_LOCALSEARCH  : HelpHandleType = $FFFE0000;
      HELPHANDLE_GLOBALSEARCH : HelpHandleType = $FFFD0000;
      HELPHANDLE_FILEMANAGER  : HelpHandleType = $FFFC0000;

{ $DEFINE DynamicHelpWindows}

{--------------------------------------------------------------------------}
{ RequestHelp                                                              }
{                                                                          }
{ This routine can be used to bring up the help screen with the referred   }
{ topic. This routine does not return before the user closes the help      }
{ system.                                                                  }
{                                                                          }
PROCEDURE RequestHelp (HelpHandle : HelpHandleType);

CONST MAX_LINKS_IN_VIEW = 80;  { index, 43 lines screen, 2 columns }

{ One line is as wide as the reader window allows. }
{ Lines are formatted from paragraphs when the topic is loaded. }
{ Color codes (cc*) can be present in the line: #255+colorcode }
{ this allows easy searching for the next code using Pos(). }
TYPE OneLineRecordPtr = ^OneLineRecord;

     OneLineRecord = RECORD
                           LinePtr     : ^STRING;
                           NextLinePtr : OneLineRecordPtr;
                     END;

     TopicRecordPtr = ^TopicRecord;
     TopicRecord = RECORD
                         PrevTopicPtr : TopicRecordPtr;

                         HelpTopicNr  : HelpHandleType;
                         FirstLinePtr : OneLineRecordPtr;
                         LastLinePtr  : OneLineRecordPtr;
                         LinesCount   : WORD;
                         MarkText     : STRING[80];

                         { window info }
                         Xb,Yb,Xl,Yl  : XYType;

                         { cursor in this help topic }
                         CursorX,
                         CursorY      : XYType;

                         { vertical scrolling only }
                         PrevTopY,
                         TopY         : WORD;

                         { in cursor coordinates }
                         LinksCount   : BYTE;
                         LinksY,
                         LinksX1,
                         LinksX2      : ARRAY[1..MAX_LINKS_IN_VIEW] OF XYType;
                         LinksNr      : ARRAY[1..MAX_LINKS_IN_VIEW] OF HelpHandleType;

                         LinkShown    : 0..MAX_LINKS_IN_VIEW;
                   END;

    {----------------------------------------------------------------------}
    { CheckHeader                                                          }
    {                                                                      }
    { This function returns TRUE when the binary help file header is       }
    { correct.                                                             }
    {                                                                      }
    FUNCTION CheckHeader : BOOLEAN;

    VAR Header : STRING[3];

    BEGIN
         Seek (HlpFile,17);
         BlockRead (HlpFile,Header[1],3);
         Header[0]:=#3;
         CheckHeader:=(Header = 'RH2');
    END;

    {----------------------------------------------------------------------}
    { CheckCRC                                                             }
    {                                                                      }
    { This function returns TRUE when the binary help file header is       }
    { correct.                                                             }
    {                                                                      }
    FUNCTION CheckCRC : BOOLEAN;

    VAR CRC : LONGINT;

    BEGIN
         Seek (HlpFile,20);
         BlockRead (HlpFile,CRC,4);
         CheckCRC:=(CRC = HlpCRC);
    END;

    {----------------------------------------------------------------------}
    { UpdateHelpTopicWindow                                                }
    {                                                                      }
    { This routine redraws the contents of the help topic window, based on }
    { the viewing parameters.                                              }
    {                                                                      }
    PROCEDURE UpdateHelpTopicWindow (TopicPtr : TopicRecordPtr);

    VAR Lp        : WORD;
        Hulp      : STRING;
        LinePtr   : OneLineRecordPtr;
        X         : XYType;
        P         : BYTE;
        OnLink    : BOOLEAN;
        LinkFound : BYTE;
        LastColor : ColorSet;

    BEGIN
         WITH TopicPtr^ DO
         BEGIN
              IF (TopY <> PrevTopY) THEN
              BEGIN
                   { position the line pointer to the TopY'th line }
                   Lp:=TopY;
                   LinePtr:=FirstLinePtr;
                   WHILE (LinePtr <> NIL) AND (Lp > 1) DO
                   BEGIN
                        LinePtr:=LinePtr^.NextLinePtr;
                        Dec (Lp);
                   END;

                   SetColor (cNewHelpNormal);

                   LinksCount:=0;
                   LinkShown:=0;

                   { draw window contents }
                   FOR Lp:=1 TO Yl-2 DO
                   BEGIN
                        IF (LinePtr = NIL) THEN
                           Hulp:='' {'<end end end>'}
                        ELSE BEGIN
                             Hulp:=LinePtr^.LinePtr^;
                             LinePtr:=LinePtr^.NextLinePtr;
                        END;

                        X:=Xb+1;
                        OnLink:=FALSE;

                        REPEAT
                              P:=Pos (#255,Hulp);

                              IF (P = 1) THEN
                              BEGIN
                                   OnLink:=FALSE;

                                   { change color }
                                   CASE Hulp[2] OF
                                        ccTitle:
                                            SetColor (cNewHelpTitle);

                                        ccNormal:
                                            SetColor (cNewHelpNormal);

                                        ccHighlight:
                                            SetColor (cNewHelpHighlight);

                                        ccLink:
                                            BEGIN
                                                 SetColor (cNewHelpLink);
                                                 OnLink:=TRUE;

                                                 Inc (LinksCount);
                                                 LinksY[LinksCount]:=Lp-1;
                                                 LinksX1[LinksCount]:=X-(Xb+1);
                                                 LinksX2[LinksCount]:=LinksX1[LinksCount];
                                                 LinksNr[LinksCount]:=HexString2Long (Copy (Hulp,3,8));

                                                 { skip the hex link number }
                                                 Delete (Hulp,1,8);
                                            END;

                                        ccSearchResult:
                                            BEGIN
                                                 LastColor:=GetColor;
                                                 SetColor (cMessage);
                                            END;

                                        ccSubTopic:
                                            Delete(Hulp,1,8);

                                        ccPrevious:
                                            SetColor (LastColor);

                                        ELSE
                                            SetColor (cError);

                                   END; { case }

                                   Delete (Hulp,1,2);
                              END ELSE
                                  IF (P > 0) THEN
                                  BEGIN
                                       { print text up to next #255 }
                                       IF OnLink THEN
                                       BEGIN
                                            Inc (LinksX2[LinksCount],P-2);
                                            IF (CursorY = LinksY[LinksCount]) AND
                                               (CursorX >= LinksX1[LinksCount]) AND
                                               (CursorX <= LinksX2[LinksCount]) THEN
                                            BEGIN
                                                 SetColor (cNewHelpLinkCursor);
                                                 LinkShown:=LinksCount;
                                            END;
                                       END;

                                       WriteXY (X,Yb+Lp,Copy (Hulp,1,P-1));
                                       Delete (Hulp,1,P-1);
                                       Inc (X,P-1);
                                  END ELSE
                                  BEGIN
                                       { print until end }
                                       IF OnLink THEN
                                       BEGIN
                                            Inc (LinksX2[LinksCount],Length (Hulp)-1);
                                            IF (CursorY = LinksY[LinksCount]) AND
                                               (CursorX >= LinksX1[LinksCount]) AND
                                               (CursorX <= LinksX2[LinksCount]) THEN
                                            BEGIN
                                                 SetColor (cNewHelpLinkCursor);
                                                 LinkShown:=LinksCount;
                                            END;
                                       END;

                                       WriteXY (X,Yb+Lp,Hulp);
                                       Inc (X,Length (Hulp));
                                       Hulp:='';
                                  END;

                              IF (Hulp = '') THEN
                              BEGIN
                                   { add with spaces until end of window }
                                   IF ((X-Xb)-1 < Xl) THEN
                                      WriteXYC (X,Yb+Lp,cNewHelpNormal,Spaces (Xl-(X-Xb)-1));

                                   SetColor (cNewHelpNormal);
                              END;

                        UNTIL (Hulp = '');

                        WriteXY (Xb+1,Yb+Lp,Hulp);
                   END; { for }

                   PrevTopY:=TopY;
              END; { if, with }

              { work out on which link the cursor is }
              LinkFound:=0;
              FOR Lp:=1 TO LinksCount DO
                  IF (CursorY = LinksY[Lp]) AND
                     (CursorX >= LinksX1[Lp]) AND
                     (CursorX <= LinksX2[Lp]) THEN
                  BEGIN
                       LinkFound:=Lp;
                       Break;
                  END;

              IF (LinkFound <> LinkShown) THEN
              BEGIN
                   IF (LinkShown <> 0) THEN
                   BEGIN
                        { remove cursor from previous link }
                        ChangeColor (Xb+1+LinksX1[LinkShown],
                                     Yb+1+LinksY[LinkShown],
                                     LinksX2[LinkShown]-LinksX1[LinkShown]+1,
                                     cNewHelpLink);
                   END;

                   LinkShown:=LinkFound;
                   IF (LinkShown <> 0) THEN
                      ChangeColor (Xb+1+LinksX1[LinkShown],
                                   Yb+1+LinksY[LinkShown],
                                   LinksX2[LinkShown]-LinksX1[LinkShown]+1,
                                   cNewHelpLinkCursor);
              END; { if}
         END; { with }
    END;

    {----------------------------------------------------------------------}
    { FreeHelpTopic                                                        }
    {                                                                      }
    { This routine frees the memory consumed by the current help topic and }
    { sets the TopicPtr to the previous topic pointer.                     }
    {                                                                      }
    PROCEDURE FreeHelpTopic (VAR TopicPtr : TopicRecordPtr);

    VAR EraseTopicPtr : TopicRecordPtr;
        EraseLinePtr  : OneLineRecordPtr;

    BEGIN
         IF (TopicPtr = NIL) THEN
            Exit;

         WindowPop;

         { free all the lines and title }
         WITH TopicPtr^ DO
              WHILE (FirstLinePtr <> NIL) DO
              BEGIN
                   EraseLinePtr:=FirstLinePtr;
                   FirstLinePtr:=FirstLinePtr^.NextLinePtr;

                   FreeMem (EraseLinePtr^.LinePtr,Length (EraseLinePtr^.LinePtr^)+1);
                   FreeMem (EraseLinePtr,SizeOf (OneLineRecord));
              END; { while, with }

         { free the top record and set the topic pointer to the previous topic }
         EraseTopicPtr:=TopicPtr;
         TopicPtr:=TopicPtr^.PrevTopicPtr;
         FreeMem (EraseTopicPtr,SizeOf (TopicRecord));
    END;

    {----------------------------------------------------------------------}
    { GetTopicOffsetInHelpfile                                             }
    {                                                                      }
    { This routine searches the index of the help file for the help topic  }
    { and fills in the offset. If the topic is not found, then FALSE is    }
    { returned.                                                            }
    {                                                                      }
    FUNCTION GetTopicOffsetInHelpfile (TopicNr : HelpHandleType;
                                       VAR Offset : LONGINT) : BOOLEAN;

    VAR Index        : HelpIndexRecord;
        SuperTopicNr : LONGINT;

    BEGIN
         { make sure we load the super-topic and do not start }
         { loading in the middle of a topic because of a sub-topic }
         SuperTopicNr:=TopicNr AND $FFFF0000;

         { avoid searching (and finding) for end marker }
         IF (SuperTopicNr = 0) THEN
         BEGIN
              GetTopicOffsetInHelpfile:=FALSE;
              Exit;
         END;

         Seek (HlpFile,24);
         REPEAT
               {## improve by reading an array of index records at once }
               BlockRead (HlpFile,Index,SizeOf (HelpIndexRecord));

               IF (Index.TopicNr = SuperTopicNr) THEN
               BEGIN
                    Offset:=Index.Offset;
                    GetTopicOffsetInHelpfile:=TRUE; { found }
                    Exit;
               END;
         UNTIL (Index.TopicNr = 0);

         GetTopicOffsetInHelpfile:=FALSE; { not found }
    END;

    {----------------------------------------------------------------------}
    { AddLineToTopic                                                       }
    {                                                                      }
    { This routine allocates dynamic memory for the line record and adds   }
    { to the topic record.                                                 }
    {                                                                      }
    PROCEDURE AddLineToTopic (TopicPtr : TopicRecordPtr; _Hulp : STRING);

    VAR NewLinePtr : OneLineRecordPtr;
        Hulp       : STRING;
        P          : BYTE;

    BEGIN
         _Hulp:=DeleteBackSpaces (_Hulp);

         { automatically mark the wanted text }
         IF (TopicPtr^.MarkText <> '') THEN
         BEGIN
              Hulp:='';

              REPEAT
                    P:=Pos (TopicPtr^.MarkText,LoCaseString (_Hulp));

                    IF (P > 0) THEN
                    BEGIN
                         Hulp:=Hulp+Copy (_Hulp,1,P-1);
                         Delete (_Hulp,1,P-1);
                         P:=Length (TopicPtr^.MarkText);
                         Hulp:=Hulp+#255+ccSearchResult+Copy (_Hulp,1,P)+#255+#7;
                         Delete (_Hulp,1,P);
                    END ELSE
                        Hulp:=Hulp+_Hulp;

              UNTIL (P = 0);
         END ELSE
             Hulp:=_Hulp;

         WITH TopicPtr^ DO
         BEGIN
              GetMem (NewLinePtr,SizeOf (OneLineRecord));
              NewLinePtr^.NextLinePtr:=NIL;
              GetMem (NewLinePtr^.LinePtr,Length (Hulp)+1);
              NewLinePtr^.LinePtr^:=Hulp;

              IF (LastLinePtr = NIL) THEN
                 FirstLinePtr:=NewLinePtr
              ELSE
                  LastLinePtr^.NextLinePtr:=NewLinePtr;

              LastLinePtr:=NewLinePtr;
              Inc (LinesCount);
         END;
    END;

    {----------------------------------------------------------------------}
    { ReadTopicText                                                        }
    {                                                                      }
    { This routine reads the entire topic text from disk and adds it to    }
    { the topic array, formatting paragraphs into lines as we go along,    }
    {                                                                      }
    PROCEDURE ReadTopicText (TopicPtr : TopicRecordPtr; Offset : LONGINT);

    CONST MAXLEN_BUFFER = 2500;

    TYPE DataBufferPtr  = ^DataBufferType;
         DataBufferType = ARRAY[1..MAXLEN_BUFFER] OF CHAR;

    VAR BufPtr    : DataBufferPtr;
        BufLen    : WordLong;
        BufGetIdx : WORD;
        Hulp      : STRING;
        Pbreak,
        Plinebrk,
        Pend      : BYTE;
        CheckIdx  : WORD;

    BEGIN
         GetMem (BufPtr,MAXLEN_BUFFER);
         BufLen:=2;
         BufGetIdx:=2;

         REPEAT
               IF (BufLen-BufGetIdx < 100) THEN
               BEGIN
                    Inc (Offset,BufGetIdx-2);

                    Seek (HlpFile,Offset);
                    BlockRead (HlpFile,BufPtr^[2],MAXLEN_BUFFER-1,BufLen);

                    IF (BufLen = 0) THEN
                    BEGIN
                         AddLineToTopic (TopicPtr,'<unexpected end of topic>');
                         Break; { error; from the repeat }
                    END;

                    BufGetIdx:=2;
               END;

               { fake a maximum window width character string }
               {## must remove the control codes and especially the }
               {## 8-char long hex reference for links before deciding }
               {## where to cut for the next line!!                    }
               {## Also, we must not cut a link or title in two! }
               BufPtr^[BufGetIdx-1]:=Char (TopicPtr^.Xl-2);
               Pbreak:=Pos (#10,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
               Plinebrk:=Pos (#13,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
               Pend:=Pos (#0,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);

               IF (Pbreak = 0) AND (Plinebrk = 0) AND (Pend = 0) THEN
               BEGIN
                    Inc (BufPtr^[BufGetIdx-1]);
                    Pbreak:=Pos (#10,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
                    Plinebrk:=Pos (#13,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
                    Pend:=Pos (#0,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);

                    IF (Pbreak = 0) AND (Plinebrk = 0) AND (Pend = 0) THEN
                       Dec (BufPtr^[BufGetIdx-1]);
               END;

               { handle Pend }
               IF (Pend > 0) AND
                  ((Pbreak = 0) OR (Pend < Pbreak)) AND
                  ((Plinebrk = 0) OR (Pend < Plinebrk)) THEN
               BEGIN
                    { last line }
                    BufPtr^[BufGetIdx-1]:=Char (Pend-1);
                    AddLineToTopic (TopicPtr,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
                    Break; { from the repeat }
               END ELSE
                   IF (Pbreak > 0) AND
                      ((Plinebrk = 0) OR (Pbreak < Plinebrk)) THEN
                   BEGIN
                        BufPtr^[BufGetIdx-1]:=Char (Pbreak-1);
                        AddLineToTopic (TopicPtr,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
                        AddLineToTopic (TopicPtr,'');
                        Inc (BufGetIdx,Pbreak);
                   END ELSE
                       IF (Plinebrk > 0) THEN
                       BEGIN
                            BufPtr^[BufGetIdx-1]:=Char (Plinebrk-1);
                            AddLineToTopic (TopicPtr,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);
                            Inc (BufGetIdx,Plinebrk);
                       END ELSE
                       BEGIN
                            CheckIdx:=TopicPtr^.Xl-2;
                            REPEAT
                                  Dec (CheckIdx);
                            UNTIL (CheckIdx = 0) OR (BufPtr^[BufGetIdx+CheckIdx] IN [' ','-']);

                            { if we cannot soft-break it, then hard-break }
                            IF (CheckIdx = 0) THEN
                               CheckIdx:=TopicPtr^.Xl-2;

                            BufPtr^[BufGetIdx-1]:=Char (CheckIdx+1);
                            AddLineToTopic (TopicPtr,StringPtr (Addr (BufPtr^[BufGetIdx-1]))^);

                            Inc (BufGetIdx,CheckIdx+1);
                       END;

         UNTIL (1=2);

         FreeMem (BufPtr,MAXLEN_BUFFER);
    END;

    {----------------------------------------------------------------------}
    { DecideWindowPlacement                                                }
    {                                                                      }
    { This routine works out where we have most place on the screen based  }
    { on the area we have to keep visible. We calculate the area and take  }
    { the biggest one.                                                     }
    {                                                                      }
    {$IFDEF DynamicHelpWindows}
    PROCEDURE DecideWindowPlacement (TopicPtr : TopicRecordPtr; AL,AT,AR,AB : XYType);

    VAR S1,S2,S3,S4 : INTEGER; { allow <0 }

    BEGIN
         { above }
         S1:=(Video.Cols-2{shadow on the right})*(AT-1{not desktop title});

         { underneath }
         S2:=(Video.Rows-AB)*Video.Cols;

         { on the left }
         S3:=AL*(Video.Rows-3{desktop title+keysline+shadow});

         { on the right }
         S4:=(Video.Cols-AR-2{shadow})*(Video.Rows-3{desktop title+keysline+shadow});

         WriteXY (1,1,SignedInteger2String (S1)+','+
         SignedInteger2String (S2)+','+
         SignedInteger2String (S3)+','+
         SignedInteger2String (S4)+' ');

         WITH TopicPtr^ DO
         BEGIN
              IF (S1 > S2) AND (S1 > S3) AND (S1 > S4) THEN
              BEGIN
                   { take above }
                   Xb:=1;
                   Yb:=2;
                   Xl:=Video.Cols-2;
                   Yl:=AT-2;
                   Exit;
              END;

              IF (S2 > S3) AND (S2 > S4) THEN
              BEGIN
                   { underneath }
                   Xb:=1;
                   Yb:=AB+1;
                   Xl:=Video.Cols-2;
                   Yl:=Video.Rows-Yb-1;
                   Exit;
              END;

              IF (S3 > S4) THEN
              BEGIN
                   { on the left }
                   Xb:=1;
                   Yb:=2;
                   Xl:=AL;
                   Yl:=Video.Rows-2;
                   Exit;
              END;

              { on the right }
              Xb:=AR;
              Yb:=2;
              Xl:=Video.Cols-AR-2;
              Yl:=Video.Rows-2;
         END; { with }
    END;
    {$ENDIF (DynamicHelpWindows)}

    {----------------------------------------------------------------------}
    { FocusAroundLine                                                      }
    {                                                                      }
    { This routine puts the focus of the view around the requested line    }
    { number, making sure that the first and last line of the view always  }
    { contain text lines. The line will be in the middle of the focus.     }
    {                                                                      }
    PROCEDURE FocusAroundLine (TopicPtr : TopicRecordPtr; LineNr : WORD);

    VAR ThirdYL : XYType;

    BEGIN
         WITH TopicPtr^ DO
         BEGIN
              IF (LinesCount <= (Yl-2)) THEN
              BEGIN
                   TopY:=1;
                   Exit;
              END;

              ThirdYL:=(Yl-2) DIV 3;

              { make sure we don't set the top before the start }
              IF (LineNr <= ThirdYl) THEN
              BEGIN
                   TopY:=1;
                   Exit;  { ## EXIT ## }
              END;

              TopY:=LineNr-ThirdYl;

              { make sure we don't get empty lines at the end }
              IF (TopY+(Yl-3) > LinesCount) THEN
                 TopY:=LinesCount-(Yl-3);
         END;
    END;

    {----------------------------------------------------------------------}
    { FocusOnSubTopic                                                      }
    {                                                                      }
    { This routine is used to put the focus on a sub-topic directly after  }
    { the super-topic has been loaded. We find the needed line, put it in  }
    { focus and put the cursor on top of it.                               }
    {                                                                      }
    PROCEDURE FocusOnSubTopic (TopicPtr : TopicRecordPtr;
                               TopicNr  : HelpHandleType);

    VAR LinePtr : OneLineRecordPtr;
        Hulp    : STRING;
        LineNr  : WORD;

    BEGIN
         Hulp:=#255+ccSubTopic+Long2HexString (TopicNr);

         LineNr:=1;
         LinePtr:=TopicPtr^.FirstLinePtr;

         WHILE (LinePtr <> NIL) DO
         BEGIN
              IF (Pos (Hulp,LinePtr^.LinePtr^) > 0) THEN
              BEGIN
                   { found in this line! }
                   FocusAroundLine (TopicPtr,LineNr);
                   TopicPtr^.CursorY:=LineNr-TopicPtr^.TopY;
                   TopicPtr^.CursorX:=0;
                   Exit;
              END;

              LinePtr:=LinePtr^.NextLinePtr;
              Inc (LineNr);
         END;

         { not found! }
         Error ('Sub-topic $'+Long2HexString (TopicNr)+' not found!');
    END;

    {----------------------------------------------------------------------}
    { LoadHelpTopic                                                        }
    {                                                                      }
    { This routine loads the requested help topic from the help file and   }
    { stores it completely into memory. There is always enough memory      }
    { since this is checked when we start the help system.                 }
    { If the topic doesn't exist or the help file could not be accessed    }
    { anymore, then an error message is generated.                         }
    { When the new topic is ready, fRedraw is set to TRUE, which causes    }
    { the main loop to draw a new window.                                  }
    {                                                                      }
    PROCEDURE LoadHelpTopic (VAR TopicPtr : TopicRecordPtr;
                             TopicNr      : HelpHandleType);

    VAR NewTopicPtr : TopicRecordPtr;
        Hulp        : STRING;
        Lp          : WORD;
        Offset      : LONGINT;
        N           : WORD;
        WindowPtr   : WindowRecordPtr;
        AL,AR,AT,AB : XYType; { area left, right, top, bottom }

    BEGIN
         IF (_MaxAvail < MIN_HELP_MEMAVAIL) THEN
         BEGIN
              Error ('Not enough free memory to load help topic');
              Exit;
         END;

         IF (NOT GetTopicOffsetInHelpfile (TopicNr,Offset)) THEN
         BEGIN
              Error ('Help topic $'+Long2HexString (TopicNr)+' not found in help file!');
              Exit;
         END;

         GetMem (NewTopicPtr,SizeOf (TopicRecord));

         WITH NewTopicPtr^ DO
         BEGIN
              PrevTopicPtr:=TopicPtr;

              HelpTopicNr:=TopicNr;
              FirstLinePtr:=NIL;
              LastLinePtr:=NIL;
              LinesCount:=0;

              IF (TopicPtr <> NIL) THEN
                 MarkText:=TopicPtr^.MarkText
              ELSE
                  MarkText:='';

              Xb:=5;
              Yb:=4;
              Xl:=Video.Cols-9;
              Yl:=Video.Rows-6;

              {$IFDEF DynamicHelpWindows}
              N:=(Video.Rows-1)*Video.Cols*2;
              WindowPtr:=CurrWindowRecordPtr;
              WHILE (WindowPtr <> NIL) DO
              BEGIN
                   IF (WindowPtr^.StartOffset < N) THEN
                      Break;

                   WindowPtr:=WindowPtr^.PrevWindowRecordPtr;
              END; { while }

              { calculate the new window size and position }
              IF (WindowPtr = NIL) THEN
              BEGIN
                   {## not smart yet }
                   IF (PrevTopicPtr <> NIL) THEN
                   BEGIN
                        {## just for testing!!}
                        Xb:=PrevTopicPtr^.Xb+1;
                        Yb:=PrevTopicPtr^.Yb+2;
                        Xl:=PrevTopicPtr^.Xl+2;
                        Yl:=PrevTopicPtr^.Yl;

                        IF (Xb+Xl > 79) THEN
                           Xl:=79-Xb;

                        IF (Yb+Yl > Video.Rows-1) THEN
                           Yl:=Video.Rows-1-Yb;

                        IF (Xl < 40) OR (Yl < 10) THEN
                        BEGIN
                             Xb:=2;
                             Yb:=3;
                             Xl:=40;
                             Yl:=10;
                        END;
                   END ELSE
                   BEGIN
                        Xb:=2;
                        Yb:=3;
                        Xl:=70;
                        Yl:=20;
                   END;
              END ELSE
                  WITH WindowPtr^ DO
                  BEGIN
                       N:=StartOffset DIV 2; { char + attr }
                       AT:=(N DIV Video.Cols);
                       AL:=N-AT*Video.Cols;
                       AR:=AL+(LineLength DIV 2);
                       AB:=AT+(WindowSize DIV LineLength);

                       Inc (AL,1); { make 1-base }
                       Inc (AT,1); { make 1-base }
                       Dec (AR,2); { keep space for shadow }
                       Dec (AB,1); { keep space for shadow }

                       { calculate how much space there is on each of the }
                       { four sides and then take the one with the most   }
                       { space.                                           }

                       DecideWindowPlacement (NewTopicPtr,AL,AT,AR,AB);
                  END; { with }
              {$ENDIF (DynamicHelpWindows)}
         END; { with }

         ReadTopicText (NewTopicPtr,Offset);

         WITH NewTopicPtr^ DO
         BEGIN
              {$IFDEF DynamicHelpWindows}
              IF (Yl > LinesCount+2) THEN
                 Yl:=LinesCount+2;
              {$ENDIF (DynamicHelpWindows)}

              WindowPush (Xb,Yb,Xl,Yl);
              BoxDrawC (cNewHelpNormal,Double,Xb,Yb,Xl,Yl);

              { cursor coordinates are offset inside window }
              CursorX:=0;
              CursorY:=0;

              PrevTopY:=0;
              TopY:=1;
         END; { with }

         IF ((TopicNr AND $0000FFFF) <> 0) THEN
            FocusOnSubTopic (NewTopicPtr,TopicNr);

         { fill the window with some contents }
         UpdateHelpTopicWindow (NewTopicPtr);

         TopicPtr:=NewTopicPtr;
    END;

    {----------------------------------------------------------------------}
    { GetLineCursorIsOn                                                    }
    {                                                                      }
    { This routine returns the text of the line the cursor is on.          }
    {                                                                      }
    FUNCTION GetLineCursorIsOn (TopicPtr : TopicRecordPtr) : STRING;

    VAR Left    : WORD;
        LinePtr : OneLineRecordPtr;
        Hulp    : STRING;
        P       : BYTE;

    BEGIN
         Hulp:='';

         WITH TopicPtr^ DO
         BEGIN
              { position the line pointer to the TopY'th line }
              Left:=TopY+CursorY;

              LinePtr:=FirstLinePtr;
              WHILE (LinePtr <> NIL) AND (Left > 1) DO
              BEGIN
                   LinePtr:=LinePtr^.NextLinePtr;
                   Dec (Left);
              END;

              IF (LinePtr <> NIL) THEN
              BEGIN
                   Hulp:=LinePtr^.LinePtr^;
                   REPEAT
                         P:=Pos (#255+ccLink,Hulp);
                         IF (P > 0) THEN
                            Delete (Hulp,P,2+8)
                         ELSE BEGIN
                              P:=Pos (#255,Hulp);
                              IF (P > 0) THEN
                                 Delete (Hulp,P,2);
                         END;
                   UNTIL (P = 0);
              END;
         END; { with }

         GetLineCursorIsOn:=Hulp;
    END;

    {----------------------------------------------------------------------}
    { CursorUp                                                             }
    {                                                                      }
    { This routine moves the cursor on line up and scrolls the view up as  }
    { well, if required. Returns FALSE if the cursor could not move up any }
    { further.                                                             }
    {                                                                      }
    FUNCTION CursorUp (TopicPtr : TopicRecordPtr) : BOOLEAN;
    BEGIN
         CursorUp:=TRUE;

         WITH TopicPtr^ DO
              IF (CursorY > 0) THEN
                 Dec (CursorY)
              ELSE
                  IF (TopY > 1) THEN
                     Dec (TopY)
                  ELSE
                      CursorUp:=FALSE;
    END;

    {----------------------------------------------------------------------}
    { CursorDown                                                           }
    {                                                                      }
    { This routine moves the cursor on line down and scrolls the view down }
    { as well, if required. This function returns FALSE if it was not      }
    { possible to go down.                                                 }
    {                                                                      }
    FUNCTION CursorDown (TopicPtr : TopicRecordPtr) : BOOLEAN;
    BEGIN
         CursorDown:=TRUE;

         WITH TopicPtr^ DO
              IF (CursorY < Yl-3) THEN
                 Inc (CursorY)
              ELSE
                  IF (TopY+CursorY < LinesCount) THEN
                     Inc (TopY)
                  ELSE
                      CursorDown:=FALSE;
    END;

    {----------------------------------------------------------------------}
    { GotoPreviousWord                                                     }
    {                                                                      }
    { This routine puts the cursor on the start of the current word, or    }
    { if already there, on the the first letter of the previous word. At   }
    { the start of the line the cursor proceeds to the end of the previous }
    { line and continues there.                                            }
    {                                                                      }
    PROCEDURE GotoPreviousWord (TopicPtr : TopicRecordPtr);

    VAR Hulp    : STRING;
        First   : BOOLEAN;

    BEGIN
         WITH TopicPtr^ DO
         BEGIN
              First:=TRUE;

              IF (CursorX > 0) THEN
                 Dec (CursorX)
              ELSE
                  First:=FALSE; { moves cursor up to previous line }

              REPEAT
                    IF (NOT First) THEN
                       IF (NOT CursorUp (TopicPtr)) THEN
                          Exit;

                    Hulp:=GetLineCursorIsOn (TopicPtr);

                    IF (NOT First) THEN
                       CursorX:=Length (Hulp)
                    ELSE
                        IF (CursorX > Length (Hulp)-1) THEN
                           CursorX:=Length (Hulp)-1;

                    First:=FALSE;

                    IF (Hulp <> '') THEN
                       REPEAT
                             { always finds something now }
                             IF (CursorX = 0) THEN
                                IF (Hulp[1] <> ' ') THEN
                                   Exit
                                ELSE
                                    Break; { from the repeat }

                             IF (Hulp[CursorX] = ' ') AND (Hulp[CursorX+1] IN ['!'..'~']) THEN
                                Exit;

                             Dec (CursorX);
                       UNTIL (First);

              UNTIL (First);
         END; { with }
    END;

    {----------------------------------------------------------------------}
    { GotoNextWord                                                         }
    {                                                                      }
    { This routine puts the cursor on the start of the next word, or if    }
    { already there, on the the first letter of the next word. At  the end }
    { of the line the cursor proceeds to the next line and searches start  }
    { of the next word there.                                              }
    {                                                                      }
    PROCEDURE GotoNextWord (TopicPtr : TopicRecordPtr);

    VAR First : BOOLEAN;
        Hulp  : STRING;

    BEGIN
         WITH TopicPtr^ DO
         BEGIN
              First:=TRUE;

              Hulp:=GetLineCursorIsOn (TopicPtr);

              IF (CursorX <> Length (Hulp)-1) THEN
                 Inc (CursorX)
              ELSE
                  First:=FALSE; { moves cursor up to previous line }

              REPEAT
                    IF (NOT First) THEN
                       IF (NOT CursorDown (TopicPtr)) THEN
                          Exit; { already on last line }

                    Hulp:=GetLineCursorIsOn (TopicPtr);

                    IF (NOT First) THEN
                    BEGIN
                         CursorX:=0;
                         IF (Hulp <> '') AND (Hulp[1] <> ' ') THEN
                            Exit;
                    END ELSE
                        IF (CursorX > Length (Hulp)-1) THEN
                           Hulp:=''; { force 'go to next line' }

                    First:=FALSE;

                    IF (Hulp <> '') THEN
                       REPEAT
                             { always finds something now }
                             IF (CursorX = Length (Hulp)-1) THEN
                                Exit;

                             IF (Hulp[CursorX] = ' ') AND (Hulp[CursorX+1] IN ['!'..'~']) THEN
                                Exit;

                             Inc (CursorX);
                       UNTIL (First);

              UNTIL (First);
         END;
    END;

    {----------------------------------------------------------------------}
    { PutCursorOnPrevLink                                                  }
    {                                                                      }
    { This routine searches for the previous link and positions the cursor }
    { on the first character of the link text. If the cursor is already on }
    { a link, then the previous link is searched for. Searching loops      }
    { around at the top of the topic.                                      }
    {                                                                      }
    PROCEDURE PutCursorOnPrevLink (TopicPtr : TopicRecordPtr);

    VAR Lp         : WORD;
        LinePtr    : OneLineRecordPtr;
        LinesLeft  : WORD;
        LineNr     : WORD;
        LastLineNr : WORD;
        Hulp       : STRING;
        P          : BYTE;
        LastX      : BYTE;

    BEGIN
         WITH TopicPtr^ DO
         BEGIN
              IF (LinkShown = 0) THEN
              BEGIN
                   { find the previous link }
                   { simple: use the cursor check array }
                   FOR Lp:=LinksCount DOWNTO 1 DO
                       IF (CursorY = LinksY[Lp]) THEN
                       BEGIN
                            IF (CursorX >= LinksX2[Lp]) THEN
                            BEGIN
                                 CursorY:=LinksY[Lp];
                                 CursorX:=LinksX1[Lp];
                                 Exit;
                            END;
                       END ELSE
                           IF (CursorY > LinksY[Lp]) THEN
                           BEGIN
                                CursorY:=LinksY[Lp];
                                CursorX:=LinksX1[Lp];
                                Exit;
                           END;
              END ELSE
                  { link shown - simply take previous one (if any) }
                  IF (LinkShown > 1) THEN
                  BEGIN
                       CursorX:=LinksX1[LinkShown-1];
                       CursorY:=LinksY[LinkShown-1];
                       Exit;
                  END;

              { nothing found }
              { search all the lines before the view until the start  }
              { of the topic for a link. Remember the last found link }
              { since all the lines are forwards-linked so we cannot  }
              { search backwards. Then take the last link found.      }
              IF (TopY > 0) THEN
              BEGIN
                   LinesLeft:=TopY;
                   LinePtr:=FirstLinePtr;
                   LineNr:=0;
                   LastLineNr:=65535;

                   WHILE (LinesLeft > 0) DO
                   BEGIN
                        IF (Pos (#255+ccLink,LinePtr^.LinePtr^) > 0) THEN
                        BEGIN
                             LastLineNr:=LineNr;
                             Hulp:=LinePtr^.LinePtr^;
                        END;

                        Inc (LineNr);
                        LinePtr:=LinePtr^.NextLinePtr;
                        Dec (LinesLeft);
                   END;

                   IF (LastLineNr <> 65535) THEN
                   BEGIN
                        { found something! }

                        { position view around this line }
                        FocusAroundLine (TopicPtr,LastLineNr);
                        CursorY:=LastLineNr-TopY+1;

                        { search the correct CursorX position }
                        CursorX:=0;
                        LastX:=0;

                        REPEAT
                              P:=Pos (#255,Hulp);
                              IF (P > 0) THEN
                              BEGIN
                                   Inc (CursorX,P-1);
                                   Delete (Hulp,1,P);
                                   IF (Hulp[1] = ccLink) THEN
                                      LastX:=CursorX;
                                   Delete (Hulp,1,1);
                              END;
                        UNTIL (P = 0);

                        CursorX:=LastX;
                        Exit;
                   END;
              END;

              { nothing found again }
              { search from the end of the topic until the first line  }
              { of the current view (not under the view!) and remember }
              { the last link found.                                   }
              LinesLeft:=TopY;
              LinePtr:=FirstLinePtr;
              WHILE (LinesLeft > 0) DO
              BEGIN
                   LinePtr:=LinePtr^.NextLinePtr;
                   Dec (LinesLeft);
              END;

              LineNr:=TopY;
              LastLineNr:=65535;

              WHILE (LinePtr <> NIL) DO
              BEGIN
                   IF (Pos (#255+ccLink,LinePtr^.LinePtr^) > 0) THEN
                   BEGIN
                        LastLineNr:=LineNr;
                        Hulp:=LinePtr^.LinePtr^;
                   END;

                   Inc (LineNr);
                   LinePtr:=LinePtr^.NextLinePtr;
              END; { while }

              IF (LastLineNr <> 65535) THEN
              BEGIN
                   { found something! }

                   { position view around this line }
                   FocusAroundLine (TopicPtr,LastLineNr);
                   CursorY:=LastLineNr-TopY+1;
                   CursorX:=0;
                   LastX:=0;

                   REPEAT
                         P:=Pos (#255,Hulp);
                         IF (P > 0) THEN
                         BEGIN
                              Inc (CursorX,P-1);
                              Delete (Hulp,1,P);
                              IF (Hulp[1] = ccLink) THEN
                                 LastX:=CursorX;
                              Delete (Hulp,1,1);
                         END;
                   UNTIL (P = 0);

                   CursorX:=LastX;
                   Exit;
              END;

              { nothing found again }
              { stay where we are }
         END; { with }
    END;

    {----------------------------------------------------------------------}
    { PutCursorOnNextLink                                                  }
    {                                                                      }
    { This routine searches for the next link and positions the cursor on  }
    { the first character of the link text. If the cursor is already on a  }
    { link, then the next link is searched for. Searching loops around at  }
    { the end of the topic.                                                }
    {                                                                      }
    PROCEDURE PutCursorOnNextLink (TopicPtr : TopicRecordPtr);

    VAR Lp        : WORD;
        LinePtr   : OneLineRecordPtr;
        LinesLeft : WORD;
        LineNr    : WORD;
        Hulp      : STRING;
        P         : BYTE;

    BEGIN
         WITH TopicPtr^ DO
         BEGIN
              IF (LinkShown = 0) THEN
              BEGIN
                   { find the next link }
                   { simple: use the cursor check array }
                   FOR Lp:=1 TO LinksCount DO
                       IF (CursorY = LinksY[Lp]) THEN
                       BEGIN
                            IF (CursorX <= LinksX1[Lp]) THEN
                            BEGIN
                                 CursorY:=LinksY[Lp];
                                 CursorX:=LinksX1[Lp];
                                 Exit;
                            END;
                       END ELSE
                           IF (CursorY < LinksY[Lp]) THEN
                           BEGIN
                                CursorY:=LinksY[Lp];
                                CursorX:=LinksX1[Lp];
                                Exit;
                           END;
              END ELSE
              BEGIN
                   { on a link - go to the next link }
                   { simple way: use cursor check array }
                   IF (LinkShown < LinksCount) THEN
                   BEGIN
                        CursorX:=LinksX1[LinkShown+1];
                        CursorY:=LinksY[Linkshown+1];
                        Exit;
                   END;
              END;

              { nothing found - then we must search the entire text array }

              { start positioning the line pointer on the line after the }
              { view and search the entire text until the end.           }
              LinesLeft:=TopY+(Yl-2);
              IF (LinesLeft < LinesCount) THEN
              BEGIN
                   LineNr:=LinesLeft;
                   LinePtr:=FirstLinePtr;
                   WHILE (LinesLeft > 0) DO
                   BEGIN
                        LinePtr:=LinePtr^.NextLinePtr;
                        Dec (LinesLeft);
                   END;

                   WHILE (LinePtr <> NIL) DO
                   BEGIN
                        IF (Pos (#255+ccLink,LinePtr^.LinePtr^) > 0) THEN
                        BEGIN
                             { line contains a link }

                             { position view around this line }
                             FocusAroundLine (TopicPtr,LineNr);
                             CursorY:=LineNr-TopY+1;
                             CursorX:=0;

                             Hulp:=LinePtr^.LinePtr^;
                             REPEAT
                                   P:=Pos (#255,Hulp);
                                   IF (P > 0) THEN
                                   BEGIN
                                        Inc (CursorX,P-1);
                                        Delete (Hulp,1,P);
                                        IF (Hulp[1] = ccLink) THEN
                                           Exit;
                                        Delete (Hulp,1,1);
                                   END;
                             UNTIL (P = 0);

                             { huh? bug!! }
                        END;

                        LinePtr:=LinePtr^.NextLinePtr;
                        Inc (LineNr);
                   END;
              END;

              { still nothing - search from the start to last line of  }
              { the view (not before the view because then we wouldn't }
              { find links already in the view, but before the cursor. }
              IF (TopY > 0) THEN
              BEGIN
                   LinePtr:=FirstLinePtr;
                   LinesLeft:=TopY+(Yl-2);
                   IF (LinesLeft > LinesCount) THEN
                      LinesLeft:=LinesCount;
                   LineNr:=0;

                   WHILE (LinesLeft > 0) DO
                   BEGIN
                        IF (Pos (#255+ccLink,LinePtr^.LinePtr^) > 0) THEN
                        BEGIN
                             { line contains a link }

                             { position view around this line }
                             FocusAroundLine (TopicPtr,LineNr);
                             CursorY:=LineNr-TopY+1;
                             CursorX:=0;
                             Hulp:=LinePtr^.LinePtr^;

                             REPEAT
                                   P:=Pos (#255,Hulp);
                                   IF (P > 0) THEN
                                   BEGIN
                                        Inc (CursorX,P-1);
                                        Delete (Hulp,1,P);
                                        IF (Hulp[1] = ccLink) THEN
                                           Exit;
                                        Delete (Hulp,1,1);
                                   END;
                             UNTIL (P = 0);

                             { huh? bug!! }
                        END;

                        LinePtr:=LinePtr^.NextLinePtr;
                        Dec (LinesLeft);
                        Inc (LineNr);
                   END; { while }
              END;

              { still nothing - stay where we are }
         END; { with }
    END;

    {----------------------------------------------------------------------}
    { LocalSearch                                                          }
    {                                                                      }
    { This routine searches the active topic and shows the results in a    }
    { different color inside the topic. Searching for nothing disables the }
    { search mode and removes the search results.                          }
    { Note that we can run seriously out of memory because of this!        }
    {                                                                      }
    PROCEDURE LocalSearch (VAR TopicPtr : TopicRecordPtr);

    CONST Xl = 65;
          Yl = 6;
          Xb = (80-Xl) DIV 2;

    VAR Yb          : XYType;
        SearchStr   : STRING[60];
        LinePtr,
        PrevLinePtr,
        NextLinePtr : OneLineRecordPtr;
        Hulp,
        Hulp2       : STRING;
        P,P2        : BYTE;

    BEGIN
         { bring up the search window and allow for input }
         Yb:=((Video.Rows-2) DIV 2)-(Yl DIV 2);
         WindowPush (Xb,Yb,Xl,Yl);
         BoxDraw (Double,Xb,Yb,Xl,Yl);

         WriteXY (Xb+2,Yb+1,'Search current topic');
         WriteXY (Xb+2,Yb+3,'Enter the case-insensitive search text below and press Enter');

         SearchStr:=Spaces (60);

         FieldInit;
         FieldAutoDefineOne (Xb+2,Yb+4,@SearchStr,RepChar (60,'$'));
         FieldSetHelp (0,0{##add help});
         FieldEditDirect;

         SearchStr:=LoCaseString (DeleteBackSpaces (SearchStr));

         WindowPop;

         WITH TopicPtr^ DO
         BEGIN
              { browse through the entire text, modifying where required }
              { increasing or decreasing the length of an item means     }
              { re-allocating the record.                                }

              LinePtr:=FirstLinePtr;
              WHILE (LinePtr <> NIL) DO
              BEGIN
                   Hulp:=LinePtr^.LinePtr^;

                   { remove old search results }
                   REPEAT
                         P:=Pos (#255+ccSearchResult,Hulp);
                         IF (P > 0) THEN
                         BEGIN
                              Hulp2:=Copy (Hulp,1,P-1);
                              Delete (Hulp,1,P+1);

                              { next color change must be end of search }
                              P:=Pos (#255+ccNormal,Hulp);
                              IF (P > 0) THEN
                              BEGIN
                                   Delete (Hulp,P,2);
                                   Hulp:=Hulp2+Hulp;
                              END;
                              {else: big mess up}
                         END;
                   UNTIL (P = 0);

                   { check for the new search string }
                   IF (SearchStr <> '') THEN
                   BEGIN
                        Hulp2:='';
                        REPEAT
                              P:=Pos (SearchStr,LoCaseString (Hulp));

                              IF (P > 0) THEN
                              BEGIN
                                   Hulp2:=Hulp2+Copy (Hulp,1,P-1)+#255+ccSearchResult;
                                   Delete (Hulp,1,P-1);
                                   Hulp2:=Hulp2+Copy (Hulp,1,Length (SearchStr))+#255+ccNormal;
                                   Delete (Hulp,1,Length (SearchStr));
                              END;
                        UNTIL (P = 0);

                        Hulp:=Hulp2+Hulp;
                   END;

                   IF (Length (Hulp) <> Length (LinePtr^.LinePtr^)) THEN
                   BEGIN
                        { reallocate }
                        FreeMem (LinePtr^.LinePtr,Length (LinePtr^.LinePtr^)+1);
                        GetMem (LinePtr^.LinePtr,Length (Hulp)+1);
                   END;

                   LinePtr^.LinePtr^:=Hulp;

                   LinePtr:=LinePtr^.NextLinePtr;
              END; { with }

              { force a redraw }
              PrevTopY:=65535;
         END; { with }
    END;

    {----------------------------------------------------------------------}
    { GlobalSearch                                                         }
    {                                                                      }
    { This routine brings up the input prompt for a global search,         }
    { searches all the help topics for the given text and shows a results  }
    { page with links to all the super- and sub-topics containing that     }
    { text.                                                                }
    {                                                                      }
    PROCEDURE GlobalSearch (VAR TopicPtr : TopicRecordPtr);

    CONST Xl = 65;
          Yl = 6;
          Xb = (80-Xl) DIV 2;

    VAR Yb          : XYType;
        SearchStr   : STRING[60];
        LoSearchStr : STRING[60];
        NewTopicPtr : TopicRecordPtr;
        Index       : HelpIndexRecord;
        IndexPos    : LONGINT;
        Title       : STRING[80];
        Hulp        : STRING;
        BytesRead   : WordLong;
        Found       : BOOLEAN;
        P           : BYTE;

    BEGIN
         { bring up the search window and allow for input }
         Yb:=((Video.Rows-2) DIV 2)-(Yl DIV 2);
         WindowPush (Xb,Yb,Xl,Yl);
         BoxDraw (Double,Xb,Yb,Xl,Yl);

         WriteXY (Xb+2,Yb+1,'Global topic search');
         WriteXY (Xb+2,Yb+3,'Enter the case-insensitive search text below and press Enter');

         SearchStr:=Spaces (60);

         FieldPushAll;

         FieldInit;
         FieldAutoDefineOne (Xb+2,Yb+4,@SearchStr,RepChar (60,'$'));
         FieldSetHelp (0,0{##add help});
         FieldEditDirect;

         FieldPopAll;

         WindowPop;

         SearchStr:=DeleteBackSpaces (SearchStr);
         IF (SearchStr = '') THEN
            Exit;

         { start a new topic }
         IF (_MaxAvail < MIN_HELP_MEMAVAIL) THEN
         BEGIN
              Error ('Not enough free memory to perform global search');
              Exit;
         END;

         GetMem (NewTopicPtr,SizeOf (TopicRecord));

         WITH NewTopicPtr^ DO
         BEGIN
              PrevTopicPtr:=TopicPtr;

              HelpTopicNr:=$FFFFFFFF;
              FirstLinePtr:=NIL;
              LastLinePtr:=NIL;
              LinesCount:=0;

              MarkText:='';

              Xb:=5;
              Yb:=4;
              Xl:=Video.Cols-9;
              Yl:=Video.Rows-6;
         END; { with }

         { add a header }
         AddLineToTopic (NewTopicPtr,#255+ccTitle+'Global search results'+#255+ccNormal);
         AddLineToTopic (NewTopicPtr,'');
         AddLineToTopic (NewTopicPtr,'Search text was "'+SearchStr+'"');
         AddLineToTopic (NewTopicPtr,'');

         { search all topics for the search text }
         LoSearchStr:=LoCaseString (SearchStr);

         { show an activity window }
         Message ('Searching, please wait...');

         IndexPos:=24;
         REPEAT
               Seek (HlpFile,IndexPos);
               BlockRead (HlpFile,Index,SizeOf (HelpIndexRecord));
               Inc (IndexPos,SizeOf (HelpIndexRecord));

               IF (Index.TopicNr <> 0) THEN
               BEGIN
                    { read and search the topic text }
                    { the topic is a stream of characters, terminated with a #0 }
                    { the title starts with #255#2 and stops at the next #255   }
                    Title:='';
                    Found:=FALSE;

                    REPEAT
                          {## speed up reading to 1K buffer and copying from there }
                          Seek (HlpFile,Index.Offset);
                          BlockRead (HlpFile,Hulp[1],255,BytesRead);
                          Hulp[0]:=Char (BytesRead);

                          P:=Pos (#0,Hulp);
                          IF (P > 0) THEN
                             Hulp:=Copy (Hulp,1,P-1);

                          { avoid finding text in or after the next title! }
                          { and believing it belongs to this title.        }
                          P:=Pos (#255+ccTitle,Copy (Hulp,2,255));
                          IF (P > 0) THEN
                             Hulp:=Copy (Hulp,1,P);

                          IF (Copy (Hulp,1,2) = #255+ccTitle) THEN
                          BEGIN
                               { end of sub-topic? }
                               IF (Title <> '') THEN
                                  Break; { from the repeat }

                               { title case }
                               P:=Pos (#255,Copy (Hulp,3,255));
                               IF (P = 0) THEN
                                  P:=Length (Hulp);

                               Title:=Copy (Hulp,3,P-1);
                          END;

                          Hulp:=LoCaseString (Hulp);
                          IF (Pos (LoSearchStr,Hulp) > 0) THEN
                             Found:=TRUE;

                          P:=Pos (#255,Copy (Hulp,2,255));
                          IF (P > 0) THEN
                             Inc (Index.Offset,P) { reads from #255 and on next time }
                          ELSE
                              Inc (Index.Offset,Length (Hulp));

                    UNTIL (Hulp = '');

                    IF Found THEN
                    BEGIN
                         { add this topic to the results list }
                         IF (Title = '') THEN
                            Title:='No title (topic $'+Long2HexString (Index.TopicNr)+')';

                         { create a link }
                         Hulp:=#255+ccLink+Long2HexString (Index.TopicNr)+Title+#255+ccNormal;
                         AddLineToTopic (NewTopicPtr,Hulp);
                    END;
               END;
         UNTIL (Index.TopicNr = 0);

         { remove the message window }
         WindowPop;

         { draw the new window }
         WITH NewTopicPtr^ DO
         BEGIN
              WindowPush (Xb,Yb,Xl,Yl);
              BoxDrawC (cNewHelpNormal,Double,Xb,Yb,Xl,Yl);

              MarkText:=LoSearchStr;

              { cursor coordinates are offset inside window }
              CursorX:=0;
              CursorY:=0;

              PrevTopY:=0;
              TopY:=1;
         END; { with }

         { fill the window with some contents }
         UpdateHelpTopicWindow (NewTopicPtr);

         TopicPtr:=NewTopicPtr;
    END;

VAR IORes       : BYTE;
    Quit        : BOOLEAN;

    OldColor    : ColorSet;
    OldCursorOn : BOOLEAN;
    OldCursorX,
    OldCursorY  : XYType;

    TopicPtr    : TopicRecordPtr;

BEGIN
     { helpfile openen }
     IF (HelpFilename = '') THEN
     BEGIN
          Error ('Helpfile not assigned!');
          Exit;         { ## EXIT ## }
     END;

     Assign (HlpFile,HelpFilename);
     {$I-} Reset (HlpFile,1); {$I+} IORes:=IOResult;
     IF (IORes <> 0) THEN
     BEGIN
          Error ('ERROR: Cannot find helpfile '+HelpFilename);
          Exit;         { ## EXIT ## }
     END;

     { check the helpfile header }
     IF (NOT CheckHeader) THEN
     BEGIN
          Error ('ERROR: Invalid helpfile structure in '+HelpFilename);
          Close (HlpFile);
          Exit; { ## EXIT ## }
     END;

     IF (NOT CheckCRC) THEN
     BEGIN
          Error2Lines ('The help file is not compatible with this program',
                       '('+HelpFilename+')');
          Close (HlpFile);
          Exit;
     END;

     { show the on-line help keys line }
     PushKeysLine;
     WriteKeysLine (OnlineHelpKeysLine);

     { save the current drawing color selection and cursor position + state }
     OldColor:=GetColor;
     OldCursorOn:=CursorIsOn;
     OldCursorX:=CursorX;
     OldCursorY:=CursorY;
     CursorOff;

     TopicPtr:=NIL;
     LoadHelpTopic (TopicPtr,HelpHandle);
     Quit:=(TopicPtr = NIL);

     WHILE (NOT Quit) DO
     BEGIN
          UpdateHelpTopicWindow (TopicPtr);

          WITH TopicPtr^ DO
               CursorGotoXY (Xb+CursorX+1,Yb+CursorY+1);

          {WriteXYC (40,1,cHeaders,Longint2String (MemAvail)+' ');}
          CursorOn;
          ReadKey;
          CursorOff;

          CASE Key OF
               kEsc :
                   BEGIN
                        FreeHelpTopic (TopicPtr);
                        IF (TopicPtr = NIL) THEN
                           Quit:=TRUE; { nothing more to show }
                   END;

               kTab :
                   PutCursorOnNextLink (TopicPtr);

               kShTab :
                   PutCursorOnPrevLink (TopicPtr);

               kF1 :
                   IF (TopicPtr^.HelpTopicNr <> HELPHANDLE_HELPONHELP) THEN
                      LoadHelpTopic (TopicPtr,HELPHANDLE_HELPONHELP);

               kDown :
                   CursorDown (TopicPtr);

               kUp :
                   CursorUp (TopicPtr);

               kRight :
                   WITH TopicPtr^ DO
                        IF (CursorX < Xb+Xl-5) THEN
                           Inc (CursorX);

               kLeft :
                   IF (TopicPtr^.CursorX > 0) THEN
                      Dec (TopicPtr^.CursorX);

               kPgDn :
                   WITH TopicPtr^ DO
                        IF (LinesCount > Yl-3) THEN
                           IF (TopY+(Yl-3) < LinesCount-(Yl-3)) THEN
                              Inc (TopY,Yl-3)
                           ELSE
                               IF (TopY <> LinesCount-(Yl-3)) THEN
                                  TopY:=LinesCount-(Yl-3)
                               ELSE
                                   CursorY:=Yl-3;

               kPgUp :
                   WITH TopicPtr^ DO
                        IF (LinesCount > (Yl-2)) THEN
                           IF (TopY > Yl-2) THEN
                              Dec (TopY,Yl-2)
                           ELSE
                               IF (TopY > 1) THEN
                                  TopY:=1
                               ELSE
                                   CursorY:=0;

               kHome :
                   TopicPtr^.CursorX:=0;

               kEnd :
                   TopicPtr^.CursorX:=Length (GetLineCursorIsOn (TopicPtr));

               kCtrlPgUp:
                   WITH TopicPtr^ DO
                   BEGIN
                        TopY:=0;
                        CursorX:=0;
                        CursorY:=0;
                   END;

               kCtrlPgDn:
                   WITH TopicPtr^ DO
                   BEGIN
                        CursorX:=0;
                        TopY:=LinesCount-(Yl-3);
                        CursorY:=Yl-3;
                   END;

               kCtrlLeft:
                   GotoPreviousWord (TopicPtr);

               kCtrlRight:
                   GotoNextWord (TopicPtr);

               kRet :
                   IF (TopicPtr^.LinkShown > 0) THEN
                   BEGIN
                        { get reference }
                        { follow link / bring up pop-up window }
                        LoadHelpTopic (TopicPtr,TopicPtr^.LinksNr[TopicPtr^.LinkShown]);
                   END;

               kF2 :
                   GlobalSearch (TopicPtr);

               ELSE
                   IF (KbdAsciiCode = 6{Ctrl+F}) THEN
                      LocalSearch (TopicPtr);

          END; { case }
     END; { while }

     { restore the keys line }
     PopKeysLine;

     { restore the old cursor position and drawing color }
     SetColor (OldColor);
     CursorGotoXY (OldCursorX,OldCursorY);
     IF OldCursorOn THEN
        CursorOn;

     { close the helpfile }
     Close (HlpFile);
END;


{--------------------------------------------------------------------------}
{ AssignHelpFile                                                           }
{                                                                          }
{ This routine assigns a path to the helpfile. This way it is possible to  }
{ have multiple help files, based on the program that uses this unit.      }
{ When help is requested, this file is opened and the related help is      }
{ looked up.
{                                                                          }
PROCEDURE AssignHelpFile (Filename : STRING; Checksum : LONGINT);
BEGIN
     HelpFilename:=Filename;
     HlpCRC:=Checksum;
END;


{ end of file newhelp.inc }
