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

interface

uses Dos, Objects, Views, Drivers, FEditor, Swaps;

const
  HexBufSize = 8192;
  MaxHexEditBytes = 523472 { $7FCD0 };

type

  { THexIndicator }

  PHexIndicator = ^THexIndicator;
  THexIndicator = object(TIndicator)
    procedure Draw; virtual;
  end;

  { THexBuffer }

  PHexBuffer = ^THexBuffer;
  THexBuffer = Array[0..HexBufSize - 1] of Char;

  { THexBufRec }

  PHexBufRec = ^THexBufRec;
  THexBufRec = record
    SwapPos: Longint;
    Size: Word;
  end;

  { THexCollection }

  PHexCollection = ^THexCollection;
  THexCollection = object(TCollection)
    procedure FreeItem(P: Pointer); virtual;
  end;

  { THexEditor }

  PHexEditor = ^THexEditor;
  THexEditor = object(TFileEditor)
    Buffer: PHexBuffer;
    BufPos: Word;
    NumLines: Integer;
    NumBytes: Longint;
    TopByte: Longint;
    IsHexy: Boolean;
    constructor Init(var Bounds: TRect;
      AHScrollBar, AVScrollBar: PScrollBar; AnIndicator: PIndicator);
    destructor Done; virtual;
    procedure Draw; virtual;
    procedure ChangeBounds(var Bounds: TRect); virtual;
    function GetHelpCtx: Word; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure GetCollectionIndex(APos: Longint;
      var Index: Integer; var Pos: Word);
    function GetLinePos: Integer; virtual;
    procedure GetValidLine(AnIndex: Integer; var ALine: String); virtual;
    procedure GetSelectedString(AnIndex: Integer; var ALine: String); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    procedure ReadFile(var FName: String); virtual;
    function ValidChar(AChar: Char): Boolean;
    procedure CharPressed(AChar: Char);
    function NewSwapBlock(AnIndex: Integer; ASize: Word): Boolean;
    procedure InsertChar(AChar: Char; AnIndex: Integer; APos: Byte;
      MoveRight: Boolean);
    procedure ReplaceChar(AChar: Char; AnIndex: Integer; APos: Byte;
      MoveRight: Boolean);
    procedure DeleteChar(AnIndex: Integer; APos: Byte;
      Backward: Boolean); virtual;
    procedure DeleteLine(AnIndex: Integer);
    procedure DeleteToEndOfFile;
    procedure Enter(AnIndex: Integer; APos: Byte);
    procedure GoOpposite;
    procedure GoToPos(APos: Byte; AnIndex: Integer); virtual;
    procedure GoToLineNum(AnIndex: LongInt); virtual;
    procedure GoRight; virtual;
    procedure GoLeft; virtual;
    procedure GoWhileKeyDown(Key: Byte); virtual;
    procedure GoDown; virtual;
    procedure GoPgUp; virtual;
    procedure GoPgDn; virtual;
    procedure GoCtrlPgDn; virtual;
    procedure GoEnd; virtual;
    procedure GoHome; virtual;
    procedure GoCtrlEnd; virtual;
    procedure GoCtrlRight; virtual;
    procedure GoCtrlLeft; virtual;
    procedure GoCtrlDown; virtual;
    procedure InsertBuffer(ABuffer: PHexBuffer; ASize: Word;
      AnIndex: Integer; APos: Byte; var InsBytes: Word);
    procedure CutBlock(AnArea: PArea); virtual;
    procedure InsertBlock(AnIndex: Integer; APos: Byte); virtual;
    procedure MoveBlock(AnIndex: Integer; APos: Byte); virtual;
    function WriteBlock(const FName: FNameStr;
      Append: Boolean): Boolean; virtual;
    function ReadBlock(const FName: FNameStr;
      ConcatLast: Boolean): Boolean; virtual;
    procedure Copy; virtual;
    procedure Paste; virtual;
    procedure ReplaceWith(const AString: String; ALength: Byte); virtual;
    function Save(const AName: FNameStr; Append: Boolean): Boolean; virtual;
    function GetPrintLine(AnIndex: Integer; Sel: Boolean;
      var ALine: String): Boolean; virtual;
    procedure Fill(AChar: Char; AStart, AFinish: LongInt);
    procedure FillHex;
    procedure ConvertBlock(Action: Byte);
    function Search(const Match: String; AOptions: Word): Boolean; virtual;
    function GetWord(var B, E: Byte): String; virtual;
    function GetLine: String; virtual;
    procedure GetPos(AnArea: PArea; AnIndex: Integer;
      var APos: TPos); virtual;
    procedure SwapTruncate; virtual;
    procedure Popup(AnOrigin: TPoint); virtual;
  end;

  { THexEditorWindow }

  PHexEditorWindow = ^THexEditorWindow;
  THexEditorWindow = object(TEditorWindow)
    constructor Init(Bounds: TRect; var FileName: String);
    procedure SizeLimits(var Min, Max: TPoint); virtual;
  end;

implementation

uses CNCrt, Memory, WinIcons, Menus, Dialogs, StdDlg, MsgBox, App,
  HistList, History, EditCmnd, EditDrag, EditFile, EditFind, EditMsg,
  FindDlg, Popups, Spooler, ClipPrim, PlcHist, DosTools, Keyboard,
  LFN, TPString, Consts, Commands, Defaults;

const
  Pattern: Array[7..77] of Byte =
    ($80, 0, $7F, $81, 1, $7F, $82, 2, $7F, $83, 3, $7F, $7F, $7F,
     $84, 4, $7F, $85, 5, $7F, $86, 6, $7F, $87, 7, $7F, $7F, $7F,
     $88, 8, $7F, $89, 9, $7F, $8A, $A, $7F, $8B, $B, $7F, $7F, $7F,
     $8C, $C, $7F, $8D, $D, $7F, $8E, $E, $7F, $8F, $F, $7F, $7F,
     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, $A, $B, $C, $D, $E, $F);
  HexPos: Array[$0..$F] of Byte =
    (7, 10, 13, 16, 21, 24, 27, 30, 35, 38, 41, 44, 49, 52, 55, 58);

  cvInvert = 0;
  cvUpcase = 1;
  cvLocase = 2;
  cvTable =  3;
  cvFTable = 4;

procedure StringConvert(var SStr, DStr; Num: Integer);
begin
  asm
        jmp @Start
@Conv:  db '0123456789ABCDEF'
@Str:   mov ah,al
        and al,0fh
SEGCS   xlat
        xchg ah,al
        and al,0f0h
        shr al,1
        shr al,1
        shr al,1
        shr al,1
SEGCS   xlat
        retn
@Start: push ds
        lds si,SStr
        mov al,Byte ptr ds:[si]
        inc si
        les di,DStr
        mov byte ptr es:[di],78
        inc di
        mov bx, OFFSET @Conv
        mov ax,Num
        xchg ah,al
        push ax
        call @Str
        stosw
        pop ax
        mov al,ah
        call @Str
        stosw
        mov ax,2030h
        stosw
        mov al,ah
        stosb
        mov cx,4
@l1:    push cx
        mov cx,4
@l0:    lodsb
        call @Str
        stosw
        mov al,20h
        stosb
        loop @l0
        mov ax,20b3h {³ }
        stosw
        pop cx
        loop @l1
        dec di
        dec di
        mov al,20h
        stosb
        sub si,16
        mov cl,16 {Count}
rep     movsb
        pop ds
  end;
end;

{ THexIndicator }

