unit DocEd;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, Types, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  ASKernelDefs, ASKernel, TextEditor, UxPanel, Vcl.ExtCtrls, Vcl.ComCtrls,
  System.Actions, Vcl.ActnList, ZoomControl, ClientDefs, UITypes, StdCtrls,
  Vcl.AppEvnts, Generics.Defaults, Generics.Collections, FormFader;

type
  TDocEdFrm = class(TForm)
    VSplitter: TUxSplitter;
    pnIdents: TUxPanel;
    lbIdents: TTextEditor;
    sbIdents: TStatusBar;
    ActionList: TActionList;
    aUINavigate: TAction;
    aBullet: TAction;
    aMinus: TAction;
    aCDot: TAction;
    aCaretAfterEOL: TAction;
    aHiddenChars: TAction;
    aCode: TAction;
    zcContent: TZoomControl;
    PreviewTimer: TTimer;
    pnClient: TPanel;
    pnContent: TUxPanel;
    teContent: TTextEditor;
    sbContent: TStatusBar;
    pnPreview: TUxPanel;
    sbPreview: TStatusBar;
    tePreview: TTextEditor;
    zcPreview: TZoomControl;
    VSplitter2: TUxSplitter;
    aSave: TAction;
    aLink: TAction;
    aGotoTopic: TAction;
    aRefreshList: TAction;
    ASRefFormattingProcessor: TASRefFormattingProcessor;
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure lbIdentsListBoxChange(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure teContentSelChange(Sender: TObject);
    procedure VSplitterPaint(Sender: TObject);
    procedure aUINavigateExecute(Sender: TObject);
    procedure aBulletExecute(Sender: TObject);
    procedure aMinusExecute(Sender: TObject);
    procedure aCDotExecute(Sender: TObject);
    procedure aCaretAfterEOLExecute(Sender: TObject);
    procedure aCaretAfterEOLUpdate(Sender: TObject);
    procedure aHiddenCharsExecute(Sender: TObject);
    procedure aHiddenCharsUpdate(Sender: TObject);
    procedure teContentOverwriteChange(Sender: TObject);
    procedure sbContentDblClick(Sender: TObject);
    procedure aCodeExecute(Sender: TObject);
    procedure sbContentDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
      const Rect: TRect);
    procedure sbContentResize(Sender: TObject);
    procedure teContentZoomChange(Sender: TObject);
    procedure teContentChange(Sender: TObject);
    procedure PreviewTimerTimer(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure pnIdentsResize(Sender: TObject);
    procedure tePreviewOverwriteChange(Sender: TObject);
    procedure tePreviewSelChange(Sender: TObject);
    procedure tePreviewZoomChange(Sender: TObject);
    procedure sbPreviewDblClick(Sender: TObject);
    procedure sbPreviewDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel;
      const Rect: TRect);
    procedure sbPreviewResize(Sender: TObject);
    procedure aSaveExecute(Sender: TObject);
    procedure aSaveUpdate(Sender: TObject);
    procedure aLinkExecute(Sender: TObject);
    procedure tePreviewHyperlinkClick(Sender: TObject;
      const ALinkRec: TLinkRec);
    procedure teContentSimpleNotification(Sender: TObject; MsgID: Cardinal;
      const AStr: string);
    procedure tePreviewSimpleNotification(Sender: TObject; MsgID: Cardinal;
      const AStr: string);
    procedure zcContentChange(Sender: TObject);
    procedure zcPreviewChange(Sender: TObject);
    procedure aGotoTopicExecute(Sender: TObject);
    procedure teContentKeyPress(Sender: TObject; var Key: Char);
    procedure aRefreshListExecute(Sender: TObject);
  private
    FFormFader: TFormFader;
    FKernel: TASKernel;
    FOwnsKernel: Boolean;
    FTopicName: string;
    FFileName: string;
    FPath: string;
    procedure SaveFile;
    procedure OpenFile(const ATopic: string);
    procedure CloseFile;
    procedure SyncList;
    procedure SyncStatus(ATopicExists: Boolean);
    procedure SetKernel(const Value: TASKernel);
    function GetIdentName(const AListItemCaption: string): string;
    function GetSizeGripSize: Integer;
  public
    procedure LoadIdents;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Kernel: TASKernel read FKernel write SetKernel;
    property OwnsKernel: Boolean read FOwnsKernel write FOwnsKernel;
    property Path: string read FPath;
  end;

