home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / PROG_PAS / TVTOYS.ZIP / HELPFILE.PAS < prev    next >
Pascal/Delphi Source File  |  1994-01-03  |  27KB  |  1,093 lines

  1. (***************************************************************************
  2.   Helpfile unit
  3.   Improved help file
  4.   PJB November 7, 1993, CompuServe mail to INTERNET:d91-pbr@nada.kth.se
  5.   Free patches, use at your own risk. All warranties void.
  6.   If even more modified, please state so if you pass this around.
  7.  
  8.   This HelpFile patched to allow back tracking and external access to
  9.   help topics (search for "HelpExtensions"). Also adds a back link for
  10.   Protected mode EXE searching a la resources, but you have to recompile
  11.   TVHC to get the effect.
  12.  
  13.   Some Borland bugs fixed (search for "fix"). Handles empty topics.
  14.   Doesn't spill long topic links any more.
  15.  
  16.   Define RangeFix to fix more Borland bugs (search for "Int->Word fix"):
  17.     Borland sometimes uses integers for help topics even though they are
  18.     words, RangeFix changes them into words so you can compile with $R+.
  19.     If you define RangeFix, you'll get a compile error if you try to
  20.     compile TVHC. In that case, modify TVHC to this (change to RefType):
  21.  
  22.     procedure HandleCrossRefs(var S: TStream; XRefValue: RefType); far; { Int->Word fix }
  23.  
  24.     This is already taken care of if you use the improved TVHC 1.1a,
  25.     written by yours truly and available electronically.
  26.  
  27.   This HelpFile requires an exact topic match. To respond to a range of
  28.   help topics, define several in a row like this:
  29.  
  30.     .topic  First,F2,F3,F4
  31.  
  32.   Added PPalette casts, Config and Prefs. Remembers last selected topic.
  33. ***************************************************************************)
  34. {************************************************}
  35. {                                                }
  36. {   Turbo Vision Demo                            }
  37. {   Copyright (c) 1992 by Borland International  }
  38. {                                                }
  39. {************************************************}
  40.  
  41. unit HelpFile;
  42.  
  43. {$I toyCfg}
  44.  
  45. {$IFDEF DPMI}
  46.  {$B-,X+}
  47. {$ELSE}
  48.  {$B-,F+,O+,X+}
  49. {$ENDIF}
  50.  
  51. {$IFNDEF RangeFix}
  52.  {$R-}              (* Borland bugs require $R- *)
  53. {$ENDIF}
  54.  
  55. interface
  56.  
  57. uses
  58.   Drivers, Objects, Views,
  59.   toyPrefs;
  60.  
  61. const
  62.   CHelpColor      = #$37#$3F#$3A#$13#$13#$30#$3E#$1E;
  63.   CHelpBlackWhite = #$07#$0F#$07#$70#$70#$07#$0F#$70;
  64.   CHelpMonochrome = #$07#$0F#$07#$70#$70#$07#$0F#$70;
  65.   CHelpViewer     = #6#7#8;
  66.   CHelpWindow     = #128#129#130#131#132#133#134#135;
  67.  
  68. type
  69.  
  70.  {$IFDEF RangeFix}
  71.   RefType = Word;                       { Int->Word fix }
  72.  {$ELSE}
  73.   RefType = Integer;
  74.  {$ENDIF}
  75.  
  76. { TParagraph }
  77.  
  78.   PParagraph = ^TParagraph;
  79.   TParagraph = record
  80.     Next: PParagraph;
  81.     Wrap: Boolean;
  82.     Size: Word;
  83.     Text: record end;
  84.   end;
  85.  
  86. { THelpTopic }
  87.  
  88.   TCrossRef = record
  89.     Ref: Word;
  90.     Offset: Integer;
  91.     Length: Byte;
  92.   end;
  93.  
  94.   PCrossRefs = ^TCrossRefs;
  95.   TCrossRefs = array[1..10000] of TCrossRef;
  96.   TCrossRefHandler = procedure (var S: TStream; XRefValue: RefType);  { Int->Word fix }
  97.  
  98.   PHelpTopic = ^THelpTopic;
  99.   THelpTopic = object(TObject)
  100.     constructor Init;
  101.     constructor Load(var S: TStream);
  102.     destructor Done; virtual;
  103.     procedure AddCrossRef(Ref: TCrossRef);
  104.     procedure AddParagraph(P: PParagraph);
  105.     procedure GetCrossRef(I: Integer; var Loc: TPoint; var Length: Byte;
  106.       var Ref: Word);
  107.     function GetLine(Line: Integer): String;
  108.     function GetNumCrossRefs: Integer;
  109.     function NumLines: Integer;
  110.     procedure SetCrossRef(I: Integer; var Ref: TCrossRef);
  111.     procedure SetNumCrossRefs(I: Integer);
  112.     procedure SetWidth(AWidth: Integer);
  113.     procedure Store(var S: TStream);
  114.   private
  115.     Paragraphs: PParagraph;
  116.     NumRefs: Integer;
  117.     CrossRefs: PCrossRefs;
  118.     Width: Integer;
  119.     LastOffset: Integer;
  120.     LastLine: Integer;
  121.     LastParagraph: PParagraph;
  122.     function WrapText(var Text; Size: Integer; var Offset: Integer;
  123.       Wrap: Boolean): String;
  124.   end;
  125.  
  126. { THelpIndex }
  127.  
  128.   PIndexArray = ^TIndexArray;
  129.   TIndexArray = array[0..16380] of LongInt;
  130.  
  131.   PContextArray = ^TContextArray;
  132.   TContextArray = array[0..16380] of Word;
  133.  
  134.   PHelpIndex = ^THelpIndex;
  135.   THelpIndex = object(TObject)
  136.     constructor Init;
  137.     constructor Load(var S: TStream);
  138.     destructor Done; virtual;
  139.     function Position(I: Word): Longint;
  140.     procedure Add(I: Word; Val: Longint);
  141.     procedure Store(var S: TStream);
  142.   private
  143.     Size: Word;
  144.     Used: Word;
  145.     Contexts: PContextArray;
  146.     Index: PIndexArray;
  147.     function Find(I: Word): Word;
  148.   end;
  149.  
  150. { THelpFile }
  151.  
  152.   PHelpFile = ^THelpFile;
  153.   THelpFile = object(TObject)
  154.     Stream: PStream;
  155.     Modified: Boolean;
  156.    {$IFDEF HelpExtensions}
  157.     HelpAlreadyPopped: Boolean;                 (* "First time" indicator *)
  158.    {$ENDIF}
  159.     constructor Init(S: PStream);
  160.     destructor Done; virtual;
  161.     function GetTopic(I: Word): PHelpTopic;
  162.     function InvalidTopic: PHelpTopic;
  163.     procedure RecordPositionInIndex(I: RefType);    { Int->Word fix }
  164.     procedure PutTopic(Topic: PHelpTopic);
  165.   private
  166.     Index: PHelpIndex;
  167.     IndexPos: LongInt;
  168.   end;
  169.  
  170. { THelpViewer }
  171.  
  172.   PHelpViewer = ^THelpViewer;
  173.   THelpViewer = object(TScroller)
  174.     HFile: PHelpFile;
  175.     Topic: PHelpTopic;
  176.     Selected: Integer;
  177.     constructor Init(var Bounds: TRect; AHScrollBar,
  178.       AVScrollBar: PScrollBar; AHelpFile: PHelpFile; Context: Word);
  179.     destructor Done; virtual;
  180.     procedure ChangeBounds(var Bounds: TRect); virtual;
  181.     procedure Draw; virtual;
  182.     function GetPalette: PPalette; virtual;
  183.     procedure HandleEvent(var Event: TEvent); virtual;
  184.   end;
  185.  
  186. { THelpWindow }
  187.  
  188.   PHelpWindow = ^THelpWindow;
  189.   THelpWindow = object(TWindow)
  190.     constructor Init(HFile: PHelpFile; Context: Word);
  191.     function GetPalette: PPalette; virtual;
  192.   end;
  193.  
  194. const
  195.   RHelpTopic: TStreamRec = (
  196.      ObjType: 10000;
  197.      VmtLink: Ofs(TypeOf(THelpTopic)^);
  198.      Load:    @THelpTopic.Load;
  199.      Store:   @THelpTopic.Store
  200.   );
  201.  
  202. const
  203.   RHelpIndex: TStreamRec = (
  204.      ObjType: 10001;
  205.      VmtLink: Ofs(TypeOf(THelpIndex)^);
  206.      Load:    @THelpIndex.Load;
  207.      Store:   @THelpIndex.Store
  208.   );
  209.  
  210. procedure RegisterHelpFile;
  211.  
  212. procedure NotAssigned(var S: TStream; Value: RefType);    { Int->Word fix }
  213.  
  214. const
  215.   CrossRefHandler: TCrossRefHandler = NotAssigned;
  216.  
  217. implementation
  218.  
  219. { THelpTopic }
  220.  
  221. constructor THelpTopic.Init;
  222. begin
  223.   inherited Init;
  224.   LastLine := MaxInt;
  225. end;
  226.  
  227. constructor THelpTopic.Load(var S: TStream);
  228.  
  229. procedure ReadParagraphs;
  230. var
  231.   I, Size: Integer;
  232.   PP: ^PParagraph;
  233. begin
  234.   S.Read(I, SizeOf(I));
  235.   PP := @Paragraphs;
  236.   while I > 0 do
  237.   begin
  238.     S.Read(Size, SizeOf(Size));
  239.     GetMem(PP^, SizeOf(PP^^) + Size);
  240.     PP^^.Size := Size;
  241.     S.Read(PP^^.Wrap, SizeOf(Boolean));
  242.     S.Read(PP^^.Text, Size);
  243.     PP := @PP^^.Next;
  244.     Dec(I);
  245.   end;
  246.   PP^ := nil;
  247. end;
  248.  
  249. procedure ReadCrossRefs;
  250. begin
  251.   S.Read(NumRefs, SizeOf(Integer));
  252.   GetMem(CrossRefs, SizeOf(TCrossRef) * NumRefs);
  253.   if CrossRefs <> nil then
  254.     S.Read(CrossRefs^, SizeOf(TCrossRef) * NumRefs);
  255. end;
  256.  
  257. begin
  258.   inherited Init;                 { BUG fix, for empty topics }
  259.   ReadParagraphs;
  260.   ReadCrossRefs;
  261.                                   {  Width:=0 handled by init }
  262.   LastLine := MaxInt;
  263. end;
  264.  
  265. destructor THelpTopic.Done;
  266.  
  267. procedure DisposeParagraphs;
  268. var
  269.   P, T: PParagraph;
  270. begin
  271.   P := Paragraphs;
  272.   while P <> nil do
  273.   begin
  274.     T := P;
  275.     P := P^.Next;
  276.     FreeMem(T, SizeOf(T^) + T^.Size);
  277.   end;
  278. end;
  279.  
  280. begin
  281.   DisposeParagraphs;
  282.   FreeMem(CrossRefs, SizeOf(TCrossRef) * NumRefs);
  283.   inherited Done
  284. end;
  285.  
  286. procedure THelpTopic.AddCrossRef(Ref: TCrossRef);
  287. var
  288.   P: PCrossRefs;
  289. begin
  290.   GetMem(P, (NumRefs+1) * SizeOf(TCrossRef));
  291.   if NumRefs > 0 then
  292.   begin
  293.     Move(CrossRefs^, P^, NumRefs * SizeOf(TCrossRef));
  294.     FreeMem(CrossRefs, NumRefs * SizeOf(TCrossRef));
  295.   end;
  296.   CrossRefs := P;
  297.   CrossRefs^[NumRefs] := Ref;
  298.   Inc(NumRefs);
  299. end;
  300.  
  301. procedure THelpTopic.AddParagraph(P: PParagraph);
  302. var
  303.   PP: ^PParagraph;
  304. begin
  305.   PP := @Paragraphs;
  306.   while PP^ <> nil do
  307.     PP := @PP^^.Next;
  308.   PP^ := P;
  309.   P^.Next := nil;
  310. end;
  311.  
  312. procedure THelpTopic.GetCrossRef(I: Integer; var Loc: TPoint;
  313.   var Length: Byte; var Ref: Word);
  314. var
  315.   OldOffset, CurOffset, Offset, ParaOffset: Integer;
  316.   P: PParagraph;
  317.   Line: Integer;
  318. begin
  319.   ParaOffset := 0;
  320.   CurOffset := 0;
  321.   OldOffset := 0;
  322.   Line := 0;
  323.   Offset := CrossRefs^[I].Offset;
  324.   P := Paragraphs;
  325.   while ParaOffset+CurOffset < Offset do
  326.   begin
  327.     OldOffset := ParaOffset + CurOffset;
  328.     WrapText(P^.Text, P^.Size, CurOffset, P^.Wrap);
  329.     Inc(Line);
  330.     if CurOffset >= P^.Size then
  331.     begin
  332.       Inc(ParaOffset, P^.Size);
  333.       P := P^.Next;
  334.       CurOffset := 0;
  335.     end;
  336.   end;
  337.   Loc.X := Offset - OldOffset - 1;
  338.   Loc.Y := Line;
  339.   Length := CrossRefs^[I].Length;
  340.   Ref := CrossRefs^[I].Ref;
  341. end;
  342.  
  343. function THelpTopic.GetLine(Line: Integer): String;
  344. var
  345.   Offset, I: Integer;
  346.   P: PParagraph;
  347. begin
  348.   if LastLine < Line then
  349.   begin
  350.     I := Line;
  351.     Dec(Line, LastLine);
  352.     LastLine := I;
  353.     Offset := LastOffset;
  354.     P := LastParagraph;
  355.   end
  356.   else
  357.   begin
  358.     P := Paragraphs;
  359.     Offset := 0;
  360.     LastLine := Line;
  361.   end;
  362.   GetLine := '';
  363.   while (P <> nil) do
  364.   begin
  365.     while Offset < P^.Size do
  366.     begin
  367.       Dec(Line);
  368.       GetLine := WrapText(P^.Text, P^.Size, Offset, P^.Wrap);
  369.       if Line = 0 then
  370.       begin
  371.         LastOffset := Offset;
  372.         LastParagraph := P;
  373.         Exit;
  374.       end;
  375.     end;
  376.     P := P^.Next;
  377.     Offset := 0;
  378.   end;
  379.   GetLine := '';
  380. end;
  381.  
  382. function THelpTopic.GetNumCrossRefs: Integer;
  383. begin
  384.   GetNumCrossRefs := NumRefs;
  385. end;
  386.  
  387. function THelpTopic.NumLines: Integer;
  388. var
  389.   Offset, Lines: Integer;
  390.   P: PParagraph;
  391. begin
  392.   Offset := 0;
  393.   Lines := 0;
  394.   P := Paragraphs;
  395.   while P <> nil do
  396.   begin
  397.     Offset := 0;
  398.     while Offset < P^.Size do
  399.     begin
  400.       Inc(Lines);
  401.       WrapText(P^.Text, P^.Size, Offset, P^.Wrap);
  402.     end;
  403.     P := P^.Next;
  404.   end;
  405.   NumLines := Lines;
  406. end;
  407.  
  408. procedure THelpTopic.SetCrossRef(I: Integer; var Ref: TCrossRef);
  409. begin
  410.   if I <= NumRefs then CrossRefs^[I] := Ref;
  411. end;
  412.  
  413. procedure THelpTopic.SetNumCrossRefs(I: Integer);
  414. var
  415.   P: PCrossRefs;
  416. begin
  417.   if NumRefs = I then Exit;
  418.   GetMem(P, I * SizeOf(TCrossRef));
  419.   if NumRefs > 0 then
  420.   begin
  421.     if I > NumRefs then Move(CrossRefs^, P^, NumRefs * SizeOf(TCrossRef))
  422.     else Move(CrossRefs^, P^, I * SizeOf(TCrossRef));
  423.     FreeMem(CrossRefs, NumRefs * SizeOf(TCrossRef));
  424.   end;
  425.   CrossRefs := P;
  426.   NumRefs := I;
  427. end;
  428.  
  429. procedure THelpTopic.SetWidth(AWidth: Integer);
  430. begin
  431.   Width := AWidth;
  432. end;
  433.  
  434. procedure THelpTopic.Store(var S: TStream);
  435.  
  436. procedure WriteParagraphs;
  437. var
  438.   I: Integer;
  439.   P: PParagraph;
  440. begin
  441.   P := Paragraphs;
  442.   I := 0;
  443.   while P <> nil do
  444.   begin
  445.     Inc(I);
  446.     P := P^.Next;
  447.   end;
  448.   S.Write(I, SizeOf(I));
  449.   P := Paragraphs;
  450.   while P <> nil do
  451.   begin
  452.     S.Write(P^.Size, SizeOf(Integer));
  453.     S.Write(P^.Wrap, SizeOf(Boolean));
  454.     S.Write(P^.Text, P^.Size);
  455.     P := P^.Next;
  456.   end;
  457. end;
  458.  
  459. procedure WriteCrossRefs;
  460. var
  461.   I: Integer;
  462. begin
  463.   S.Write(NumRefs, SizeOf(Integer));
  464.   if @CrossRefHandler = @NotAssigned then
  465.     S.Write(CrossRefs^, SizeOf(TCrossRef) * NumRefs)
  466.   else
  467.     for I := 1 to NumRefs do
  468.     begin
  469.       CrossRefHandler(S, CrossRefs^[I].Ref);
  470.       S.Write(CrossRefs^[I].Offset, SizeOf(Integer) + SizeOf(Byte));
  471.     end;
  472. end;
  473.  
  474. begin
  475.   WriteParagraphs;
  476.   WriteCrossRefs;
  477. end;
  478.  
  479. function THelpTopic.WrapText(var Text; Size: Integer;
  480.   var Offset: Integer; Wrap: Boolean): String;
  481. type
  482.   PCArray = ^CArray;
  483.   CArray = array[0..32767] of Char;
  484. var
  485.   Line: String;
  486.   I, P: Integer;
  487.  
  488. function IsBlank(Ch: Char): Boolean;
  489. begin
  490.   IsBlank := (Ch = ' ') or (Ch = #13) or (Ch = #10);
  491. end;
  492.  
  493. function Scan(var P; Offset, Size: Integer; C: Char): Integer; assembler;
  494. asm
  495.     CLD
  496.     LES    DI,P
  497.         ADD    DI,&Offset
  498.         MOV    DX,Size
  499.         SUB    DX,&Offset
  500.         OR    DH,DH
  501.         JZ    @@1
  502.         MOV    DX,256
  503. @@1:    MOV    CX,DX
  504.     MOV    AL, C
  505.         REPNE    SCASB
  506.     SUB    CX,DX
  507.         NEG    CX
  508.         XCHG    AX,CX
  509. end;
  510.  
  511. procedure TextToLine(var Text; Offset, Length: Integer; var Line: String);
  512.   assembler;
  513. asm
  514.     CLD
  515.     PUSH    DS
  516.     LDS    SI,Text
  517.         ADD    SI,&Offset
  518.         LES     DI,Line
  519.         MOV    AX,Length
  520.         STOSB
  521.         XCHG    AX,CX
  522.         REP    MOVSB
  523.         POP    DS
  524. end;
  525.  
  526. begin
  527.   I := Scan(Text, Offset, Size, #13);
  528.   if (I >= Width) and Wrap then
  529.   begin
  530.     I := Offset + Width;
  531.     if I > Size then I := Size
  532.     else
  533.     begin
  534.       while (I > Offset) and not IsBlank(PCArray(@Text)^[I]) do Dec(I);
  535.       if I = Offset then
  536.       begin
  537.         I := Offset + Width;
  538.  
  539.         (*******************************************************************
  540.           This is a bug fix to avoid wrapping long topic links
  541.           Note that this changes the default behaviour, words longer than
  542.           the current help window width no longer spill over to the next
  543.           line.
  544.         *******************************************************************)
  545.         while (I<Size) and not IsBlank(PCArray(@Text)^[I]) do
  546.           Inc(I);
  547.         if I<Size then          
  548.           Inc(I);                           (* Skip that blank *)
  549.       end
  550.       else Inc(I);
  551.     end;
  552.     if I = Offset then I := Offset + Width;
  553.     Dec(I, Offset);
  554.   end;
  555.   TextToLine(Text, Offset, I, Line);
  556.   if Line[Length(Line)] = #13 then Dec(Line[0]);
  557.   Inc(Offset, I);
  558.   WrapText := Line;
  559. end;
  560.  
  561. { THelpIndex }
  562.  
  563. constructor THelpIndex.Init;
  564. begin
  565.   inherited Init;
  566.   Size := 0;
  567.   Contexts := nil;
  568.   Index := nil;
  569. end;
  570.  
  571. constructor THelpIndex.Load(var S: TStream);
  572. begin
  573.   S.Read(Used, SizeOf(Used));
  574.   S.Read(Size, SizeOf(Size));
  575.   if Size = 0 then
  576.   begin
  577.     Contexts := nil;
  578.     Index := nil;
  579.   end
  580.   else
  581.   begin
  582.     GetMem(Contexts, SizeOf(Contexts^[0]) * Size);
  583.     S.Read(Contexts^, SizeOf(Contexts^[0]) * Size);
  584.     GetMem(Index, SizeOf(Index^[0]) * Size);
  585.     S.Read(Index^, SizeOf(Index^[0]) * Size);
  586.   end;
  587. end;
  588.  
  589. destructor THelpIndex.Done;
  590. begin
  591.   FreeMem(Index, SizeOf(Index^[0]) * Size);
  592.   FreeMem(Contexts, SizeOf(Contexts^[0]) * Size);
  593.   inherited Done;
  594. end;
  595.  
  596. function THelpIndex.Find(I: Word): Word;
  597. var
  598.   Hi, Lo, Pos: Integer;
  599. begin
  600.   Lo := 0;
  601.   if Used > 0 then
  602.   begin
  603.     Hi := Used - 1;
  604.     while Lo <= Hi do
  605.     begin
  606.       Pos := (Lo + Hi) div 2;
  607.       if I > Contexts^[Pos] then
  608.         Lo := Pos + 1
  609.       else
  610.       begin
  611.         Hi := Pos - 1;
  612.         if I = Contexts^[Pos] then
  613.           Lo := Pos;
  614.       end;
  615.     end;
  616.   end;
  617.   Find := Lo;
  618. end;
  619.  
  620. function THelpIndex.Position(I: Word): Longint;
  621. var
  622.   f : Word;
  623. begin
  624.   f := Find(I);
  625.  
  626.   if Contexts^[f] <> I then                { Fix: Match topic exactly }
  627.     Position := 0
  628.   else
  629.     Position := Index^[f];
  630. end;
  631.  
  632. procedure THelpIndex.Add(I: Word; Val: Longint);
  633. const
  634.   Delta = 10;
  635. var
  636.   P: PIndexArray;
  637.   NewSize: Integer;
  638.   Pos: Integer;
  639.  
  640.   function Grow(P: Pointer; OldSize, NewSize, ElemSize: Integer): Pointer;
  641.   var
  642.     NewP: PByteArray;
  643.   begin
  644.     GetMem(NewP, NewSize * ElemSize);
  645.     if NewP <> nil then
  646.     begin
  647.       if P <> nil then
  648.         Move(P^, NewP^, OldSize * ElemSize);
  649.       FillChar(NewP^[OldSize * ElemSize], (NewSize - Size) * ElemSize, $FF);
  650.     end;
  651.     if OldSize > 0 then FreeMem(P, OldSize * ElemSize);
  652.     Grow := NewP;
  653.   end;
  654.  
  655. begin
  656.   Pos := Find(I);
  657.   if (Contexts = nil) or (Contexts^[Pos] <> I) then
  658.   begin
  659.     Inc(Used);
  660.     if Used >= Size then
  661.     begin
  662.       NewSize := (Used + Delta) div Delta * Delta;
  663.       Contexts := Grow(Contexts, Size, NewSize, SizeOf(Contexts^[0]));
  664.       Index := Grow(Index, Size, NewSize, SizeOf(Index^[0]));
  665.       Size := NewSize;
  666.     end;
  667.     if Pos < Used then
  668.     begin
  669.       Move(Contexts^[Pos], Contexts^[Pos + 1], (Used - Pos - 1) *
  670.         SizeOf(Contexts^[0]));
  671.       Move(Index^[Pos], Index^[Pos + 1], (Used - Pos - 1) *
  672.         SizeOf(Index^[0]));
  673.     end;
  674.   end;
  675.   Contexts^[Pos] := I;
  676.   Index^[Pos] := Val;
  677. end;
  678.  
  679. procedure THelpIndex.Store(var S: TStream);
  680. begin
  681.   S.Write(Used, SizeOf(Used));
  682.   S.Write(Size, SizeOf(Size));
  683.   S.Write(Contexts^, SizeOf(Contexts^[0]) * Size);
  684.   S.Write(Index^, SizeOf(Index^[0]) * Size);
  685. end;
  686.  
  687. { THelpFile }
  688.  
  689. const
  690.   MagicHeader = $46484246; {'FBHF'}
  691.  
  692. constructor THelpFile.Init(S: PStream);
  693. var
  694.   Magic: Longint;
  695. begin
  696.   Magic := 0;
  697.   S^.Seek(0);
  698.   if S^.GetSize > SizeOf(Magic) then
  699.     S^.Read(Magic, SizeOf(Magic));
  700.   if Magic <> MagicHeader then
  701.   begin
  702.     IndexPos := 12;
  703.     S^.Seek(IndexPos);
  704.     Index := New(PHelpIndex, Init);
  705.     Modified := True;
  706.   end
  707.   else
  708.   begin
  709.     S^.Seek(8);
  710.     S^.Read(IndexPos, SizeOf(IndexPos));
  711.     S^.Seek(IndexPos);
  712.     Index := PHelpIndex(S^.Get);
  713.     Modified := False;
  714.   end;
  715.   Stream := S;
  716. end;
  717.  
  718. destructor THelpFile.Done;
  719. {$IFDEF HelpExtensions}
  720. const
  721.   RStreamBackLink: Longint = $4C424246; { 'FBBL' }
  722. {$ENDIF}
  723. var
  724.   Magic, Size: Longint;
  725. begin
  726.   if Modified then
  727.   begin
  728.     Stream^.Seek(IndexPos);
  729.     Stream^.Put(Index);
  730.    {$IFDEF HelpExtensions}
  731.     (* Add a back link for DPMI EXEs, used by ExeStream *)
  732.     Size := Stream^.GetSize + SizeOf(Longint)*2;
  733.     Stream^.Write(RStreamBackLink, SizeOf(Longint));
  734.     Stream^.Write(Size, SizeOf(Size));
  735.    {$ENDIF}
  736.     Stream^.Seek(0);
  737.     Magic := MagicHeader;
  738.     Size := Stream^.GetSize - 8;
  739.     Stream^.Write(Magic, SizeOf(Magic));
  740.     Stream^.Write(Size, SizeOf(Size));
  741.     Stream^.Write(IndexPos, SizeOf(IndexPos));
  742.   end;
  743.   Dispose(Stream, Done);
  744.   Dispose(Index, Done);
  745. end;
  746.  
  747.  {$IFDEF HelpExtensions}
  748.   var
  749.     OldTopics   : array [0..MaxOldTopics] of Word;
  750.     OldSelected : array [0..MaxOldTopics] of Integer;
  751.     OldFront    : Integer;
  752.     OldCount    : Integer;
  753.  {$ENDIF}
  754.  
  755. function THelpFile.GetTopic(I: Word): PHelpTopic;
  756.   var
  757.     Pos: Longint;
  758. begin
  759.  {$IFDEF HelpExtensions}
  760.   if I=PreviousTopic then   (* Show previous help *)
  761.     if OldCount=0 then
  762.       (* No previous topics *)
  763.       I:=0
  764.     else
  765.     begin
  766.       if HelpAlreadyPopped then
  767.       begin
  768.         (* Skip current (saved) topic *)
  769.         Dec(OldFront);
  770.         if OldFront<0 then
  771.           OldFront:=MaxOldTopics;
  772.         Dec(OldCount);
  773.       end;
  774.  
  775.       if HelpAlreadyPopped and (OldCount=0) then
  776.         (* No previous topic *)
  777.         I:=0
  778.       else
  779.         (* Get previous topic *)
  780.         I:=OldTopics[OldFront];
  781.     end
  782.   else
  783.     (* Don't save duplicate entries *)
  784.     if (OldCount=0) or (I<>OldTopics[OldFront]) then
  785.     begin
  786.       (* Save new topic in OldTopics stack *)
  787.       Inc(OldFront);
  788.       if OldFront>MaxOldTopics then
  789.         OldFront:=0;
  790.       OldTopics[OldFront]:=I;
  791.       if OldCount<MaxOldTopics then
  792.         Inc(OldCount);
  793.     end;
  794.  
  795.   HelpAlreadyPopped:=True;
  796.  {$ENDIF}
  797.  
  798.   Pos := Index^.Position(I);
  799.   if Pos > 0 then
  800.   begin
  801.     Stream^.Seek(Pos);
  802.     GetTopic := PHelpTopic(Stream^.Get);
  803.   end
  804.   else
  805.     GetTopic := InvalidTopic;
  806. end;
  807.  
  808. function THelpFile.InvalidTopic: PHelpTopic;
  809. var
  810.   Topic: PHelpTopic;
  811.   Para: PParagraph;
  812. const
  813.   InvalidStr = #13' No help available in this context.';
  814.   InvalidText: array[1..Length(InvalidStr)] of Char = InvalidStr;
  815. begin
  816.   Topic := New(PHelpTopic, Init);
  817.   GetMem(Para, SizeOf(Para^) + SizeOf(InvalidText));
  818.   Para^.Size := SizeOf(InvalidText);
  819.   Para^.Wrap := False;
  820.   Para^.Next := nil;
  821.   Move(InvalidText, Para^.Text, SizeOf(InvalidText));
  822.   Topic^.AddParagraph(Para);
  823.   InvalidTopic := Topic;
  824. end;
  825.  
  826. procedure THelpFile.RecordPositionInIndex(I: RefType);     { Int->Word fix }
  827. begin
  828.   Index^.Add(I, IndexPos);
  829.   Modified := True;
  830. end;
  831.  
  832. procedure THelpFile.PutTopic(Topic: PHelpTopic);
  833. begin
  834.   Stream^.Seek(IndexPos);
  835.   Stream^.Put(Topic);
  836.   IndexPos := Stream^.GetPos;
  837.   Modified := True;
  838. end;
  839.  
  840. { THelpViewer }
  841.  
  842. constructor THelpViewer.Init(var Bounds: TRect; AHScrollBar,
  843.   AVScrollBar: PScrollBar; AHelpFile: PHelpFile; Context: Word);
  844. begin
  845.   inherited Init(Bounds, AHScrollBar, AVScrollBar);
  846.   Options := Options or ofSelectable;
  847.   GrowMode := gfGrowHiX + gfGrowHiY;
  848.   HFile := AHelpFile;
  849.  {$IFDEF HelpExtensions}
  850.   Limit.X:=78;
  851.   Message(@Self, evCommand, cmSwitchToTopic, Pointer(Context));
  852.  {$ELSE}
  853.   Topic := AHelpFile^.GetTopic(Context);
  854.   Topic^.SetWidth(Size.X);
  855.   SetLimit(78, Topic^.NumLines);
  856.   Selected := 1;
  857.  {$ENDIF}
  858. end;
  859.  
  860. destructor THelpViewer.Done;
  861. begin
  862.   inherited Done;
  863.   Dispose(HFile, Done);
  864.   Dispose(Topic, Done);
  865. end;
  866.  
  867. procedure THelpViewer.ChangeBounds(var Bounds: TRect);
  868. begin
  869.   inherited ChangeBounds(Bounds);
  870.   Topic^.SetWidth(Size.X);
  871.   SetLimit(Limit.X, Topic^.NumLines);
  872. end;
  873.  
  874. procedure THelpViewer.Draw;
  875. var
  876.   B: TDrawBuffer;
  877.   Line: String;
  878.   I, J, L: Integer;
  879.   KeyCount: Integer;
  880.   Normal, Keyword, SelKeyword, C: Byte;
  881.   KeyPoint: TPoint;
  882.   KeyLength: Byte;
  883.   KeyRef: Word;
  884. begin
  885.   Normal := GetColor(1);
  886.   Keyword := GetColor(2);
  887.   SelKeyword := GetColor(3);
  888.   KeyCount := 0;
  889.   KeyPoint.X := 0;
  890.   KeyPoint.Y := 0;
  891.   Topic^.SetWidth(Size.X);
  892.   if Topic^.GetNumCrossRefs > 0 then
  893.     repeat
  894.       Inc(KeyCount);
  895.       Topic^.GetCrossRef(KeyCount, KeyPoint, KeyLength, KeyRef);
  896.     until (KeyCount >= Topic^.GetNumCrossRefs) or (KeyPoint.Y > Delta.Y);
  897.   for I := 1 to Size.Y do
  898.   begin
  899.     MoveChar(B, ' ', Normal, Size.X);
  900.     Line := Topic^.GetLine(I + Delta.Y);
  901.     MoveStr(B, Copy(Line, Delta.X+1, Size.X), Normal);
  902.     while I + Delta.Y = KeyPoint.Y do
  903.     begin
  904.       L := KeyLength;
  905.       if KeyPoint.X < Delta.X then
  906.       begin
  907.         Dec(L, Delta.X - KeyPoint.X);
  908.         KeyPoint.X := Delta.X;
  909.       end;
  910.       if KeyCount = Selected then C := SelKeyword
  911.       else C := Keyword;
  912.       for J := 0 to L-1 do
  913.         WordRec(B[KeyPoint.X - Delta.X + J]).Hi := C;
  914.       Inc(KeyCount);
  915.       if KeyCount <= Topic^.GetNumCrossRefs then
  916.         Topic^.GetCrossRef(KeyCount, KeyPoint, KeyLength, KeyRef)
  917.       else KeyPoint.Y := 0;
  918.     end;
  919.     WriteLine(0, I-1, Size.X, 1, B);
  920.   end;
  921. end;
  922.  
  923. function THelpViewer.GetPalette: PPalette;
  924. const
  925.   P: String[Length(CHelpViewer)] = CHelpViewer;
  926. begin
  927.   GetPalette := PPalette(@P);                      { Fix: Added cast }
  928. end;
  929.  
  930. procedure THelpViewer.HandleEvent(var Event: TEvent);
  931. var
  932.   KeyPoint, Mouse: TPoint;
  933.   KeyLength: Byte;
  934.   KeyRef: Word;
  935.   KeyCount: Integer;
  936.  
  937. procedure MakeSelectVisible;
  938. var
  939.   D: TPoint;
  940. begin
  941.   Topic^.GetCrossRef(Selected, KeyPoint, KeyLength, KeyRef);
  942.   D := Delta;
  943.   if KeyPoint.X < D.X then D.X := KeyPoint.X
  944.   else if KeyPoint.X + KeyLength > D.X + Size.X then
  945.     D.X := KeyPoint.X + KeyLength - Size.X + 1;
  946.   if KeyPoint.Y <= D.Y then D.Y := KeyPoint.Y - 1;
  947.   if KeyPoint.Y > D.Y + Size.Y then D.Y := KeyPoint.Y - Size.Y;
  948.   if (D.X <> Delta.X) or (D.Y <> Delta.Y) then ScrollTo(D.X, D.Y);
  949. end;
  950.  
  951. procedure SwitchToTopic(KeyRef: Word);
  952. begin
  953.  {$IFDEF HelpExtensions}
  954.   if HFile^.HelpAlreadyPopped then
  955.     OldSelected[OldCount]:=Selected;
  956.  
  957.   if Owner<>Nil then
  958.     Owner^.Lock;
  959.  {$ENDIF}
  960.  
  961.   if Topic <> nil then
  962.     Dispose(Topic, Done);
  963.   Topic := HFile^.GetTopic(KeyRef);
  964.   Topic^.SetWidth(Size.X);
  965.   ScrollTo(0, 0);
  966.   SetLimit(Limit.X, Topic^.NumLines);
  967.   Selected := 1;
  968.  
  969.  {$IFDEF HelpExtensions}
  970.   if (KeyRef=PreviousTopic) and (OldSelected[OldCount]<>0) then
  971.   begin
  972.     Selected:=OldSelected[OldCount];
  973.     DrawView;
  974.     if Selected<=Topic^.GetNumCrossRefs then
  975.       MakeSelectVisible;
  976.   end
  977.   else
  978.     DrawView;
  979.  
  980.   if Owner<>Nil then
  981.     Owner^.Unlock;
  982.  {$ELSE}
  983.   DrawView;
  984.  {$ENDIF}
  985. end;
  986.  
  987. begin
  988.   inherited HandleEvent(Event);
  989.   case Event.What of
  990.     evKeyDown:
  991.       begin
  992.         case Event.KeyCode of
  993.           kbTab:
  994.             if Topic^.GetNumCrossRefs > 0 then
  995.             begin
  996.               Inc(Selected);
  997.               if Selected > Topic^.GetNumCrossRefs then Selected := 1;
  998.               MakeSelectVisible;
  999.             end;
  1000.           kbShiftTab:
  1001.             if Topic^.GetNumCrossRefs > 0 then
  1002.             begin
  1003.               Dec(Selected);
  1004.               if Selected = 0 then Selected := Topic^.GetNumCrossRefs;
  1005.               MakeSelectVisible;
  1006.             end;
  1007.           kbEnter:
  1008.             if Selected <= Topic^.GetNumCrossRefs then
  1009.             begin
  1010.               Topic^.GetCrossRef(Selected, KeyPoint, KeyLength, KeyRef);
  1011.               SwitchToTopic(KeyRef);
  1012.             end;
  1013.           kbEsc:
  1014.             begin
  1015.               Event.What := evCommand;
  1016.               Event.Command := cmClose;
  1017.               PutEvent(Event);
  1018.             end;
  1019.         else
  1020.           Exit;
  1021.         end;
  1022.         DrawView;
  1023.         ClearEvent(Event);
  1024.       end;
  1025.     evMouseDown:
  1026.       begin
  1027.         MakeLocal(Event.Where, Mouse);
  1028.         Inc(Mouse.X, Delta.X); Inc(Mouse.Y, Delta.Y);
  1029.         KeyCount := 0;
  1030.         repeat
  1031.           Inc(KeyCount);
  1032.           if KeyCount > Topic^.GetNumCrossRefs then Exit;
  1033.           Topic^.GetCrossRef(KeyCount, KeyPoint, KeyLength, KeyRef);
  1034.         until (KeyPoint.Y = Mouse.Y+1) and (Mouse.X >= KeyPoint.X) and
  1035.           (Mouse.X < KeyPoint.X + KeyLength);
  1036.         Selected := KeyCount;
  1037.         DrawView;
  1038.         if Event.Double then SwitchToTopic(KeyRef);
  1039.         ClearEvent(Event);
  1040.       end;
  1041.     evCommand:
  1042.       if (Event.Command = cmClose) and (Owner^.State and sfModal <> 0) then
  1043.       begin
  1044.        {$IFDEF HelpExtensions}
  1045.         OldSelected[OldCount]:=Selected;
  1046.        {$ENDIF}
  1047.         EndModal(cmClose);
  1048.         ClearEvent(Event);
  1049.       end
  1050.       {$IFDEF HelpExtensions}
  1051.       else
  1052.         if Event.Command=cmSwitchToTopic then
  1053.           SwitchToTopic(Event.InfoWord);
  1054.       {$ENDIF}
  1055.   end;
  1056. end;
  1057.  
  1058. { THelpWindow }
  1059.  
  1060. constructor THelpWindow.Init(HFile: PHelpFile; Context: Word);
  1061. var
  1062.   R: TRect;
  1063. begin
  1064.   R.Assign(0, 0, 50, 18);
  1065.   TWindow.Init(R, 'Help', wnNoNumber);
  1066.   Options := Options or ofCentered;
  1067.   R.Grow(-2,-1);
  1068.   Insert(New(PHelpViewer, Init(R,
  1069.     StandardScrollBar(sbHorizontal + sbHandleKeyboard),
  1070.     StandardScrollBar(sbVertical + sbHandleKeyboard), HFile, Context)));
  1071. end;
  1072.  
  1073. function THelpWindow.GetPalette: PPalette;
  1074. const
  1075.   P: String[Length(CHelpWindow)] = CHelpWindow;
  1076. begin
  1077.   GetPalette := PPalette(@P);                           { Fix: Added cast }
  1078. end;
  1079.  
  1080. procedure RegisterHelpFile;
  1081. begin
  1082.   RegisterType(RHelpTopic);
  1083.   RegisterType(RHelpIndex);
  1084. end;
  1085.  
  1086. procedure NotAssigned(var S: TStream; Value: RefType);     { Int->Word fix }
  1087. begin
  1088. end;
  1089.  
  1090. end.
  1091.  
  1092.  
  1093.