home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / tvision / newed / helpfile.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1993-10-31  |  21.1 KB  |  943 lines

  1. { FILE:  helpfile.pas }
  2.  
  3.  
  4.  
  5. Unit HelpFile;
  6.  
  7. {$F+,O+,X+,S-,R-}
  8.  
  9.  
  10.  
  11.   { -------------------------------------------- }
  12.   {                                              }
  13.   { This unit manipulates the helpfile contexts. }
  14.   {                                              }
  15.   { -------------------------------------------- }
  16.  
  17.  
  18.  
  19. INTERFACE
  20.  
  21.  
  22.  
  23. uses Objects,
  24.      Drivers,
  25.      {VmtData,}
  26.      Views;
  27.  
  28.  
  29.  
  30. CONST
  31.  
  32.  
  33.  
  34.   CHelpColor      = #$37#$3F#$3A#$13#$13#$30#$3E#$1E;
  35.   CHelpBlackWhite = #$07#$0F#$07#$70#$70#$07#$0F#$70;
  36.   CHelpMonochrome = #$07#$0F#$07#$70#$70#$07#$0F#$70;
  37.   CHelpViewer     = #6#7#8;
  38.   CHelpWindow     = #128#129#130#131#132#133#134#135;
  39.  
  40.  
  41.  
  42. TYPE
  43.  
  44.  
  45.  
  46. { TParagraph }
  47.  
  48.   PParagraph = ^TParagraph;
  49.   TParagraph = record
  50.     Next: PParagraph;
  51.     Wrap: Boolean;
  52.     Size: Word;
  53.     Text: record end;
  54.   end;
  55.  
  56. { THelpTopic }
  57.  
  58.   TCrossRef = record
  59.     Ref: Word;
  60.     Offset: Integer;
  61.     Length: Byte;
  62.   end;
  63.  
  64.   PCrossRefs = ^TCrossRefs;
  65.   TCrossRefs = array[1..10000] of TCrossRef;
  66.   TCrossRefHandler = procedure (var S: TStream; XRefValue: Integer);
  67.  
  68.   PHelpTopic = ^THelpTopic;
  69.   THelpTopic = object(TObject)
  70.     constructor Init;
  71.     constructor Load(var S: TStream);
  72.     destructor Done; virtual;
  73.     procedure AddCrossRef(Ref: TCrossRef);
  74.     procedure AddParagraph(P: PParagraph);
  75.     procedure GetCrossRef(I: Integer; var Loc: TPoint; var Length: Byte;
  76.       var Ref: Word);
  77.     function GetLine(Line: Integer): String;
  78.     function GetNumCrossRefs: Integer;
  79.     function NumLines: Integer;
  80.     procedure SetCrossRef(I: Integer; var Ref: TCrossRef);
  81.     procedure SetNumCrossRefs(I: Integer);
  82.     procedure SetWidth(AWidth: Integer);
  83.     procedure Store(var S: TStream);
  84.   private
  85.     Paragraphs: PParagraph;
  86.     NumRefs: Integer;
  87.     CrossRefs: PCrossRefs;
  88.     Width: Integer;
  89.     LastOffset: Integer;
  90.     LastLine: Integer;
  91.     LastParagraph: PParagraph;
  92.     function WrapText(var Text; Size: Integer; var Offset: Integer;
  93.       Wrap: Boolean): String;
  94.   end;
  95.  
  96. { THelpIndex }
  97.  
  98.   PIndexArray = ^TIndexArray;
  99.   TIndexArray = array[0..16380] of LongInt;
  100.  
  101.   PContextArray = ^TContextArray;
  102.   TContextArray = array[0..16380] of Word;
  103.  
  104.   PHelpIndex = ^THelpIndex;
  105.   THelpIndex = object(TObject)
  106.     constructor Init;
  107.     constructor Load(var S: TStream);
  108.     destructor Done; virtual;
  109.     function Position(I: Word): Longint;
  110.     procedure Add(I: Word; Val: Longint);
  111.     procedure Store(var S: TStream);
  112.   private
  113.     Size: Word;
  114.     Used: Word;
  115.     Contexts: PContextArray;
  116.     Index: PIndexArray;
  117.     function Find(I: Word): Word;
  118.   end;
  119.  
  120. { THelpFile }
  121.  
  122.   PHelpFile = ^THelpFile;
  123.   THelpFile = object(TObject)
  124.     Stream: PStream;
  125.     Modified: Boolean;
  126.     constructor Init(S: PStream);
  127.     destructor Done; virtual;
  128.     function GetTopic(I: Word): PHelpTopic;
  129.     function InvalidTopic: PHelpTopic;
  130.     procedure RecordPositionInIndex(I: Integer);
  131.     procedure PutTopic(Topic: PHelpTopic);
  132.   private
  133.     Index: PHelpIndex;
  134.     IndexPos: LongInt;
  135.   end;
  136.  
  137. { THelpViewer }
  138.  
  139.   PHelpViewer = ^THelpViewer;
  140.   THelpViewer = object(TScroller)
  141.     HFile: PHelpFile;
  142.     Topic: PHelpTopic;
  143.     Selected: Integer;
  144.     constructor Init(var Bounds: TRect; AHScrollBar,
  145.       AVScrollBar: PScrollBar; AHelpFile: PHelpFile; Context: Word);
  146.     destructor Done; virtual;
  147.     procedure ChangeBounds(var Bounds: TRect); virtual;
  148.     procedure Draw; virtual;
  149.     function GetPalette: PPalette; virtual;
  150.     procedure HandleEvent(var Event: TEvent); virtual;
  151.   end;
  152.  
  153. { THelpWindow }
  154.  
  155.   PHelpWindow = ^THelpWindow;
  156.   THelpWindow = object(TWindow)
  157.     constructor Init(HFile: PHelpFile; Context: Word);
  158.     function GetPalette: PPalette; virtual;
  159.   end;
  160.  
  161.  
  162. const
  163.  
  164.   RHelpIndex: TStreamRec = (
  165.      {ObjType: vmtHelpIndex;}
  166.      ObjType: 10000;
  167.      VmtLink: Ofs(TypeOf(THelpIndex)^);
  168.      Load:    @THelpIndex.Load;
  169.      Store:   @THelpIndex.Store
  170.   );
  171.   RHelpTopic: TStreamRec = (
  172.      {ObjType: vmtHelpTopic;}
  173.      ObjType: 10001;
  174.      VmtLink: Ofs(TypeOf(THelpTopic)^);
  175.      Load:    @THelpTopic.Load;
  176.      Store:   @THelpTopic.Store
  177.   );
  178.  
  179. procedure RegisterHelpFile;
  180.  
  181. procedure NotAssigned(var S: TStream; Value: Integer);
  182.  
  183. const
  184.   CrossRefHandler: TCrossRefHandler = NotAssigned;
  185.  
  186. implementation
  187.  
  188. { THelpTopic }
  189.  
  190. constructor THelpTopic.Init;
  191. begin
  192.   inherited Init;
  193.   LastLine := MaxInt;
  194. end;
  195.  
  196. constructor THelpTopic.Load(var S: TStream);
  197.  
  198. procedure ReadParagraphs;
  199. var
  200.   I, Size: Integer;
  201.   PP: ^PParagraph;
  202. begin
  203.   S.Read(I, SizeOf(I));
  204.   PP := @Paragraphs;
  205.   while I > 0 do
  206.   begin
  207.     S.Read(Size, SizeOf(Size));
  208.     GetMem(PP^, SizeOf(PP^^) + Size);
  209.     PP^^.Size := Size;
  210.     S.Read(PP^^.Wrap, SizeOf(Boolean));
  211.     S.Read(PP^^.Text, Size);
  212.     PP := @PP^^.Next;
  213.     Dec(I);
  214.   end;
  215.   PP^ := nil;
  216. end;
  217.  
  218. procedure ReadCrossRefs;
  219. begin
  220.   S.Read(NumRefs, SizeOf(Integer));
  221.   GetMem(CrossRefs, SizeOf(TCrossRef) * NumRefs);
  222.   if CrossRefs <> nil then
  223.     S.Read(CrossRefs^, SizeOf(TCrossRef) * NumRefs);
  224. end;
  225.  
  226. begin
  227.   ReadParagraphs;
  228.   ReadCrossRefs;
  229.   Width := 0;
  230.   LastLine := MaxInt;
  231. end;
  232.  
  233. destructor THelpTopic.Done;
  234.  
  235. procedure DisposeParagraphs;
  236. var
  237.   P, T: PParagraph;
  238. begin
  239.   P := Paragraphs;
  240.   while P <> nil do
  241.   begin
  242.     T := P;
  243.     P := P^.Next;
  244.     FreeMem(T, SizeOf(T^) + T^.Size);
  245.   end;
  246. end;
  247.  
  248. begin
  249.   DisposeParagraphs;
  250.   FreeMem(CrossRefs, SizeOf(TCrossRef) * NumRefs);
  251.   inherited Done
  252. end;
  253.  
  254. procedure THelpTopic.AddCrossRef(Ref: TCrossRef);
  255. var
  256.   P: PCrossRefs;
  257. begin
  258.   GetMem(P, (NumRefs+1) * SizeOf(TCrossRef));
  259.   if NumRefs > 0 then
  260.   begin
  261.     Move(CrossRefs^, P^, NumRefs * SizeOf(TCrossRef));
  262.     FreeMem(CrossRefs, NumRefs * SizeOf(TCrossRef));
  263.   end;
  264.   CrossRefs := P;
  265.   CrossRefs^[NumRefs] := Ref;
  266.   Inc(NumRefs);
  267. end;
  268.  
  269. procedure THelpTopic.AddParagraph(P: PParagraph);
  270. var
  271.   PP: ^PParagraph;
  272. begin
  273.   PP := @Paragraphs;
  274.   while PP^ <> nil do
  275.     PP := @PP^^.Next;
  276.   PP^ := P;
  277.   P^.Next := nil;
  278. end;
  279.  
  280. procedure THelpTopic.GetCrossRef(I: Integer; var Loc: TPoint;
  281.   var Length: Byte; var Ref: Word);
  282. var
  283.   OldOffset, CurOffset, Offset, ParaOffset: Integer;
  284.   P: PParagraph;
  285.   Line: Integer;
  286. begin
  287.   ParaOffset := 0;
  288.   CurOffset := 0;
  289.   OldOffset := 0;
  290.   Line := 0;
  291.   Offset := CrossRefs^[I].Offset;
  292.   P := Paragraphs;
  293.   while ParaOffset+CurOffset < Offset do
  294.   begin
  295.     OldOffset := ParaOffset + CurOffset;
  296.     WrapText(P^.Text, P^.Size, CurOffset, P^.Wrap);
  297.     Inc(Line);
  298.     if CurOffset >= P^.Size then
  299.     begin
  300.       Inc(ParaOffset, P^.Size);
  301.       P := P^.Next;
  302.       CurOffset := 0;
  303.     end;
  304.   end;
  305.   Loc.X := Offset - OldOffset - 1;
  306.   Loc.Y := Line;
  307.   Length := CrossRefs^[I].Length;
  308.   Ref := CrossRefs^[I].Ref;
  309. end;
  310.  
  311. function THelpTopic.GetLine(Line: Integer): String;
  312. var
  313.   Offset, I: Integer;
  314.   P: PParagraph;
  315. begin
  316.   if LastLine < Line then
  317.   begin
  318.     I := Line;
  319.     Dec(Line, LastLine);
  320.     LastLine := I;
  321.     Offset := LastOffset;
  322.     P := LastParagraph;
  323.   end
  324.   else
  325.   begin
  326.     P := Paragraphs;
  327.     Offset := 0;
  328.     LastLine := Line;
  329.   end;
  330.   GetLine := '';
  331.   while (P <> nil) do
  332.   begin
  333.     while Offset < P^.Size do
  334.     begin
  335.       Dec(Line);
  336.       GetLine := WrapText(P^.Text, P^.Size, Offset, P^.Wrap);
  337.       if Line = 0 then
  338.       begin
  339.         LastOffset := Offset;
  340.         LastParagraph := P;
  341.         Exit;
  342.       end;
  343.     end;
  344.     P := P^.Next;
  345.     Offset := 0;
  346.   end;
  347.   GetLine := '';
  348. end;
  349.  
  350. function THelpTopic.GetNumCrossRefs: Integer;
  351. begin
  352.   GetNumCrossRefs := NumRefs;
  353. end;
  354.  
  355. function THelpTopic.NumLines: Integer;
  356. var
  357.   Offset, Lines: Integer;
  358.   P: PParagraph;
  359. begin
  360.   Offset := 0;
  361.   Lines := 0;
  362.   P := Paragraphs;
  363.   while P <> nil do
  364.   begin
  365.     Offset := 0;
  366.     while Offset < P^.Size do
  367.     begin
  368.       Inc(Lines);
  369.       WrapText(P^.Text, P^.Size, Offset, P^.Wrap);
  370.     end;
  371.     P := P^.Next;
  372.   end;
  373.   NumLines := Lines;
  374. end;
  375.  
  376. procedure THelpTopic.SetCrossRef(I: Integer; var Ref: TCrossRef);
  377. begin
  378.   if I <= NumRefs then CrossRefs^[I] := Ref;
  379. end;
  380.  
  381. procedure THelpTopic.SetNumCrossRefs(I: Integer);
  382. var
  383.   P: PCrossRefs;
  384. begin
  385.   if NumRefs = I then Exit;
  386.   GetMem(P, I * SizeOf(TCrossRef));
  387.   if NumRefs > 0 then
  388.   begin
  389.     if I > NumRefs then Move(CrossRefs^, P^, NumRefs * SizeOf(TCrossRef))
  390.     else Move(CrossRefs^, P^, I * SizeOf(TCrossRef));
  391.     FreeMem(CrossRefs, NumRefs * SizeOf(TCrossRef));
  392.   end;
  393.   CrossRefs := P;
  394.   NumRefs := I;
  395. end;
  396.  
  397. procedure THelpTopic.SetWidth(AWidth: Integer);
  398. begin
  399.   Width := AWidth;
  400. end;
  401.  
  402. procedure THelpTopic.Store(var S: TStream);
  403.  
  404. procedure WriteParagraphs;
  405. var
  406.   I: Integer;
  407.   P: PParagraph;
  408. begin
  409.   P := Paragraphs;
  410.   I := 0;
  411.   while P <> nil do
  412.   begin
  413.     Inc(I);
  414.     P := P^.Next;
  415.   end;
  416.   S.Write(I, SizeOf(I));
  417.   P := Paragraphs;
  418.   while P <> nil do
  419.   begin
  420.     S.Write(P^.Size, SizeOf(Integer));
  421.     S.Write(P^.Wrap, SizeOf(Boolean));
  422.     S.Write(P^.Text, P^.Size);
  423.     P := P^.Next;
  424.   end;
  425. end;
  426.  
  427. procedure WriteCrossRefs;
  428. var
  429.   I: Integer;
  430. begin
  431.   S.Write(NumRefs, SizeOf(Integer));
  432.   if @CrossRefHandler = @NotAssigned then
  433.     S.Write(CrossRefs^, SizeOf(TCrossRef) * NumRefs)
  434.   else
  435.     for I := 1 to NumRefs do
  436.     begin
  437.       CrossRefHandler(S, CrossRefs^[I].Ref);
  438.       S.Write(CrossRefs^[I].Offset, SizeOf(Integer) + SizeOf(Byte));
  439.     end;
  440. end;
  441.  
  442. begin
  443.   WriteParagraphs;
  444.   WriteCrossRefs;
  445. end;
  446.  
  447. function THelpTopic.WrapText(var Text; Size: Integer;
  448.   var Offset: Integer; Wrap: Boolean): String;
  449. type
  450.   PCArray = ^CArray;
  451.   CArray = array[0..32767] of Char;
  452. var
  453.   Line: String;
  454.   I, P: Integer;
  455.  
  456. function IsBlank(Ch: Char): Boolean;
  457. begin
  458.   IsBlank := (Ch = ' ') or (Ch = #13) or (Ch = #10);
  459. end;
  460.  
  461. function Scan(var P; Offset, Size: Integer; C: Char): Integer; assembler;
  462. asm
  463.         CLD
  464.         LES     DI,P
  465.         ADD     DI,&Offset
  466.         MOV     DX,Size
  467.         SUB     DX,&Offset
  468.         OR      DH,DH
  469.         JZ      @@1
  470.         MOV     DX,256
  471. @@1:    MOV     CX,DX
  472.         MOV     AL, C
  473.         REPNE   SCASB
  474.         SUB     CX,DX
  475.         NEG     CX
  476.         XCHG    AX,CX
  477. end;
  478.  
  479. procedure TextToLine(var Text; Offset, Length: Integer; var Line: String);
  480.   assembler;
  481. asm
  482.         CLD
  483.         PUSH    DS
  484.         LDS     SI,Text
  485.         ADD     SI,&Offset
  486.         LES     DI,Line
  487.         MOV     AX,Length
  488.         STOSB
  489.         XCHG    AX,CX
  490.         REP     MOVSB
  491.         POP     DS
  492. end;
  493.  
  494. begin
  495.   I := Scan(Text, Offset, Size, #13);
  496.   if (I >= Width) and Wrap then
  497.   begin
  498.     I := Offset + Width;
  499.     if I > Size then I := Size
  500.     else
  501.     begin
  502.       while (I > Offset) and not IsBlank(PCArray(@Text)^[I]) do Dec(I);
  503.       if I = Offset then I := Offset + Width
  504.       else Inc(I);
  505.     end;
  506.     if I = Offset then I := Offset + Width;
  507.     Dec(I, Offset);
  508.   end;
  509.   TextToLine(Text, Offset, I, Line);
  510.   if Line[Length(Line)] = #13 then Dec(Line[0]);
  511.   Inc(Offset, I);
  512.   WrapText := Line;
  513. end;
  514.  
  515. { THelpIndex }
  516.  
  517. constructor THelpIndex.Init;
  518. begin
  519.   inherited Init;
  520.   Size := 0;
  521.   Contexts := nil;
  522.   Index := nil;
  523. end;
  524.  
  525. constructor THelpIndex.Load(var S: TStream);
  526. begin
  527.   S.Read(Used, SizeOf(Used));
  528.   S.Read(Size, SizeOf(Size));
  529.   if Size = 0 then
  530.   begin
  531.     Contexts := nil;
  532.     Index := nil;
  533.   end
  534.   else
  535.   begin
  536.     GetMem(Contexts, SizeOf(Contexts^[0]) * Size);
  537.     S.Read(Contexts^, SizeOf(Contexts^[0]) * Size);
  538.     GetMem(Index, SizeOf(Index^[0]) * Size);
  539.     S.Read(Index^, SizeOf(Index^[0]) * Size);
  540.   end;
  541. end;
  542.  
  543. destructor THelpIndex.Done;
  544. begin
  545.   FreeMem(Index, SizeOf(Index^[0]) * Size);
  546.   FreeMem(Contexts, SizeOf(Contexts^[0]) * Size);
  547.   inherited Done;
  548. end;
  549.  
  550. function THelpIndex.Find(I: Word): Word;
  551. var
  552.   Hi, Lo, Pos: Integer;
  553. begin
  554.   Lo := 0;
  555.   if Used > 0 then
  556.   begin
  557.     Hi := Used - 1;
  558.     while Lo <= Hi do
  559.     begin
  560.       Pos := (Lo + Hi) div 2;
  561.       if I > Contexts^[Pos] then
  562.         Lo := Pos + 1
  563.       else
  564.       begin
  565.         Hi := Pos - 1;
  566.         if I = Contexts^[Pos] then
  567.           Lo := Pos;
  568.       end;
  569.     end;
  570.   end;
  571.   Find := Lo;
  572. end;
  573.  
  574. function THelpIndex.Position(I: Word): Longint;
  575. begin
  576.   Position := Index^[Find(I)];
  577. end;
  578.  
  579. procedure THelpIndex.Add(I: Word; Val: Longint);
  580. const
  581.   Delta = 10;
  582. var
  583.   P: PIndexArray;
  584.   NewSize: Integer;
  585.   Pos: Integer;
  586.  
  587.   function Grow(P: Pointer; OldSize, NewSize, ElemSize: Integer): Pointer;
  588.   var
  589.     NewP: PByteArray;
  590.   begin
  591.     GetMem(NewP, NewSize * ElemSize);
  592.     if NewP <> nil then
  593.     begin
  594.       if P <> nil then
  595.         Move(P^, NewP^, OldSize * ElemSize);
  596.       FillChar(NewP^[OldSize * ElemSize], (NewSize - Size) * ElemSize, $FF);
  597.     end;
  598.     if OldSize > 0 then FreeMem(P, OldSize * ElemSize);
  599.     Grow := NewP;
  600.   end;
  601.  
  602. begin
  603.   Pos := Find(I);
  604.   if (Contexts = nil) or (Contexts^[Pos] <> I) then
  605.   begin
  606.     Inc(Used);
  607.     if Used >= Size then
  608.     begin
  609.       NewSize := (Used + Delta) div Delta * Delta;
  610.       Contexts := Grow(Contexts, Size, NewSize, SizeOf(Contexts^[0]));
  611.       Index := Grow(Index, Size, NewSize, SizeOf(Index^[0]));
  612.       Size := NewSize;
  613.     end;
  614.     if Pos < Used then
  615.     begin
  616.       Move(Contexts^[Pos], Contexts^[Pos + 1], (Used - Pos - 1) *
  617.         SizeOf(Contexts^[0]));
  618.       Move(Index^[Pos], Index^[Pos + 1], (Used - Pos - 1) *
  619.         SizeOf(Index^[0]));
  620.     end;
  621.   end;
  622.   Contexts^[Pos] := I;
  623.   Index^[Pos] := Val;
  624. end;
  625.  
  626. procedure THelpIndex.Store(var S: TStream);
  627. begin
  628.   S.Write(Used, SizeOf(Used));
  629.   S.Write(Size, SizeOf(Size));
  630.   S.Write(Contexts^, SizeOf(Contexts^[0]) * Size);
  631.   S.Write(Index^, SizeOf(Index^[0]) * Size);
  632. end;
  633.  
  634. { THelpFile }
  635.  
  636. const
  637.   MagicHeader = $46484246; {'FBHF'}
  638.  
  639. constructor THelpFile.Init(S: PStream);
  640. var
  641.   Magic: Longint;
  642. begin
  643.   Magic := 0;
  644.   S^.Seek(0);
  645.   if S^.GetSize > SizeOf(Magic) then
  646.     S^.Read(Magic, SizeOf(Magic));
  647.   if Magic <> MagicHeader then
  648.   begin
  649.     IndexPos := 12;
  650.     S^.Seek(IndexPos);
  651.     Index := New(PHelpIndex, Init);
  652.     Modified := True;
  653.   end
  654.   else
  655.   begin
  656.     S^.Seek(8);
  657.     S^.Read(IndexPos, SizeOf(IndexPos));
  658.     S^.Seek(IndexPos);
  659.     Index := PHelpIndex(S^.Get);
  660.     Modified := False;
  661.   end;
  662.   Stream := S;
  663. end;
  664.  
  665. destructor THelpFile.Done;
  666. var
  667.   Magic, Size: Longint;
  668. begin
  669.   if Modified then
  670.   begin
  671.     Stream^.Seek(IndexPos);
  672.     Stream^.Put(Index);
  673.     Stream^.Seek(0);
  674.     Magic := MagicHeader;
  675.     Size := Stream^.GetSize - 8;
  676.     Stream^.Write(Magic, SizeOf(Magic));
  677.     Stream^.Write(Size, SizeOf(Size));
  678.     Stream^.Write(IndexPos, SizeOf(IndexPos));
  679.   end;
  680.   Dispose(Stream, Done);
  681.   if Index <> NIL then
  682.     Dispose(Index, Done);
  683. end;
  684.  
  685. function THelpFile.GetTopic(I: Word): PHelpTopic;
  686. var
  687.   Pos: Longint;
  688. begin
  689.   Pos := Index^.Position(I);
  690.   if Pos > 0 then
  691.   begin
  692.     Stream^.Seek(Pos);
  693.     GetTopic := PHelpTopic(Stream^.Get);
  694.   end
  695.   else GetTopic := InvalidTopic;
  696. end;
  697.  
  698. function THelpFile.InvalidTopic: PHelpTopic;
  699. var
  700.   Topic: PHelpTopic;
  701.   Para: PParagraph;
  702. const
  703.   InvalidStr = #13' No help available in this context.';
  704.   InvalidText: array[1..Length(InvalidStr)] of Char = InvalidStr;
  705. begin
  706.   Topic := New(PHelpTopic, Init);
  707.   GetMem(Para, SizeOf(Para^) + SizeOf(InvalidText));
  708.   Para^.Size := SizeOf(InvalidText);
  709.   Para^.Wrap := False;
  710.   Para^.Next := nil;
  711.   Move(InvalidText, Para^.Text, SizeOf(InvalidText));
  712.   Topic^.AddParagraph(Para);
  713.   InvalidTopic := Topic;
  714. end;
  715.  
  716. procedure THelpFile.RecordPositionInIndex(I: Integer);
  717. begin
  718.   Index^.Add(I, IndexPos);
  719.   Modified := True;
  720. end;
  721.  
  722. procedure THelpFile.PutTopic(Topic: PHelpTopic);
  723. begin
  724.   Stream^.Seek(IndexPos);
  725.   Stream^.Put(Topic);
  726.   IndexPos := Stream^.GetPos;
  727.   Modified := True;
  728. end;
  729.  
  730. { THelpViewer }
  731.  
  732. constructor THelpViewer.Init(var Bounds: TRect; AHScrollBar,
  733.   AVScrollBar: PScrollBar; AHelpFile: PHelpFile; Context: Word);
  734. begin
  735.   inherited Init(Bounds, AHScrollBar, AVScrollBar);
  736.   Options := Options or ofSelectable;
  737.   GrowMode := gfGrowHiX + gfGrowHiY;
  738.   HFile := AHelpFile;
  739.   Topic := AHelpFile^.GetTopic(Context);
  740.   Topic^.SetWidth(Size.X);
  741.   SetLimit(78, Topic^.NumLines);
  742.   Selected := 1;
  743. end;
  744.  
  745. destructor THelpViewer.Done;
  746. begin
  747.   inherited Done;
  748.   Dispose(HFile, Done);
  749.   Dispose(Topic, Done);
  750. end;
  751.  
  752. procedure THelpViewer.ChangeBounds(var Bounds: TRect);
  753. begin
  754.   inherited ChangeBounds(Bounds);
  755.   Topic^.SetWidth(Size.X);
  756.   SetLimit(Limit.X, Topic^.NumLines);
  757. end;
  758.  
  759. procedure THelpViewer.Draw;
  760. var
  761.   B: TDrawBuffer;
  762.   Line: String;
  763.   I, J, L: Integer;
  764.   KeyCount: Integer;
  765.   Normal, Keyword, SelKeyword, C: Byte;
  766.   KeyPoint: TPoint;
  767.   KeyLength: Byte;
  768.   KeyRef: Word;
  769. begin
  770.   Normal := GetColor(1);
  771.   Keyword := GetColor(2);
  772.   SelKeyword := GetColor(3);
  773.   KeyCount := 0;
  774.   KeyPoint.X := 0;
  775.   KeyPoint.Y := 0;
  776.   Topic^.SetWidth(Size.X);
  777.   if Topic^.GetNumCrossRefs > 0 then
  778.     repeat
  779.       Inc(KeyCount);
  780.       Topic^.GetCrossRef(KeyCount, KeyPoint, KeyLength, KeyRef);
  781.     until (KeyCount >= Topic^.GetNumCrossRefs) or (KeyPoint.Y > Delta.Y);
  782.   for I := 1 to Size.Y do
  783.   begin
  784.     MoveChar(B, ' ', Normal, Size.X);
  785.     Line := Topic^.GetLine(I + Delta.Y);
  786.     MoveStr(B, Copy(Line, Delta.X+1, Size.X), Normal);
  787.     while I + Delta.Y = KeyPoint.Y do
  788.     begin
  789.       L := KeyLength;
  790.       if KeyPoint.X < Delta.X then
  791.       begin
  792.         Dec(L, Delta.X - KeyPoint.X);
  793.         KeyPoint.X := Delta.X;
  794.       end;
  795.       if KeyCount = Selected then C := SelKeyword
  796.       else C := Keyword;
  797.       for J := 0 to L-1 do
  798.         WordRec(B[KeyPoint.X - Delta.X + J]).Hi := C;
  799.       Inc(KeyCount);
  800.       if KeyCount <= Topic^.GetNumCrossRefs then
  801.         Topic^.GetCrossRef(KeyCount, KeyPoint, KeyLength, KeyRef)
  802.       else KeyPoint.Y := 0;
  803.     end;
  804.     WriteLine(0, I-1, Size.X, 1, B);
  805.   end;
  806. end;
  807.  
  808. function THelpViewer.GetPalette: PPalette;
  809. const
  810.   P: String[Length(CHelpViewer)] = CHelpViewer;
  811. begin
  812.   GetPalette := @P;
  813. end;
  814.  
  815. procedure THelpViewer.HandleEvent(var Event: TEvent);
  816. var
  817.   KeyPoint, Mouse: TPoint;
  818.   KeyLength: Byte;
  819.   KeyRef: Word;
  820.   KeyCount: Integer;
  821.  
  822. procedure MakeSelectVisible;
  823. var
  824.   D: TPoint;
  825. begin
  826.   Topic^.GetCrossRef(Selected, KeyPoint, KeyLength, KeyRef);
  827.   D := Delta;
  828.   if KeyPoint.X < D.X then D.X := KeyPoint.X
  829.   else if KeyPoint.X + KeyLength > D.X + Size.X then
  830.     D.X := KeyPoint.X + KeyLength - Size.X + 1;
  831.   if KeyPoint.Y <= D.Y then D.Y := KeyPoint.Y - 1;
  832.   if KeyPoint.Y > D.Y + Size.Y then D.Y := KeyPoint.Y - Size.Y;
  833.   if (D.X <> Delta.X) or (D.Y <> Delta.Y) then ScrollTo(D.X, D.Y);
  834. end;
  835.  
  836. procedure SwitchToTopic(KeyRef: Integer);
  837. begin
  838.   if Topic <> nil then Dispose(Topic, Done);
  839.   Topic := HFile^.GetTopic(KeyRef);
  840.   Topic^.SetWidth(Size.X);
  841.   ScrollTo(0, 0);
  842.   SetLimit(Limit.X, Topic^.NumLines);
  843.   Selected := 1;
  844.   DrawView;
  845. end;
  846.  
  847. begin
  848.   inherited HandleEvent(Event);
  849.   case Event.What of
  850.     evKeyDown:
  851.       begin
  852.         case Event.KeyCode of
  853.           kbTab:
  854.             if Topic^.GetNumCrossRefs > 0 then
  855.             begin
  856.               Inc(Selected);
  857.               if Selected > Topic^.GetNumCrossRefs then Selected := 1;
  858.               MakeSelectVisible;
  859.             end;
  860.           kbShiftTab:
  861.             if Topic^.GetNumCrossRefs > 0 then
  862.             begin
  863.               Dec(Selected);
  864.               if Selected = 0 then Selected := Topic^.GetNumCrossRefs;
  865.               MakeSelectVisible;
  866.             end;
  867.           kbEnter:
  868.             if Selected <= Topic^.GetNumCrossRefs then
  869.             begin
  870.               Topic^.GetCrossRef(Selected, KeyPoint, KeyLength, KeyRef);
  871.               SwitchToTopic(KeyRef);
  872.             end;
  873.           kbEsc:
  874.             begin
  875.               Event.What := evCommand;
  876.               Event.Command := cmClose;
  877.               PutEvent(Event);
  878.             end;
  879.         else
  880.           Exit;
  881.         end;
  882.         DrawView;
  883.         ClearEvent(Event);
  884.       end;
  885.     evMouseDown:
  886.       begin
  887.         MakeLocal(Event.Where, Mouse);
  888.         Inc(Mouse.X, Delta.X); Inc(Mouse.Y, Delta.Y);
  889.         KeyCount := 0;
  890.         repeat
  891.           Inc(KeyCount);
  892.           if KeyCount > Topic^.GetNumCrossRefs then Exit;
  893.           Topic^.GetCrossRef(KeyCount, KeyPoint, KeyLength, KeyRef);
  894.         until (KeyPoint.Y = Mouse.Y+1) and (Mouse.X >= KeyPoint.X) and
  895.           (Mouse.X < KeyPoint.X + KeyLength);
  896.         Selected := KeyCount;
  897.         DrawView;
  898.         if Event.Double then SwitchToTopic(KeyRef);
  899.         ClearEvent(Event);
  900.       end;
  901.     evCommand:
  902.       if (Event.Command = cmClose) and (Owner^.State and sfModal <> 0) then
  903.       begin
  904.         EndModal(cmClose);
  905.         ClearEvent(Event);
  906.       end;
  907.   end;
  908. end;
  909.  
  910. { THelpWindow }
  911.  
  912. constructor THelpWindow.Init(HFile: PHelpFile; Context: Word);
  913. var
  914.   R: TRect;
  915. begin
  916.   R.Assign(0, 0, 50, 18);
  917.   TWindow.Init(R, 'Help', wnNoNumber);
  918.   Options := Options or ofCentered;
  919.   R.Grow(-2,-1);
  920.   Insert(New(PHelpViewer, Init(R,
  921.     StandardScrollBar(sbHorizontal + sbHandleKeyboard),
  922.     StandardScrollBar(sbVertical + sbHandleKeyboard), HFile, Context)));
  923. end;
  924.  
  925. function THelpWindow.GetPalette: PPalette;
  926. const
  927.   P: String[Length(CHelpWindow)] = CHelpWindow;
  928. begin
  929.   GetPalette := @P;
  930. end;
  931.  
  932. procedure RegisterHelpFile;
  933. begin
  934.   RegisterType(RHelpIndex);
  935.   RegisterType(RHelpTopic);
  936. end;
  937.  
  938. procedure NotAssigned(var S: TStream; Value: Integer);
  939. begin
  940. end;
  941.  
  942. end.
  943.