var
  DocEdFrm: TDocEdFrm;

implementation

{$R *.dfm}

uses
  MainForm, Math, StrUtils, DateUtils, IOUtils, UnicodeData, MultiInput, ASDoc,
  Registry, TDMB, PngImage, Jpeg, GifImg, BitmapEffects, WinMgrForm;

function _scale(X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, 96);
end;

procedure TDocEdFrm.aBulletExecute(Sender: TObject);
begin
  if teContent.Focused then
    teContent.SelText := '•';
end;

procedure TDocEdFrm.aCaretAfterEOLExecute(Sender: TObject);
begin
  if teContent.Focused then
    teContent.CaretAfterEOL := not teContent.CaretAfterEOL;
end;

procedure TDocEdFrm.aCaretAfterEOLUpdate(Sender: TObject);
begin
  aCaretAfterEOL.Checked := teContent.CaretAfterEOL;
end;

procedure TDocEdFrm.aCDotExecute(Sender: TObject);
begin
  if teContent.Focused then
    teContent.SelText := '⋅';
end;

procedure TDocEdFrm.aCodeExecute(Sender: TObject);
begin
  if teContent.Focused then
    teContent.SurroundText('`', '`');
end;

procedure TDocEdFrm.aGotoTopicExecute(Sender: TObject);
var
  S: string;
begin
  if teContent.TextFile.HasSelection then
    S := teContent.SelText
  else
    S := teContent.GetWord;
  if TMultiInputBox.TextInputBox(Self, 'Algosim', 'Please enter the topic to open:',
    S, ecNormal, False)
  then
  begin
    SaveFile;
    OpenFile(S);
    SyncList;
  end;
end;

procedure TDocEdFrm.aHiddenCharsExecute(Sender: TObject);
begin
  teContent.ShowHiddenCharacters := not teContent.ShowHiddenCharacters;
  tePreview.ShowHiddenCharacters := teContent.ShowHiddenCharacters;
end;

procedure TDocEdFrm.aHiddenCharsUpdate(Sender: TObject);
begin
  aHiddenChars.Checked := teContent.ShowHiddenCharacters;
end;

procedure TDocEdFrm.aLinkExecute(Sender: TObject);
begin
  if teContent.Focused then
    teContent.SurroundText('‹', '›');
end;

procedure TDocEdFrm.aMinusExecute(Sender: TObject);
begin
  if teContent.Focused then
    teContent.SelText := '−';
end;

procedure TDocEdFrm.aRefreshListExecute(Sender: TObject);
begin
  LoadIdents;
end;

procedure TDocEdFrm.aSaveExecute(Sender: TObject);
begin
  SaveFile;
end;

procedure TDocEdFrm.aSaveUpdate(Sender: TObject);
begin
  aSave.Enabled := not FFileName.IsEmpty;
end;

procedure TDocEdFrm.aUINavigateExecute(Sender: TObject);
begin
  GUINav(ClientDefs.TCustomFormCr(Self), [lbIdents, pnContent, pnPreview]);
end;

procedure TDocEdFrm.CloseFile;
begin
  FTopicName := '';
  FFileName := '';
  pnContent.Caption := '';
  teContent.NewFile;
  teContent.Enabled := False;
end;

