{$F+,I-,O+,X+}
unit HTMLView;

interface

uses Objects, Drivers, Views, Swaps, FEditor, EditFile, FindDlg, HTMLPrim;

type
  PHTMLItem = ^THTMLItem;
  THTMLItem = record
    Line: Integer;
    Pos: Byte;
    PFName, PRefName: PString;
  end;

  PHTMLList = ^THTMLList;
  THTMLList = object(TCollection)
    procedure FreeItem(Item: Pointer); virtual;
    function GetItem(var S: TStream): Pointer; virtual;
    procedure PutItem(var S: TStream; Item: Pointer); virtual;
    function AddItem(Index: Integer;
      const AFName, ARefName: String;
      ALine: Integer; APos: Byte): Boolean;
  end;

  PHTMLIndicator = ^THTMLIndicator;
  THTMLIndicator = object(TView)
    procedure Draw; virtual;
    function GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    function GetMouseShape(var Event: TEvent): Char; virtual;
    function GetMousePos(X: Integer): Byte;
  end;

  PHTMLTitle = ^THTMLTitle;
  THTMLTitle = object(TView)
    procedure Draw; virtual;
    function GetPalette: PPalette; virtual;
  end;

  PHTMLInfo = ^THTMLInfo;
  THTMLInfo = object(TView)
    procedure Draw; virtual;
    function GetPalette: PPalette; virtual;
  end;

  PHTMLViewer = ^THTMLViewer;
  THTMLViewer = object(TScroller)
    FileLines, NewLines, FindAllList, HTMLList: PCollection;
    SwapStream: PStream;
    TheHTML: PHTML;
{ storable fields }
    SourceName: PString;
    Area: PArea;
    SelectFromX: Byte;
    SelectFromY: Integer;
    Places: TPlaces;
    Status: Word;
    HistLock: Byte;
    HistIndex: Integer;
    XLATTable: ConvertTable;
{ non-storable fields }
    XLATKind: Byte;
    CurRef: PRefItem;
    NotFoundRef: String[MaxViewWidth - 32];
    TrackLine: Integer;
    LastTime: Longint;
    TopLine: Integer;
    SwapSize: Longint;
    SearchingAll: Boolean;
    SearchingFrom: Boolean;
    NoSearchMsg: Boolean;
    FindAllIndex: Integer;
    FindAllData: TSearchData;
    IncSearchStr: String[IncSearchLen];
    IncSearchPos: Integer;
    IncSearchOpt: Word;
    IncSearch: Boolean;
    DrawFlag: Boolean;
    IsValid: Boolean;
    constructor Init(var Bounds: TRect;
      AHScrollBar, AVScrollBar: PScrollBar);
    constructor Load(var S: TStream);
    destructor Done; virtual;
    procedure ChangeBounds(var Bounds: TRect); virtual;
    procedure Draw; virtual;
    function GetHelpCtx: Word; virtual;
    function GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    procedure Store(var S: TStream);
    function Valid(Command: Word): Boolean; virtual;
    function Inserted: Boolean;
    procedure UpdateSubViews;
    procedure UpdateCopyCommands;
    procedure ReadFile(const Name: FNameStr);
    procedure FastScrollTo(Y: Integer);
    function SameHTML(const Name: FNameStr): Boolean;
    procedure GetValidLine(AnIndex: Integer; var ALine: String);
    procedure GetTextLine(AnIndex: Integer; var ALine: String);
    procedure Scroll(Key: Word);
    procedure GoRight;
    procedure GoLeft;
    procedure GoWhileKeyDown(Key: Byte);
    procedure GoDown;
    procedure GoUp;
    procedure GoPgDn;
    procedure GoPgUp;
    procedure GoCtrlPgDn;
    procedure GoCtrlPgUp;
    procedure GoCtrlRight;
    procedure GoCtrlLeft;
    procedure GoCtrlUp;
    procedure GoCtrlDown;
    procedure GoEnd;
    procedure GoHome;
    procedure GoCtrlEnd;
    procedure GoCtrlHome;
    procedure GoToPos(APos: Byte; AnIndex: Integer; OnTop: Boolean);
    procedure GoToLineNum(AnIndex: Integer);
    procedure GoToRef(Back: Boolean);
    procedure GoToRefLink;
    procedure GoBack;
    procedure AdjustHistIndex;
    procedure GoToHistoryItem(Index: Integer);
    procedure HTMLExecute(const Name: String);
    procedure FindRef(const RefName: String);
    procedure SwitchToFile(const Name: FNameStr);
    procedure UpdateTime;
    procedure Highlight(AKB: Integer; APB: Byte; AKK: Integer; APK: Byte);
    procedure Unselect;
    procedure SelectOn(CurY: Integer; CurX: Byte);
    function Selected(AnIndex: Integer): Boolean;
    procedure GetPos(AnArea: PArea; AnIndex: Integer; var APos: TPos);
    procedure GetSelectedString(AnIndex: Integer; var ALine: String);
    procedure SetPlaceMarker(AChar: Char);
    procedure GoToPlaceMarker(AChar: Char);
    procedure CopyToClipboard;
    procedure WriteTextToFile(const Name: FNameStr; Sel, Append: Boolean);
    procedure WriteBlockToFile(NamePtr: PString; Append: Boolean);
    procedure SaveAs;
    procedure SetXLATTable(const Name: FNameStr; Prompt: Boolean);
    procedure ClearXLATTable;
    function Search(const Match: String; AOptions: Word): Boolean;
    procedure Find(All: Boolean);
    procedure FindAllDlg;
    procedure HistoryListDlg;
    procedure AddToHistory;
    procedure InitIncSearch;
    procedure DoneIncSearch;
    procedure DoIncSearch(C: Char);
    procedure GoToLineNumber;
    function WordChar(Ch: Char): Boolean;
    function GetWord(var B, E: Byte): String;
    function GetLine: String;
    function GetPrintLine(AnIndex: Integer; Sel: Boolean;
      var ALine: String): Boolean;
    procedure SwapTruncate;
    procedure UpdateHistory;
    procedure ToggleTables;
    procedure PopUp(AnOrigin: TPoint);
  end;

  PHTMLWindow = ^THTMLWindow;
  THTMLWindow = object(TWindow)
    Indicator: PHTMLIndicator;
    HTMLTitle: PHTMLTitle;
    HTMLInfo: PHTMLInfo;
    Viewer: PHTMLViewer;
    constructor Init(Bounds: TRect; const Name: FNameStr);
    constructor Load(var S: TStream);
    procedure ChangeBounds(var Bounds: TRect); virtual;
    procedure Close; virtual;
    function GetPalette: PPalette; virtual;
    function GetTitle(MaxSize: Integer): TTitleStr; virtual;
    procedure SizeLimits(var Min, Max: TPoint); virtual;
    procedure Store(var S: TStream);
    procedure Zoom; virtual;
  end;

implementation

uses Dos, CNCRT, Memory, Menus, Dialogs, App, WinIcons, HistList, History,
  StdDlg, StrCol, PopUps, HeapUMB, DirPrim, ExecPrim, EditCmnd, EditMsg,
  HTMLFind, HTMLHist, Clipbrd, ClipPrim, RegExp, RevPos, Spooler, PlcHist,
  CNInputs, DosTools, LFN, Keyboard, TPString, Consts, Commands, Defaults;

const
  ofHTML = $8000;

  sfCtrlKPressed = $0001;
  sfCtrlQPressed = $0002;
  sfHilit        = $0004;
  sfNeedRedraw   = $0008;
  sfXLAT         = $0010;
  sfBrowser      = $0020;
  sfNoTables     = $0040;
  sfFromArchive  = $0080;
  sfError        = $8000;

  NumLines = 128;

  HTMLViewerCommands =
    [cmSaveAs, cmPrint, cmFind, cmFindAll, cmShowAllFound,
     cmSearchAgain, cmIncSearch, cmGoToLine];
  HTMLViewCommands =
    [cmCopy, cmCtrlKW, cmCtrlQB, cmCtrlQK];

{
function HTMLExists(const Name: FNameStr): FNameStr;
var
  S: String;
begin
  S := Name;
  if not ThisFileExists(S) then
  begin
    S := QuasiShortName(S);
    if (S = Name) or not ThisFileExists(S) then S := '';
  end;
  HTMLExists := S;
end;
}

function ColoredLine(const Source: String;
  Attr: Char; Limit: Integer): String;
var
  C, I: Integer;
  S: String;
begin
  S := '';
  C := 0;
  I := 1;
  while (C < Limit) and (I <= Length(Source)) do
  begin
    Inc(S[0], 2);
    S[Length(S) - 1] := Source[I];
    S[Length(S)] := Attr;
    Inc(C);
    Inc(I);
  end;
  ColoredLine := S;
end;

procedure HTMLFullName(BaseName, FileName: FNameStr; var S: String);
var
  I, N: Integer;