procedure THexIndicator.Draw;
var
  C: Byte;
  Ch: Char;
  L: Longint;
  B: TDrawBuffer;
  S: String;
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 PHexEditor(PEditorWindow(Owner)^.Editor)^ do
  begin
    L := LongMul(Delta.Y + Cursor.Y, 16) +
      (Pattern[Delta.X + Cursor.X] and $7F);
    FormatStr(S, ' %05x ', L);
    if IsModified then MoveChar(B, #15, C, 1);
  end;
  MoveStr(B[3], S, C);
  WriteLine(0, 0, Size.X, 1, B);
end;

procedure THexCollection.FreeItem(P: Pointer);
begin
  if P <> nil then FreeMem(P, SizeOf(THexBufRec));
end;

{ THexEditor }
constructor THexEditor.Init(var Bounds: TRect;
  AHScrollBar, AVScrollBar: PScrollBar; AnIndicator: PIndicator);
begin
  TFileEditor.Init(Bounds, AHScrollBar, AVScrollBar, AnIndicator);
  State := State or sfCursorIns;
  IsInsert := False;
end;

destructor THexEditor.Done;
begin
  if Buffer <> nil then FreeMem(Buffer, HexBufSize);
  TFileEditor.Done;
end;

procedure THexEditor.Draw;
var
  C, LineCount: Byte;
  Y, CurY: Integer;
  BP, EP: Integer;
  CurStr: Longint;
  Pos: TPos;
  B: TDrawBuffer;
  S, HS, P: String;
begin
  if DrawFlag then Exit;
  C := GetColor(1);
  for Y := 0 to Size.Y - 1 do
  begin
    MoveChar(B, ' ', C, Size.X);
    CurY := Delta.Y + Y;
    GetValidLine(CurY, P);
    if CurY < NumLines - 1 then
    begin
      StringConvert(P, S, CurY);
      MoveStr(B, System.Copy(S, Succ(Delta.X), Size.X), C);
    end
    else if CurY = NumLines - 1 then
    begin
      CurStr := LongMul(CurY, 16);
      S := System.Copy(HexL(CurStr), 4, 5) +
        '              ³             ³             ³              ' + P;
      for LineCount := 0 to 15 do
      if CurStr + LineCount < NumBytes then
      begin
        HS := HexB(Byte(S[LineCount + 63]));
        Move(HS[1], S[HexPos[LineCount] + 1], 2);
      end;
      MoveStr(B, System.Copy(S, Succ(Delta.X), Size.X), C);
    end;
    if (Y = Cursor.Y) and (LocalEditOptions and eoHilitCurLine <> 0) then
      MoveChar(B, #0, Byte(Application^.GetPalette^[cCurLine]), Size.X);
    if IsSelected and Selected(CurY) then
    begin
      GetPos(Area, CurY, Pos);
      if Pos.AStart < Pos.AnEnd then
      begin
        BP := HexPos[Pos.AStart] - Delta.X;
        if BP < 0 then BP := 0;
        if Pos.AnEnd > 15 then EP := HexPos[15] - Delta.X + 1
        else EP := HexPos[Pos.AnEnd - 1] - Delta.X + 1;
        if EP >= 0 then MoveChar(B[BP], #0, GetColor(2), EP - BP + 1);
        MoveChar(B[62 + Pos.AStart - Delta.X],
          #0, GetColor(2), Pos.AnEnd - Pos.AStart);
      end;
    end
    else Longint(Pos) := 0;
    if (LocalEditOptions and eoHilitCurColumn <> 0) and
       (Cursor.X >= 0) and (Cursor.X < MaxViewWidth) and
       ((Longint(Pos) = 0) or
        (Pattern[Delta.X + Cursor.X] and $7F < Pos.AStart) or
        (Pattern[Delta.X + Cursor.X] and $7F >= Pos.AnEnd)) then
      MoveChar(B[Cursor.X], #0, Byte(Application^.GetPalette^[cCurCol]), 1);
    WriteLine(0, Y, Size.X, 1, B);
  end;
end;

procedure THexEditor.ChangeBounds(var Bounds: TRect);
begin
  TScroller.ChangeBounds(Bounds);
  DrawFlag := True;
  SetLimit(Limit.X, NumLines + Size.Y);
  DrawFlag := False;
end;

function THexEditor.GetHelpCtx: Word;
begin
  if State and sfDragging <> 0 then GetHelpCtx := hcDragging
  else if GetShiftState and kbAltShift <> 0 then GetHelpCtx := hcAltHexEditor
  else if IsCtrlKPressed then GetHelpCtx := hcHexCtrlKPressed
  else if IsCtrlQPressed then GetHelpCtx := hcHexCtrlQPressed
  else if IsCtrlPPressed then GetHelpCtx := hcCtrlPPressed
  else if IsCtrlOPressed then GetHelpCtx := hcCtrlOPressed
  else GetHelpCtx := hcHexEditor;
end;

procedure THexEditor.HandleEvent(var Event: TEvent);
var
  SameName, WasSelected, Dropped: Boolean;
  B, E: Byte;
  C: Word;
  I: Integer;
  Spot: TPoint;
  S: String;

procedure SelectAll;
begin
  if NumLines = 0 then Exit;
  Highlight(0, 0, NumLines - 1, NumBytes mod 16);
  DrawView;
end;

begin
  if Event.What = evKeyDown then
    if (Event.KeyCode = kbCtrlHome) or (Event.KeyCode = kbCtrlEnd) or
       (Event.KeyCode = kbCtrlPgUp) or (Event.KeyCode = kbCtrlPgDn) or
       (Event.CharCode in WordStarChars) then
      if PreprocessEnabledPrim(@Self, Event) then
	ConvertWSKey(WordStarKeys, Event);
  TScroller.HandleEvent(Event);
  if Event.What = evKeyDown then
  begin
    if DoRedraw then
    begin
      DrawView;
      DoRedraw := False;
    end;
    if (Cursor.X < 0) or (Cursor.X >= Size.X) or
       (Cursor.Y < 0) or (Cursor.Y >= Size.Y) then
         GoToPos(GetLinePos, Delta.Y + Cursor.Y);
    if IsSelected and (LocalEditOptions and eoNonPersistent <> 0) then
      HideNonPersistentBlock(@Self, Event);
    if Event.CharCode <> #0 then
    begin
      if IsCtrlKPressed then
      begin
        IsCtrlKPressed := False;
        C := 0;
        case Event.CharCode of
          'Q', 'q', '©', '‰', ^Q:
            begin
              Pending.What := evCommand;
              Pending.Command := cmMenu;
            end;
          '0'..'9':
            begin
              Places[Event.CharCode].X := GetLinePos;
              Places[Event.CharCode].Y := Delta.Y + Cursor.Y;
            end;
          else GetCtrlKCommand(Event.CharCode, C);
          case C of
            cmCtrlKD, cmCtrlKM, cmCtrlKI, cmCtrlKU: C := 0;
          end;
        end;
        Event.What := evCommand;
        Event.Command := C;
        if C <> 0 then HandleEvent(Event);
      end
      else if IsCtrlQPressed then
      begin
        IsCtrlQPressed := False;
        C := 0;
        case Event.CharCode of
          'F', 'f', ' ', '€', ^F: C := cmFind;
	  'A', 'a', 'ä', '”', ^A: C := cmReplace;
          'U', 'u', '£', 'ƒ', ^U: C := cmCtrlKI;
          'L', 'l', '¤', '„', ^L: C := cmCtrlKU;
          'X', 'x', 'ç', '—', ^X: C := cmCtrlQI;
          'Q', 'q', '©', '‰', ^Q: C := cmCtrlQX;
          'W', 'w', 'æ', '–', ^W: C := cmCtrlQW;
          'B', 'b', '¨', 'ˆ', ^G: C := cmCtrlQB;
          'K', 'k', '«', '‹', ^K: C := cmCtrlQK;
          'Z', 'z', 'ï', 'Ÿ', ^Z: C := cmCtrlQZ;
          'P', 'p', '§', '‡', ^P:
            TheSpooler^.NewEditor(@Self, Delta.Y, Delta.Y + Size.Y - 1);
          '0'..'9':
            if (Places[Event.CharCode].X <> -1) and
               (Places[Event.CharCode].Y <> -1) then
              GoToPos(Places[Event.CharCode].X, Places[Event.CharCode].Y);
        end;
        Event.What := evCommand;
        Event.Command := C;
        if C <> 0 then HandleEvent(Event);
      end
      else if IsCtrlPPressed or (State and sfActive = 0) then
      begin
        IsCtrlPPressed := False;
        if ValidChar(Event.CharCode) then
          if IsInsert then InsertChar(
            Event.CharCode, Delta.Y + Cursor.Y, Delta.X + Cursor.X, True)
	  else ReplaceChar(
            Event.CharCode, Delta.Y + Cursor.Y, Delta.X + Cursor.X, True);
      end
      else if IsCtrlOPressed then Inherited HandleEvent(Event)
      else if Event.CharCode = #27 then
        if LocalEditOptions and eoEscCloses <> 0 then
        begin
          Pending.What := evCommand;
          Pending.Command := cmClose;
          Pending.InfoPtr := Owner;
        end else
      else if (Event.CharCode = #32) and
        (GetShiftState and kbCtrlShift <> 0) then
          SearchData.MatchStr := GetWord(B, E)
      else if Event.CharCode = ^\ then
        if State and sfNoname <> 0 then
          Message(Desktop, evBroadcast, cmFileManager, nil)
	else Message(Desktop, evBroadcast, cmReopen, FileName)
      else
      begin
        CharPressed(Event.CharCode);
        if ((KbdStatus = 21) and
           (GetShiftState and kbCtrlShift <> 0)) or
           (KbdStatus in [28, 38]) then
        begin
          repeat until KeyPressed or not (KbdStatus in [21, 28, 38]);
          while ((KbdStatus = 21) and
            (GetShiftState and kbCtrlShift <> 0)) or
            (KbdStatus in [28, 38]) do
          begin
            CharPressed(Event.CharCode);
            Indicator^.DrawView;
            if kbdDelay <> 0 then Delay(kbdDelay);
            ClearKbd;
          end;
        end;
      end;
    end
    else
    begin
      if Event.KeyCode = kbAltY then
        PlaceMarkerDlg(@Self, not IsCtrlQPressed)
      else if Event.KeyCode = kbShiftTab then GoOpposite
      else if GetShiftState and (kbLeftShift + kbRightShift) <> 0 then
      begin
        if IsSelected and
          (((Delta.Y + Cursor.Y = Area^.KB) and
          (GetLinePos = Area^.PB)) or
          ((Delta.Y + Cursor.Y = Area^.KK) and
          (GetLinePos = Area^.PK))) then {}
        else
        begin
          IsSelected := False;
          Highlight(Delta.Y + Cursor.Y, GetLinePos,
            Delta.Y + Cursor.Y, GetLinePos);
        end;
        DrawFlag := True;
        Scroll(Event);
        DrawFlag := False;
        if Event.What <> evNothing then Exit;
        SelectOn(Delta.Y + Cursor.Y, GetLinePos);
        if IsSelected and
           (Area^.KB = Area^.KK) and (Area^.PB = Area^.PK) then Unselect;
        DrawView;
      end
      else
      begin
        Scroll(Event);
        if Event.What <> evNothing then Exit;
      end;
    end;
    Indicator^.DrawView;
    Event.What := evNothing;
  end
  else if (Event.What = evMouseDown) and MouseInView(Event.Where) then
  begin
    if DoRedraw then
    begin
      DrawView;
      DoRedraw := False;
    end;
    if IsSelected and (LocalEditOptions and eoNonPersistent <> 0) then
      HideNonPersistentBlock(@Self, Event);
    if Event.Buttons = mbRightButton then
    begin
      repeat until not MouseEvent(Event, evMouseDown);
      PopUp(Event.Where);
      Event.What := evNothing;
    end
    else
    begin
      WasSelected := IsSelected;
      Dropped := False;
      repeat
        DrawFlag := True;
        MakeLocal(Event.Where, Spot);
        if MouseInView(Event.Where) then
        begin
          if not IsSelected and
             ((Event.What and evMouseMove <> 0) or
             (GetShiftState and (kbLeftShift + kbRightShift) <> 0)) then
            Highlight(Delta.Y + Cursor.Y, GetLinePos,
              Delta.Y + Cursor.Y, GetLinePos);
          if Delta.X + Spot.X < 7 then Spot.X := 7 - Delta.X;
          while Pattern[Delta.X + Spot.X] = $7F do Inc(Spot.X);
          while Delta.Y + Spot.Y >= NumLines do Dec(Spot.Y);
          SetCursor(Spot.X, Spot.Y);
          IsHexy := Delta.X + Cursor.X < 60;
        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 IsSelected and
           (not WasSelected or
            (GetShiftState and (kbLeftShift + kbRightShift) <> 0)) then
          SelectOn(Delta.Y + Cursor.Y, GetLinePos);
        if (MouseButtons = mbLeftButton + mbRightButton) and
           Selected(Delta.Y + Cursor.Y) then
        begin
          DragDropPrim(@Self, Event.Where);
          Dropped := True;
        end
        else if Event.Double then
        begin
          if IsSelected then Unselect
          else HighLight(Delta.Y + Cursor.Y, 0, Delta.Y + Cursor.Y, 16);
          Dropped := True;
        end;
        DrawFlag := False;
        DrawView;
        Indicator^.DrawView;
      until Dropped or (not MouseEvent(Event, evMouseMove + evMouseAuto));
      if IsSelected and
         (Area^.KB = Area^.KK) and (Area^.PB = Area^.PK) then Unselect;
    end;
  end
  else if Event.What = evBroadcast then
    case Event.Command of
      cmIsHex: if Owner^.State and sfVisible <> 0 then ClearEvent(Event);
      cmHexOpened:
        begin
          SameName :=
            CompUCString(PString(Event.InfoPtr)^, FileName^) = Equal;
          if not SameName and not SameWinFlag then Exit;
          if not SameName then UpdateLines(PString(Event.InfoPtr)^);
          PWindow(Owner)^.Deiconize;
          Owner^.Show;
          Owner^.Select;
          ClearEvent(Event);
        end;
      cmIsPrintable: if Event.InfoPtr = @Self then ClearEvent(Event);
      cmSaveAll: if IsModified then Save('', False);
      cmQuit: if Event.InfoPtr <> nil then IsModified := False else
        begin
          C := SaveOrCancel;
          if C = cmCancel then ClearEvent(Event)
          else if C = cmYes then Save('', False);
        end;
      cmGetSource: if Owner^.State and sfVisible <> 0 then
        begin
          Event.What := evNothing;
          Event.InfoPtr := FileName;
        end;
      cmUpdateTime:
        if (Event.InfoPtr = nil) or
           (CompUCString(PString(Event.InfoPtr)^, FileName^) = Equal) then
          UpdateTime;
      cmGoToLine: if Owner^.State and sfVisible <> 0 then
        begin
          GoToPos(LongRec(Event.InfoPtr).Lo, LongRec(Event.InfoPtr).Hi);
          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
            GetValidLine(Places[Event.InfoChar].Y, S);
            Event.InfoPtr := @S;
          end
          else Event.InfoPtr := nil;
        end;
      cmGetBlockDest: if (Owner^.State and sfVisible <> 0) and
        MouseInView(PView(Event.InfoPtr)^.Origin) then
        begin
          MakeLocal(PView(Event.InfoPtr)^.Origin, Spot);
          if Delta.X + Spot.X < 7 then Spot.X := 7 - Delta.X;
          while Pattern[Delta.X + Spot.X] = $7F do Inc(Spot.X);
          while Delta.Y + Spot.Y >= NumLines do Dec(Spot.Y);
          SetCursor(Spot.X, Spot.Y);
          IsHexy := Delta.X + Cursor.X < 60;
          ClearEvent(Event);
        end;
      cmEditOptions:
        begin
          InitLocalFields(False);
          DrawView;
        end;
      cmNewSwapStream: SwapStream := Event.InfoPtr;
      cmCtrlKW:
        begin
          WriteBlockToFile(PString(Event.InfoPtr)^, False, False);
          ClearEvent(Event);
        end;
      cmCtrlKR:
        begin
          if IsSelected then Clear(True, False);
          ReadBlockFromFile(PString(Event.InfoPtr)^, False);
          DrawView;
          Indicator^.DrawView;
          ClearEvent(Event);
        end;
      cmlAPIChanged:
        begin
          lGetFileNameForChangedAPI(lAPIType(Event.InfoByte),
            FileName^, S);
          NewTitle(S);
          if PWindow(Owner)^.Icon <> nil then
            PWinIcon(PWindow(Owner)^.Icon)^.Update;
        end;
    end
  else if Event.What = evCommand then
  begin
    case Event.Command of
      cmSave: Save('', False);
      cmSaveAs: SaveAsPrim(@Self);
      cmPrint: TheSpooler^.NewEditor(@Self, -1, 0);
      cmCut: Cut;
      cmCopy: Copy;
      cmPaste: Paste;
      cmClear: Clear(True, True);
      cmFillHex: FillHex;
      cmFind: FindPrim(@Self, False);
      cmReplace: ReplacePrim(@Self, True);
      cmFindAll: FindPrim(@Self, True);
      cmShowAllFound: FindAllDlgPrim(@Self);
      cmSearchAgain: Search(SearchData.MatchStr,
        SearchData.Options or frFromCursor);
      cmGoToLine: GoToLineNumberPrim(@Self);
      cmUpdateHex: 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, GetLinePos,
            Area^.KK, Area^.PK);
          cmCtrlKK: Highlight(Area^.KB, Area^.PB,
            Delta.Y + Cursor.Y, GetLinePos);
          cmCtrlKH:
            if not IsSelected then Highlight(
              Area^.KB, Area^.PB, Area^.KK, Area^.PK)
            else
            begin
              IsSelected := False;
              DisableCommands(BlockCommands + MTCopyCommands);
            end;
          cmCtrlKY: if IsSelected then Clear(True, False)
            else
            begin
              Event.Command := cmCtrlKH;
              HandleEvent(Event);
            end;
          cmCtrlKC: if IsSelected then InsertBlock(
            Delta.Y + Cursor.Y, GetLinePos)
            else
            begin
              Event.Command := cmCtrlKH;
              HandleEvent(Event);
            end;
          cmCtrlKV: if IsSelected then MoveBlock(
            Delta.Y + Cursor.Y, GetLinePos)
            else
            begin
              Event.Command := cmCtrlKH;
              HandleEvent(Event);
            end;
          cmCtrlKP: if IsSelected then
            TheSpooler^.NewEditor(@Self, Area^.KB, Area^.KK);
          cmCtrlKR: ReadBlockFromFile('', True);
          cmCtrlKW,
          cmCtrlKA: WriteBlockToFile('', True, Event.Command = cmCtrlKA);
          cmCtrlKI: ConvertBlock(cvUpcase);
          cmCtrlKU: ConvertBlock(cvLocase);
          cmCtrlKL:
            begin
              Unselect;
              HighLight(Delta.Y + Cursor.Y, 0, Delta.Y + Cursor.Y, 16);
            end;
          cmCtrlKT:
            begin
              S := GetWord(B, E);
              if E > B then
              begin
                Unselect;
                HighLight(Delta.Y + Cursor.Y, B-1, Delta.Y + Cursor.Y, E-1);
              end;
            end;
          cmCtrlQI: ConvertBlock(cvInvert);
          cmCtrlQQ,
          cmCtrlQX: ConvertBlock(cvFTable);
          cmCtrlQW: ConvertBlock(cvTable);
          cmCtrlQB: if IsSelected then GoToPos(Area^.PB, Area^.KB);
          cmCtrlQK: if IsSelected then GoToPos(Area^.PK, Area^.KK);
          cmCtrlQZ: DeleteToEndOfFile;
          else Exit;
        end;
        if IsSelected and
           (Area^.KB = Area^.KK) and (Area^.PB = Area^.PK) then Unselect;
        DrawView;
        Indicator^.DrawView;
      end;
    end;
    Event.What := evNothing;
  end;
end;

function THexEditor.GetLinePos: Integer;
begin
  GetLinePos := Pattern[Delta.X + Cursor.X] and $7F;
end;

procedure THexEditor.GetPos(AnArea: PArea; AnIndex: Integer; var APos: TPos);
begin
  if AnIndex = AnArea^.KB then APos.AStart := AnArea^.PB
  else APos.AStart := 0;
  if AnIndex = AnArea^.KK then APos.AnEnd := AnArea^.PK
  else APos.AnEnd := 16;
end;

procedure THexEditor.GetCollectionIndex(APos: Longint;
  var Index: Integer; var Pos: Word);
var
  Found: Boolean;
  I: Integer;
  PrevSize: Longint;
  BR: PHexBufRec;
begin
  GiveTicks;
  Index := 0;
  Pos := 0;
  PrevSize := 0;
  Found := False;
  while (Index < FileLines^.Count) and not Found do
  begin
    BR := FileLines^.At(Index);
    Found := (APos < PrevSize + BR^.Size) or
      ((Index = FileLines^.Count - 1) and
      (APos < PrevSize + HexBufSize));
    Inc(PrevSize, BR^.Size);
    if not Found then Inc(Index);
  end;
  if Found then
  begin
    Dec(PrevSize, BR^.Size);
    Inc(PrevSize, HexBufSize);
    Pos := HexBufSize - (PrevSize - APos);
  end;
end;

procedure THexEditor.GetValidLine(AnIndex: Integer; var ALine: String);
var
  Count, Pos: Word;
  L: Longint;
  S: String[16];

procedure FillBuffer;
var
  B: Boolean;
  Index: Integer;
  Shift, W: Word;
  FromPos: Longint;
  BR: PHexBufRec;
begin
  if FastGetLine then FromPos := L
  else FromPos := L - HexBufSize div 2;
  if FromPos + HexBufSize > NumBytes then
    FromPos := NumBytes - HexBufSize;
  if FromPos < 0 then FromPos := 0;
  GetCollectionIndex(FromPos, Index, Shift);
  FillChar(Buffer^, HexBufSize, 0);
  TopByte := FromPos;
  BufPos := 0;
  B := False;
  with SwapStream^ do
  begin
    BR := FileLines^.At(Index);
    if Shift > BR^.Size then Shift := BR^.Size;
    W := BR^.Size - Shift;
    Seek(BR^.SwapPos + Shift);
    Read(Buffer^, W);
    if Status <> stOk then
    begin
      Reset;
      SwapReadErrorMsgBox;
      B := True;
    end;
    BufPos := W;
    while (BufPos < HexBufSize) and
      (TopByte + BufPos < NumBytes) and
      (Index < FileLines^.Count - 1) do
    begin
      Inc(Index);
      BR := FileLines^.At(Index);
      W := BR^.Size;
      if BufPos + W > HexBufSize then W := HexBufSize - BufPos;
      Seek(BR^.SwapPos);
      Read(Buffer^[BufPos], W);
      if Status <> stOk then
      begin
        Reset;
        if not B then SwapReadErrorMsgBox;
        B := True;
      end;
      Inc(BufPos, W);
    end;
  end;
end;

begin
  S[0] := #16;
  FillChar(S[1], 16, 0);
  if (NumBytes > 0) and (AnIndex < NumLines) then
  begin
    L := LongMul(AnIndex, 16);
    if L < NumBytes then
    begin
      if (L < TopByte) or (L >= TopByte + HexBufSize) then FillBuffer;
      Pos := L - TopByte;
      if Pos + 16 > HexBufSize then Count := 16 - (Pos mod 16)
      else Count := 16;
      Move(Buffer^[Pos], S[1], Count);
    end;
  end;
  ALine := S;
end;

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

procedure THexEditor.SetState(AState: Word; Enable: Boolean);
begin
  TScroller.SetState(AState, Enable);
  if Enable and (AState = sfExposed) then
  begin
    Indicator^.Hide;
    EnableCommands(SaveAllCommands);
  end;
  if Enable and (AState = sfFocused) then
  begin
    Indicator^.Show;
    EnableCommands(HexCommands);
    if ClipAreaSelected then EnableCommands(PasteCommands)
    else DisableCommands(PasteCommands);
    EnableCommands(MTPasteCommands);
    if IsSelected then
      EnableCommands(BlockCommands + MTCopyCommands)
    else DisableCommands(BlockCommands + MTCopyCommands);
  end
  else if not Enable and
    (((AState = sfActive) and GetState(sfExposed)) or
    (AState = sfExposed)) then DisableCommands(
      HexCommands + PasteCommands + BlockCommands + MTCommands);
end;

procedure THexEditor.CharPressed(AChar: Char);
begin
  case AChar of
    ^I: GoOpposite;
    ^M: Enter(Delta.Y + Cursor.Y, GetLinePos);
    ^G: DeleteChar(Delta.Y + Cursor.Y, GetLinePos, False);
    ^H: DeleteChar(Delta.Y + Cursor.Y, GetLinePos, True);
    ^Y: DeleteLine(Delta.Y + Cursor.Y);
    ^L: Search(SearchData.MatchStr, SearchData.Options or frFromCursor);
    ^K: IsCtrlKPressed := True;
    ^Q: IsCtrlQPressed := True;
    ^P: IsCtrlPPressed := True;
    ^O: IsCtrlOPressed := True;
    ^J: GoHome;
    ^S: GoLeft;
    ^D: GoRight;
    ^A: GoCtrlLeft;
    ^F: GoCtrlRight;
    ^E: GoUp;
    ^X: GoDown;
    ^R: GoPgUp;
    ^C: GoPgDn;
    ^V: ToggleInsert;
    ^W: GoCtrlUp;
    ^Z: GoCtrlDown;
    else if ValidChar(AChar) then
      if IsInsert then InsertChar(
        AChar, Delta.Y + Cursor.Y, Delta.X + Cursor.X, True)
      else ReplaceChar(AChar, Delta.Y + Cursor.Y, Delta.X + Cursor.X, True);
  end;
end;

function THexEditor.ValidChar(AChar: Char): Boolean;
begin
  if IsHexy and not (AChar in ['0'..'9', 'A'..'F', 'a'..'f']) then
  begin
    if not DisableSounds then Write(^G);
    ValidChar := False;
  end
  else ValidChar := True;
end;

function THexEditor.NewSwapBlock(AnIndex: Integer; ASize: Word): Boolean;
var
  B: Boolean;
  L: Longint;
  BR: PHexBufRec;
begin
  B := False;
  L := SwapStream^.GetSize;
  SwapStream^.Seek(L);
  SwapWrite(SwapStream, L, Buffer^, HexBufSize);
  if SwapStream^.Status = stOk then
  begin
    BR := MemAlloc(SizeOf(THexBufRec));
    if BR <> nil then
    begin
      BR^.SwapPos := L;
      BR^.Size := ASize;
      FileLines^.AtInsert(AnIndex, BR);
      B := True;
    end
    else Application^.OutOfMemory;
  end
  else
  begin
    SwapStream^.Reset;
    SwapWriteErrorMsgBox;
  end;
  NewSwapBlock := B;
end;

procedure THexEditor.InsertChar(AChar: Char; AnIndex: Integer; APos: Byte;
  MoveRight: Boolean);
var
  Index: Integer;
  Pos: Word;
  CurByte, AreaBeg, AreaEnd: Longint;
  BR: PHexBufRec;
begin
  CurByte := LongMul(AnIndex, 16) + Pattern[APos] and $7F;
  if (CurByte < NumBytes) and
     (not IsHexy or (Pattern[APos] > $F)) then
  begin
    if NumBytes = MaxHexEditBytes then
    begin
      Application^.OutOfMemory;
      Exit;
    end;
    TopByte := MaxLongint;
    GetCollectionIndex(CurByte, Index, Pos);
    if (Pos = 0) and (Index > 0) and
       (PHexBufRec(FileLines^.At(Index - 1))^.Size < HexBufSize) then
      Inc(PHexBufRec(FileLines^.At(Index - 1))^.Size)
    else
    begin
      FillChar(Buffer^, HexBufSize, 0);
      BR := FileLines^.At(Index);
      if BR^.Size < HexBufSize then
      begin
        SwapStream^.Seek(BR^.SwapPos);
        SwapStream^.Read(Buffer^, BR^.Size);
        Move(Buffer^[Pos], Buffer^[Pos + 1], HexBufSize - Pos - 1);
        Buffer^[Pos] := #0;
        Inc(BR^.Size);
        SwapStream^.Seek(BR^.SwapPos);
        SwapWrite(SwapStream, BR^.SwapPos, Buffer^, BR^.Size);
        if SwapStream^.Status <> stOk then
        begin
          SwapStream^.Reset;
          SwapWriteErrorMsgBox;
          Exit;
        end;
      end
      else
      begin
        SwapStream^.Seek(BR^.SwapPos + Pos);
        SwapStream^.Read(Buffer^, BR^.Size - Pos);
        if not NewSwapBlock(Index + 1, BR^.Size - Pos) then Exit;
        FillChar(Buffer^, HexBufSize, 0);
        SwapStream^.Seek(BR^.SwapPos);
        SwapStream^.Read(Buffer^, Pos);
        BR^.Size := Pos + 1;
        SwapStream^.Seek(BR^.SwapPos);
        SwapWrite(SwapStream, BR^.SwapPos, Buffer^, HexBufSize);
        if SwapStream^.Status <> stOk then
        begin
          SwapStream^.Reset;
          SwapWriteErrorMsgBox;
          Exit;
        end;
      end;
    end;
    Inc(NumBytes);
    if IsSelected then
    begin
      AreaBeg := LongMul(Area^.KB, 16) + Area^.PB;
      AreaEnd := LongMul(Area^.KK, 16) + Area^.PK;
      if CurByte < AreaBeg then Inc(AreaBeg);
      if CurByte < AreaEnd then Inc(AreaEnd);
      IsSelected := False;
      Highlight(AreaBeg div 16, AreaBeg mod 16,
        AreaEnd div 16, AreaEnd mod 16);
    end;
  end;
  ReplaceChar(AChar, AnIndex, APos, MoveRight);
end;

procedure THexEditor.ReplaceChar(AChar: Char; AnIndex: Integer; APos: Byte;
  MoveRight: Boolean);
var
  W: Word;
  CurByte: Longint;
  S, HS: String;

function ReplaceSwapChar: Boolean;
var
  B: Boolean;
  Index: Integer;
  Shift: Word;
  BR: PHexBufRec;
begin
  B := False;
  GetCollectionIndex(CurByte, Index, Shift);
  BR := FileLines^.At(Index);
  if BR <> nil then
  begin
    SwapStream^.Seek(BR^.SwapPos + Shift);
    SwapWrite(SwapStream, BR^.SwapPos + Shift, AChar, 1);
    if BR^.Size <= Shift then BR^.Size := Shift + 1;
    B := SwapStream^.Status = stOk;
    if not B then SwapStream^.Reset;
  end;
  if not B then SwapWriteErrorMsgBox;
  ReplaceSwapChar := B;
end;

begin
  CurByte := LongMul(AnIndex, 16) + Pattern[APos] and $7F;
  if CurByte >= MaxHexEditBytes then
  begin
    Application^.OutOfMemory;
    Exit;
  end;
  GetValidLine(AnIndex, S);
  if IsHexy then
  begin
    HS := HexB(Byte(S[Pattern[APos] and $7F + 1]));
    if Pattern[APos] > $F then Str2Word('$' + AChar + HS[2], W)
    else Str2Word('$' + HS[1] + AChar, W);
    AChar := Char(W);
  end;
  TopByte := MaxLongint;
  if CurByte >= LongMul(FileLines^.Count, HexBufSize) then
  begin
    FillChar(Buffer^, HexBufSize, 0);
    if not NewSwapBlock(FileLines^.Count, 0) then Exit;
  end;
  if not ReplaceSwapChar then Exit;
  if CurByte >= NumBytes then NumBytes := CurByte + 1;
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  DrawFlag := False;
  if MoveRight then GoRight;
  IsModified := True;
  DrawView;
  ClearKbd;
end;

procedure THexEditor.Enter(AnIndex: Integer; APos: Byte);
begin
  GoDown;
  GoHome;
end;

procedure THexEditor.Fill(AChar: Char; AStart, AFinish: Longint);
var
  Index: Integer;
  Pos, W: Word;
  CurByte: Longint;
  BR: PHexBufRec;

function SwapPad: Boolean;
begin
  SwapPad := False;
  while AStart > NumBytes do
  begin
    Index := FileLines^.Count - 1;
    BR := FileLines^.At(Index);
    if (BR <> nil) and (NumBytes - BR^.Size + HexBufSize >= AStart) then
    begin
      BR^.Size := AStart - NumBytes;
      NumBytes := AStart;
    end
    else
    begin
      if BR <> nil then
      begin
        Inc(NumBytes, HexBufSize - BR^.Size);
        BR^.Size := HexBufSize;
      end;
      FillChar(Buffer^, HexBufSize, 0);
      if not NewSwapBlock(Index + 1, 0) then Exit;
    end;
  end;
  SwapPad := True;
end;

procedure SwapFill;
begin
  while CurByte <= AFinish do
  begin
    GetCollectionIndex(CurByte, Index, Pos);
    if Index = FileLines^.Count then
    begin
      FillChar(Buffer^, HexBufSize, 0);
      if not NewSwapBlock(Index, 0) then Exit;
    end;
    BR := FileLines^.At(Index);
    if BR = nil then Exit;
    W := HexBufSize - Pos;
    if CurByte + W > AFinish + 1 then W := AFinish - CurByte + 1;
    FillChar(Buffer^, W, AChar);
    SwapStream^.Seek(BR^.SwapPos + Pos);
    SwapWrite(SwapStream, BR^.SwapPos + Pos, Buffer^, W);
    if SwapStream^.Status <> stOk then
    begin
      SwapStream^.Reset;
      SwapWriteErrorMsgBox;
      Exit;
    end;
    if Pos + W > BR^.Size then BR^.Size := Pos + W;
    Inc(CurByte, W);
    if CurByte > NumBytes then NumBytes := CurByte;
    if (KbdStatus = 1) and AbortAction then Break;
  end;
end;

begin
  if (AStart < 0) or (AFinish < AStart) then Exit;
  if AStart >= MaxHexEditBytes then AStart := MaxHexEditBytes - 1;
  if AFinish >= MaxHexEditBytes then AFinish := MaxHexEditBytes - 1;
  TopByte := MaxLongint;
  if not SwapPad then Exit;
  CurByte := AStart;
  SwapFill;
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  GoToLineNum(AStart);
  DrawFlag := False;
  IsModified := True;
  DrawView;
  Indicator^.DrawView;
end;

procedure THexEditor.FillHex;
type
  DialogData = record
    Range: String[11];
    Character: String[2];
  end;
const
  Filler: String[2] = 'FF';
var
  APos: Byte;
  Ch: Char;
  W, C: Word;
  D: PDialog;
  Bruce: PView;
  Start, Finish: Longint;
  Params: Array[0..1] of Longint;
  R: TRect;
  AData: DialogData;
begin
  R.Assign(0, 0, 33, 9);
  D := New(PDialog, Init(R, 'Fill Hex'));
  with D^ do
  begin
    Options := Options or ofCentered;
    HelpCtx := hcEFillHex;
    R.Assign(17, 2, 30, 3);
    Bruce := New(PInputLine, Init(R, 11));
    Insert(Bruce);
    R.Assign(2, 2, 17, 3);
    Insert(New(PLabel, Init(R, '~R~ange to fill', Bruce)));

    R.Assign(21, 4, 25, 5);
    Bruce := New(PInputLine, Init(R, 2));
    Insert(Bruce);
    R.Assign(2, 4, 21, 5);
    Insert(New(PLabel, Init(R, '~C~har to fill with', Bruce)));

    R.Assign(3, 6, 13, 8);
    StandardOkButton(R, D);
    R.Assign(20, 6, 30, 8);
    StandardCancelButton(R, D);
    SelectNext(False);
  end;
  if IsSelected then
  begin
    Params[0] := LongMul(Area^.KB, 16) + Area^.PB;
    Params[1] := LongMul(Area^.KK, 16) + Area^.PK - 1;
    if Params[1] < Params[0] then Params[1] := Params[0];
  end
  else
  begin
    Params[0] := LongMul(Delta.Y + Cursor.Y, 16) + GetLinePos;
    Params[1] := Params[0];
  end;
  FormatStr(AData.Range, '%05x:%05x', Params);
  AData.Character := Filler;
  if Application^.ExecuteDialog(D, @AData, False) <> cmCancel then
  begin
    Filler := AData.Character;
    APos := Pos(':', AData.Range);
    if APos <> 0 then
    begin
      if not Str2Long('$' + System.Copy(
        AData.Range, 1, APos - 1), Start) then Exit;
      if not Str2Long('$' + System.Copy(
        AData.Range, APos + 1,
        Length(AData.Range) - APos), Finish) then Exit;
      if AData.Character[1] = #39 then Ch := AData.Character[2]
      else if Str2Word('$' + AData.Character, W) then Ch := Chr(W)
      else Exit;
      Fill(Ch, Start, Finish);
    end;
  end;
end;

procedure THexEditor.ConvertBlock(Action: Byte);
const
  lgAnd = 0;
  lgOr = 1;
  lgXor = 2;
  lgAdd = 3;
  lgSub = 4;
  MaskLogic: Word = lgAnd;
var
  C: Boolean;
  MaskByte, MaskIndex: Byte;
  Index: Integer;
  Pos, W: Word;
  S: String[1];
  BR: PHexBufRec;
  Start, Finish, CurByte: Longint;
  T: ConvertTable;
  MaskString: String;

procedure ReadBuffer;
begin
  with SwapStream^ do
  begin
    GetCollectionIndex(CurByte, Index, Pos);
    BR := FileLines^.At(Index);
    if BR = nil then
    begin
      CurByte := MaxLongint;
      Exit;
    end;
    W := HexBufSize - Pos;
    if CurByte + W > Finish + 1 then W := Finish - CurByte + 1;
    Seek(BR^.SwapPos + Pos);
    Read(Buffer^, W);
    if Status <> stOk then
    begin
      Reset;
      SwapReadErrorMsgBox;
      CurByte := MaxLongint;
    end
    else Inc(CurByte, W);
  end;
end;

procedure WriteBuffer;
begin
  SwapStream^.Seek(BR^.SwapPos + Pos);
  SwapWrite(SwapStream, BR^.SwapPos + Pos, Buffer^, W);
  if SwapStream^.Status <> stOk then
  begin
    SwapStream^.Reset;
    SwapWriteErrorMsgBox;
    CurByte := MaxLongint;
  end;
end;

procedure ConvertBuffer;
var
  Count: Word;
begin
  for Count := 0 to W - 1 do
    case Action of
      cvInvert:
        begin
          Inc(MaskIndex);
          if MaskIndex > Length(MaskString) then
            MaskIndex := 1;
          MaskByte := Byte(MaskString[MaskIndex]);
          case MaskLogic of
            lgAnd: Byte(Buffer^[Count]) := Byte(Buffer^[Count]) and MaskByte;
            lgOr:  Byte(Buffer^[Count]) := Byte(Buffer^[Count]) or  MaskByte;
            lgXor: Byte(Buffer^[Count]) := Byte(Buffer^[Count]) xor MaskByte;
            lgAdd: Byte(Buffer^[Count]) := Byte(Buffer^[Count]) + MaskByte;
            lgSub: Byte(Buffer^[Count]) := Byte(Buffer^[Count]) - MaskByte;
          end;
        end;
      cvUpcase: Buffer^[Count] := TPString.Upcase(Buffer^[Count]);
      cvLocase: Buffer^[Count] := TPString.Locase(Buffer^[Count]);
      else      Buffer^[Count] := T[Buffer^[Count]];
    end;
end;

function MaskToStr(const S: String): Boolean;
var
  W: Word;
  I: Integer;
  C: String[2];
begin
  if S[1] = #39 then MaskString := System.Copy(S, 2, 255)
  else
  begin
    MaskString := '';
    C := '';
    for I := 1 to Length(S) do
      if S[I] in ['0'..'9', 'A'..'F', 'a'..'f'] then
      begin
        Inc(C[0]);
        C[Length(C)] := S[I];
        if Length(C) = 2 then
        begin
          Str2Word('$' + C, W);
          C := '';
          Inc(MaskString[0]);
          MaskString[Length(MaskString)] := Char(W);
        end;
      end;
  end;
  if (MaskString = '') and (S <> '') then
    MessageBox(^C'Invalid mask string:'#13#13^C'"' +
      System.Copy(S, 1, 32) + '".', nil, mfError + mfOkButton);
  MaskToStr := MaskString <> '';
end;

function GetMask: Boolean;
type
  TData = record
    Logic: Word;
    Mask: String[255];
  end;
var
  D: PDialog;
  Bruce: PView;
  R: TRect;
  Data: TData;
begin
  GetMask := False;
  R.Assign(0, 0, 27, 14);
  D := New(PDialog, Init(R, 'Convert block'));
  with D^ do
  begin
    Options := Options or ofCentered;
    HelpCtx := hcCtrlQI;

    R.Assign(3, 3, 24, 8);
    Bruce := New(PRadioButtons, Init(R,
      NewSItem('Logical ~A~ND',
      NewSItem('Logical ~O~R',
      NewSItem('Logical ~X~OR',
      NewSItem('A~d~d (+)',
      NewSItem('~S~ubtract (-)', nil)))))));
    Insert(Bruce);
    R.Assign(2, 2, 24, 3);
    Insert(New(PLabel, Init(R, 'Operator', Bruce)));

    R.Assign(8, 9, Size.X - 6, 10);
    Bruce := New(PInputLine, Init(R, 255));
    Insert(Bruce);
    StandardHistory(Bruce, HexMaskHistoryID);
    R.Assign(2, 9, 8, 10);
    Insert(New(PLabel, Init(R, '~M~ask', Bruce)));

    R.Assign(3, 11, 13, 13);
    StandardOkButton(R, D);
    R.Assign(14, 11, 24, 13);
    StandardCancelButton(R, D);
    SelectNext(False);
  end;
  Data.Logic := MaskLogic;
  Data.Mask := HistoryStr(HexMaskHistoryID, 0);
  if Application^.ExecuteDialog(D, @Data, False) = cmCancel then Exit;
  MaskLogic := Data.Logic;
  if not MaskToStr(Data.Mask) then Exit;
  MaskIndex := 0;
  GetMask := True;
end;

begin
  if not IsSelected or ((Action = cvInvert) and not GetMask) or
    ((Action in [cvTable, cvFTable]) and
     not GetConvertTable(T, Action = cvFTable, C)) then Exit;
  DrawStatusLine('Converting characters in the selected block.');
  Start := LongMul(Area^.KB, 16) + Area^.PB;
  Finish := LongMul(Area^.KK, 16) + Area^.PK - 1;
  if Finish >= NumBytes then Finish := NumBytes - 1;
  TopByte := MaxLongint;
  CurByte := Start;
  repeat
    if CurByte > Finish then Break;
    GetCollectionIndex(CurByte, Index, W);
    ReadBuffer;
    ConvertBuffer;
    WriteBuffer;
    if (KbdStatus = 1) and AbortAction then Break;
  until False;
  DrawFlag := True;
  GoToLineNum(Start);
  DrawFlag := False;
  IsModified := True;
end;

procedure THexEditor.DeleteChar(AnIndex: Integer; APos: Byte;
  Backward: Boolean);
var
  Index: Integer;
  Pos: Word;
  CurByte, AreaBeg, AreaEnd: Longint;
  BR: PHexBufRec;
begin
  if not Backward then Dec(APos);
  CurByte := LongMul(AnIndex, 16) + APos;
  if (CurByte = 0) and Backward then Exit;
  if Backward then
  begin
    GoLeft;
    if IsHexy then GoLeft;
    Dec(CurByte);
  end;
  if CurByte >= NumBytes then Exit;
  TopByte := MaxLongint;
  GetCollectionIndex(CurByte, Index, Pos);
  BR := FileLines^.At(Index);
  if (Pos = 0) and (BR^.Size = 1) then FileLines^.AtFree(Index)
  else
  begin
    SwapStream^.Seek(BR^.SwapPos);
    SwapStream^.Read(Buffer^, BR^.Size);
    Move(Buffer^[Pos + 1], Buffer^[Pos], BR^.Size - Pos - 1);
    Dec(BR^.Size);
    Buffer^[BR^.Size] := #0;
    SwapStream^.Seek(BR^.SwapPos);
    SwapWrite(SwapStream, BR^.SwapPos, Buffer^, BR^.Size);
    if SwapStream^.Status <> stOk then
    begin
      SwapStream^.Reset;
      SwapWriteErrorMsgBox;
      Exit;
    end;
  end;
  Dec(NumBytes);
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  DrawFlag := False;
  IsModified := True;
  if IsSelected then
  begin
    AreaBeg := LongMul(Area^.KB, 16) + Area^.PB;
    AreaEnd := LongMul(Area^.KK, 16) + Area^.PK;
    if CurByte < AreaBeg then Dec(AreaBeg);
    if CurByte < AreaEnd then Dec(AreaEnd);
    IsSelected := False;
    Highlight(AreaBeg div 16, AreaBeg mod 16,
      AreaEnd div 16, AreaEnd mod 16);
  end;
  DrawView;
  ClearKbd;
end;

procedure THexEditor.DeleteLine(AnIndex: Integer);
var
  LineArea: TArea;
begin
  FillChar(LineArea, SizeOf(TArea), 0);
  LineArea.KB := AnIndex;
  LineArea.KK := AnIndex + 1;
  CutBlock(@LineArea);
  if IsSelected then
  begin
    if AnIndex = Area^.KB then Area^.PB := 0;
    if AnIndex < Area^.KB then Dec(Area^.KB);
    if AnIndex = Area^.KK then Area^.PK := 0;
    if AnIndex < Area^.KK then Dec(Area^.KK);
    IsSelected := False;
    Highlight(Area^.KB, Area^.PB, Area^.KK, Area^.PK);
  end;
  DrawView;
end;

procedure THexEditor.DeleteToEndOfFile;
var
  DelArea: TArea;
begin
  DelArea.KB := Delta.Y + Cursor.Y;
  DelArea.PB := GetLinePos;
  DelArea.KK := NumLines;
  DelArea.PK := 0;
  CutBlock(@DelArea);
  if IsSelected and (Area^.KK > DelArea.KB) then
    if Area^.KB > DelArea.KB then Unselect
    else
    begin
      Area^.KK := NumLines;
      Area^.PK := 0;
    end;
  DrawView;
end;

procedure THexEditor.CutBlock(AnArea: PArea);
var
  Index: Integer;
  Pos, W: Word;
  StartByte, EndByte, BytesToDel, DelBytes: Longint;
  BR: PHexBufRec;
begin
  StartByte := LongMul(AnArea^.KB, 16) + AnArea^.PB;
  EndByte := LongMul(AnArea^.KK, 16) + AnArea^.PK;
  if EndByte > NumBytes then EndByte := NumBytes;
  BytesToDel := EndByte - StartByte;
  if BytesToDel <= 0 then Exit;
  TopByte := MaxLongint;
  DelBytes := 0;
  while DelBytes < BytesToDel do
  begin
    GetCollectionIndex(StartByte, Index, Pos);
    BR := FileLines^.At(Index);
    SwapStream^.Seek(BR^.SwapPos);
    SwapStream^.Read(Buffer^, BR^.Size);
    W := BR^.Size - Pos;
    if DelBytes + W > BytesToDel then   W := BytesToDel - DelBytes;
    Move(Buffer^[Pos + W], Buffer^[Pos], BR^.Size - Pos - W);
    FillChar(Buffer^[BR^.Size - W], W, 0);
    SwapStream^.Seek(BR^.SwapPos);
    SwapWrite(SwapStream, BR^.SwapPos, Buffer^, BR^.Size);
    if SwapStream^.Status <> stOk then
    begin
      SwapStream^.Reset;
      SwapWriteErrorMsgBox;
      Exit;
    end
    else
    begin
      if BR^.Size = W then FileLines^.AtFree(Index)
      else Dec(BR^.Size, W);
      Inc(DelBytes, W);
      Dec(NumBytes, W);
    end;
  end;
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  DrawFlag := False;
  IsModified := True;
end;

procedure THexEditor.InsertBuffer(ABuffer: PHexBuffer; ASize: Word;
  AnIndex: Integer; APos: Byte; var InsBytes: Word);
var
  CurByte, CurExp: Longint;
  StartByte, BytesToIns, BytesToExp, ExpBytes, Pos, W: Word;
  Index: Integer;
  BR: PHexBufRec;

procedure FixBytes;
begin
  if IsInsert then
  begin
    if NumBytes + BytesToIns > MaxHexEditBytes then
      BytesToIns := MaxHexEditBytes - NumBytes;
    if CurByte < NumBytes then BytesToExp := BytesToIns
    else BytesToExp := 0;
  end
  else
  begin
    if CurByte + BytesToIns > MaxHexEditBytes then
      BytesToIns := MaxHexEditBytes - CurByte;
    BytesToExp := 0;
  end;
end;

procedure SwapExpand;
begin
  CurExp := CurByte;
  ExpBytes := 0;
  while ExpBytes < BytesToExp do
  begin
    GetCollectionIndex(CurExp, Index, Pos);
    if (Pos = 0) and (Index > 0) and
       (PHexBufRec(FileLines^.At(Index - 1))^.Size < HexBufSize) then
    begin
      if BytesToExp - ExpBytes >
        HexBufSize - PHexBufRec(FileLines^.At(Index - 1))^.Size then
          W := HexBufSize - PHexBufRec(FileLines^.At(Index - 1))^.Size
      else W := BytesToExp - ExpBytes;
      Inc(PHexBufRec(FileLines^.At(Index - 1))^.Size, W);
    end
    else
    begin
      FillChar(Buffer^, HexBufSize, 0);
      BR := FileLines^.At(Index);
      if BR^.Size < HexBufSize then
      begin
        if BytesToExp - ExpBytes > HexBufSize - BR^.Size then
          W := HexBufSize - BR^.Size
        else W := BytesToExp - ExpBytes;
        SwapStream^.Seek(BR^.SwapPos);
        SwapStream^.Read(Buffer^, BR^.Size);
        Move(Buffer^[Pos], Buffer^[Pos + W], HexBufSize - Pos - W);
        FillChar(Buffer^[Pos], W, 0);
        Inc(BR^.Size, W);
        SwapStream^.Seek(BR^.SwapPos);
        SwapWrite(SwapStream, BR^.SwapPos, Buffer^, BR^.Size);
        if SwapStream^.Status <> stOk then
        begin
          SwapStream^.Reset;
          SwapWriteErrorMsgBox;
          Exit;
        end;
      end
      else
      begin
        SwapStream^.Seek(BR^.SwapPos + Pos);
        SwapStream^.Read(Buffer^, BR^.Size - Pos);
        if not NewSwapBlock(Index + 1, BR^.Size - Pos) then Exit;
        FillChar(Buffer^, HexBufSize, 0);
        SwapStream^.Seek(BR^.SwapPos);
        SwapStream^.Read(Buffer^, Pos);
        if BytesToExp - ExpBytes > HexBufSize - Pos then
          W := HexBufSize - Pos
        else W := BytesToExp - ExpBytes;
        BR^.Size := Pos + W;
        SwapStream^.Seek(BR^.SwapPos);
        SwapWrite(SwapStream, BR^.SwapPos, Buffer^, HexBufSize);
        if SwapStream^.Status <> stOk then
        begin
          SwapStream^.Reset;
          SwapWriteErrorMsgBox;
          Exit;
        end;
      end;
    end;
    Inc(ExpBytes, W);
    Inc(CurExp, W);
    Inc(NumBytes, W);
  end;
end;

procedure SwapFill;
begin
  InsBytes := 0;
  while InsBytes < BytesToIns do
  begin
    GetCollectionIndex(CurByte, Index, Pos);
    if Index = FileLines^.Count then
    begin
      FillChar(Buffer^, HexBufSize, 0);
      if not NewSwapBlock(Index, 0) then Exit;
    end;
    BR := FileLines^.At(Index);
    if BR = nil then Exit;
    W := HexBufSize - Pos;
    if InsBytes + W > BytesToIns then W := BytesToIns - InsBytes;
    Move(ABuffer^[StartByte], Buffer^, W);
    SwapStream^.Seek(BR^.SwapPos + Pos);
    SwapWrite(SwapStream, BR^.SwapPos + Pos, Buffer^, W);
    if SwapStream^.Status <> stOk then
    begin
      SwapStream^.Reset;
      SwapWriteErrorMsgBox;
      Exit;
    end;
    if Pos + W > BR^.Size then BR^.Size := Pos + W;
    Inc(InsBytes, W);
    Inc(StartByte, W);
    Inc(CurByte, W);
    if CurByte > NumBytes then NumBytes := CurByte;
    if (KbdStatus = 1) and AbortAction then Break;
  end;
end;

begin
  CurByte := LongMul(AnIndex, 16) + APos;
  StartByte := 0;
  BytesToIns := ASize;
  FixBytes;
  TopByte := MaxLongint;
  SwapExpand;
  SwapFill;
  if BytesToIns <> ASize then Application^.OutOfMemory;
end;

procedure THexEditor.InsertBlock(AnIndex: Integer; APos: Byte);
var
  Index: Integer;
  Pos, W, Bytes: Word;
  B: PHexBuffer;
  CurByte, StartByte, EndByte, InsBytes, BytesToIns, SaveStart: Longint;
  BR: PHexBufRec;

procedure FillBuffer;
begin
  GetCollectionIndex(StartByte, Index, Pos);
  FillChar(B^, HexBufSize, 0);
  BufPos := 0;
  with SwapStream^ do
  begin
    BR := FileLines^.At(Index);
    if Pos > BR^.Size then Pos := BR^.Size;
    W := BR^.Size - Pos;
    if InsBytes + W > BytesToIns then W := BytesToIns - InsBytes;
    Seek(BR^.SwapPos + Pos);
    Read(B^, W);
    Inc(InsBytes, W);
    BufPos := W;
    while (BufPos < HexBufSize) and (InsBytes < BytesToIns) do
    begin
      Inc(Index);
      BR := FileLines^.At(Index);
      W := BR^.Size;
      if InsBytes + W > BytesToIns then W := BytesToIns - InsBytes;
      if BufPos + W > HexBufSize then W := HexBufSize - BufPos;
      Seek(BR^.SwapPos);
      Read(B^[BufPos], W);
      Inc(InsBytes, W);
      Inc(BufPos, W);
    end;
  end;
end;

begin
  if IsInsert then DrawStatusLine('Inserting the selected block.')
  else DrawStatusLine('Overwriting data with the selected block.');
  CurByte := LongMul(AnIndex, 16) + APos;
  StartByte := LongMul(Area^.KB, 16) + Area^.PB;
  EndByte := LongMul(Area^.KK, 16) + Area^.PK;
  if EndByte > NumBytes then EndByte := NumBytes;
  BytesToIns := EndByte - StartByte;
  if BytesToIns <= 0 then Exit;
  B := MemAlloc(HexBufSize);
  if B = nil then
  begin
    Application^.OutOfMemory;
    Exit;
  end;
  SaveStart := StartByte;
  InsBytes := 0;
  repeat
    FillBuffer;
    Inc(StartByte, BufPos);
    InsertBuffer(B, BufPos, CurByte div 16, CurByte mod 16, Bytes);
    Inc(CurByte, Bytes);
    if CurByte < SaveStart then Inc(StartByte, Bytes);
  until (InsBytes >= BytesToIns) or (BufPos <> Bytes);
  FreeMem(B, HexBufSize);
  IsSelected := False;
  Highlight(AnIndex, GetLinePos, CurByte div 16, CurByte mod 16);
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  DrawFlag := False;
  IsModified := True;
end;

procedure THexEditor.MoveBlock(AnIndex: Integer; APos: Byte);
var
  CurByte, StartByte, EndByte: Longint;
  F: File;
  S: FNameStr;
begin
  DrawStatusLine('Moving the selected block.');
  GetValidSwapDir(S, False);
  S := S + TempName + '.!!!';
  if not WriteBlock(S, False) then Exit;
  CutBlock(Area);
  CurByte := LongMul(AnIndex, 16) + APos;
  StartByte := LongMul(Area^.KB, 16) + Area^.PB;
  EndByte := LongMul(Area^.KK, 16) + Area^.PK;
  if CurByte > StartByte then
  begin
    if CurByte > EndByte then Dec(CurByte, EndByte - StartByte)
    else CurByte := StartByte;
    if CurByte > NumBytes then CurByte := NumBytes;
    DrawFlag := True;
    GoToLineNum(CurByte);
    DrawFlag := False;
  end;
  ReadBlock(S, False);
  Assign(F, S);
  Erase(F);
  InOutRes := 0;
end;

function THexEditor.ReadBlock(const FName: FNameStr;
  ConcatLast: Boolean): Boolean;
var
  BytesRead, Bytes: Word;
  F: lFile;
  CurByte: Longint;
  B: PHexBuffer;
begin
  ReadBlock := False;
  lAssignFile(F, FName);
  lResetReadOnly(F, 1);
  if IOResult <> 0 then
  begin
    CouldNotOpenMsgBox(FName);
    Exit;
  end;
  if IsDevice(FileRec(F.F).Handle) then
  begin
    Close(F.F);
    InOutRes := 0;
    FileIsDeviceMsgBox(FName);
    Exit;
  end;
  B := MemAlloc(HexBufSize);
  if B = nil then
  begin
    Close(F.F);
    InOutRes := 0;
    Application^.OutOfMemory;
    Exit;
  end;
  CurByte := LongMul(Delta.Y + Cursor.Y, 16) + GetLinePos;
  repeat
    BlockRead(F.F, B^, HexBufSize, BytesRead);
    if BytesRead > 0 then
    begin
      InsertBuffer(B, BytesRead, CurByte div 16, CurByte mod 16, Bytes);
      Inc(CurByte, Bytes);
    end;
  until (BytesRead = 0) or (BytesRead <> Bytes) or (IOResult <> 0);
  FreeMem(B, HexBufSize);
  Close(F.F);
  InOutRes := 0;
  IsSelected := False;
  Highlight(Delta.Y + Cursor.Y, GetLinePos,
    CurByte div 16, CurByte mod 16);
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  if (NewLocalOptions and neoPasteGoesToEnd <> 0) and
    IsSelected then GoToPos(Area^.PK, Area^.KK);
  DrawFlag := False;
  IsModified := True;
  ReadBlock := True;
end;

function THexEditor.WriteBlock(const FName: FNameStr;
  Append: Boolean): Boolean;
var
  Index: Integer;
  Pos, W, BytesWritten: Word;
  CurByte, StartByte, EndByte, InsBytes, BytesToIns: Longint;
  F: lFile;
  BR: PHexBufRec;

procedure FillBuffer;
begin
  GetCollectionIndex(CurByte, Index, Pos);
  FillChar(Buffer^, HexBufSize, 0);
  BufPos := 0;
  with SwapStream^ do
  begin
    BR := FileLines^.At(Index);
    if Pos > BR^.Size then Pos := BR^.Size;
    W := BR^.Size - Pos;
    if InsBytes + W > BytesToIns then W := BytesToIns - InsBytes;
    Seek(BR^.SwapPos + Pos);
    Read(Buffer^, W);
    Inc(CurByte, W);
    Inc(InsBytes, W);
    BufPos := W;
    while (BufPos < HexBufSize) and (InsBytes < BytesToIns) do
    begin
      Inc(Index);
      BR := FileLines^.At(Index);
      W := BR^.Size;
      if InsBytes + W > BytesToIns then W := BytesToIns - InsBytes;
      if BufPos + W > HexBufSize then W := HexBufSize - BufPos;
      Seek(BR^.SwapPos);
      Read(Buffer^[BufPos], W);
      Inc(CurByte, W);
      Inc(InsBytes, W);
      Inc(BufPos, W);
    end;
  end;
end;

begin
  WriteBlock := False;
  StartByte := LongMul(Area^.KB, 16) + Area^.PB;
  EndByte := LongMul(Area^.KK, 16) + Area^.PK;
  if EndByte > NumBytes then EndByte := NumBytes;
  BytesToIns := EndByte - StartByte;
  if BytesToIns <= 0 then Exit;
  CurByte := StartByte;
  lAssignFile(F, FName);
  if Append then
  begin
    lResetFile(F, 1);
    if IOResult <> 0 then lRewriteFile(F, 1)
    else Seek(F.F, FileSize(F.F));
  end
  else lRewriteFile(F, 1);
  if IOResult <> 0 then
  begin
    CouldNotOpenMsgBox(FName);
    Exit;
  end;
  if NotAccessible(FileRec(F.F).Handle) then
  begin
    Close(F.F);
    InOutRes := 0;
    FileIsDeviceMsgBox(FName);
    Exit;
  end;
  TopByte := MaxLongint;
  InsBytes := 0;
  repeat
    FillBuffer;
    BlockWrite(F.F, Buffer^, BufPos, BytesWritten);
    if (IOResult <> 0) or (BufPos <> BytesWritten) then
    begin
      CouldNotWriteMsgBox(FName);
      Break;
    end;
    if (KbdStatus = 1) and AbortAction then Break;
  until InsBytes >= BytesToIns;
  Close(F.F);
  InOutRes := 0;
  WriteBlock := True;
end;

procedure THexEditor.Copy;
var
  B: Byte;
  I, SaveCount, MaxLen: Integer;
  S, C: String;
begin
  if DiskClipStream = nil then Exit;
  DrawClipCopyStatus;
  SetExpandClipAreaFlag;
  I := Area^.KB;
  SaveCount := ClipLines^.Count;
  MaxLen := 0;
  while I <= Area^.KK do
  begin
    GetSelectedString(I, S);
    if IsHexy then
    begin
      C := S;
      S := '';
      for B := 1 to Length(C) do S := S + HexB(Byte(C[B]));
    end;
    if S <> '' then
    begin
      PutClipLine(S);
      if not MarkColumns or (Length(S) > MaxLen) then MaxLen := Length(S);
    end;
    if ClipOverflow or ((KbdStatus = 1) and AbortAction) then Break;
    Inc(I);
  end;
  SetOrExpandClipArea(SaveCount, MaxLen + 1);
  Message(Desktop, evBroadcast, cmUpdateViews, nil);
  EnableCommands(PasteCommands);
end;

procedure THexEditor.Paste;
var
  Full: Boolean;
  Index: Integer;
  Bytes: Word;
  CurByte: Longint;
  S: String;
begin
  if (DiskClipStream = nil) or not ClipAreaSelected then Exit;
  DrawStatusLine('Pasting the selected text from the Clipboard.');
  Message(Desktop, evBroadcast, cmUpdateViews, nil);
  CurByte := LongMul(Delta.Y + Cursor.Y, 16) + GetLinePos;
  Index := ClipArea^.KB;
  Full := False;
  while (Index <= ClipArea^.KK) and not Full do
  begin
    GetClipLine(Index, S);
    InsertBuffer(PHexBuffer(@S[1]), Byte(S[0]),
      CurByte div 16, CurByte mod 16, Bytes);
    Full := Byte(S[0]) <> Bytes;
    Inc(CurByte, Bytes);
    Inc(Index);
  end;
  IsSelected := False;
  Highlight(Delta.Y + Cursor.Y, GetLinePos, CurByte div 16, CurByte mod 16);
  NumLines := NumBytes div 16 + 1;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  if (NewLocalOptions and neoPasteGoesToEnd <> 0) and
    IsSelected then GoToPos(Area^.PK, Area^.KK);
  DrawFlag := False;
  IsModified := True;
  DrawView;
  Indicator^.DrawView;
end;

procedure THexEditor.ReplaceWith(const AString: String; ALength: Byte);
var
  SaveSelect: Boolean;
  Bytes: Word;
  Index, Pos, Len, D: Integer;
  CurByte, AreaBeg, AreaEnd: Longint;
  CutArea, SaveArea: TArea;
begin
  Index := Delta.Y + Cursor.Y;
  Pos := GetLinePos;
  Len := Length(AString);
  SetArea(@CutArea, Index, Pos,
    Index + ALength div 16, Pos + ALength mod 16);
  if IsInsert then CutBlock(@CutArea);
  InsertBuffer(PHexBuffer(@AString[1]), Len,
    CutArea.KB, CutArea.PB, Bytes);
  NumLines := NumBytes div 16 + 1;
  IsModified := True;
  DrawFlag := True;
  SetLimit(78, NumLines + Size.Y);
  CurByte := LongMul(Index, 16) + Pos;
  if not IsInsert and (Len < ALength) then Inc(CurByte, ALength)
  else Inc(CurByte, Len);
  GoToLineNum(CurByte);
  SaveArea := Area^;
  SaveSelect := IsSelected;
  IsSelected := True;
  if not IsInsert and (Len <> 0) then
    CurByte := LongMul(Index, 16) + Pos + Len;
  SetArea(Area, Index, Pos, CurByte div 16, CurByte mod 16);
  DrawFlag := False;
  DrawView;
  Area^ := SaveArea;
  IsSelected := SaveSelect;
  D := Len - ALength;
  if IsInsert and (D <> 0) then
  begin
    CurByte := LongMul(CutArea.KB, 16) + Pos;
    AreaBeg := LongMul(Area^.KB, 16) + Area^.PB;
    AreaEnd := LongMul(Area^.KK, 16) + Area^.PK;
    if CurByte < AreaBeg then Inc(AreaBeg, D);
    if CurByte < AreaEnd then Inc(AreaEnd, D);
    SetArea(Area, AreaBeg div 16, AreaBeg mod 16,
      AreaEnd div 16, AreaEnd mod 16);
  end;
end;

procedure THexEditor.GoToPos(APos: Byte; AnIndex: Integer);
var
  X, SX: Byte;
  Within: Boolean;
  Y, SY: Integer;
begin
  if AnIndex > Pred(NumLines) then AnIndex := Pred(NumLines);
  Within := (AnIndex < Delta.Y + Size.Y) and (AnIndex >= Delta.Y);
  if Within and
    ((IsHexy and (HexPos[APos] >= Delta.X) and
     (HexPos[APos] < Delta.X + Size.X)) or
     (not IsHexy and (APos + 62 >= Delta.X) and
     (APos + 62 < Delta.X + Size.X))) then
       if IsHexy then SetCursor(HexPos[APos] - Delta.X, AnIndex - Delta.Y)
       else SetCursor(APos + 62 - Delta.X, AnIndex - Delta.Y)
  else
  begin
    if IsHexy then X := HexPos[APos] else X := APos + 62;
    if X > Pred(Size.X) then SX := X - Pred(Size.X) else SX := 0;
    if X > Pred(Size.X) then X := Pred(Size.X);
    if AnIndex = Pred(NumLines) then SY := AnIndex - Pred(Size.Y)
    else if Within then SY := Delta.Y else SY := AnIndex - Size.Y div 2;
    if SY < 0 then SY := 0;
    if AnIndex = Pred(NumLines) then Y := Pred(Size.Y)
    else if Within then Y := AnIndex - Delta.Y else Y := Size.Y div 2;
    if SY = 0 then Y := AnIndex;
    ScrollTo(SX, SY);
    SetCursor(X, Y);
  end;
  if LocalEditOptions and (eoHilitCurLine + eoHilitCurColumn) <> 0 then
    DrawView;
  Indicator^.DrawView;
end;

procedure THexEditor.GoToLineNum(AnIndex: Longint);
var
  APos: Byte;
  I: Integer;
begin
  if (AnIndex >= LongMul(NumLines, 16)) or (AnIndex < 0) then Exit;
  I := AnIndex div 16;
  APos := AnIndex - LongMul(I, 16);
  GoToPos(APos, I);
end;

procedure THexEditor.GoOpposite;
begin
  IsHexy := not IsHexy;
  GoToPos(GetLinePos, Delta.Y + Cursor.Y);
end;

procedure THexEditor.GoRight;
begin
  if Pattern[Delta.X + Cursor.X] = $F then
  begin
    GoDown;
    GoHome;
  end
  else
  begin
    repeat
      SetCursor(Cursor.X + 1, Cursor.Y);
      if Cursor.X = Size.X then ScrollTo(Delta.X + 1, Delta.Y);
    until Pattern[Delta.X + Cursor.X] <> $7F;
    if LocalEditOptions and eoHilitCurColumn <> 0 then DrawView;
  end;
end;

procedure THexEditor.GoLeft;
begin
  if (Delta.Y + Cursor.Y = 0) and
    ((Delta.X + Cursor.X = 7) or (Delta.X + Cursor.X = 62)) then Exit;
  if (Delta.X + Cursor.X = 7) or (Delta.X + Cursor.X = 62) then
  begin
    GoUp;
    GoEnd;
  end
  else
  begin
    repeat
      SetCursor(Cursor.X - 1, Cursor.Y);
      if Cursor.X < 0 then ScrollTo(Delta.X - 1, Delta.Y);
    until Pattern[Delta.X + Cursor.X] <> $7F;
    if LocalEditOptions and eoHilitCurColumn <> 0 then DrawView;
  end;
end;

procedure THexEditor.GoWhileKeyDown(Key: Byte);
begin
  if MultiScrolling or
    ((KeyboardOptions and koDisableMultiScroll = 0) and
     (GetShiftState and kbScrollState <> 0)) then Exit;
  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 >= NumLines - 1 then Exit
          else FastScrollTo(Delta.Y + 1);
      81: if Delta.Y + Size.Y >= NumLines - 1 then Exit
          else FastScrollTo(Delta.Y + Size.Y - 1);
    end;
    Indicator^.DrawView;
    if KbdDelay <> 0 then Delay(KbdDelay);
  until False;
end;

procedure THexEditor.GoDown;
begin
  if Delta.Y + Cursor.Y >= NumLines - 1 then Exit;
  if Cursor.Y <> Size.Y - 1 then
  begin
    SetCursor(Cursor.X, Cursor.Y + 1);
    if LocalEditOptions and eoHilitCurLine <> 0 then DrawView;
  end
  else
  begin
    FastScrollTo(Delta.Y + 1);
    if ((KbdStatus = 80) or (KbdStatus = 224)) and
       (GetShiftState and (kbLeftShift + kbRightShift) = 0) then
      GoWhileKeyDown(80);
  end;
end;

procedure THexEditor.GoPgDn;
begin
  if Delta.Y + Size.Y >= NumLines - 1 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 >= NumLines then
    SetCursor(Cursor.X, NumLines - Delta.Y - 1);
end;

procedure THexEditor.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 THexEditor.GoCtrlPgDn;
begin
  GoToLineNum(NumBytes);
end;

procedure THexEditor.GoCtrlDown;
begin
  if Delta.Y < Limit.Y - Size.Y - 1 then
  begin
    FastScrollTo(Delta.Y + 1);
    if Delta.Y + Cursor.Y >= NumLines then
    begin
      SetCursor(Cursor.X, Cursor.Y - 1);
      if LocalEditOptions and eoHilitCurLine <> 0 then DrawView;
    end;
  end;
end;

procedure THexEditor.GoEnd;
begin
  GoToPos($F, Delta.Y + Cursor.Y);
end;

procedure THexEditor.GoHome;
begin
  GoToPos($0, Delta.Y + Cursor.Y);
end;

procedure THexEditor.GoCtrlEnd;
var
  NewY: Integer;
begin
  if Delta.Y + Size.Y >= NumLines then
    NewY := NumLines - 1 else NewY := Size.Y - 1;
  SetCursor(Cursor.X, NewY);
  if LocalEditOptions and eoHilitCurLine <> 0 then DrawView;
end;

procedure THexEditor.GoCtrlRight;
var
  L: Longint;
begin
  L := LongMul(Delta.Y + Cursor.Y, 16) + GetLinePos;
  GoToLineNum(L + 2);
end;

procedure THexEditor.GoCtrlLeft;
var
  L: Longint;
begin
  L := LongMul(Delta.Y + Cursor.Y, 16) + GetLinePos;
  GoToLineNum(L - 2);
end;

function THexEditor.Save(const AName: FNameStr; Append: Boolean): Boolean;
var
  Aborted, Success: Boolean;
  Index: Integer;
  Bytes: Word;
  BR: PHexBufRec;
  F: lFile;
  SR: lSearchRec;
  D, N, E: String;
  Name, BackupName: FNameStr;
begin
  if State and sfNoname <> 0 then
  begin
    Save := SaveAsPrim(@Self);
    Exit;
  end;
  Save := False;
  Aborted := False;
  if AName = '' then Name := FileName^
  else Name := AName;
  LogEvent := lcDiskWrite;
  DrawStatusLine('Saving ' + TrimmedName(Name, 62) + '.');
  if LocalEditOptions and eoBackupFiles <> 0 then
  begin
    lFSplit(Name, D, N, E);
    if E <> '.BAK' then
    begin
      BackupName := D + N + '.BAK';
      lAssignFile(F, BackupName);
      lEraseFile(F);
      InOutRes := 0;
      lAssignFile(F, Name);
      lRenameFile(F, BackupName);
      InOutRes := 0;
    end;
  end;
  lAssignFile(F, Name);
  if FileAttr and (ReadOnly + Hidden + SysFile) <> 0 then
    if OverwriteReadOnlyMsgBox(Name) = cmCancel then Exit
    else
    begin
      DrawStatusLine('');
      lSetFAttr(F, Archive);
    end;
  if Append then
  begin
    lResetFile(F, 1);
    if IOResult <> 0 then lRewriteFile(F, 1)
    else Seek(F.F, FileSize(F.F));
  end
  else lRewriteFile(F, 1);
  if IOResult <> 0 then
  begin
    CouldNotCreateMsgBox(Name);
    Exit;
  end;
  if NotAccessible(FileRec(F.F).Handle) then
  begin
    Close(F.F);
    InOutRes := 0;
    FileIsDeviceMsgBox(Name);
    Exit;
  end;
  NewTitle(Name);
  TopByte := MaxLongint;
  Index := 0;
  Success := True;
  with SwapStream^ do while Index < FileLines^.Count do
  begin
    GiveTicks;
    BR := FileLines^.At(Index);
    Seek(BR^.SwapPos);
    Read(Buffer^, BR^.Size);
    BlockWrite(F.F, Buffer^, BR^.Size, Bytes);
    Inc(Index);
    if (IOResult <> 0) or (BR^.Size <> Bytes) then
    begin
      CouldNotWriteMsgBox(Name);
      Success := False;
      Break;
    end;
    if (KbdStatus = 1) and AbortAction then
    begin
      Aborted := True;
      Break;
    end;
  end;
  Close(F.F);
  if FileAttr <> Archive then lSetFAttr(F, FileAttr);
  lFindFirst(Name, AnyFile and not (VolumeID + Directory), SR);
  lFindClose(SR);
  if DosError = 0 then LastTime := SR.SR.Time;
  InOutRes := 0;
  IsModified := Aborted;
  Indicator^.DrawView;
  Save := Success;
  UpdateFiles(Name);
  if Append then UpdateLines(Name);
end;

function THexEditor.GetPrintLine(AnIndex: Integer; Sel: Boolean;
  var ALine: String): Boolean;
var
  LineCount: Byte;
  HS: String[2];
  CurStr: LongInt;
  S: String;
begin
  if AnIndex >= NumLines then GetPrintLine := False
  else
  begin
    GetValidLine(AnIndex, S);
    CurStr := LongMul(AnIndex, 16);
    ALine := System.Copy(HexL(CurStr), 4, 5) +
      '              ³             ³             ³            ';
    for LineCount := 0 to 15 do
    if CurStr + LineCount < NumBytes then
    begin
      HS := HexB(Byte(S[LineCount + 1]));
      Move(HS[1], ALine[HexPos[LineCount] + 1], 2);
    end;
    for LineCount := 1 to Length(S) do
      if S[LineCount] in [#1..#31] then S[LineCount] := '.';
    ALine := ALine + '  ' + S;
    GetPrintLine := True;
  end;
end;

function THexEditor.GetWord(var B, E: Byte): String;
var
  Index: Integer;
  S: String;
begin
  B := GetLinePos + 1;
  if B = 16 then Dec(B);
  E := B + 2;
  Index := Delta.Y + Cursor.Y;
  GetValidLine(Index, S);
  if IsHexy then GetWord := '$' + HexB(Byte(S[B])) + HexB(Byte(S[B + 1]))
  else GetWord := System.Copy(S, B, 2);
end;

function THexEditor.GetLine: String;
var
  Pos: Byte;
  Index: Integer;
  S: String;
begin
  Pos := GetLinePos + 1;
  Index := Delta.Y + Cursor.Y;
  GetLine := '';
  if IsHexy then Exit;
  GetValidLine(Index, S);
  while (Pos > 0) and not WordChar(S[Pos]) do Dec(Pos);
  while (Pos > 0) and WordChar(S[Pos]) do Dec(Pos);
  GetLine := System.Copy(S, Pos + 1, 16);
end;

procedure THexEditor.ReadFile(var FName: String);
var
  BytesToRead, BytesRead: Word;
  L, Result: Longint;
  F: lFile;
  BR: PHexBufRec;
begin
  SwapStream := DiskSwapStream;
  if SwapStream = nil then
  begin
    SwapAccessErrorMsgBox;
    IsValid := False;
    Exit;
  end;
  if FName = '' then State := State or sfNoname
  else State := State and not sfNoname;
  if State and sfNoname <> 0 then FName := 'NONAME'
  else
  begin
    LogEvent := lcDiskRead;
    DrawStatusLine('Loading ' + TrimmedName(FName, 61) + '.');
  end;
  NewTitle(FName);
  IsValid := True;
  IsModified := False;
  FileAttr := Archive;
  Options := Options or ofHex;
  IsCtrlKPressed := False;
  IsCtrlQPressed := False;
  IsCtrlPPressed := False;
  IsInsert := State and sfCursorIns = 0;
  IsHexy := Delta.X + Cursor.X < 60;
  IsClip := False;
  NumBytes := 0;
  TopByte := MaxLongint;
  SetLastLine('', -1);
  InitLocalFields(False);
  Byte(IsSelected) := Byte(IsSelected) and not ofSaveLocal;
  FileLines := New(PHexCollection, Init(4, 4));
  FindAllList := New(PSwapLinesCollection, Init(8, 8));
  if Buffer = nil then Buffer := MemAlloc(HexBufSize);
  if Buffer = nil then
  begin
    Application^.OutOfMemory;
    IsValid := False;
    Exit;
  end;
  if State and sfNoname = 0 then
  begin
    lAssignFile(F, FName);
    lGetFAttr(F, FileAttr);
    if DosError <> 0 then FileAttr := Archive;
    lResetReadOnly(F, 1);
    Result := IOResult;
    if Result = 0 then
    begin
      if IsDevice(FileRec(F.F).Handle) then
      begin
        FileIsDeviceMsgBox(FName);
        IsValid := False;
      end
      else
      begin
        L := SwapStream^.GetSize;
        SwapStream^.Seek(L);
        repeat
          FillChar(Buffer^, HexBufSize, 0);
          if NumBytes + HexBufSize > MaxHexEditBytes then
            BytesToRead := MaxHexEditBytes - NumBytes + 1
          else BytesToRead := HexBufSize;
          BlockRead(F.F, Buffer^, BytesToRead, BytesRead);
          InOutRes := 0;
          if BytesRead > 0 then
          begin
            if NumBytes + BytesRead > MaxHexEditBytes then
              Buffer^[BytesRead - 1] := #0;
            SwapWrite(SwapStream, -1, Buffer^, HexBufSize);
            if SwapStream^.Status = stOk then
            begin
              BR := MemAlloc(SizeOf(THexBufRec));
              if BR <> nil then
              begin
                BR^.SwapPos := L;
                BR^.Size := BytesRead;
                FileLines^.Insert(BR);
                Inc(NumBytes, BytesRead);
              end;
              Inc(L, HexBufSize);
            end
            else Break;
            if (KbdStatus = 1) and AbortAction then
            begin
              IsValid := False;
              IsModified := True;
              Break;
            end;
          end;
        until (BR = nil) or (BytesRead = 0) or (NumBytes > MaxHexEditBytes);
        SwapSize := L;
        if SwapStream^.Status <> stOk then
        begin
          if TruncateNoSwapMsgBox(FName) = cmCancel then IsValid := False;
          IsModified := True;
        end
        else if (BR = nil) or (NumBytes > MaxHexEditBytes) then
        begin
          if TruncateLinesMsgBox(FName) = cmCancel then IsValid := False;
          IsModified := True;
        end;
        SwapStream^.Reset;
        GetFTime(F.F, LastTime);
      end;
      Close(F.F);
      InOutRes := 0;
    end
    else if Result = 2 then
      if (EditOptions and eoPromptOnNew <> 0) and
         (CreateNewFileMsgBox(FName) = cmCancel) then
        IsValid := False else
    else
    begin
      CouldNotOpenCodeMsgBox(FName, Result);
      IsValid := False;
    end;
  end;
  if NumBytes > MaxHexEditBytes then NumBytes := MaxHexEditBytes;
  NumLines := NumBytes div 16 + 1;
  Limit.X := 78;
  Limit.Y := NumLines;
  if Limit.Y > 1 then Inc(Limit.Y, Size.Y);
  SetLimit(Limit.X, Limit.Y);
  if Cursor.X < 7 then SetCursor(7, Cursor.Y);
  ShowCursor;
  if IsValid and (State and sfNoname = 0) then
  begin
    UpdateHistory;
    if EditOptions and eoChangeDirOnOpen <> 0 then
    begin
      FailSysErrors := True;
      lChDir(JustPathName(FName));
      FailSysErrors := False;
      InOutRes := 0;
    end;
  end;
  if Delta.Y + Cursor.Y >= NumLines then GoCtrlPgDn;
end;

function THexEditor.Search(const Match: String; AOptions: Word): Boolean;
var
  Found, SaveSelect, B, Hex: Boolean;
  Index: Integer;
  Pos, W: Word;
  CurByte, EndByte: Longint;
  BR: PHexBufRec;
  SaveArea: TArea;
  BT: BTable;
  S: String;

procedure FillBuffer;
begin
  GetCollectionIndex(CurByte, Index, Pos);
  FillChar(Buffer^, HexBufSize, 0);
  BufPos := 0;
  B := False;
  with SwapStream^ do
  begin
    BR := FileLines^.At(Index);
    Seek(BR^.SwapPos + Pos);
    W := BR^.Size - Pos;
    if CurByte + W > EndByte then W := EndByte - CurByte;
    Read(Buffer^, W);
    if Status <> stOk then
    begin
      Reset;
      B := True;
      SwapReadErrorMsgBox;
    end;
    BufPos := W;
    while (BufPos < HexBufSize) and
      (CurByte + BufPos <= EndByte - Length(S)) and
      (Index < FileLines^.Count - 1) do
    begin
      Inc(Index);
      BR := FileLines^.At(Index);
      W := BR^.Size;
      if CurByte + W > EndByte then W := EndByte - CurByte;
      if BufPos + W > HexBufSize then W := HexBufSize - BufPos;
      Seek(BR^.SwapPos);
      Read(Buffer^[BufPos], W);
      if Status <> stOk then
      begin
        Reset;
        if not B then SwapReadErrorMsgBox;
        B := True;
      end;
      Inc(BufPos, W);
    end;
  end;
end;

begin
  Search := False;
  if Match = '' then Exit;
  if Byte(IsSearchingAll) <= 1 then DrawStatusLine(SearchMsg);
  S := Match;
  if AOptions and frCaseSensitive = 0 then UpcaseSt(S);
  if S[1] = '$' then S := Hex2Match(S, Hex);
  if not IsSelected then AOptions := AOptions and not frSelectedText;
  Index := Delta.Y + Cursor.Y;
  if Byte(IsSearchingAll) > 1 then
    CurByte := LongMul(FindAllIndex, 16)
  else if AOptions and frSelectedText <> 0 then
  begin
    CurByte := LongMul(Area^.KB, 16) + Area^.PB;
    if AOptions and frFromCursor <> 0 then
      if ((Index > Area^.KB) or ((Index = Area^.KB) and
           (GetLinePos + 1 >= Area^.PB))) and
         ((Index < Area^.KK) or ((Index = Area^.KK) and
           (GetLinePos + 1 <= Area^.PK))) then
        CurByte := LongMul(Index, 16) + GetLinePos +
          Byte(not IsSearchingFrom)
      else if ((Index > Area^.KK) or ((Index = Area^.KK) and
          (GetLinePos + 1 > Area^.PK))) then CurByte := MaxLongint;
  end
  else if AOptions and frFromCursor <> 0 then
    CurByte := LongMul(Index, 16) + GetLinePos +
      Byte(not IsSearchingFrom)
  else CurByte := 0;
  if AOptions and frSelectedText <> 0 then
    EndByte := LongMul(Area^.KK, 16) + Area^.PK
  else EndByte := NumBytes;
  Found := False;
  BMMakeTable(S, BT);
  while (CurByte <= EndByte - Length(S)) and not Found do
  begin
    FillBuffer;
    if BufPos < Length(S) then Pos := $FFFF
    else if (AOptions and frCaseSensitive <> 0) or
      ((Match[1] = '$') and Hex) then
        Pos := BMSearch(Buffer^, BufPos, BT, S)
    else Pos := BMSearchUC(Buffer^, BufPos, BT, S);
    Found := Pos <> $FFFF;
    if Found then Inc(CurByte, Pos)
    else
    begin
      Inc(CurByte, BufPos);
      if CurByte < EndByte - Length(S) then Dec(CurByte, Length(S) - 1);
    end;
    if (KbdStatus = 1) and AbortAction then
    begin
      Found := False;
      FindAllIndex := -1;
      IsSearchingAll := True;
      Break;
    end;
  end;
  TopByte := MaxLongint;
  if not Found then
    if not IsSearchingAll and not IsReplacing then
      SearchStringNotFoundMsgBox
    else IsSearchingAll := False
  else
  begin
    if IsSearchingAll then
    begin
      FindAllIndex := CurByte div 16 + 1;
      FindAllList^.Insert(Pointer(FindAllIndex - 1));
      Byte(IsSearchingAll) := 2;
    end
    else
    begin
      ReplaceLen := Length(S);
      DrawFlag := True;
      GoToLineNum(CurByte);
      DrawFlag := False;
      EndByte := LongMul(Delta.Y + Cursor.Y, 16) + GetLinePos + Length(S);
      SaveArea := Area^;
      SaveSelect := IsSelected;
      IsSelected := True;
      SetArea(Area, Delta.Y + Cursor.Y, GetLinePos,
        EndByte div 16, EndByte mod 16);
      DrawView;
      Area^ := SaveArea;
      IsSelected := SaveSelect;
      DoRedraw := True;
    end;
    Search := True;
  end;
end;

procedure THexEditor.SwapTruncate;
var
  P: PHexBufRec;
begin
  if (DiskSwapStream <> nil) and (SwapStream^.GetSize = SwapSize) then
  begin
    P := FileLines^.At(0);
    if P <> nil then
    begin
      SwapStream^.Seek(P^.SwapPos);
      SwapStream^.Truncate;
    end;
  end;
end;

procedure THexEditor.PopUp(AnOrigin: TPoint);
begin
  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('M~a~rk line               Ctrl+K L', '', kbNoKey, cmCtrlKL, hcCtrlKL,
    NewItem('Copy block              Ctrl+K ~C~', '', kbNoKey, cmCtrlKC, hcCtrlKC,
    NewItem('Move block              Ctrl+K ~V~', '', kbNoKey, cmCtrlKV, hcCtrlKV,
    NewItem('Delete block            Ctrl+K ~Y~', '', kbNoKey, cmCtrlKY, hcCtrlKY,
    NewItem('Read block from disk... Ctrl+K ~R~', '', kbNoKey, cmCtrlKR, hcCtrlKR,
    NewItem('Write block to disk...  Ctrl+K ~W~', '', kbNoKey, cmCtrlKW, hcCtrlKW,
    NewItem('Hide/Display block      Ctrl+K ~H~', '', kbNoKey, cmCtrlKH, hcCtrlKH,
    NewItem('Print block             Ctrl+K ~P~', '', kbNoKey, cmCtrlKP, hcCtrlKP,
    Menus.NewLine(
    NewItem('Fill...                 Ctrl+K ~F~', '', kbNoKey, cmFillHex, hcEFillHex,
    Menus.NewLine(
    NewItem('Move to be~g~in of block  Ctrl+Q B', '', kbNoKey, cmCtrlQB, hcCtrlQB,
    NewItem('Move to ~e~nd of block    Ctrl+Q K', '', kbNoKey, cmCtrlQK, hcCtrlQK,
    Menus.NewLine(
    NewItem('Uppercase block         Ctrl+Q ~U~', '', kbNoKey, cmCtrlKI, hcCtrlQU,
    NewItem('Lowercase block         Ctrl+Q ~L~', '', kbNoKey, cmCtrlKU, hcCtrlQL,
    NewItem('Convert block...        Ctrl+Q ~X~', '', kbNoKey, cmCtrlQI, hcCtrlQI,
    NewItem('XLAT table...           Ctrl+Q ~Q~', '', kbNoKey, cmCtrlQX, hcCtrlQW,
      nil)))))))))))))))))))))));
end;

{ THexEditorWindow }
constructor THexEditorWindow.Init(Bounds: TRect; var FileName: String);
var
  Control, IControl: PView;
  R: TRect;
begin
  TWindow.Init(Bounds, FileName, WindowNumAvail);
  Options := Options or (ofTileable + ofHex);

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

  GetExtent(R);
  Inc(R.A.X, 2); R.A.Y := Pred(R.B.Y); R.B.X := R.A.X + 11;
  IControl := New(PHexIndicator, Init(R));
  IControl^.GrowMode := gfGrowLoY + gfGrowHiY;
  Insert(IControl);

  GetExtent(R);
  R.Grow(-1, -1);
  Editor := New(PHexEditor, Init(R, PScrollBar(Control),
    StandardScrollBar(sbVertical + sbHandleKeyboard), PHexIndicator(IControl)));
  Insert(Editor);
  PHexEditor(Editor)^.ReadFile(FileName);
end;

procedure THexEditorWindow.SizeLimits(var Min, Max: TPoint);
begin
  TWindow.SizeLimits(Min, Max);
  Min.X := 19;
end;

end.