constructor TDocEdFrm.Create(AOwner: TComponent);
begin

  with TRegistry.Create do
    try
      RootKey := HKEY_CURRENT_USER;
      if OpenKey('Software\Rejbrand\Algosim', False) and ValueExists('DocPath') then
        FPath := ReadString('DocPath');
    finally
      Free;
    end;

  if not DirectoryExists(Path) then
    raise EDocException.Create('You don''t have access to the documentation editor.');

  inherited;

  TASDoc.PrepareViewer(tePreview);

  FFormFader := TFormFader.Create(Self);
  TWindowWatcher.Create(Self);

end;

destructor TDocEdFrm.Destroy;
begin
  if FOwnsKernel then
    FKernel.Free;
  inherited;
end;

procedure TDocEdFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  SaveFile;
  FFormFader.Fade;
  Screen.Cursor := crHourGlass;
  try
    TASDoc.CompileFullTextIndex;
  finally
    Screen.Cursor := crDefault;
    FFormFader.Unfade;
  end;
  tePreview.RemoveSizeHooks;
  teContent.RemoveSizeHooks;
  lbIdents.RemoveSizeHooks;
  Action := caFree;
end;

procedure TDocEdFrm.FormResize(Sender: TObject);
begin
  pnPreview.Width := (ClientWidth - pnIdents.Width - VSplitter.Width - VSplitter2.Width) div 2;
end;

procedure TDocEdFrm.FormShow(Sender: TObject);
begin
  lbIdents.SetSizeHooks;
  teContent.SetSizeHooks;
  tePreview.SetSizeHooks;
  zcContent.Parent := sbContent;
  zcPreview.Parent := sbPreview;
  teContent.MarginLeft := _scale(10);
end;

function TDocEdFrm.GetIdentName(const AListItemCaption: string): string;
var
  p: Integer;
begin
  p := Pos('(', AListItemCaption);
  if p > 0 then
    Result := Copy(AListItemCaption, 1, p - 1)
  else
    Result := AListItemCaption;
  Result := Result.Trim;
end;

function TDocEdFrm.GetSizeGripSize: Integer;
begin
  if (WindowState = wsNormal) and sbPreview.SizeGrip then
    Result := 16
  else
    Result := 0;
end;

procedure TDocEdFrm.lbIdentsListBoxChange(Sender: TObject);
begin
  SaveFile;
  if lbIdents.ListBoxItemIndex <> -1 then
    OpenFile(GetIdentName(lbIdents.Lines[lbIdents.ListBoxItemIndex]))
  else
    CloseFile;
end;

procedure TDocEdFrm.LoadIdents;
var
  Ident: TASKernel.TIdentInfo;
  LIdentName,
  LFileName: string;
  LFileTopic: string;
  Documented: Boolean;
  c, docc: Integer;
  OldItem: string;
  DocFile: string;
  i: Integer;
  TopicExists: Boolean;
  Topics: TList<string>;