begin
  TrimSt(FileName);
  if FileName = '' then
  begin
    S := BaseName;
    Exit;
  end;
  for I := 1 to Length(FileName) do
    if FileName[I] = '/' then FileName[I] := '\';
  I := Pos('\\', FileName);
  if I <> 0 then
  begin
    N := FIPos('\', FileName, I + 1);
    if N <> 0 then S := Copy(FileName, N + 1, 255)
    else S := '';
  end
  else if (Length(FileName) > 1) and
    (FileName[1] in ['A'..'Z', 'a'..'z']) and
    (FileName[2] = ':') then S := FileName
  else if FileName[1] = '\' then
    S := Concat(RootName(BaseName), FileName)
  else
  begin
    S := JustPathName(BaseName);
    if Pos('\', FileName) <> 0 then
    repeat
      if Pos('..\', FileName) <> 1 then Break;
      Delete(FileName, 1, 3);
      S := PrevDir(S);
    until False;
    S := Concat(AddedSlash(S), FileName);
  end;
  if S[Length(S)] = '\' then S := Concat(S, 'index.html');
  if not ThisFileExists(S) then
    S := AddedSlash(JustPathName(BaseName)) + JustFileName(S);
end;

procedure THTMLList.FreeItem(Item: Pointer);
begin
  if Item <> nil then
  begin
{
    DisposeStr(PHTMLItem(Item)^.PFName);
    DisposeStr(PHTMLItem(Item)^.PRefName);
    FreeMem(Item, SizeOf(THTMLItem));
}
    DisposeStrUMB(PHTMLItem(Item)^.PFName);
    DisposeStrUMB(PHTMLItem(Item)^.PRefName);
    FreeMemUMB(Item, SizeOf(THTMLItem));
  end;
end;

function THTMLList.GetItem(var S: TStream): Pointer;
var
  Item: PHTMLItem;
begin
{  GetMem(Item, SizeOf(THTMLItem));}
  GetMemUMB(Item, SizeOf(THTMLItem));
  S.Read(Item^.Line, SizeOf(Integer));
  S.Read(Item^.Pos, SizeOf(Byte));
{
  Item^.PFName := S.ReadStr;
  Item^.PRefName := S.ReadStr;
}
  Item^.PFName := ReadStrUMB(@S);
  Item^.PRefName := ReadStrUMB(@S);
  if LowMemory then
  begin
    FreeItem(Item);
    Item := nil;
  end;
  GetItem := Item;
end;

procedure THTMLList.PutItem(var S: TStream; Item: Pointer);
begin
  S.Write(PHTMLItem(Item)^.Line, SizeOf(Integer));
  S.Write(PHTMLItem(Item)^.Pos, SizeOf(Byte));
  S.WriteStr(PHTMLItem(Item)^.PFName);
  S.WriteStr(PHTMLItem(Item)^.PRefName);
end;

function THTMLList.AddItem(Index: Integer;
  const AFName, ARefName: String;
  ALine: Integer; APos: Byte): Boolean;
var
  Item: PHTMLItem;
begin
  if LowMemory then
  begin
    Application^.OutOfMemory;
    AddItem := False;
  end
  else
  begin
{    GetMem(Item, SizeOf(THTMLItem));}
    GetMemUMB(Item, SizeOf(THTMLItem));
    Item^.Line := ALine;
    Item^.Pos := APos;
{
    Item^.PFName := NewStr(AFName);
    Item^.PRefName := NewStr(ARefName);
}
    Item^.PFName := NewStrUMB(AFName);
    Item^.PRefName := NewStrUMB(ARefName);
    AtInsert(Index, Item);
    AddItem := True;
  end;
end;

procedure THTMLIndicator.Draw;
var
  Ch: Char;
  C: Word;
  Params: Array[0..1] of Longint;
  S: String;
  B: TDrawBuffer;
begin
  if State and sfActive = 0 then
  begin
    C := GetColor(1);
    Ch := #196;
  end
  else if State and sfDragging <> 0 then
  begin
    C := GetColor(3);
    Ch := #196;
  end
  else
  begin
    Ch := #205;
    C := GetColor(2);
  end;
  MoveChar(B, Ch, C, Size.X);
  with PHTMLViewer(PHTMLWindow(Owner)^.Viewer)^ do
  begin
    if CurRef <> nil then MoveChar(B, '', C, 1);
    Params[0] := Delta.Y + Cursor.Y + 1;
    Params[1] := Delta.X + Cursor.X + 1;
    FormatStr(S, ' %d:%d ', Params);
    MoveChar(B[Self.Size.X - 3], '[', C, 1);
    if MarkColumns then MoveChar(B[Self.Size.X - 2], '', C, 1)
    else MoveChar(B[Self.Size.X - 2], '', C, 1);
    MoveChar(B[Self.Size.X - 1], ']', C, 1);
  end;
  MoveStr(B[2], S, C);
  WriteLine(0, 0, Size.X, 1, B);
end;

function THTMLIndicator.GetPalette: PPalette;
const
  P: String[3] = #1#2#3;
begin
  GetPalette := @P;
end;

const
  ipNo = 0;
  ipRef = 1;
  ipRowCol = 2;

function THTMLIndicator.GetMousePos(X: Integer): Byte;
begin
  if (X = 0) and
     (PHTMLViewer(PHTMLWindow(Owner)^.Viewer)^.CurRef <> nil) then
    GetMousePos := ipRef
  else if X < Size.X - 3 then GetMousePos := ipRowCol
  else GetMousePos := ipNo;
end;

procedure THTMLIndicator.HandleEvent(var Event: TEvent);
var
  B: Byte;
  Mouse: TPoint;
begin
  Inherited HandleEvent(Event);
  if (Event.What = evMouseDown) and MouseInView(Event.Where) then
  begin
    MakeLocal(Event.Where, Mouse);
    B := GetMousePos(Mouse.X);
    if B = ipRef then
    begin
      Pending.What := evKeyDown;
      Pending.KeyCode := kbEnter;
      Event.What := evNothing;
    end;
    if B = ipRowCol then Event.Command := cmGoToLine
    else if Mouse.X >= Size.X - 3 then Event.Command := cmToggleMarkMode
    else Exit;
    Message(Application, evCommand, Event.Command, nil);
    Event.What := evNothing;
  end;
end;

procedure THTMLIndicator.SetState(AState: Word; Enable: Boolean);
begin
  Inherited SetState(AState, Enable);
  DrawView;
end;

function THTMLIndicator.GetMouseShape(var Event: TEvent): Char;
var
  B: Byte;
  C: Char;
  P: TPoint;
begin
  C := #0;
  if (State and sfActive <> 0) and (Owner^.State and sfFocused <> 0) then
  begin
    MakeLocal(Event.Where, P);
    B := GetMousePos(P.X);
    if B = ipRef then C := ''
    else if B = ipRowCol then C := '';
  end;
  GetMouseShape := C;
end;

procedure THTMLTitle.Draw;
var
  C: Word;
  I: Integer;
  S: String;
  B: TDrawBuffer;
begin
  C := GetColor(2);
  MoveChar(B, ' ', C, Size.X);
  with PHTMLViewer(PHTMLWindow(Owner)^.Viewer)^ do
  begin
    if TheHTML^.HTMLTitle = '' then S := 'Untitled'
    else
    begin
      S := TheHTML^.HTMLTitle;
      if Length(S) > MaxViewWidth - 10 then
        S[0] := Char(MaxViewWidth - 10);
      if Status and sfXLAT <> 0 then
        for I := 1 to Length(S) do S[I] := XLATTable[S[I]];
    end;
    MoveStr(B[8], S, C);
  end;
  MoveStr(B, ' Title: ', GetColor(1));
  WriteLine(0, 0, Size.X, 1, B);
end;

function THTMLTitle.GetPalette: PPalette;
const
  P: String[3] = #11#12;
begin
  GetPalette := @P;
end;

procedure THTMLInfo.Draw;
var
  NotFound: Boolean;
  C: Word;
  S: String;
  B: TDrawBuffer;
begin
  NotFound := False;
  C := GetColor(2);
  MoveChar(B, ' ', C, Size.X);
  with PHTMLViewer(PHTMLWindow(Owner)^.Viewer)^ do
    if NotFoundRef <> '' then
    begin
      MoveStr(B[8], Concat('Could not find "', NotFoundRef, '".'), C);
      NotFoundRef := '';
      NotFound := True;
    end
    else if CurRef <> nil then
    begin
      S := '';
      if CurRef^.PFName <> nil then S := CurRef^.PFName^;
      if CurRef^.PRefName <> nil then
        S := Concat(S, '#', CurRef^.PRefName^);
      if Length(S) > MaxViewWidth - 10 then
        S[0] := Char(MaxViewWidth - 10);
      MoveStr(B[8], S, C);
    end;
  if NotFound then MoveStr(B, ' Error: ', GetColor(1))
  else MoveStr(B, ' Go to: ', GetColor(1));
  WriteLine(0, 0, Size.X, 1, B);
end;

function THTMLInfo.GetPalette: PPalette;
const
  P: String[3] = #13#14;
begin
  GetPalette := @P;
end;

constructor THTMLViewer.Init(var Bounds: TRect;
  AHScrollBar, AVScrollBar: PScrollBar);
begin
  Inherited Init(Bounds, AHScrollbar, AVScrollBar);
  Options := Options or ofHTML;
  GrowMode := gfGrowHiX + gfGrowHiY;
{  GetMem(Area, SizeOf(TArea));}
  GetMemUMB(Area, SizeOf(TArea));
  Unselect;
  FillChar(Places, SizeOf(Places), $FF);
end;

constructor THTMLViewer.Load(var S: TStream);
begin
  Inherited Load(S);
{  SourceName := S.ReadStr;}
  SourceName := ReadStrUMB(@S);
{  GetMem(Area, SizeOf(TArea));}
  GetMemUMB(Area, SizeOf(TArea));
{
  S.Read(Area^, SizeOf(Area^) +
    SizeOf(SelectFromX) + SizeOf(SelectFromY) + SizeOf(Places) +
    SizeOf(Status) + SizeOf(HistLock) + SizeOf(HistIndex));
}

  S.Read(Area^, SizeOf(Area^));
  S.Read(SelectFromX, SizeOf(SelectFromX));
  S.Read(SelectFromY, SizeOf(SelectFromY));
  S.Read(Places, SizeOf(Places));
  S.Read(Status, SizeOf(Status));
  S.Read(HistLock, SizeOf(HistLock));
  S.Read(HistIndex, SizeOf(HistIndex));

  HTMLList := PHTMLList(S.Get);
  if Status and sfXLAT <> 0 then
    S.Read(XLATTable, SizeOf(ConvertTable));
end;

destructor THTMLViewer.Done;
begin
  SwapTruncate;
{  FreeMem(Area, SizeOf(TArea));}
  FreeMemUMB(Area, SizeOf(TArea));
  if TheHTML <> nil then Dispose(TheHTML, Done);
  if HTMLList <> nil then Dispose(HTMLList, Done);
  if FindAllList <> nil then Dispose(FindAllList, Done);
  if NewLines <> nil then Dispose(NewLines, Done);
  if FileLines <> nil then Dispose(FileLines, Done);
{  DisposeStr(SourceName);}
  DisposeStrUMB(SourceName);
  Inherited Done;
end;

procedure THTMLViewer.ChangeBounds(var Bounds: TRect);
begin
  Inherited ChangeBounds(Bounds);
  DrawFlag := True;
  SetLimit(Limit.X, FileLines^.Count + Size.Y);
  DrawFlag := False;
end;

procedure FillWord(var Dest; Count, Filler: Word);
  inline(
    $58/                     {pop ax    ;AX = Filler}
    $59/                     {pop cx    ;CX = Count}
    $5F/                     {pop di    ;ES:DI => Dest}
    $07/                     {pop es}
    $FC/                     {cld       ;go forward}
    $F2/$AB);                {rep stosw ;fill memory}

procedure THTMLViewer.Draw;
var
  NormalColor, SelectColor, CurRefColor: Word;
  I, Index, From, Len: Integer;
  SelPos: TPos;
  S: String;
  B: TDrawBuffer;

procedure HilitRef;
var
  Pos, LoPos, HiPos, L: Integer;
begin
  if (Index >= CurRef^.Line1) and
     (Index < CurRef^.Line2) then
  begin
    L := Length(S) + 1;
    FillWord(S[L], (255 - L) shr 1, NormalColor shl 8 + $20);
    S[0] := #255;
  end;
  if Index = CurRef^.Line1 then LoPos := CurRef^.Pos1 shl 1
  else LoPos := 2;
  if Index = CurRef^.Line2 then HiPos := CurRef^.Pos2 shl 1
  else HiPos := Length(S);
  Pos := LoPos;
  while (Pos <= Length(S)) and (Pos < HiPos) do
  begin
    S[Pos] := Char(CurRefColor);
    Inc(Pos, 2);
  end;
end;

procedure HilitSel;
var
  Pos, LoPos, HiPos: Integer;
begin
  LoPos := SelPos.AStart shl 1;
  if LoPos > 255 then LoPos := 255;
  HiPos := SelPos.AnEnd shl 1 - 2;
  if HiPos > 255 then HiPos := 255;
  if (S = '') and (HiPos <= 1) then Exit;
  while (Length(S) <> 255) and (Length(S) < LoPos) do
    S := S + #$20 + Char(SelectColor);
  PadSt(S, HiPos);
  Pos := LoPos;
  while (Pos <= Length(S)) and (Pos <= HiPos) do
  begin
    S[Pos] := Char(SelectColor);
    Inc(Pos, 2);
  end;
end;

begin
  if DrawFlag then Exit;
  NormalColor := GetColor(1);
  SelectColor := GetColor(3);
  CurRefColor := GetColor(5);
  CurRef := TheHTML^.Refs^.CurRef(
    Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1);
  for I := 0 to Size.Y - 1 do
  begin
    MoveChar(B, ' ', NormalColor, Size.X);
    Index := Delta.Y + I;
    GetValidLine(Index, S);
    if (CurRef <> nil) and
       (Index >= CurRef^.Line1) and
       (Index <= CurRef^.Line2) then HilitRef;
    if TrackLine = Index then
    begin
      SelPos.AStart := 1;
      SelPos.AnEnd := 127;
    end
    else if (Status and sfHilit <> 0) and Selected(Index) then
      GetPos(Area, Index, SelPos)
    else SelPos.AStart := 0;
    if SelPos.AStart <> 0 then HilitSel;
    From := Delta.X * 2 + 1;
    Len := Length(S) - From + 1;
    if Len >= 0 then Move(S[From], B, Len);
    WriteLine(0, I, Size.X, 1, B);
  end;
  UpdateSubViews;
  if (State and sfFocused = 0) and not MultiScrolling then
  begin
    NewLines^.FreeAll;
    TopLine := MaxInt;
  end;
end;

function THTMLViewer.GetHelpCtx: Word;
begin
  if State and sfDragging <> 0 then GetHelpCtx := hcDragging
  else if IncSearch then
    GetHelpCtx := hcEditIncSearch
  else if Status and sfCtrlKPressed <> 0 then
    GetHelpCtx := hcViewerCtrlKPressed
  else if Status and sfCtrlQPressed <> 0 then
    GetHelpCtx := hcViewerCtrlQPressed
  else GetHelpCtx := hcHTMLViewer;
end;

function THTMLViewer.GetPalette: PPalette;
const
  P: String[5] = #6#7#8#9#10;
begin
  GetPalette := @P;
end;

procedure HandleHTMLCommand(Viewer: PHTMLViewer; var Event: TEvent); forward;

procedure HandleHTMLKey(Viewer: PHTMLViewer; var Event: TEvent);
var
  B, E: Byte;
  C: Word;
begin
  if (Event.KeyCode = kbCtrlHome) or (Event.KeyCode = kbCtrlEnd) or
     (Event.KeyCode = kbCtrlPgUp) or (Event.KeyCode = kbCtrlPgDn) or
     (Event.CharCode in WordStarChars) then
    if (Event.CharCode = #0) or
       (Viewer^.Status and (sfCtrlKPressed + sfCtrlQPressed) = 0) then
    begin
      ConvertWSKey(WordStarKeys and not wsW, Event);
      if Event.What = evCommand then
      begin
        HandleHTMLCommand(Viewer, Event);
        Exit;
      end;
    end;
  with Viewer^ do
  begin
    if IncSearch then
    begin
      if Event.CharCode <> #0 then
      begin
        DoIncSearch(Event.CharCode);
        Event.What := evNothing;
        Exit;
      end;
      if (Event.KeyCode = kbShiftIns) or
         (Event.KeyCode = kbShiftF12) then Exit;
      DoneIncSearch;
    end
    else if Status and sfNeedRedraw <> 0 then
    begin
      DrawView;
      Status := Status and not sfNeedRedraw;
    end;
    if (Cursor.X < 0) or (Cursor.X >= Size.X) or
       (Cursor.Y < 0) or (Cursor.Y >= Size.Y) then
         GoToPos(Delta.X + Cursor.X, Delta.Y + Cursor.Y, False);
    if Event.CharCode <> #0 then
    begin
      if Status and sfCtrlKPressed <> 0 then
      begin
        Status := Status and not sfCtrlKPressed;
        C := 0;
        case Event.CharCode of
          'Q', 'q', '©', '‰', ^Q:
            begin
              Pending.What := evCommand;
              Pending.Command := cmMenu;
            end;
          '0'..'9': SetPlaceMarker(Event.CharCode);
          else GetCtrlKCommand(Event.CharCode, C);
        end;
        Event.What := evCommand;
        Event.Command := C;
        if C <> 0 then HandleEvent(Event);
      end
      else if Status and sfCtrlQPressed <> 0 then
      begin
        Status := Status and not sfCtrlQPressed;
        C := 0;
        case Event.CharCode of
          '0'..'9': GoToPlaceMarker(Event.CharCode);
          else GetCtrlQCommand(Event.CharCode, C);
        end;
        Event.What := evCommand;
        Event.Command := C;
        if C <> 0 then HandleEvent(Event);
      end
      else case Event.CharCode of
        ^K: Status := Status or sfCtrlKPressed;
        ^Q: Status := Status or sfCtrlQPressed;
        ^L: Search(SearchData.MatchStr,
              SearchData.Options or frFromCursor);
        ^B: Search(SearchData.MatchStr,
              SearchData.Options or (frFromCursor + frBackward));
        ^U: Search(SearchData.MatchStr,
              (SearchData.Options or frFromCursor) and not frBackward);
        ^W: HistoryListDlg;
        ^T: ToggleTables;
        ^V: SwitchToFile('');
       #27:
          begin
            Pending.What := evCommand;
            Pending.Command := cmClose;
            Pending.InfoPtr := Owner;
          end;
       #32: SearchData.MatchStr := GetWord(B, E);
        ^\: Message(Desktop, evBroadcast, cmReopen, SourceName);
       else Scroll(CtrlToArrow(Event.KeyCode));
      end;
    end
    else if Event.KeyCode = kbCtrl2 then InitIncSearch
    else if Event.KeyCode = kbCtrlIns then
      Message(Application, evCommand, cmCopy, nil)
    else if Event.KeyCode = kbCtrlF12 then
      Message(Application, evCommand, cmCopyToMT, nil)
    else if (GetShiftState and (kbLeftShift + kbRightShift) <> 0) and
      (Event.KeyCode <> kbShiftTab) then
    begin
      if (Status and sfHilit <> 0) and
        (((Delta.Y + Cursor.Y = Area^.KB) and
        (Delta.X + Cursor.X + 1 = Area^.PB)) or
        ((Delta.Y + Cursor.Y = Area^.KK) and
        (Delta.X + Cursor.X + 1 = Area^.PK))) then {}
      else
      begin
        Status := Status and not sfHilit;
        Highlight(Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1,
          Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1);
      end;
      DrawFlag := True;
      Scroll(Event.KeyCode);
      DrawFlag := False;
      SelectOn(Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1);
      if (Status and sfHilit <> 0) and
         (Area^.KB = Area^.KK) and (Area^.PB = Area^.PK) then Unselect;
      DrawView;
    end
    else Scroll(Event.KeyCode);
    Event.What := evNothing;
  end;
end;

procedure HandleHTMLMouse(Viewer: PHTMLViewer; var Event: TEvent);
var
  WasSelected: Boolean;
  C: Word;
  Spot: TPoint;
begin
  with Viewer^ do
  begin
    if IncSearch then DoneIncSearch
    else if Status and sfNeedRedraw <> 0 then
    begin
      DrawView;
      Status := Status and not sfNeedRedraw;
    end;
    if Event.Buttons = mbRightButton then
    begin
      repeat until not MouseEvent(Event, evMouseDown);
      PopUp(Event.Where);
      Event.What := evNothing;
    end
    else
    begin
      WasSelected := Status and sfHilit <> 0;
      repeat
        DrawFlag := True;
        MakeLocal(Event.Where, Spot);
        if MouseInView(Event.Where) then
        begin
          if (Status and sfHilit = 0) and
             ((Event.What and evMouseMove <> 0) or
              (GetShiftState and (kbLeftShift + kbRightShift) <> 0)) then
            Highlight(Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1,
              Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1);
          while Delta.Y + Spot.Y > FileLines^.Count do Dec(Spot.Y);
          SetCursor(Spot.X, Spot.Y);
        end
        else
        begin
          Spot.X := 0;
          Spot.Y := 0;
          MakeGlobal(Spot, Spot);
          if Event.Where.X < Spot.X then GoLeft;
          if Event.Where.X > Spot.X + Size.X - 1 then GoRight;
          if Event.Where.Y < Spot.Y then GoUp;
          if Event.Where.Y > Spot.Y + Size.Y - 1 then GoDown;
        end;
        if (Status and sfHilit <> 0) and
           (not WasSelected or
             (GetShiftState and (kbLeftShift + kbRightShift) <> 0)) then
          SelectOn(Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1);
        if Event.Double then
        begin
          if CurRef <> nil then GoToRefLink
          else if Status and sfHilit <> 0 then Unselect
          else HighLight(Delta.Y + Cursor.Y, 1, Delta.Y + Cursor.Y + 1, 1);
          DrawFlag := False;
          DrawView;
          Exit;
        end;
        DrawFlag := False;
        DrawView;
      until not MouseEvent(Event, evMouseMove + evMouseAuto);
      if (Status and sfHilit <> 0) and
         (Area^.KB = Area^.KK) and (Area^.PB = Area^.PK) then Unselect;
    end;
  end;
end;

procedure HandleHTMLCommand(Viewer: PHTMLViewer; var Event: TEvent);

procedure SelectAll;
begin
  with Viewer^ do
  begin
    if FileLines^.Count = 0 then Exit;
    if MarkColumns then Message(
      Application, evCommand, cmToggleMarkMode, nil);
    Highlight(0, 1, FileLines^.Count, 1);
    DrawView;
  end;
end;

var
  B, E: Byte;
  Spot: TPoint;
  S: String;
begin
  with Viewer^ do
  begin
    if IncSearch and
      (Event.Command <> cmPaste) and
      (Event.Command <> cmPasteFromMT) then DoneIncSearch;
    case Event.Command of
      cmSaveAs: SaveAs;
      cmPrint: TheSpooler^.NewEditor(Viewer, -1, 0);
      cmCopy: CopyToClipboard;
      cmPaste: if IncSearch then IncSearchFromClipboard(Viewer)
        else SwitchToFile('');
      cmFind: Find(False);
      cmReplace: SwitchToFile('');
      cmFindAll: Find(True);
      cmShowAllFound: FindAllDlg;
      cmSearchAgain: Search(SearchData.MatchStr,
        SearchData.Options or frFromCursor);
      cmIncSearch: InitIncSearch;
      cmGoToLine: GoToLineNumber;
      cmHTMLHistory: HistoryListDlg;
      cmToggleHTMLTables: ToggleTables;
      cmHTMLReload: SwitchToFile('');
{     cmUpdateASCII: UpdateLines('');}
      cmSelectAll: SelectAll;
      cmPopUp:
        begin
          Spot.X := Size.X div 2;
          Spot.Y := 0;
          MakeGlobal(Spot, Spot);
          PopUp(Spot);
        end;
      else
      begin
        case Event.Command of
          cmCtrlKB: Highlight(Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1,
            Area^.KK, Area^.PK);
          cmCtrlKK: Highlight(Area^.KB, Area^.PB,
            Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1);
          cmCtrlKH:
            if Status and sfHilit = 0 then Highlight(
              Area^.KB, Area^.PB, Area^.KK, Area^.PK)
            else
            begin
              Status := Status and not sfHilit;
              DisableCommands(BlockCommands + MTCopyCommands);
            end;
          cmCtrlKW, cmCtrlKA: WriteBlockToFile(nil, Event.Command = cmCtrlKA);
          cmCtrlKL:
            begin
              Unselect;
              HighLight(Delta.Y + Cursor.Y, 1, Delta.Y + Cursor.Y, 255);
            end;
          cmCtrlKT:
            begin
              S := GetWord(B, E);
              if E > B then
              begin
                Unselect;
                HighLight(Delta.Y + Cursor.Y, B, Delta.Y + Cursor.Y, E);
              end;
            end;
          cmCtrlKP: if Status and sfHilit <> 0 then
            TheSpooler^.NewEditor(Viewer, Area^.KB, Area^.KK);
{
          cmCtrlQN: OpenFileAtCursorPrim(Editor);
          cmCtrlQPath: MkDirAtCursorPrim(Editor);
}
          cmCtrlQB: if Status and sfHilit <> 0 then
            GoToPos(Area^.PB - 1, Area^.KB, False);
          cmCtrlQK: if Status and sfHilit <> 0 then
            GoToPos(Area^.PK - 1, Area^.KK, False);
          cmCtrlQW, cmCtrlQQ: SetXLATTable('', Event.Command = cmCtrlQQ);
          cmCtrlQX: ClearXLATTable;
          else Exit;
        end;
        if (Status and sfHilit <> 0) and
           (Area^.KB = Area^.KK) and (Area^.PB = Area^.PK) then Unselect;
        DrawView;
      end;
    end;
    Event.What := evNothing;
  end;
end;

procedure HandleHTMLBroadcast(Viewer: PHTMLViewer; var Event: TEvent);
var
  SameName: Boolean;
  B, E: Byte;
  C: Word;
  I: Integer;
  Spot: TPoint;
  S: String;
begin
  with Viewer^ do
  begin
    case Event.Command of
      cmOpenHTML: if Event.InfoPtr = @ExecRec then
        begin
          SameName := SameHTML(ExecRec.Path);
          if not SameName then
          begin
            if (Status and sfBrowser <> 0) or
               (ExecRec.Options and opInfoBit = 0) then Exit;
            DrawFlag := True;
            SwitchToFile(ExecRec.Path);
          end
          else DrawFlag := True;
          if not SameName or (Trim(ExecRec.Params) <> '') then
            FindRef(ExecRec.Params);
          DrawFlag := False;
          DrawView;
          PWindow(Owner)^.Deiconize;
          Owner^.Show;
          Owner^.Select;
          ClearEvent(Event);
        end;
      cmHRef: if CurRef <> nil then
        begin
          Event.What := evNothing;
          Event.InfoPtr := CurRef^.PRefName;
        end;
      cmIsPrintable: if Event.InfoPtr = Viewer then ClearEvent(Event);
      cmUpdateTime:
        if (Event.InfoPtr = nil) or SameHTML(PString(Event.InfoPtr)^) then
          UpdateTime;
      cmToggleMarkMode: DrawView;
      cmGetSource: if Owner^.State and sfVisible <> 0 then
        begin
          Event.What := evNothing;
          if (CurRef <> nil) and (CurRef^.PFName <> nil) then
          begin
            S := CurRef^.PFName^;
            HTMLFullName(SourceName^, S, S);
            Event.InfoPtr := @S;
          end
          else Event.InfoPtr := SourceName;
        end;
      cmGoToLine: if Owner^.State and sfVisible <> 0 then
        begin
          GoToPos(LongRec(Event.InfoPtr).Lo, LongRec(Event.InfoPtr).Hi,
            False);
          ClearEvent(Event);
        end;
      cmTrackSource: if Owner^.State and sfVisible <> 0 then
        begin
          TrackLine := LongRec(Event.InfoPtr).Hi;
          DrawView;
          TrackLine := -1;
          Status := Status or sfNeedRedraw;
          ClearEvent(Event);
        end;
      cmGetWord:
        begin
          Event.What := evNothing;
          S := GetWord(B, E);
          if S = '' then Event.InfoPtr := nil
          else Event.InfoPtr := @S;
        end;
      cmGetLine:
        begin
          Event.What := evNothing;
          S := GetLine;
          if S = '' then Event.InfoPtr := nil
          else Event.InfoPtr := @S;
        end;
      cmGetPrintLine, cmGetSelPrintLine:
        begin
          I := Event.InfoInt;
          ClearEvent(Event);
          if GetPrintLine(I, Event.Command = cmGetSelPrintLine, S) then
            if S = '' then Event.InfoPtr := nil
            else Event.InfoPtr := @S;
        end;
      cmGetMarkerLine:
        begin
          Event.What := evNothing;
          if (Event.InfoChar in ['0'..'9']) and
             (Places[Event.InfoChar].X <> -1) and
             (Places[Event.InfoChar].Y <> -1) then
          begin
            GetTextLine(Places[Event.InfoChar].Y, S);
            Event.InfoPtr := @S;
          end
          else Event.InfoPtr := nil;
        end;
      cmNewSwapStream: SwapStream := Event.InfoPtr;
      cmCtrlKW:
        begin
          WriteBlockToFile(Event.InfoPtr, False);
          ClearEvent(Event);
        end;
      cmlAPIChanged:
        begin
          lGetFileNameForChangedAPI(lAPIType(Event.InfoByte),
            SourceName^, S);
{
          DisposeStr(SourceName);
          SourceName := NewStr(S);
}
          DisposeStrUMB(SourceName);
          SourceName := NewStrUMB(S);
          PWindow(Owner)^.Frame^.DrawView;
          if PWindow(Owner)^.Icon <> nil then
            PWinIcon(PWindow(Owner)^.Icon)^.Update;
        end;
    end
  end;
end;

procedure THTMLViewer.HandleEvent(var Event: TEvent);
begin
  Inherited HandleEvent(Event);
  if Event.What = evKeyDown then HandleHTMLKey(@Self, Event)
  else if (Event.What = evMouseDown) and MouseInView(Event.Where) then
    HandleHTMLMouse(@Self, Event)
  else if Event.What = evCommand then HandleHTMLCommand(@Self, Event)
  else if Event.What = evBroadcast then HandleHTMLBroadcast(@Self, Event);
end;

procedure THTMLViewer.SetState(AState: Word; Enable: Boolean);
begin
  Inherited SetState(AState, Enable);
  if Enable and (AState = sfExposed) and (Owner <> nil) then
    PHTMLWindow(Owner)^.Indicator^.Hide;
  if Enable and (AState = sfFocused) then
  begin
    PHTMLWindow(Owner)^.Indicator^.Show;
    EnableCommands(HTMLViewerCommands);
    if Status and sfHilit <> 0 then
      EnableCommands(HTMLViewCommands + MTCopyCommands)
    else DisableCommands(HTMLViewCommands + MTCopyCommands);
    if IncSearch then EnableCommands(PasteCommands + MTPasteCommands)
    else DisableCommands(PasteCommands + MTPasteCommands);
    UpdateCopyCommands;
  end
  else if not Enable and
    (((AState = sfActive) and GetState(sfExposed)) or
     (AState = sfExposed)) then DisableCommands(
       HTMLViewerCommands + HTMLViewCommands +
       PasteCommands + MTCopyCommands + MTPasteCommands);
  if not Enable and (AState = sfFocused) then
  begin
    NewLines^.FreeAll;
    TopLine := MaxInt;
  end;
end;

procedure THTMLViewer.Store(var S: TStream);
begin
  Inherited Store(S);
  S.WriteStr(SourceName);
{
  S.Write(Area^, SizeOf(Area^) +
    SizeOf(SelectFromX) + SizeOf(SelectFromY) + SizeOf(Places) +
    SizeOf(Status) + SizeOf(HistLock) + SizeOf(HistIndex));
}
  S.Write(Area^, SizeOf(Area^));
  S.Write(SelectFromX, SizeOf(SelectFromX));
  S.Write(SelectFromY, SizeOf(SelectFromY));
  S.Write(Places, SizeOf(Places));
  S.Write(Status, SizeOf(Status));
  S.Write(HistLock, SizeOf(HistLock));
  S.Write(HistIndex, SizeOf(HistIndex));

  S.Put(HTMLList);
  if Status and sfXLAT <> 0 then
    S.Write(XLATTable, SizeOf(ConvertTable));
end;

function THTMLViewer.Valid(Command: Word): Boolean;
begin
  Valid := Inherited Valid(Command) and
    (IsValid or (State and sfExposed <> 0));
end;

function THTMLViewer.Inserted: Boolean;
begin
  Inserted := (Owner <> nil) and (Owner^.Owner <> nil);
end;

procedure THTMLViewer.UpdateSubViews;
begin
  with PHTMLWindow(Owner)^ do
  begin
    Indicator^.DrawView;
    HTMLTitle^.DrawView;
    HTMLInfo^.DrawView;
    UpdateCopyCommands;
  end;
end;

procedure THTMLViewer.UpdateCopyCommands;
begin
  if CurRef <> nil then
    EnableCommands(CopyCommands + MTCopyCommands)
  else if Status and sfHilit = 0 then
    DisableCommands(CopyCommands + MTCopyCommands);
end;

function THTMLViewer.SameHTML(const Name: FNameStr): Boolean;
var
  SR: SearchRec;
  S: FNameStr;
begin
  if CompUCString(Name, SourceName^) = Equal then SameHTML := True
  else if Status and sfFromArchive <> 0 then SameHTML := False
  else
  begin
    S := QuasiShortName(Name);
    if CompUCString(S, QuasiShortName(SourceName^)) <> Equal then
      SameHTML := False
    else
    begin
      FindFirst(S, AnyFile, SR);
      SameHTML := (DosError = 0) and (SR.Time = LastTime);
    end;
  end;
end;

procedure THTMLViewer.GetValidLine(AnIndex: Integer; var ALine: String);
var
  XLAT: Boolean;
  I, C: Integer;
  P: PString;
  L: Longint;
  S: String;
label
  Quit;
begin
  if AnIndex >= FileLines^.Count then ALine := ''
  else
  begin
    if (AnIndex < TopLine) or
       (AnIndex >= TopLine + NewLines^.Count) then
    begin
      GiveTicks;
      NewLines^.FreeAll;
      TopLine := AnIndex - NumLines div 2;
      if TopLine + NumLines > FileLines^.Count then
        TopLine := FileLines^.Count - NumLines;
      if TopLine < 0 then TopLine := 0;
      I := TopLine;
      while (I < TopLine + NumLines) and (I < FileLines^.Count) do
      begin
        L := Longint(PSwapLinesCollection(FileLines)^.ExtraAt(I));
        if L = 0 then NewLines^.Insert(nil)
        else with SwapStream^ do
        begin
          Seek(L);
          Read(S[0], 1);
          Read(S[1], Byte(S[0]));
          if Status <> stOk then
          begin
            Reset;
            I := TopLine;
            SwapReadErrorMsgBox;
            TopLine := I;
            for I := NewLines^.Count to NumLines - 1 do
              NewLines^.Insert(nil);
            Break;
          end;
          C := NewLines^.Count;
{          NewLines^.Insert(NewStr(S));}
          NewLines^.Insert(NewStrUMB(S));
          if C = NewLines^.Count then
          begin
            L := Longint(PSwapLinesCollection(FileLines)^.ExtraAt(AnIndex));
            if L = 0 then ALine := ''
            else with SwapStream^ do
            begin
              Seek(L);
              Read(ALine[0], 1);
              Read(ALine[1], Byte(ALine[0]));
              if Status <> stOk then Reset;
            end;
            Goto Quit;
          end;
        end;
        Inc(I);
      end;
    end;
    I := AnIndex - TopLine;
    P := NewLines^.At(I);
    if P = nil then ALine := ''
    else ALine := P^;
Quit:
    if ALine <> '' then
    begin
      XLAT := Status and sfXLAT <> 0;
      I := 1;
      while I <= Length(ALine) do
      begin
        if XLAT then ALine[I] := XLATTable[ALine[I]];
        Inc(I);
        ALine[I] := Application^.GetPalette^[Byte(ALine[I])];
        Inc(I);
      end;
    end;
  end;
end;

procedure THTMLViewer.GetTextLine(AnIndex: Integer; var ALine: String);
var
  I: Integer;
  S: String;
begin
  GetValidLine(AnIndex, S);
  ALine := '';
  I := 1;
  while I < Length(S) do
  begin
    Inc(ALine[0]);
    ALine[Length(ALine)] := S[I];
    Inc(I, 2);
  end;
end;

procedure THTMLViewer.FastScrollTo(Y: Integer);
var
  OldValue: Integer;
begin
  with VScrollBar^ do
  begin
    OldValue := Value;
    if Y < Min then Value := Min
    else if Y > Max then Value := Max
    else Value := Y;
    if Value <> OldValue then DrawView;
    Delta.Y := Value;
  end;
  DrawView;
end;

procedure THTMLViewer.Highlight(AKB: Integer; APB: Byte;
  AKK: Integer; APK: Byte);
begin
  if AKB > FileLines^.Count then AKB := FileLines^.Count;
  if AKK > FileLines^.Count then AKK := FileLines^.Count;
  if Status and sfHilit = 0 then
  begin
    SelectFromX := APB;
    SelectFromY := AKB;
  end;
  SetArea(Area, AKB, APB, AKK, APK);
  if (AKB < AKK) or ((AKB = AKK) and (APB <= APK)) then
    Status := Status or sfHilit
  else Status := Status and not sfHilit;
  if Status and sfHilit <> 0 then
    EnableCommands(BlockCommands + MTCopyCommands)
  else DisableCommands(BlockCommands + MTCopyCommands);
end;

procedure THTMLViewer.Unselect;
begin
  Highlight(0, 1, 0, 1);
  SelectFromX := 1; SelectFromY := 0;
  Status := Status and not sfHilit;
  DisableCommands(BlockCommands + MTCopyCommands);
end;

procedure THTMLViewer.SelectOn(CurY: Integer; CurX: Byte);
begin
  if (CurY = SelectFromY) and (CurX = SelectFromX) then
    Highlight(CurY, CurX, CurY, CurX)
  else if (CurY < SelectFromY) or
    ((CurY = SelectFromY) and (CurX < SelectFromX)) then
      Highlight(CurY, CurX, SelectFromY, SelectFromX)
  else Highlight(SelectFromY, SelectFromX, CurY, CurX);
end;

function THTMLViewer.Selected(AnIndex: Integer): Boolean;
begin
  Selected := (Status and sfHilit <> 0) and
    ((AnIndex >= Area^.KB) and (AnIndex <= Area^.KK));
end;

procedure THTMLViewer.GetPos(AnArea: PArea; AnIndex: Integer;
  var APos: TPos);
begin
  if MarkColumns then
  begin
    if AnArea^.PB > AnArea^.PK then
    begin
      APos.AStart := AnArea^.PK;
      APos.AnEnd := AnArea^.PB;
    end
    else
    begin
      APos.AStart := AnArea^.PB;
      APos.AnEnd := AnArea^.PK;
    end;
  end
  else
  begin
    if AnIndex = AnArea^.KB then APos.AStart := AnArea^.PB
    else APos.AStart := 1;
    if AnIndex = AnArea^.KK then APos.AnEnd := AnArea^.PK
    else APos.AnEnd := SizeOf(String);
  end;
end;

procedure THTMLViewer.GetSelectedString(AnIndex: Integer; var ALine: String);
var
  Pos: TPos;
begin
  GetTextLine(AnIndex, ALine);
  GetPos(Area, AnIndex, Pos);
  ALine := System.Copy(ALine, Pos.AStart, Pos.AnEnd - Pos.AStart);
end;

procedure THTMLViewer.Scroll(Key: Word);
begin
  DrawFlag := True;
  case Key of
    kbDown:      GoDown;
    kbUp:        GoUp;
    kbRight:     GoRight;
    kbLeft:      GoLeft;
    kbPgDn:      GoPgDn;
    kbPgUp:      GoPgUp;
    kbCtrlPgDn:  GoCtrlPgDn;
    kbCtrlPgUp:  GoCtrlPgUp;
    kbCtrlRight: GoCtrlRight;
    kbCtrlLeft:  GoCtrlLeft;
    kbCtrlUp:    GoCtrlUp;
    kbCtrlDn:    GoCtrlDown;
    kbHome:      GoHome;
    kbEnd:       GoEnd;
    kbCtrlHome:  GoCtrlHome;
    kbCtrlEnd:   GoCtrlEnd;
    kbEnter:     GoToRefLink;
    kbBack:      GoBack;
    kbTab:       GoToRef(False);
    kbShiftTab:  GoToRef(True);
    kbAltY:      PlaceMarkerDlg(@Self, Status and sfCtrlQPressed = 0);
  end;
  DrawFlag := False;
  DrawView;
  if (KeyboardOptions and koDisableMultiScroll = 0) and
     (GetShiftState and kbScrollState <> 0) and
     not MultiScrolling and IsScrollKey(Key) then
       MultiScroll(PWindow(Owner), Key);
end;

procedure THTMLViewer.GoRight;
begin
  if Delta.X + Cursor.X < Limit.X - 1 then
  begin
    SetCursor(Cursor.X + 1, Cursor.Y);
    if Cursor.X = Size.X then ScrollTo(Delta.X + 1, Delta.Y);
  end;
end;

procedure THTMLViewer.GoLeft;
begin
  if Delta.X + Cursor.X > 0 then
  begin
    SetCursor(Cursor.X - 1, Cursor.Y);
    if Cursor.X < 0 then ScrollTo(Delta.X - 1, Delta.Y);
  end;
end;

procedure THTMLViewer.GoWhileKeyDown(Key: Byte);
begin
  if MultiScrolling or
    ((KeyboardOptions and koDisableMultiScroll = 0) and
     (GetShiftState and kbScrollState <> 0)) then Exit;
  DrawFlag := False;
  DrawView;
  repeat
    if (KbdStatus <> Key) and (KbdStatus <> 224) then Exit;
  until KeyPressed;
  repeat
    ClearKbd;
    if (KbdStatus <> Key) and (KbdStatus <> 224) then Exit;
    case Key of
      72: if Delta.Y + Cursor.Y = 0 then Exit
          else FastScrollTo(Delta.Y - 1);
      73: if Delta.Y = 0 then Exit
          else FastScrollTo(Delta.Y - Size.Y + 1);
      80: if Delta.Y + Cursor.Y >= FileLines^.Count then Exit
          else FastScrollTo(Delta.Y + 1);
      81: if Delta.Y + Size.Y >= FileLines^.Count then Exit
          else FastScrollTo(Delta.Y + Size.Y - 1);
    end;
    if KbdDelay <> 0 then Delay(KbdDelay);
  until False;
end;

procedure THTMLViewer.GoDown;
begin
  if Delta.Y + Cursor.Y < FileLines^.Count then
  begin
    if Cursor.Y <> Size.Y - 1 then SetCursor(Cursor.X, Cursor.Y + 1)
    else
    begin
      FastScrollTo(Delta.Y + 1);
      if ((KbdStatus = 80) or (KbdStatus = 224)) and
         (GetShiftState and (kbLeftShift + kbRightShift) = 0) then
        GoWhileKeyDown(80);
    end;
  end;
end;

procedure THTMLViewer.GoUp;
begin
  if Delta.Y + Cursor.Y > 0 then
  begin
    if Cursor.Y > 0 then SetCursor(Cursor.X, Cursor.Y - 1)
    else
    begin
      FastScrollTo(Delta.Y - 1);
      if ((KbdStatus = 72) or (KbdStatus = 224)) and
         (GetShiftState and (kbLeftShift + kbRightShift) = 0) then
        GoWhileKeyDown(72);
    end;
  end;
end;

procedure THTMLViewer.GoPgDn;
begin
  if Delta.Y + Size.Y >= FileLines^.Count then GoCtrlPgDn
  else
  begin
    FastScrollTo(Delta.Y + Size.Y - 1);
    if ((KbdStatus = 81) or (KbdStatus = 224)) and
       (GetShiftState and (kbLeftShift + kbRightShift) = 0) then
      GoWhileKeyDown(81);
  end;
  if Delta.Y + Cursor.Y > FileLines^.Count then
    SetCursor(Cursor.X, FileLines^.Count - Delta.Y);
end;

procedure THTMLViewer.GoPgUp;
begin
  if Delta.Y = 0 then GoCtrlPgUp
  else
  begin
    FastScrollTo(Delta.Y - Size.Y + 1);
    if ((KbdStatus = 73) or (KbdStatus = 224)) and
       (GetShiftState and (kbLeftShift + kbRightShift) = 0) then
      GoWhileKeyDown(73);
  end;
end;

procedure THTMLViewer.GoCtrlPgDn;
begin
  GoToPos(0, FileLines^.Count, False);
  GoEnd;
end;

procedure THTMLViewer.GoCtrlPgUp;
begin
  GoToPos(0, 0, False);
end;

procedure THTMLViewer.GoCtrlRight;
var
  S: String;
begin
  GetTextLine(Delta.Y + Cursor.Y, S);
  TrailTrim(S);
  if (Delta.X + Cursor.X >= Length(S)) or
     (Delta.X + Cursor.X >= Limit.X - 1) then
  begin
    GoDown;
    GoHome;
  end
  else if GetShiftState and (kbAltShift + kbCtrlShift) =
    kbAltShift + kbCtrlShift then
  begin
    while (Delta.X + Cursor.X < Length(S)) and
      (Delta.X + Cursor.X < Limit.X - 1) and
      not WordChar(S[Delta.X + Cursor.X + 1]) do GoRight;
    while (Delta.X + Cursor.X < Length(S)) and
      (Delta.X + Cursor.X < Limit.X - 1) and
      WordChar(S[Delta.X + Cursor.X + 1]) do GoRight;
  end
  else
  begin
    while (Delta.X + Cursor.X < Length(S)) and
      (Delta.X + Cursor.X < Limit.X - 1) and
      WordChar(S[Delta.X + Cursor.X + 1]) do GoRight;
    while (Delta.X + Cursor.X < Length(S)) and
      (Delta.X + Cursor.X < Limit.X - 1) and
      not WordChar(S[Delta.X + Cursor.X + 1]) do GoRight;
  end;
end;

procedure THTMLViewer.GoCtrlLeft;
var
  S: String;
begin
  GetTextLine(Delta.Y + Cursor.Y, S);
  TrailTrim(S);
  if Delta.X + Cursor.X = 0 then
  begin
    GoUp;
    GoEnd;
  end
  else if GetShiftState and (kbAltShift + kbCtrlShift) =
    kbAltShift + kbCtrlShift then
  begin
    if Delta.X + Cursor.X > Length(S) then GoEnd
    else
    begin
      while (Delta.X + Cursor.X > 0) and
        WordChar(S[Delta.X + Cursor.X]) do GoLeft;
      while (Delta.X + Cursor.X > 0) and
        not WordChar(S[Delta.X + Cursor.X]) do GoLeft;
    end;
  end
  else
  begin
    if Delta.X + Cursor.X > Length(S) then GoEnd;
    while (Delta.X + Cursor.X > 0) and
      not WordChar(S[Delta.X + Cursor.X]) do GoLeft;
    while (Delta.X + Cursor.X > 0) and
      WordChar(S[Delta.X + Cursor.X]) do GoLeft;
  end;
end;

procedure THTMLViewer.GoCtrlUp;
begin
  FastScrollTo(Delta.Y - 1);
end;

procedure THTMLViewer.GoCtrlDown;
begin
  FastScrollTo(Delta.Y + 1);
  if Delta.Y + Cursor.Y > FileLines^.Count then
    SetCursor(Cursor.X, Cursor.Y - 1);
end;

procedure THTMLViewer.GoHome;
begin
  GoToPos(0, Delta.Y + Cursor.Y, False);
end;

procedure THTMLViewer.GoEnd;
var
  Y: Integer;
  S: String;
begin
  Y := Delta.Y + Cursor.Y;
  GetTextLine(Y, S);
  TrailTrim(S);
  GoToPos(Length(S), Y, False);
end;

procedure THTMLViewer.GoCtrlHome;
begin
  SetCursor(Cursor.X, 0);
end;

procedure THTMLViewer.GoCtrlEnd;
var
  NewY: Integer;
begin
  if Delta.Y + Size.Y > FileLines^.Count then
    NewY := FileLines^.Count - Delta.Y
  else NewY := Size.Y - 1;
  SetCursor(Cursor.X, NewY);
end;

procedure THTMLViewer.GoToPos(APos: Byte; AnIndex: Integer; OnTop: Boolean);
var
  X, SX: Byte;
  Within: Boolean;
  Y, SY: Integer;
begin
  if AnIndex > FileLines^.Count then AnIndex := FileLines^.Count;
  Within := not OnTop and
    (AnIndex < Delta.Y + Size.Y) and (AnIndex >= Delta.Y);
  if Within and (APos < Delta.X + Size.X) and (APos >= Delta.X) then
    SetCursor(APos - Delta.X, AnIndex - Delta.Y)
  else
  begin
    X := APos;
    if X > Size.X - 1 then X := Size.X - 1;
    SX := 0;
    if SX + Size.X - 1 < APos then SX := APos - (Size.X - 1);
    if AnIndex = FileLines^.Count then SY := AnIndex - (Size.Y - 1)
    else if Within then SY := Delta.Y
    else if OnTop then SY := AnIndex
    else SY := AnIndex - Size.Y div 2;
    if SY < 0 then SY := 0;
    if AnIndex = FileLines^.Count then Y := Size.Y - 1
    else if Within then Y := AnIndex - Delta.Y
    else if OnTop then Y := 0
    else Y := Size.Y div 2;
    if SY = 0 then Y := AnIndex;
    ScrollTo(SX, SY);
    SetCursor(X, Y);
  end;
  DrawView;
end;

procedure THTMLViewer.GoBack;
begin
  AdjustHistIndex;
  if HistIndex > 0 then GoToHistoryItem(HistIndex - 1);
end;

procedure THTMLViewer.GoToLineNum(AnIndex: Integer);
begin
  if (AnIndex <= FileLines^.Count + 1) and (AnIndex > 0) then
    GoToPos(0, AnIndex - 1, False);
end;

procedure THTMLViewer.GoToRef(Back: Boolean);
var
  P: PRefItem;
begin
  P := TheHTML^.Refs^.NextRef(
    Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1, Back);
  if P <> nil then
    if not Back and (P^.Line1 = Delta.Y + Cursor.Y) and
      (P^.Pos1 - 1 = Delta.X + Cursor.X) then GoRight
    else GoToPos(P^.Pos1 - 1, P^.Line1, False);
end;

procedure THTMLViewer.ReadFile(const Name: FNameStr);
var
  B: Boolean;
  Len, MaxLen: Word;
  L: Longint;
  Line: String;
begin
  SwapStream := DiskSwapStream;
  if SwapStream = nil then
  begin
    SwapAccessErrorMsgBox;
    IsValid := False;
    Exit;
  end;
  Status := Status and not (sfFromArchive + sfError);
  if Inserted then Status := Status and not sfBrowser;
  GetValidSwapDir(Line, False);
  if UCPos(Line, Name) <> 0 then Status := Status or sfFromArchive;
  XLATKind := xAlt;
  IsValid := True;
  TopLine := MaxInt;
  TrackLine := -1;
  Len := ScreenWidth - 3;
  if Len > 126 then Len := 126;
  MaxLen := Len;
  FileLines := New(PSwapLinesCollection, Init(16, 128));
  NewLines := New(PStringsCollection, Init(NumLines, 1));
  FindAllList := New(PSwapLinesCollection, Init(8, 8));
  if HTMLList = nil then HTMLList := New(PHTMLList, Init(8, 8));
  TheHTML := New(PHTML, Init(Len, Status and sfNoTables = 0));
  if not TheHTML^.InitStream(Name, Status and sfFromArchive <> 0) then
    if Inserted then
    begin
      Status := Status or sfError;
      TheHTML^.HTMLTitle := 'Error';
      L := SwapStream^.GetSize;
      SwapStream^.Seek(L);
      Line := Concat(
        ColoredLine(' Could not open ', #231, Len),
        ColoredLine(TrimmedName(Name, Len - 18), #232, Len - 18),
        ColoredLine('.', #231, Len));
      SwapWrite(SwapStream, -1, Line, Length(Line) + 1);
      if SwapStream^.Status = stOk then
      begin
        FileLines^.Insert(Pointer(L));
        SwapSize := L + Length(Line) + 1;
        LastTime := 0;
      end;
    end
    else
    begin
      CouldNotOpenMsgBox(Name);
      IsValid := False;
    end
  else if IsDevice(TheHTML^.FHTML^.Handle) then
  begin
    FileIsDeviceMsgBox(Name);
    IsValid := False;
  end
  else
  begin
    LogEvent := lcDiskRead;
    DrawStatusLine('Processing HTML file ' + TrimmedName(Name, 46) + '.');
    L := SwapStream^.GetSize;
    SwapStream^.Seek(L);
    while TheHTML^.FHtml^.Status = stOk do
    begin
      Line := TheHTML^.GetLine;
      if not TheHTML^.FirstLine then Continue;
      if Line = '' then FileLines^.Insert(nil)
      else
      begin
        SwapWrite(SwapStream, -1, Line, Length(Line) + 1);
        if SwapStream^.Status = stOk then
        begin
          FileLines^.Insert(Pointer(L));
          Inc(L, Length(Line) + 1);
          if MaxLen < Length(Line) shr 1 then
            MaxLen := Length(Line) shr 1;
        end
        else Break;
      end;
      if PSwapLinesCollection(FileLines)^.Status = coOverflow then Break;
      if (KbdStatus = 1) and AbortAction then
      begin
        IsValid := False;
        Break;
      end;
    end;
    SwapSize := L;
    if SwapStream^.Status <> stOk then
      if TruncateNoSwapMsgBox(Name) = cmCancel then IsValid := False;
    if PSwapLinesCollection(FileLines)^.Status = coOverflow then
      if TruncateLinesMsgBox(Name) = cmCancel then IsValid := False;
    SwapStream^.Reset;
    PSwapLinesCollection(FileLines)^.Status := 0;
    GetFTime(FileRec(Addr(TheHTML^.FHTML^.Handle)^), LastTime);
    if ReadLocalConvertTable(XLATTable, B) then
      Status := Status or sfXLAT
    else
    begin
      L := TheHTML^.Alt;
      if TheHTML^.Koi > L then L := TheHTML^.Koi;
      if TheHTML^.Win > L then L := TheHTML^.Win;
      if L <> 0 then
      begin
        if L = TheHTML^.Koi then XLATKind := xKoi
        else if L = TheHTML^.Win then XLATKind := xWin;
        if XLATKind <> xAlt then
        begin
          FillXLATTable(XLATKind, XLATTable);
          Status := Status or sfXLAT;
        end;
      end;
    end;
  end;
  TheHTML^.DoneStream;
  TheHTML^.Names^.IgnorePos := True;
{
  DisposeStr(SourceName);
  SourceName := NewStr(Name);
}
  DisposeStrUMB(SourceName);
  SourceName := NewStrUMB(Name);
  if MaxLen > 126 then MaxLen := 126;
  Limit.X := MaxLen + 1;
  Limit.Y := FileLines^.Count;
  if Limit.Y > 0 then Inc(Limit.Y, Size.Y);
  SetLimit(Limit.X, Limit.Y);
  ShowCursor;
  if Inserted then GoCtrlPgUp;
  if IsValid then UpdateHistory;
  if Delta.Y + Cursor.Y > FileLines^.Count then GoCtrlPgDn;
end;

procedure THTMLViewer.SwapTruncate;

function IsFirst(P: Pointer): Boolean; far;
begin
  IsFirst := P <> nil;
end;

var
  P: Pointer;
begin
  if (DiskSwapStream <> nil) and (SwapStream = DiskSwapStream) and
     (SwapStream^.GetSize = SwapSize) then
  begin
    P := FileLines^.FirstThat(@IsFirst);
    if P <> nil then
    begin
      SwapStream^.Seek(Longint(P));
      SwapStream^.Truncate;
    end;
  end;
end;

function THTMLViewer.WordChar(Ch: Char): Boolean;
begin
  WordChar := not (Ch in WordDelims);
end;

function THTMLViewer.GetWord(var B, E: Byte): String;
var
  APos, BPos, EPos: Byte;
  AnIndex: Integer;
  S: String;
begin
  APos := Delta.X + Cursor.X + 1;
  AnIndex := Delta.Y + Cursor.Y;
  GetWord := '';
  B := 0; E := 0;
  GetTextLine(AnIndex, S);
  TrailTrim(S);
  if APos > Length(S) then Exit;
  EPos := APos;
  while (EPos > 1) and not WordChar(S[EPos]) do Dec(EPos);
  if not WordChar(S[EPos]) then Exit;
  BPos := EPos;
  while (BPos > 0) and WordChar(S[BPos]) do Dec(BPos);
  while (EPos < 255) and (EPos <= Length(S)) and
    WordChar(S[EPos]) do Inc(EPos);
  B := BPos + 1; E := EPos;
  GetWord := System.Copy(S, B, EPos - BPos - 1);
end;

function THTMLViewer.GetLine: String;
var
  APos: Byte;
  AnIndex: Integer;
  S: String;
begin
  APos := Delta.X + Cursor.X + 1;
  AnIndex := Delta.Y + Cursor.Y;
  GetTextLine(AnIndex, S);
  TrailTrim(S);
  if APos > Length(S) then APos := 1;
  while (APos > 0) and not WordChar(S[APos]) do Dec(APos);
  while (APos > 0) and WordChar(S[APos]) do Dec(APos);
  GetLine := System.Copy(S, APos + 1, 255);
end;

function THTMLViewer.GetPrintLine(AnIndex: Integer; Sel: Boolean;
  var ALine: String): Boolean;
begin
  if (AnIndex >= 0) and (AnIndex < FileLines^.Count) then
  begin
    if Sel and (Status and sfHilit <> 0) then
      GetSelectedString(AnIndex, ALine)
    else GetTextLine(AnIndex, ALine);
    GetPrintLine := True;
  end
  else GetPrintLine := False;
end;

procedure THTMLViewer.AdjustHistIndex;
begin
  if HistIndex < 0 then HistIndex := 0
  else if HistIndex > HTMLList^.Count then
    HistIndex := HTMLList^.Count;
end;

procedure THTMLViewer.AddToHistory;
var
  B, E: Byte;
  X, Y: Integer;
  S: String;

function SameRefPos(P: PHTMLItem): Boolean;
begin
  if CurRef = nil then SameRefPos := False
  else with CurRef^, P^ do
  begin
    SameRefPos :=
      ((Line > Line1) and (Line < Line2)) or
      ((Line1 = Line2) and (Line = Line1) and
       (Pos + 1 >= Pos1) and (Pos < Pos2)) or
      ((Line1 <> Line2) and
       (((Line = Line1) and (Pos + 1 >= Pos1)) or
       ((Line = Line2) and (Pos < Pos2))));
  end;
end;

function SameNames(P: PHTMLItem): Boolean;
begin
  SameNames :=
    (P <> nil) and (P^.PFName^ = SourceName^) and SameRefPos(P) and
    (((P^.PRefName = nil) and (S = '')) or
     ((P^.PRefName <> nil) and (P^.PRefName^ = S)));
end;

begin
  X := Delta.X + Cursor.X;
  Y := Delta.Y + Cursor.Y;
  if CurRef = nil then S := GetWord(B, E)
  else
  begin
    GetTextLine(Y, S);
    if Y = CurRef^.Line1 then B := CurRef^.Pos1
    else B := 1;
    if Y = CurRef^.Line2 then E := CurRef^.Pos2
    else E := 255;
    S := Trim(Copy(S, B, E - B));
  end;
  AdjustHistIndex;
  if SameNames(HTMLList^.At(HistIndex - 1)) then Dec(HistIndex);
  if SameNames(HTMLList^.At(HistIndex)) then HTMLList^.AtFree(HistIndex);
  if PHTMLList(HTMLList)^.AddItem(HistIndex, SourceName^, S, Y, X) then
    Inc(HistIndex);
end;

procedure THTMLViewer.FindRef(const RefName: String);
var
  I: Integer;
  P: PNameItem;
  R: TNameItem;
begin
  if RefName <> '' then
  begin
    R.PRefName := @RefName;
    if TheHTML^.Names^.Search(@R, I) then
    begin
      P := TheHTML^.Names^.At(I);
      GoToPos(P^.Pos, P^.Line, True);
    end
    else NotFoundRef := RefName;
  end;
end;

procedure THTMLViewer.HTMLExecute(const Name: String);
var
  HTML, Browser, SaveExplode: Boolean;
  Key, SaveOpt: Word;
  SaveEditOpt: Longint;
  R: TRect;
  Event: TEvent;

function Executable: Boolean;
var
  Ext: String[3];
begin
  Ext := StUpcase(JustExtension(Name));
  Executable := (Ext = 'BAT') or (Ext = 'COM') or (Ext = 'EXE');
end;

begin
  HTML := False;
  Browser := False;
  FillChar(ExecRec, SizeOf(ExecRec), 0);
  Key := GetShiftState;
  if Key and kbAltShift <> 0 then Key := 1
  else if Key and (kbLeftShift + kbRightShift) <> 0 then Key := 2
  else Key := 0;
  if (Key = 0) and Executable then ExecRec.Path := Name
  else
  begin
    if not ExtractExecRec(ExecRec, JustFileName(Name), Key, True) then
      ExecRec.Path := Concat('$VIEW(', Name, ')');
    if UCPos('$HTML', ExecRec.Path) = 0 then
    begin
      Dec(HistIndex);
      if HistIndex >= 0 then HTMLList^.AtFree(HistIndex);
    end
    else
    begin
      HTML := True;
      Browser := Status and sfBrowser <> 0;
      if not Browser then
        ExecRec.Params := Concat(ExecRec.Params, '$SAMEWIN');
    end;
  end;
  PrepareExecEvent;
  if not HTML and not Browser then Exit;
  if Browser then
  begin
    SaveExplode := DoExplode;
    DoExplode := False;
    Desktop^.Lock;
    if Owner^.Size.Y > Desktop^.Size.Y - 5 then
    begin
      Owner^.GetBounds(R);
      R.B.Y := R.B.Y div 2;
      Owner^.Locate(R);
      GoToPos(Delta.X + Cursor.X, Delta.Y + Cursor.Y, False);
    end;
    SaveEditOpt := EditOptions;
    EditOptions := EditOptions or eoCascade;
    SaveOpt := Owner^.Options;
    Owner^.Options := (Owner^.Options and not ofTileable) or ofValidate;
  end;
  Event := Pending;
  Pending.What := evNothing;
  Application^.HandleEvent(Event);
  if Browser then
  begin
    Owner^.Select;
    Desktop^.Unlock;
    DoExplode := SaveExplode;
    EditOptions := SaveEditOpt;
    Owner^.Options := SaveOpt;
  end;
end;

procedure THTMLViewer.GoToRefLink;
var
  Browser: Boolean;
  Name: String;
begin
  if (CurRef = nil) or
     ((CurRef^.PFName = nil) and (CurRef^.PRefName = nil)) then Exit;
  Browser := CurRef^.Scrolling;
  AddToHistory;
  if CurRef^.PFName = nil then Name := ''
  else Name := CurRef^.PFName^;
  HTMLFullName(SourceName^, Name, Name);
  if not SameHTML(Name) then HTMLExecute(Name)
  else if CurRef^.PRefName <> nil then FindRef(CurRef^.PRefName^);
  if Browser then Status := Status or sfBrowser;
end;

procedure THTMLViewer.GoToHistoryItem(Index: Integer);
var
  P: PHTMLItem;
begin
  P := HTMLList^.At(Index);
  if P = nil then Exit;
  HistIndex := Index;
  DrawFlag := True;
  if not SameHTML(P^.PFName^) then SwitchToFile(P^.PFName^);
  GoToPos(P^.Pos, P^.Line, False);
  DrawFlag := False;
  DrawView;
end;

procedure THTMLViewer.SwitchToFile(const Name: FNameStr);
var
  Same, SaveFlag: Boolean;
  SaveX, SaveY: Integer;
  S: FNameStr;
begin
  SwapTruncate;
  if TheHTML <> nil then Dispose(TheHTML, Done);
  if FindAllList <> nil then Dispose(FindAllList, Done);
  if NewLines <> nil then Dispose(NewLines, Done);
  if FileLines <> nil then Dispose(FileLines, Done);
  Same := Name = '';
  if Same then
  begin
    S := SourceName^;
    SaveX := Delta.X + Cursor.X;
    SaveY := Delta.Y + Cursor.Y;
  end
  else S := Name;
  SaveFlag := DrawFlag;
  DrawFlag := True;
  ReadFile(S);
  IsValid := True;
  Unselect;
  FillChar(Places, SizeOf(Places), $FF);
  if Same then GoToPos(SaveX, SaveY, False);
  DrawFlag := SaveFlag;
  DrawView;
  PWindow(Owner)^.Frame^.DrawView;
end;

procedure THTMLViewer.UpdateTime;
var
  SR: lSearchRec;
begin
  lFindFirst(SourceName^, AnyFile and not (VolumeID + Directory), SR);
  lFindClose(SR);
  if (DosError = 0) and (SR.SR.Time <> LastTime) and
     (TimeChangedMsgBox('HTML', SourceName^) = cmOk) then
    SwitchToFile('');
end;

procedure THTMLViewer.SetPlaceMarker(AChar: Char);
begin
  Places[AChar].X := Delta.X + Cursor.X;
  Places[AChar].Y := Delta.Y + Cursor.Y;
end;

procedure THTMLViewer.GoToPlaceMarker(AChar: Char);
begin
  if (Places[AChar].X <> -1) and (Places[AChar].Y <> -1) then
    GoToPos(Places[AChar].X, Places[AChar].Y, False);
end;

procedure THTMLViewer.CopyToClipboard;
var
  I, SaveCount: Integer;
  SaveArea: TArea;
  C, S: String;
begin
  if DiskClipStream = nil then Exit;
  DrawClipCopyStatus;
  SetExpandClipAreaFlag;
  SaveCount := ClipLines^.Count;
  SaveArea := Area^;
  if (Status and sfHilit = 0) and (CurRef <> nil) then
  begin
    if CurRef^.PFName = nil then C := ''
    else C := CurRef^.PFName^;
    if CurRef^.PRefName = nil then S := ''
    else S := CurRef^.PRefName^;
    if Length(C) + Length(S) >= 255 then PutClipLine(C)
    else if S = '' then S := C
    else if C <> '' then S := Concat(C, '#', S);
    PutClipLine(S);
  end
  else
  begin
    if Status and sfHilit = 0 then
    begin
      I := Delta.Y + Cursor.Y;
      GetValidLine(I, S);
      Area^.KB := I;
      Area^.KK := I;
      Area^.PB := 1;
      I := Length(S);
      if I < 255 then Inc(I);
      Area^.PK := I;
    end;
    I := Area^.KB;
    while I <= Area^.KK do
    begin
      GetSelectedString(I, S);
      PutClipLine(S);
      if ClipOverflow or ((KbdStatus = 1) and AbortAction) then Break;
      Inc(I);
    end;
  end;
  SetOrExpandClipArea(SaveCount, Length(S) + 1);
  if MarkColumns then SetArea(ClipArea, ClipArea^.KB, ClipArea^.PB,
    ClipArea^.KK, Area^.PK - Area^.PB + 1);
  Area^ := SaveArea;
  Message(Desktop, evBroadcast, cmUpdateViews, nil);
  EnableCommands(PasteCommands);
end;

procedure THTMLViewer.WriteTextToFile(const Name: FNameStr;
  Sel, Append: Boolean);
var
  I, IHi: Integer;
  F: lText;
  S: String;
begin
  DrawWriteDataStatus(Name);
  lAssignText(F, Name);
  if Append then
  begin
    System.Append(F.T);
    if IOResult <> 0 then Rewrite(F.T);
  end
  else Rewrite(F.T);
  if IOResult <> 0 then
  begin
    CouldNotCreateMsgBox(Name);
    Exit;
  end;
  if NotAccessible(TextRec(F.T).Handle) then
  begin
    Close(F.T);
    InOutRes := 0;
    FileIsDeviceMsgBox(Name);
    Exit;
  end;
  if TextReadMode = trUnix then SetUNIXTextMode;
  if Sel then
  begin
    I := Area^.KB;
    IHi := Area^.KK;
  end
  else
  begin
    I := 0;
    IHi := FileLines^.Count - 1;
  end;
  while I <= IHi do
  begin
    if Sel then
    begin
      GetSelectedString(I, S);
      if MarkColumns then PadSt(S, Area^.PK - Area^.PB);
    end
    else GetTextLine(I, S);
    Writeln(F.T, S);
    if IOResult <> 0 then
    begin
      CouldNotWriteMsgBox(Name);
      Break;
    end;
    if (KbdStatus = 1) and AbortAction then Break;
    Inc(I);
  end;
  SetDOSTextMode;
  Close(F.T);
  InOutRes := 0;
  UpdateFiles(Name);
end;

procedure THTMLViewer.WriteBlockToFile(NamePtr: PString; Append: Boolean);
var
  C: Word;
  S: String[7];
  FName: FNameStr;
begin
  if Status and sfHilit = 0 then Exit;
  if NamePtr <> nil then FName := NamePtr^
  else
  begin
    if Append then S := 'Append' else S := 'Write';
    FName := '';
    if Application^.ExecuteDialog(New(PFileDialog, Init(
      '*.*', S + ' block to file', '~W~rite block to',
      fdOkButton + fdHelpButton, HistoryID)),
      @FName, False) = cmCancel then Exit;
  end;
  if SameHTML(FName) then
  begin
    CannotWriteToItselfMsgBox;
    Exit;
  end;
  if (NamePtr = nil) and not Append and ThisFileExists(FName) then
  begin
    C := AppendMessageBox(FName);
    case C of
      cmYes: Append := True;
      cmCancel: Exit;
    end;
  end;
  WriteTextToFile(FName, True, Append);
end;

procedure THTMLViewer.SaveAs;
var
  Append: Boolean;
  C: Word;
  FName: FNameStr;
begin
  Append := False;
  FName := HistoryStr(HistoryID, 0);
  if Application^.ExecuteDialog(New(PFileDialog, Init(
    '*.*', 'Save HTML text to file', '~N~ame',
    fdOkButton + fdHelpButton, HistoryID)),
    @FName, False) = cmCancel then Exit;
  if SameHTML(FName) then
  begin
    CannotWriteToItselfMsgBox;
    Exit;
  end;
  if ThisFileExists(FName) then
  begin
    C := AppendMessageBox(FName);
    case C of
      cmYes: Append := True;
      cmCancel: Exit;
    end;
  end;
  WriteTextToFile(FName, False, Append);
end;

procedure THTMLViewer.SetXLATTable(const Name: FNameStr; Prompt: Boolean);
var
  B, C: Boolean;
begin
  if Name <> '' then B := ReadConvertTable(XLATTable, Name, C)
  else B := GetConvertTable(XLATTable, Prompt, C);
  if C then ClearXLATTable;
  if B then Status := Status or sfXLAT;
end;

procedure THTMLViewer.ClearXLATTable;
begin
  Status := Status and not sfXLAT;
  XLATKind := xAlt;
  PWindow(Owner)^.Frame^.DrawView;
end;

function THTMLViewer.Search(const Match: String; AOptions: Word): Boolean;
var
  Back: Boolean;
  FoundPos, CutLen: Byte;
  SaveStatus: Word;
  Index, EI: Integer;
  RegExpr: Pointer;
  SelPos: TPos;
  SaveArea: TArea;
  Str, SubS, RGS: String;

procedure FindPos;
begin
  if AOptions and frWholeWordsOnly <> 0 then
    FoundPos := WordPos(SubS, Str, WordDelims, Back)
  else if Back then FoundPos := RPos(SubS, Str)
  else FoundPos := Pos(SubS, Str);
  if FoundPos <> 0 then Inc(FoundPos, CutLen - 1);
end;

begin
  Search := False;
  if Match = '' then Exit;
  if Byte(SearchingAll) <= 1 then DrawStatusLine(SearchMsg);
  SubS := Match;
  if AOptions and frCaseSensitive = 0 then UpcaseSt(SubS);
  if AOptions and frRegExp <> 0 then RegExpr := RegCompile(SubS)
  else RegExpr := nil;
  Back := AOptions and frBackward <> 0;
  if Status and sfHilit = 0 then AOptions := AOptions and not frSelectedText;
  if Byte(SearchingAll) > 1 then Index := FindAllIndex
  else if AOptions and frSelectedText <> 0 then
  begin
    if Back then Index := Area^.KK
    else Index := Area^.KB;
    if AOptions and frFromCursor <> 0 then
    begin
      EI := Delta.Y + Cursor.Y;
      if (EI >= Area^.KB) and (EI <= Area^.KK) then Index := EI
      else if (Back and (EI < Area^.KB)) or
        (not Back and (EI > Area^.KK)) then Index := -1;
    end;
  end
  else if AOptions and frFromCursor <> 0 then Index := Delta.Y + Cursor.Y
  else if Back then Index := FileLines^.Count - 1
  else Index := 0;
  if AOptions and frSelectedText <> 0 then
    if Back then EI := Area^.KB
    else EI := Area^.KK
  else if Back then EI := 0
  else EI := FileLines^.Count - 1;
  if Back and IncSearch and (EI < IncSearchPos) then
    EI := IncSearchPos;
  FoundPos := 0;
  if (Back and (Index >= EI)) or (not Back and (Index <= EI)) then
  repeat
    if (AOptions and frSelectedText <> 0) and Selected(Index) then
    begin
      GetSelectedString(Index, Str);
      GetPos(Area, Index, SelPos);
      CutLen := SelPos.AStart;
      if Back and (SelPos.AnEnd <= Length(Str)) then
        Str[0] := Char(SelPos.AnEnd - 1);
    end
    else
    begin
      GetTextLine(Index, Str);
      CutLen := 1;
    end;
    if (Index = Delta.Y + Cursor.Y) and
       (AOptions and frFromCursor <> 0) then
      if Back and (Delta.X + Cursor.X + 1 < Length(Str) + CutLen) then
        if Delta.X + Cursor.X < CutLen then Str := ''
        else Str[0] := Char(Delta.X + Cursor.X + 1 - CutLen)
      else if not Back and
        (Delta.X + Cursor.X + 1 + Byte(not SearchingFrom) > CutLen) then
      begin
        System.Delete(Str, 1,
          Delta.X + Cursor.X + 1 + Byte(not SearchingFrom) - CutLen);
        CutLen := Delta.X + Cursor.X + 1 + Byte(not SearchingFrom);
      end;
    if Str <> '' then
    begin
      if (AOptions and frCaseSensitive = 0) then UpcaseSt(Str);
      if (AOptions and frRegExp <> 0) and (RegExpr <> nil) then
      begin
        if Back then RGS := RegSearchBack(RegExpr, Str)
        else RGS := RegSearch(RegExpr, Str);
        if RGS <> '' then SubS := RGS;
      end;
      FindPos;
    end;
    if (KbdStatus = 1) and AbortAction then
    begin
      FoundPos := 0;
      FindAllIndex := -1;
      SearchingAll := True;
      Break;
    end;
    if FoundPos <> 0 then Break;
    if Back then
    begin
      Dec(Index);
      if Index < EI then Break;
    end
    else
    begin
      Inc(Index);
      if Index > EI then Break;
    end;
  until False;
  RegFree(RegExpr);
  if FoundPos = 0 then
    if not SearchingAll and not NoSearchMsg then
      SearchStringNotFoundMsgBox
    else SearchingAll := False
  else
  begin
    if SearchingAll then
    begin
      if Back then
        if Index = 0 then FindAllIndex := -2
        else FindAllIndex := Index - 1
      else FindAllIndex := Index + 1;
      FindAllList^.Insert(Pointer(Index));
      Byte(SearchingAll) := 2;
    end
    else
    begin
      DrawFlag := True;
      GoToPos(FoundPos - 1, Index, False);
      DrawFlag := False;
      SaveArea := Area^;
      SaveStatus := Status;
      Status := Status or sfHilit;
      SetArea(Area, Delta.Y + Cursor.Y, Delta.X + Cursor.X + 1,
        Delta.Y + Cursor.Y, Delta.X + Cursor.X + Length(SubS) + 1);
      DrawView;
      Area^ := SaveArea;
      Status := SaveStatus or sfNeedRedraw;
    end;
    Search := True;
  end;
end;

procedure THTMLViewer.Find(All: Boolean);
var
  DoShow: Boolean;
  B, E: Byte;
  C: Word;
begin
  if EditOptions and eoFindTextAtCursor <> 0 then
    SearchData.MatchStr := GetWord(B, E)
  else SearchData.MatchStr := HistoryStr(FindHistoryID, 0);
  C := Application^.ExecuteDialog(LoadFindDialog(All, False,
    frSelectedText * Byte(Status and sfHilit = 0)), @SearchData, False);
  if C = cmOk then Search(SearchData.MatchStr, SearchData.Options)
  else if C = cmYes then
  begin
    FindAllList^.FreeAll;
    FindAllIndex := 0;
    FindAllData := SearchData;
    FindAllData.Options := FindAllData.Options and not frSelectedText;
    SearchingAll := True;
    repeat until not Search(SearchData.MatchStr, SearchData.Options);
    DoShow := FindAllIndex <> -1;
    if DoShow then FindAllDlg
    else FindAllList^.FreeAll;
  end;
end;

procedure THTMLViewer.FindAllDlg;
var
  P: Pointer;
begin
  P := @Self;
  if (Application^.ExecuteDialog(
    HTMLFindAllDialog(FindAllList^.Count, Status and sfXLAT <> 0),
    @P, False) <> cmCancel) and (FindAllList^.Count <> 0) then
  begin
    GoToPos(0, Integer(P), False);
    if FindAllData.Options and frBackward <> 0 then
      SetCursor(255, Cursor.Y)
    else SetCursor(-1, Cursor.Y);
    Search(FindAllData.MatchStr, FindAllData.Options or frFromCursor);
    if (Cursor.X < 0) or (Cursor.X >= Size.X) then
      SetCursor(0, Cursor.Y);
  end;
end;

procedure THTMLViewer.HistoryListDlg;
var
  I: Integer;
begin
  AdjustHistIndex;
  I := GetHTMLHistIndex(HTMLList, HistIndex);
  if (I >= 0) and (I < HTMLList^.Count) then GoToHistoryItem(I);
end;

procedure THTMLViewer.InitIncSearch;
begin
  IncSearchStr := '';
  IncSearchPos := Delta.Y + Cursor.Y;
  IncSearchOpt := (SearchData.Options or frFromCursor) and
    not (frWholeWordsOnly + frRegExp);
  if Status and sfHilit = 0 then
    IncSearchOpt := IncSearchOpt and not frSelectedText;
  IncSearch := True;
  SetState(sfFocused, True);
end;

procedure THTMLViewer.DoneIncSearch;
begin
  if IncSearchStr <> '' then
    SearchData.MatchStr := IncSearchStr;
  DrawView;
  Status := Status and not sfNeedRedraw;
  IncSearch := False;
  SetState(sfFocused, True);
end;

procedure THTMLViewer.DoIncSearch(C: Char);
begin
  if C = #27 then
  begin
    DoneIncSearch;
    Exit;
  end;
  if C = #9 then C := #32;
  if C = #8 then
  begin
    if IncSearchStr <> '' then Dec(IncSearchStr[0]);
    if IncSearchStr = '' then
    begin
      DoneIncSearch;
      Exit;
    end;
    IncSearchOpt := IncSearchOpt or frBackward;
    NoSearchMsg := True;
  end
  else
  begin
    if C = ^B then IncSearchOpt := IncSearchOpt or frBackward
    else IncSearchOpt := IncSearchOpt and not frBackward;
    if not (C in [^B, ^L]) then
    begin
      if Length(IncSearchStr) = IncSearchLen then
      begin
        SearchStringTooLongMsgBox;
        Exit;
      end;
      Inc(IncSearchStr[0]);
      IncSearchStr[Length(IncSearchStr)] := C;
      SearchingFrom := True;
      NoSearchMsg := True;
    end;
  end;
  if not Search(IncSearchStr, IncSearchOpt) then
    if C = #8 then
    begin
      Inc(Cursor.X, Length(IncSearchStr));
      if not Search(IncSearchStr, IncSearchOpt) then
        Dec(Cursor.X, Length(IncSearchStr));
    end
    else if not (C in [^B, ^L]) then Dec(IncSearchStr[0]);
  SearchingFrom := False;
  NoSearchMsg := False;
end;

procedure THTMLViewer.GoToLineNumber;
const
  GoToLineData: String[6] = '';
var
  L: Longint;
  D: PDialog;
  Control: PView;
  R: TRect;
begin
  R.Assign(0, 0, 40, 7);
  D := New(PDialog, Init(R, 'Go to line number'));
  with D^ do
  begin
    Options := Options or ofCentered;
    HelpCtx := hcSHGoToLineNumber;
    R.Assign(25, 2, 34, 3);
    Control := New(PCInputLine, Init(R, 6, 1, FileLines^.Count + 1));
    Insert(Control);
    StandardHistory(Control, GoToLineNumHistoryID);
    R.Assign(2, 2, 25, 3);
    Insert(New(PLabel, Init(R, '~E~nter new line number', Control)));

    StandardButtonRow(D, True);
  end;
  if (Application^.ExecuteDialog(D, @GoToLineData, False) <> cmCancel) and
    Str2Long(GoToLineData, L) then GoToLineNum(L);
end;

procedure THTMLViewer.UpdateHistory;
var
  P, X: Byte;
  I, Y: Integer;
  R: PHistoryRec;
  DX, DY: String[5];
  S: String;
begin
  I := HistoryIndex(ReopenHistoryID, 'HTML  ' + SourceName^ + #$FF);
  if I <> -1 then
  begin
    R := HistoryList^.At(I);
    S := R^.StrPtr^;
    HistLock := Byte(R^.Lock);
    HistoryList^.AtFree(I);
    if Inserted then Exit;
    S := System.Copy(S, Pos(#$FF, S) + 1, 255);
    P := Pos(#$FF, S);
    if P <> 0 then
    begin
      if S[Length(S)] = #$FF then
      begin
        Dec(S[0]);
        Status := Status or sfBrowser;
      end;
      DX := System.Copy(S, 1, P - 1);
      DY := System.Copy(S, P + 1, 5);
      Val(DX, X, I);
      Val(DY, Y, I);
      GoToPos(X, Y, False);
      P := Pos(#$FF'PLACES', S);
      if P <> 0 then Move(S[P + 7], Places, SizeOf(TPlaces));
    end;
  end;
end;

procedure THTMLViewer.ToggleTables;
begin
  Status := Status xor sfNoTables;
  SwitchToFile('');
end;

procedure THTMLViewer.PopUp(AnOrigin: TPoint);
var
  S: String[7];
begin
  if Status and sfNoTables = 0 then S := '~D~is' else S := '~E~n';
  ShowPopup(AnOrigin, NewMenu(
    NewItem('Mark block begin        Ctrl+K ~B~', '', kbNoKey, cmCtrlKB, hcCtrlKB,
    NewItem('Mark block end          Ctrl+K ~K~', '', kbNoKey, cmCtrlKK, hcCtrlKK,
    NewItem('Mark single word        Ctrl+K ~T~', '', kbNoKey, cmCtrlKT, hcCtrlKT,
    NewItem('Mark line               Ctrl+K ~L~', '', kbNoKey, cmCtrlKL, hcCtrlKL,
    NewItem('Hide/Display block      Ctrl+K ~H~', '', kbNoKey, cmCtrlKH, hcCtrlKH,
    NewItem('Write block to disk...  Ctrl+K ~W~', '', kbNoKey, cmCtrlKW, hcCtrlKW,
    NewItem('Print block             Ctrl+K ~P~', '', kbNoKey, cmCtrlKP, hcCtrlKP,
    NewItem('~C~opy to Clipboard       Ctrl+Ins', '', kbNoKey, cmCopy, hcECopy,
    NewLine(
    NewItem('~F~ind...                       F7', '', kbF7, cmFind, hcSHFind,
    NewItem('Find ~a~ll...', '', kbNoKey, cmFindAll, hcSHFindAll,
    NewItem('Sh~o~w all found...', '', kbNoKey, cmShowAllFound, hcSHShowAllFound,
    NewItem('~S~earch again              Ctrl+L', '', kbNoKey, cmSearchAgain, hcSHSearchAgain,
    NewItem('~I~ncremental search        Ctrl+@', '', kbNoKey, cmIncSearch, hcSHIncSearch,
    NewItem('~G~o to line number...       Alt+G', '', kbAltG, cmGoToLine, hcSHGoToLineNumber,
    NewItem('HTML histor~y~...           Ctrl+W', '', kbNoKey, cmHTMLHistory, hcHTMLHistoryList,
    NewLine(
    NewItem('~M~ove to begin of block  Ctrl+Q B', '', kbNoKey, cmCtrlQB, hcCtrlQB,
    NewItem('Mo~v~e to end of block    Ctrl+Q K', '', kbNoKey, cmCtrlQK, hcCtrlQK,
    NewLine(
    NewItem(S + 'able HTML tables', 'Ctrl+T', kbNoKey, cmToggleHTMLTables, hcHTMLTables,
    NewItem('~R~eload', 'Ctrl+V', kbNoKey, cmHTMLReload, hcHTMLReload,
      nil))))))))))))))))))))))));
end;

constructor THTMLWindow.Init(Bounds: TRect; const Name: FNameStr);
var
  Control: PView;
  R: TRect;
begin
  Inherited Init(Bounds, '', WindowNumAvail);
  Options := Options or (ofTileable + ofHTML);

  GetExtent(R);
  Inc(R.A.X, 20); Dec(R.B.X, 2); R.A.Y := R.B.Y - 1;
  Control := New(PScrollBar, Init(R));
  Insert(Control);

  GetExtent(R);
  Inc(R.A.X, 2); R.A.Y := R.B.Y - 1; R.B.X := R.A.X + 17;
  Indicator := New(PHTMLIndicator, Init(R));
  Indicator^.GrowMode := gfGrowLoY + gfGrowHiY;
  Insert(Indicator);

  GetExtent(R);
  R.Grow(-1, -1); R.B.Y := R.A.Y + 1;
  HTMLTitle := New(PHTMLTitle, Init(R));
  HTMLTitle^.GrowMode := gfGrowHiX;
  Insert(HTMLTitle);

  GetExtent(R);
  R.Grow(-1, -1); R.A.Y := R.B.Y - 1;
  HTMLInfo := New(PHTMLInfo, Init(R));
  HTMLInfo^.GrowMode := gfGrowHiX + gfGrowLoY + gfGrowHiY;
  Insert(HTMLInfo);

  GetExtent(R);
  R.Grow(-1, -2);
  Viewer := New(PHTMLViewer, Init(R, PScrollBar(Control),
    StandardScrollBar(sbVertical + sbHandleKeyboard)));
  Insert(Viewer);
  Viewer^.ReadFile(Name);
end;

constructor THTMLWindow.Load(var S: TStream);
var
  Name: FNameStr;
begin
  Inherited Load(S);
  GetSubViewPtr(S, Indicator);
  GetSubViewPtr(S, HTMLTitle);
  GetSubViewPtr(S, HTMLInfo);
  GetSubViewPtr(S, Viewer);
  Name := Viewer^.SourceName^;
  Viewer^.ReadFile(Name);
end;

procedure THTMLWindow.ChangeBounds(var Bounds: TRect);
var
  R: TRect;
begin
  Inherited ChangeBounds(Bounds);
  if ((Size.X = Viewer^.Size.X) and (Size.X < Owner^.Size.X)) or
     ((Size.Y = Viewer^.Size.Y) and (Size.Y < Owner^.Size.Y)) then
  begin
    GetExtent(R);
    R.Grow(-1, -1);
    R.B.Y := R.A.Y + 1;
    HTMLTitle^.Locate(R);
    GetExtent(R);
    R.Grow(-1, -1);
    R.A.Y := R.B.Y - 1;
    HTMLInfo^.Locate(R);
    GetExtent(R);
    R.Grow(-1, -2);
    Viewer^.Locate(R);
  end;
end;

procedure THTMLWindow.Close;
var
  M: Byte;
  I: Integer;
  DX, DY: String[5];
  S: String;

function UsingSwapFile(P: PView): Boolean; far;
begin
  if TypeOf(P^) = TypeOf(TWinIcon) then P := PWinIcon(P)^.Window;
  UsingSwapFile := (P <> @Self) and
    (P^.Options and (ofASCII + ofHex + ofHTML) <> 0);
end;

begin
  if TheSpooler^.InQueue(Viewer) then
    if PrinterQueueMsgBox <> cmOk then Exit
    else TheSpooler^.DoUpdate := True;
  if (Viewer^.Status and sfError = 0) and
     (Length(Viewer^.SourceName^) <= 222) then
  begin
    S := 'HTML  ' + Viewer^.SourceName^;
    with Viewer^ do
    begin
      Str(Delta.X + Cursor.X, DX);
      Str((Delta.Y + Cursor.Y):5, DY);
    end;
    S := S + #$FF + DX + #$FF + DY + #$FF#$FF;
    if Size.X = Viewer^.Size.X then
      FillChar(S[Length(S) + 1], 8, OpenWideFiller)
    else Move(Origin, S[Length(S) + 1], 8);
    Inc(S[0], 8);
    if Length(S) < 254 - SizeOf(TPlaces) - 8 then
    begin
      S := S + #$FF'PLACES';
      Move(Viewer^.Places, S[Length(S) + 1], SizeOf(TPlaces));
      Inc(S[0], SizeOf(TPlaces) + 1);
      S[Length(S)] := #0;
    end;
    if Viewer^.Status and sfBrowser <> 0 then
    begin
      Inc(S[0]);
      S[Length(S)] := #$FF;
    end;
    HistoryAdd(ReopenHistoryID, S, False);
    if Viewer^.HistLock <> 0 then
    begin
      I := HistoryIndex(ReopenHistoryID, S);
      if I <> -1 then
        Byte(PHistoryRec(HistoryList^.At(I))^.Lock) := Viewer^.HistLock;
    end;
  end;
  if (Desktop^.FirstThat(@UsingSwapFile) = nil) and (TheBack = nil) then
  begin
    Viewer^.SwapStream^.Seek(10);
    Viewer^.SwapStream^.Truncate;
  end;
  Inherited Close;
end;

function THTMLWindow.GetPalette: PPalette;
const
  P: String[14] = #226#227#228#229#230#231#232#233#234#235#236#237#238#239;
begin
  GetPalette := @P;
end;

function THTMLWindow.GetTitle(MaxSize: Integer): TTitleStr;
var
  S, C: String[15];
begin
  Str(Viewer^.FileLines^.Count, S);
  if MaxSize > 34 then
  begin
    if Viewer^.Status and sfError <> 0 then S := 'load error'
    else
    begin
      S := S + ' lines';
      if Viewer^.FileLines^.Count = 1 then Dec(S[0]);
    end;
  end
  else S := '';
  if S <> '' then S := ' (' + S + ')';
  if MaxSize < Length(S) + 10 then MaxSize := Length(S) + 10;
  C := '';
  if Viewer^.Status and sfXLAT <> 0 then
    if Viewer^.XLATKind = xKoi then C := 'KOI '
    else if Viewer^.XLATKind = xWin then C := 'WIN ';
  GetTitle := C + 'HTML view: ' + TrimmedName(Viewer^.SourceName^,
    Byte(MaxSize - (Length(C) + Length(S) + 10))) + S;
end;

procedure THTMLWindow.SizeLimits(var Min, Max: TPoint);
begin
  TWindow.SizeLimits(Min, Max);
  Min.X := 25;
  Min.Y := 5;
end;

procedure THTMLWindow.Store(var S: TStream);
begin
  Inherited Store(S);
  PutSubViewPtr(S, Indicator);
  PutSubViewPtr(S, HTMLTitle);
  PutSubViewPtr(S, HTMLInfo);
  PutSubViewPtr(S, Viewer);
end;

procedure THTMLWindow.Zoom;
var
  CtrlDown, Full, Wide: Boolean;
  R: TRect;
begin
  CtrlDown := GetShiftState and
    (kbCtrlShift + kbLeftShift + kbRightShift) <> 0;
  Full := Longint(Size) = Longint(Owner^.Size);
  Wide := Viewer^.Origin.X = 0;
  if not CtrlDown or not Full then Inherited Zoom;
  if CtrlDown or Wide then
  begin
    GetExtent(R);
    if Wide then R.Grow(-1, -1);
    R.B.Y := R.A.Y + 1;
    HTMLTitle^.Locate(R);
    GetExtent(R);
    if Wide then R.Grow(-1, -1);
    R.A.Y := R.B.Y - 1;
    HTMLInfo^.Locate(R);
    GetExtent(R);
    if Wide then R.Grow(-1, -2) else R.Grow(0, -1);
    Viewer^.Locate(R);
  end;
end;

end.