begin

  if FKernel = nil then
    Exit;

  Screen.Cursor := crHourGlass;
  try

    if lbIdents.ListBoxItemIndex <> -1 then
      OldItem := lbIdents.Lines[lbIdents.ListBoxItemIndex];

    c := 0;
    docc := 0;

    lbIdents.OnListBoxChange := nil;
    try

      lbIdents.BeginVisualUpdate;
      try

        lbIdents.NewFile;

        lbIdents.AddClass(MakeClass('Existing file', lbIdents.Font.Size, [], clBlue));
        lbIdents.AddClass(MakeClass('Nonexisting file', lbIdents.Font.Size, [], clRed));

        lbIdents.BeginAddLine;
        try
          for Ident in FKernel.GetMatchingIdents(TASKernel.AllIdentTypes) do
          begin
            if lbIdents.TextFile.IndexOf(Ident.FullDescription) <> -1 then
              Continue;
            LIdentName := Ident.Name;
            LFileName := TASDoc.GetDocFileName(LIdentName, False);
            Documented := FileExists(LFileName);
            lbIdents.AddLine(Ident.FullDescription, IfThen(Documented, 'Existing file', 'Nonexisting file'));
            Inc(c);
            if Documented then
              Inc(docc);
          end;
          Topics := TList<string>.Create;
          try
            for DocFile in TDirectory.GetFiles(FPath, '*.asdoc') do
            begin
              LFileTopic := TASDoc.GetTopicName(DocFile);
              Topics.Add(TPath.GetFileNameWithoutExtension(DocFile) + #9 + LFileTopic);
              TopicExists := False;
              for i := 0 to lbIdents.LineCount - 1 do
                if GetIdentName(lbIdents.Lines[i]) = LFileTopic then
                begin
                  TopicExists := True;
                  Break;
                end;
              if not TopicExists then
              begin
                lbIdents.AddLine(LFileTopic, 'Existing file');
                Inc(c);
                Inc(docc);
              end;
            end;
            TFile.WriteAllLines(TPath.Combine(FPath, 'index.rdx'), Topics.ToArray, TEncoding.UTF8);
          finally
            Topics.Free;
          end;
        finally
          lbIdents.EndAddLine;
        end;

      finally
        lbIdents.EndVisualUpdate(True);
      end;

      lbIdents.FormattingProcessor := TBracketListFormattingProcessor.Create(Self);
      lbIdents.MarginLeft := _scale(10);

      if OldItem.IsEmpty then
        lbIdents.TextFile.GotoSOF
      else
        lbIdents.ListBoxItemIndex := lbIdents.TextFile.IndexOf(OldItem);

    finally
      lbIdents.OnListBoxChange := lbIdentsListBoxChange;
    end;

    sbIdents.SimplePanel := True;
    sbIdents.SimpleText := c.ToString + ' identifier(s); ' +
      FormatFloat('0.#', 100*docc/c, TFormatSettings.Invariant) + '% documented.';

  finally
    Screen.Cursor := crDefault;
  end;

end;

procedure TDocEdFrm.OpenFile(const ATopic: string);
var
  LTopicInfo: TTopicInfo;
begin
  FTopicName := ATopic;
  FFileName := TASDoc.GetDocFileName(FTopicName, False);
  pnContent.Caption := FTopicName;
  teContent.Enabled := True;
  if FileExists(FFileName) then
    TASDoc.LoadFromFile(teContent, FFileName, LTopicInfo, dmEdit)
  else
    teContent.NewFile;
  teContent.RulerVisible := False;
  teContent.MarginLeft := _scale(10);
end;

procedure TDocEdFrm.pnIdentsResize(Sender: TObject);
begin
  FormResize(Sender);
end;

procedure TDocEdFrm.PreviewTimerTimer(Sender: TObject);
var
  LTopicInfo: TTopicInfo;
begin
  PreviewTimer.Enabled := False;
  if not FFileName.IsEmpty then
  begin
    SaveFile;
    if FileExists(FFileName) then
    begin
      tePreview.Enabled := True;
      TASDoc.LoadFromFile(tePreview, FFileName, LTopicInfo, dmView);
      tePreview.RulerVisible := False;
      tePreview.MarginLeft := _scale(10);
      Exit;
    end;
  end;
  tePreview.NewFile;
  tePreview.Enabled := False;
end;

procedure TDocEdFrm.SaveFile;
begin
  if not FFileName.IsEmpty then
    if teContent.TextFile.Empty then
    begin
      DeleteFile(FFileName);
      SyncStatus(False);
    end
    else
    begin
      TASDoc.SaveToFile(FTopicName, teContent, FFileName);
      SyncStatus(True);
    end;
end;

procedure TDocEdFrm.SetKernel(const Value: TASKernel);
begin
  FKernel := Value;
end;

procedure TDocEdFrm.SyncList;

  function TryFind: Boolean;
  var
    i: Integer;
  begin
    Result := (lbIdents.ListBoxItemIndex <> -1) and
      (GetIdentName(lbIdents.Lines[lbIdents.ListBoxItemIndex]) = FTopicName);
    if Result then
      Exit;
    lbIdents.OnListBoxChange := nil;
    try
      for i := 0 to lbIdents.LineCount - 1 do
        if GetIdentName(lbIdents.Lines[i]) = FTopicName then
        begin
          lbIdents.ListBoxItemIndex := i;
          Exit(True);
        end;
    finally
      lbIdents.OnListBoxChange := lbIdentsListBoxChange;
    end;
  end;

begin

  if not TryFind then
  begin
    LoadIdents;
    TryFind;
  end;

end;

procedure TDocEdFrm.SyncStatus(ATopicExists: Boolean);
var
  i: Integer;
begin
  for i := 0 to lbIdents.LineCount - 1 do
    if GetIdentName(lbIdents.Lines[i]) = FTopicName then
    begin
      lbIdents.EditMode := emText;
      try
        lbIdents.LineClasses[i] := IfThen(ATopicExists, 'Existing file', 'Nonexisting file');
      finally
        lbIdents.EditMode := emReadOnly;
      end;
    end;
end;

procedure TDocEdFrm.sbContentDblClick(Sender: TObject);
var
  v: Integer;
  p: TPoint;
begin

  if not GetCursorPos(p) then Exit;
  p := sbContent.ScreenToClient(p);

  if InRange(p.X, 0, sbContent.Panels[STATUS_FILE_POSITION].Width) then
  begin
    v := teContent.CaretPos.Y + 1;
    if
      TMultiInputBox.NumInputBox(
        Self,
        'Go to',
        'Please enter a line number to go to:',
        v,
        1,
        teContent.LineCount)
    then
      teContent.CaretPos := Point(0, v - 1);
  end
  else if InRange(p.X,
    sbContent.Panels[STATUS_FILE_POSITION].Width,
    sbContent.Panels[STATUS_FILE_POSITION].Width + sbContent.Panels[STATUS_OVERWRITE].Width) then
  begin
    teContent.Overwrite := not teContent.Overwrite;
  end;

end;

procedure TDocEdFrm.sbContentDrawPanel(StatusBar: TStatusBar;
  Panel: TStatusPanel; const Rect: TRect);
begin
  if Panel = sbContent.Panels[STATUS_ZOOM_CONTROL] then
    zcContent.BoundsRect := System.Classes.Rect(Rect.Left, Rect.Top,
      Rect.Right - GetSizeGripSize, Rect.Bottom);
end;

procedure TDocEdFrm.sbContentResize(Sender: TObject);
begin
  sbContent.Panels[STATUS_MESSAGE].Width := sbContent.Width -
    sbContent.Panels[STATUS_FILE_POSITION].Width -
    sbContent.Panels[STATUS_OVERWRITE].Width -
//    sbContent.Panels[STATUS_INPUT_MODE].Width -
    sbContent.Panels[STATUS_ZOOM_CONTROL].Width;
end;

procedure TDocEdFrm.sbPreviewDblClick(Sender: TObject);
var
  v: Integer;
  p: TPoint;
begin

  if not GetCursorPos(p) then Exit;
  p := sbPreview.ScreenToClient(p);

  if InRange(p.X, 0, sbPreview.Panels[STATUS_FILE_POSITION].Width) then
  begin
    v := tePreview.CaretPos.Y + 1;
    if
      TMultiInputBox.NumInputBox(
        Self,
        'Go to',
        'Please enter a line number to go to:',
        v,
        1,
        tePreview.LineCount)
    then
      tePreview.CaretPos := Point(0, v - 1);
  end
  else if InRange(p.X,
    sbPreview.Panels[STATUS_FILE_POSITION].Width,
    sbPreview.Panels[STATUS_FILE_POSITION].Width + sbPreview.Panels[STATUS_OVERWRITE].Width) then
  begin
    tePreview.Overwrite := not tePreview.Overwrite;
  end;

end;

procedure TDocEdFrm.sbPreviewDrawPanel(StatusBar: TStatusBar;
  Panel: TStatusPanel; const Rect: TRect);
begin
  if Panel = sbPreview.Panels[STATUS_ZOOM_CONTROL] then
    zcPreview.BoundsRect := System.Classes.Rect(Rect.Left, Rect.Top,
      Rect.Right - GetSizeGripSize, Rect.Bottom);
end;

procedure TDocEdFrm.sbPreviewResize(Sender: TObject);
begin
  sbPreview.Panels[STATUS_MESSAGE].Width := sbPreview.Width -
    sbPreview.Panels[STATUS_FILE_POSITION].Width -
    sbPreview.Panels[STATUS_OVERWRITE].Width -
//    sbPreview.Panels[STATUS_INPUT_MODE].Width -
    sbPreview.Panels[STATUS_ZOOM_CONTROL].Width;
end;

procedure TDocEdFrm.teContentChange(Sender: TObject);
begin
  RestartTimer(PreviewTimer);
end;

procedure TDocEdFrm.teContentKeyPress(Sender: TObject; var Key: Char);

  procedure Surround(const ALeft, ARight: string);
  begin
    teContent.SurroundText(ALeft, ARight);
    Key := #0;
  end;

  procedure InsertBinOp(const AOp: string);
  begin
    if (teContent.GetCharBeforeCaret = #32) and not teContent.TextFile.HasSelection then
      teContent.SelText := AOp + #32
    else
      teContent.SelText := AOp;
    Key := #0;
  end;

  procedure InsertUnOp(const AOp: string);
  begin
    teContent.SelText := AOp;
    Key := #0;
  end;

begin

  if (GetKeyState(VK_CONTROL) < 0) and (GetKeyState(VK_SHIFT) < 0) then
  begin
    case Key of
      ^S:
        begin
          Surround('{', '}');
          Exit;
        end;
      ^V, ^E:
        begin
          Surround('❨', '❩');
          Exit;
        end;
      ^F:
        begin
          Surround('⌊', '⌋');
          Exit;
        end;
      ^C:
        begin
          Surround('⌈', '⌉');
          Exit;
        end;
      ^L:
        begin
          Surround('''(', ')');
          Exit;
        end;
      ^U:
        begin
          InsertBinOp('∪');
          Exit;
        end;
      ^I:
        begin
          InsertBinOp('∩');
          Exit;
        end;
      ^D:
        begin
          InsertBinOp('∧');
          Exit;
        end;
      ^O:
        begin
          InsertBinOp('∨');
          Exit;
        end;
      ^X:
        begin
          InsertBinOp('⊻');
          Exit;
        end;
      ^N:
        begin
          InsertUnOp('¬');
          Exit;
        end;
      ^T:
        begin
          InsertBinOp('↦');
          Exit;
        end;
    end;
  end;

end;

procedure TDocEdFrm.teContentOverwriteChange(Sender: TObject);
begin
  sbContent.Panels[STATUS_OVERWRITE].Text := IfThen(teContent.Overwrite, 'OVER', 'INS');
end;

procedure TDocEdFrm.teContentSelChange(Sender: TObject);
begin

  if teContent.TextFile.HasSelection then
    if teContent.CaretPos.Y = teContent.SelEndPos.Y then
      sbContent.Panels[STATUS_FILE_POSITION].Text :=
        Format('Line: %d  Cols: %d–%d  Len: %d',
          [teContent.CaretPos.Y + 1,
           min(teContent.CaretPos.X, teContent.SelEndPos.X) + 1,
           max(teContent.CaretPos.X, teContent.SelEndPos.X) + 1,
           teContent.SelLength])
    else
      sbContent.Panels[STATUS_FILE_POSITION].Text :=
        Format('Lines: %d–%d  Len: %d',
          [min(teContent.CaretPos.Y, teContent.SelEndPos.Y) + 1,
           max(teContent.CaretPos.Y, teContent.SelEndPos.Y) + 1,
           teContent.SelLength])
  else
    sbContent.Panels[STATUS_FILE_POSITION].Text :=
      Format('Line: %d  Col: %d  Chr: %d',
        [teContent.CaretPos.Y + 1, teContent.CaretPos.X + 1, teContent.SelStart + 1]);

end;

procedure TDocEdFrm.teContentSimpleNotification(Sender: TObject;
  MsgID: Cardinal; const AStr: string);
begin
  sbContent.SimplePanel := not AStr.IsEmpty;
  sbcontent.SimpleText := AStr;
end;

procedure TDocEdFrm.teContentZoomChange(Sender: TObject);
begin
  zcContent.UpdateZoomLevelSilently(teContent.Zoom);
end;

procedure TDocEdFrm.tePreviewHyperlinkClick(Sender: TObject;
  const ALinkRec: TLinkRec);
begin
  SaveFile;
  OpenFile(ALinkRec.URL);
  SyncList;
end;

procedure TDocEdFrm.tePreviewOverwriteChange(Sender: TObject);
begin
  sbPreview.Panels[STATUS_OVERWRITE].Text := IfThen(tePreview.Overwrite, 'OVER', 'INS');
end;

procedure TDocEdFrm.tePreviewSelChange(Sender: TObject);
begin

  if tePreview.TextFile.HasSelection then
    if tePreview.CaretPos.Y = tePreview.SelEndPos.Y then
      sbPreview.Panels[STATUS_FILE_POSITION].Text :=
        Format('Line: %d  Cols: %d–%d  Len: %d',
          [tePreview.CaretPos.Y + 1,
           min(tePreview.CaretPos.X, tePreview.SelEndPos.X) + 1,
           max(tePreview.CaretPos.X, tePreview.SelEndPos.X) + 1,
           tePreview.SelLength])
    else
      sbPreview.Panels[STATUS_FILE_POSITION].Text :=
        Format('Lines: %d–%d  Len: %d',
          [min(tePreview.CaretPos.Y, tePreview.SelEndPos.Y) + 1,
           max(tePreview.CaretPos.Y, tePreview.SelEndPos.Y) + 1,
           tePreview.SelLength])
  else
    sbPreview.Panels[STATUS_FILE_POSITION].Text :=
      Format('Line: %d  Col: %d  Chr: %d',
        [tePreview.CaretPos.Y + 1, tePreview.CaretPos.X + 1, tePreview.SelStart + 1]);

end;

procedure TDocEdFrm.tePreviewSimpleNotification(Sender: TObject;
  MsgID: Cardinal; const AStr: string);
begin
  sbPreview.Panels[STATUS_MESSAGE].Text := AStr;
end;

procedure TDocEdFrm.tePreviewZoomChange(Sender: TObject);
begin
  zcPreview.UpdateZoomLevelSilently(tePreview.Zoom);
end;

procedure TDocEdFrm.VSplitterPaint(Sender: TObject);
var
  Splitter: TSplitter absolute Sender;
begin
  if Sender is TSplitter then
    if sbContent.Visible then
    begin
      Splitter.Canvas.Brush.Color := sbContent.Color;
      Splitter.Canvas.FillRect(
        Rect(
          0,
          Splitter.ClientHeight - sbContent.Height,
          Splitter.ClientWidth,
          Splitter.ClientHeight
        )
      );
    end;
end;

procedure TDocEdFrm.zcContentChange(Sender: TObject);
begin
  teContent.Zoom := Round(zcContent.ZoomLevel);
end;

procedure TDocEdFrm.zcPreviewChange(Sender: TObject);
begin
  tePreview.Zoom := Round(zcPreview.ZoomLevel);
end;

end.
