////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// This unit implement xxxx.                                                  //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 2001xxxx - Created by A.K.                                                 //
// 202305xx - Modified for FPC - A.K.                                         //
// 20240625 - Undo/Redo support, ApplyParams                                  //
////////////////////////////////////////////////////////////////////////////////

unit form_texteditdialog; // Text Edit Dialog (modal)

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math, lclintf, lcltype, lmessages,
 graphics, controls, forms, dialogs, stdctrls, buttons, extctrls,
 comctrls, clipbrd, ActnList, menus, strutils,
 _crw_alloc, _crw_rtc, _crw_ef, _crw_str, _crw_eldraw, _crw_fio,
 _crw_memo, _crw_hash, _crw_plut, _crw_sect, _crw_colors, _crw_gloss,
 _crw_appforms, _crw_apputils, _crw_apptools;

type

  { TFormTextEditDialog }

  TFormTextEditDialog = class(TMasterForm)
    PanelEditButtons: TPanel;
    PanelEditor: TPanel;
    GroupBoxEditor: TGroupBox;
    Editor: TMemo;
    PopupMenuEdit: TPopupMenu;
    TimerUpdateState: TTimer;
    ImageList: TImageList;
    ActionList: TActionList;
    ActionEditShiftRight: TAction;
    ActionEditShiftLeft: TAction;
    ActionEditWordWrap: TAction;
    ActionEditFont: TAction;
    ActionEditRefresh: TAction;
    ActionEditGoToEnd: TAction;
    ActionEditGoToStart: TAction;
    ActionEditSelectNone: TAction;
    ActionEditSelectAll: TAction;
    ActionEditOblivion: TAction;
    ActionEditRedo: TAction;
    ActionEditUndo: TAction;
    ActionEditPaste: TAction;
    ActionEditCopy: TAction;
    ActionEditCut: TAction;
    ActionEditDelete: TAction;
    ActionEditReadOnly: TAction;
    ActionEditGoToLineN: TAction;
    FontDialog: TFontDialog;
    PanelButtons: TPanel;
    BitBtnOk: TBitBtn;
    BitBtnCancel: TBitBtn;
    BitBtnEditCopy: TBitBtn;
    BitBtnEditCut: TBitBtn;
    BitBtnEditGoToLineN: TBitBtn;
    BitBtnEditOblivion: TBitBtn;
    BitBtnEditPaste: TBitBtn;
    BitBtnEditReadOnly: TBitBtn;
    BitBtnEditRedo: TBitBtn;
    BitBtnEditShiftLeft: TBitBtn;
    BitBtnEditShiftRight: TBitBtn;
    BitBtnEditUndo: TBitBtn;
    BitBtnEditWordWrap: TBitBtn;
    BitBtnEditRefresh: TBitBtn;
    BitBtnEditFont: TBitBtn;
    BitBtnEditSelectAll: TBitBtn;
    BitBtnEditSelectNone: TBitBtn;
    BitBtnEditGoToStart: TBitBtn;
    BitBtnEditGoToEnd: TBitBtn;
    MenuEditReadOnly: TMenuItem;
    Separator1: TMenuItem;
    MenuEditDelete: TMenuItem;
    MenuEditCut: TMenuItem;
    MenuEditCopy: TMenuItem;
    MenuEditPaste: TMenuItem;
    Separator2: TMenuItem;
    MenuEditUndo: TMenuItem;
    MenuEditOblivion: TMenuItem;
    MenuEditRedo: TMenuItem;
    Separator3: TMenuItem;
    MenuEditGoToStart: TMenuItem;
    MenuEditGoToEnd: TMenuItem;
    MenuEditGoToLineN: TMenuItem;
    Separator4: TMenuItem;
    MenuEditSelectAll: TMenuItem;
    MenuEditSelectNone: TMenuItem;
    Separator5: TMenuItem;
    MenuEditShiftLeft: TMenuItem;
    MenuEditShiftRight: TMenuItem;
    Separator6: TMenuItem;
    MenuEditFont: TMenuItem;
    MenuEditRefresh: TMenuItem;
    MenuEditWordWrap: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure TimerUpdateStateTimer(Sender: TObject);
    procedure ActionEditCopyExecute(Sender: TObject);
    procedure ActionEditCutExecute(Sender: TObject);
    procedure ActionEditDeleteExecute(Sender: TObject);
    procedure ActionEditFontExecute(Sender: TObject);
    procedure ActionEditGoToEndExecute(Sender: TObject);
    procedure ActionEditGoToStartExecute(Sender: TObject);
    procedure ActionEditOblivionExecute(Sender: TObject);
    procedure ActionEditPasteExecute(Sender: TObject);
    procedure ActionEditRedoExecute(Sender: TObject);
    procedure ActionEditRefreshExecute(Sender: TObject);
    procedure ActionEditSelectAllExecute(Sender: TObject);
    procedure ActionEditSelectNoneExecute(Sender: TObject);
    procedure ActionEditShiftLeftExecute(Sender: TObject);
    procedure ActionEditShiftRightExecute(Sender: TObject);
    procedure ActionEditUndoExecute(Sender: TObject);
    procedure ActionEditWordWrapExecute(Sender: TObject);
    procedure ActionEditReadOnlyExecute(Sender: TObject);
    procedure ActionEditGoToLineNExecute(Sender: TObject);
  private
    { Private declarations }
    procedure UpdateState;
  public
    { Public declarations }
    procedure ResetParams;
    function  DoApplyParams(const Params:LongString):Integer; override;
  private
    myLinePos  : TPoint;
    myLastSel  : packed record
     Start,Len : Integer;
     HashCode  : QWord;
     TimeStamp : QWord;
    end;
    myAnnals   : TUndoHistory;
    function  GetIsReadOnly:Boolean;
    procedure SetIsReadOnly(aReadOnly:Boolean);
    function  EvalHash(const S:LongString):QWord;
    procedure ZeroLastSelection;
    procedure SaveLastSelection;
    procedure BackLastSelection;
    procedure SetFocusToEditor(PM:Integer=1);
    function  GetAnnals:TUndoHistory;
  public
    property  IsReadOnly:Boolean read GetIsReadOnly write SetIsReadOnly;
    procedure TextShiftExecute(Shift:Integer);
    procedure GoToRowCol(aRow,aCol:Integer);
    procedure RefreshWindow;
    function  HasAnnals:Boolean;
    property  Annals:TUndoHistory read GetAnnals;
  private
    function  GetPerformReadOnly:Boolean;
    procedure SetPerformReadOnly(aReadOnly:Boolean);
    function  GetPerformModified:Boolean;
    procedure SetPerformModified(aModified:Boolean);
    function  GetPerformSelLength:Integer;
    procedure SetPerformSelLength(aLength:Integer);
    function  GetPerformSelText:LongString;
    procedure SetPerformSelText(const aText:LongString);
    function  GetPerformText:LongString;
    procedure SetPerformText(const aText:LongString);
    function  GetPerformWideText:WideString;
    procedure SetPerformWideText(const aText:WideString);
    function  GetPerformLinesText:LongString;
    procedure SetPerformLinesText(const aText:LongString);
    function  GetPerformSelStart:Integer;
    procedure SetPerformSelStart(aSelStart:Integer);
    function  GetPerformCaretPos:TPoint;
    procedure SetPerformCaretPos(const aCaretPos:TPoint);
    function  GetPerformWordWrap:Boolean;
    procedure SetPerformWordWrap(aWordWrap:Boolean);
  public
    procedure PerformUndo;
    procedure PerformRedo;
    function  PerformCanUndo:Boolean;
    function  PerformCanRedo:Boolean;
    procedure PerformClearSelection;
    procedure PerformCutToClipboard;
    procedure PerformCopyToClipboard;
    procedure PerformPasteFromClipboard;
    procedure PerformPasteText(const aText:LongString);
    procedure PerformOblivion(Mode:Integer=3);
    procedure PerformSelectAll;
    procedure PerformSelectNone;
    procedure PerformGotoStart;
    procedure PerformGotoEnd;
    procedure PerformGoToRowCol(aRow,aCol:Integer);
    procedure PerformSetFocus(PM:Integer=1);
  public
    property  PerformText:LongString read GetPerformText write SetPerformText;
    property  PerformWideText:WideString read GetPerformWideText write SetPerformWideText;
    property  PerformSelText:LongString read GetPerformSelText write SetPerformSelText;
    property  PerformSelLength:Integer read GetPerformSelLength write SetPerformSelLength;
    property  PerformLinesText:LongString read GetPerformLinesText write SetPerformLinesText;
    property  PerformSelStart:Integer read GetPerformSelStart write SetPerformSelStart;
    property  PerformModified:Boolean read GetPerformModified write SetPerformModified;
    property  PerformReadOnly:Boolean read GetPerformReadOnly write SetPerformReadOnly;
    property  PerformCaretPos:TPoint read GetPerformCaretPos write SetPerformCaretPos;
    property  PerformWordWrap:Boolean read GetPerformWordWrap write SetPerformWordWrap;
  public
   class var DefaultEditorMode : Integer;
  end;

const
 TextEditDialogsPalette : packed record
  MainBackColor : TColor;
  MainTextColor : TColor;
  ReadOnlyColor : TColor;
 end = (
  MainBackColor : clWhite;
  MainTextColor : clBlack;
  ReadOnlyColor : clSilver;
 );
 DefaultEditDialogFont : TFontParams = (
  CharSet : RUSSIAN_CHARSET;
  Color   : clBlack;
  Height  : -13;
  Name    : 'PT Mono';
  Pitch   : fpFixed;
  Style   : [];
 );

const                        // TFormTextEditDialog.DefaultEditorMode flags
 tedem_ReadOnly = $00000001; // Open editor as read only

function TextEditDialog(const aCaption    : LongString;
                        const aTitle      : LongString;
                        const aTextToEdit : TText;
                        const aParams     : LongString='';
                              aMode       : Integer=-1
                              ):TModalResult; overload;
function TextEditDialog(const aCaption    : LongString;
                        const aTitle      : LongString;
                        const aTextToEdit : LongString;
                        const aParams     : LongString='';
                              aResult     : PInteger=nil;
                              aMode       : Integer=-1
                               ):LongString; overload;

procedure Init_TextEditDialogsPalette;

const
  FormTextEditDialog : TFormTextEditDialog = nil;

implementation

{$R *.lfm}

function TextEditDialog(const aCaption    : LongString;
                        const aTitle      : LongString;
                        const aTextToEdit : TText;
                        const aParams     : LongString='';
                              aMode       : Integer=-1
                              ):TModalResult; overload;
var Temp:LongString; apFlags:Integer;
begin
 Result:=mrCancel;
 if CanShowModal(FormTextEditDialog) then
 try
  if (FormTextEditDialog=nil) then begin
   Application.CreateForm(TFormTextEditDialog, FormTextEditDialog);
   FormTextEditDialog.Master:=@FormTextEditDialog;
   FormTextEditDialog.Show;
   FormTextEditDialog.Hide;
  end;
  if (FormTextEditDialog<>nil) then
  with FormTextEditDialog do
  try
   ResetParams;
   apFlags:=ApplyParams(aParams);
   TimerUpdateState.Enabled:=true;
   if (aMode=-1) then aMode:=DefaultEditorMode;
   if not HasFlags(apFlags,apf_FormPos)
   then LocateFormToCenterOfScreen(FormTextEditDialog);
   Caption:=aCaption;
   SmartUpdate(GroupBoxEditor,aTitle);
   Temp:=CreateTempFile('texteditdialog_.tmp');
   try
    if FileExists(Temp) and (aTextToEdit.WriteFile(Temp)=0) then begin
     PerformText:=ValidateEol(StringFromFile(Temp,MaxInt));
     PerformReadOnly:=HasFlags(aMode,tedem_ReadOnly);
     PerformModified:=false;
     ZeroLastSelection;
     PerformGoToStart;
     PerformOblivion;
     RefreshWindow;
     UpdateState;
     ValidateFormPosition(FormTextEditDialog);
     if (mrVoice(ShowModal)=mrOk) then
     if Editor.Modified then begin
      WriteBufferToFile(Temp,PerformText);
      aTextToEdit.ReadFile(Temp);
      Result:=mrOk;
     end;
    end else begin
     Error(RusEng('Не могу редактировать текст, ошибка создания/записи временного файла!',
                  'Could not edit, temporary file create/write error!'));
    end;
   finally
    FileErase(Temp);
   end;
  finally
   ResetParams;
   PerformText:='';
   TimerUpdateState.Enabled:=false;
  end;
 except
  on E:Exception do BugReport(E,nil,'TextEditDialog');
 end;
end;

function TextEditDialog(const aCaption    : LongString;
                        const aTitle      : LongString;
                        const aTextToEdit : LongString;
                        const aParams     : LongString='';
                              aResult     : PInteger=nil;
                              aMode       : Integer=-1
                              ):LongString; overload;
var p:TText; res:Integer;
begin
 try
  p:=NewText;
  try
   p.Text:=aTextToEdit;
   res:=TextEditDialog(aCaption,aTitle,p,aParams,aMode);
   if (res=mrOk)
   then Result:=p.Text
   else Result:=aTextToEdit;
   if Assigned(aResult) then aResult^:=res;
  finally
   Kill(p);
  end;
 except
  on E:Exception do BugReport(E,nil,'TextEditDialog');
 end;
end;

/////////////////////////////////////
// TFormTextEditDialog implementation
/////////////////////////////////////

function TFormTextEditDialog.GetIsReadOnly:Boolean;
begin
 Result:=PerformReadOnly;
end;

procedure TFormTextEditDialog.SetIsReadOnly(aReadOnly:Boolean);
begin
 PerformReadOnly:=aReadOnly;
end;

function TFormTextEditDialog.GetAnnals:TUndoHistory;
begin
 if Assigned(Self)
 then Result:=myAnnals
 else Result:=nil;
end;

function TFormTextEditDialog.HasAnnals:Boolean;
begin
 if Assigned(Self)
 then Result:=Assigned(myAnnals)
 else Result:=false;
end;

function TFormTextEditDialog.EvalHash(const S:LongString):QWord;
begin
 if (S='') then Exit(0);
 if IsCpu64
 then Result:=Hash64_RS(PChar(S),Length(S))
 else Result:=Hash32_RS(PChar(S),Length(S));
end;

procedure TFormTextEditDialog.ZeroLastSelection;
begin
 if Assigned(Self) then begin
  myLastSel.Len:=0;
  myLastSel.Start:=0;
  myLastSel.HashCode:=0;
  myLastSel.TimeStamp:=0;
 end;
end;

procedure TFormTextEditDialog.SaveLastSelection;
begin
 if Assigned(Self) then begin
  ZeroLastSelection;
  if (PerformSelLength>0) then begin
   myLastSel.Len:=PerformSelLength;
   myLastSel.Start:=PerformSelStart;
   myLastSel.TimeStamp:=GetTickCount64;
   myLastSel.HashCode:=EvalHash(PerformLinesText);
  end;
 end;
end;

procedure TFormTextEditDialog.BackLastSelection;
var Hash:QWord;
begin
 if Assigned(Self) then begin
  if (myLastSel.Len>0) then begin
   Hash:=EvalHash(PerformLinesText);
   if (Hash=myLastSel.HashCode) then begin
    PerformSelStart:=myLastSel.Start;
    PerformSelLength:=myLastSel.Len;
   end;
  end;
  ZeroLastSelection;
 end;
end;

procedure TFormTextEditDialog.SetFocusToEditor(PM:Integer=1);
begin
 if Assigned(Self) then PerformSetFocus(PM);
end;

procedure TFormTextEditDialog.GoToRowCol(aRow,aCol:Integer);
begin
 if Assigned(Self) then PerformGoToRowCol(aRow,aCol);
end;

procedure TFormTextEditDialog.RefreshWindow;
begin
 if Assigned(Self) then begin
  Refresh;
 end;
end;

procedure TFormTextEditDialog.PerformUndo;
begin
 if Assigned(Self) then
 try
  if HasAnnals
  then Annals.Undo
  else Editor.Undo;
 except
  on E:Exception do BugReport(E,Self,'PerformUndo');
 end;
end;

procedure TFormTextEditDialog.PerformRedo;
begin
 if Assigned(Self) then
 try
  if HasAnnals
  then Annals.Redo;
 except
  on E:Exception do BugReport(E,Self,'PerformRedo');
 end;
end;

function TFormTextEditDialog.PerformCanUndo:Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 try
  if HasAnnals
  then Result:=Annals.CanUndo
  else Result:=Editor.CanUndo;
 except
  on E:Exception do BugReport(E,Self,'PerformCanUndo');
 end;
end;

function TFormTextEditDialog.PerformCanRedo:Boolean;
begin
 Result:=false;
 if Assigned(Self) then
 try
  if HasAnnals
  then Result:=Annals.CanRedo;
 except
  on E:Exception do BugReport(E,Self,'PerformCanRedo');
 end;
end;

procedure TFormTextEditDialog.PerformClearSelection;
begin
 if Assigned(Self) then
 try
  if HasAnnals
  then Annals.ClearSelection
  else Editor.ClearSelection;
 except
  on E:Exception do BugReport(E,Self,'PerformClearSelection');
 end;
end;

procedure TFormTextEditDialog.PerformCutToClipboard;
begin
 if Assigned(Self) then
 try
  if HasAnnals
  then Annals.CutToClipboard
  else Editor.CutToClipboard;
 except
  on E:Exception do BugReport(E,Self,'PerformCutToClipboard');
 end;
end;

procedure TFormTextEditDialog.PerformCopyToClipboard;
begin
 if Assigned(Self) then
 try
  Editor.CopyToClipboard;
 except
  on E:Exception do BugReport(E,Self,'PerformCopyToClipboard');
 end;
end;

procedure TFormTextEditDialog.PerformPasteFromClipboard;
begin
 if Assigned(Self) then
 try
  if HasAnnals
  then Annals.PasteFromClipboard
  else Editor.PasteFromClipboard;
 except
  on E:Exception do BugReport(E,Self,'PerformPasteFromClipboard');
 end;
end;

procedure TFormTextEditDialog.PerformPasteText(const aText:LongString);
begin
 if Assigned(Self) then
 try
  if HasAnnals
  then Annals.PasteText(aText)
  else Editor.SelText:=aText;
 except
  on E:Exception do BugReport(E,Self,'PerformPasteText');
 end;
end;

procedure TFormTextEditDialog.PerformOblivion(Mode:Integer);
begin
 if Assigned(Self) then
 try
  if HasFlags(Mode,1) then Editor.Modified:=false;
  if HasFlags(Mode,2) then if HasAnnals then Annals.Reset;
 except
  on E:Exception do BugReport(E,Self,'PerformOblivion');
 end;
end;

procedure TFormTextEditDialog.PerformSelectAll;
begin
 if Assigned(Self) then
 try
  Editor.SelectAll;
 except
  on E:Exception do BugReport(E,Self,'PerformSelectAll');
 end;
end;

procedure TFormTextEditDialog.PerformSelectNone;
begin
 if Assigned(Self) then
 try
  Editor.SelLength:=0;
 except
  on E:Exception do BugReport(E,Self,'PerformSelectNone');
 end;
end;

procedure TFormTextEditDialog.PerformGoToStart;
begin
 if Assigned(Self) then
 try
  Editor.SelLength:=0;
  Editor.SelStart:=0;
  PerformGoToRowCol(1,1);
 except
  on E:Exception do BugReport(E,Self,'PerformGoToStart');
 end;
end;

procedure TFormTextEditDialog.PerformGoToEnd;
begin
 if Assigned(Self) then
 try
  Editor.SelLength:=0;
  Editor.SelStart:=MaxInt;
  PerformGoToRowCol(Editor.Lines.Count,1);
 except
  on E:Exception do BugReport(E,Self,'PerformGoToEnd');
 end;
end;

procedure TFormTextEditDialog.PerformGoToRowCol(aRow,aCol:Integer);
begin
 if Assigned(Self) then
 try
  Editor.ApplyGoToRowCol(aRow,aCol);
 except
  on E:Exception do BugReport(E,Self,'PerformGoToRowCol');
 end;
end;

function TFormTextEditDialog.GetPerformReadOnly:Boolean;
begin
 if Assigned(Self)
 then Result:=Editor.ReadOnly
 else Result:=false;
end;

procedure TFormTextEditDialog.SetPerformReadOnly(aReadOnly:Boolean);
begin
 if Assigned(Self) then
 try
  Editor.ReadOnly:=aReadOnly;
  if Editor.ReadOnly
  then Editor.Color:=TextEditDialogsPalette.ReadOnlyColor
  else Editor.Color:=TextEditDialogsPalette.MainBackColor;
  Editor.Font.Color:=TextEditDialogsPalette.MainTextColor;
 except
  on E:Exception do BugReport(E,Self,'SetPerformReadOnly');
 end;
end;

function TFormTextEditDialog.GetPerformModified:Boolean;
begin
 if Assigned(Self)
 then Result:=Editor.Modified
 else Result:=false;
end;

procedure TFormTextEditDialog.SetPerformModified(aModified:Boolean);
begin
 if Assigned(Self) then
 try
  Editor.Modified:=aModified;
  if not aModified then PerformOblivion;
 except
  on E:Exception do BugReport(E,Self,'SetPerformModified');
 end;
end;

function TFormTextEditDialog.GetPerformSelStart:Integer;
begin
 if Assigned(Self)
 then Result:=Editor.SelStart
 else Result:=0;
end;

procedure TFormTextEditDialog.SetPerformSelStart(aSelStart:Integer);
begin
 if Assigned(Self) then Editor.SelStart:=aSelStart;
end;

function TFormTextEditDialog.GetPerformSelLength:Integer;
begin
 if Assigned(Self)
 then Result:=Editor.SelLength
 else Result:=0;
end;

procedure TFormTextEditDialog.SetPerformSelLength(aLength:Integer);
begin
 if Assigned(Self) then Editor.SelLength:=aLength;
end;

function TFormTextEditDialog.GetPerformSelText:LongString;
begin
 if Assigned(Self)
 then Result:=Editor.SelText
 else Result:='';
end;

procedure TFormTextEditDialog.SetPerformSelText(const aText:LongString);
begin
 if Assigned(Self) then PerformPasteText(aText);
end;

function TFormTextEditDialog.GetPerformText:LongString;
begin
 if Assigned(Self)
 then Result:=Editor.Text
 else Result:='';
end;

procedure TFormTextEditDialog.SetPerformText(const aText:LongString);
begin
 if Assigned(Self) then begin
  Editor.Text:=aText;
  PerformOblivion;
 end;
end;

function TFormTextEditDialog.GetPerformWideText:WideString;
begin
 if Assigned(Self)
 then Result:=StrToWide(Editor.Text)
 else Result:='';
end;

procedure TFormTextEditDialog.SetPerformWideText(const aText:WideString);
begin
 if Assigned(Self) then begin
  Editor.Text:=WideToStr(aText);
  PerformOblivion;
 end;
end;

function TFormTextEditDialog.GetPerformLinesText:LongString;
begin
 if Assigned(Self)
 then Result:=Editor.Lines.Text
 else Result:='';
end;

procedure TFormTextEditDialog.SetPerformLinesText(const aText:LongString);
begin
 if Assigned(Self) then begin
  Editor.Lines.Text:=aText;
  PerformOblivion;
 end;
end;

procedure TFormTextEditDialog.PerformSetFocus(PM:Integer=1);
begin
 if Assigned(Self) then
 try
  if Editor.CanSetFocus and not Editor.Focused then begin
   Editor.SetFocus; if HasFlags(PM,1) then SafeApplicationProcessMessages;
  end;
 except
  on E:Exception do BugReport(E,Self,'PerformSetFocus');
 end;
end;

function  TFormTextEditDialog.GetPerformCaretPos:TPoint;
begin
 if Assigned(Self)
 then Result:=Editor.CaretPos
 else Result:=Point(0,0);
end;

procedure TFormTextEditDialog.SetPerformCaretPos(const aCaretPos:TPoint);
begin
 if Assigned(Self) then begin
  if MemoCaretPosNeedBugFix
  then PerformGoToRowCol(aCaretPos.Y+1,aCaretPos.X+1)
  else Editor.CaretPos:=aCaretPos;
 end;
end;

function  TFormTextEditDialog.GetPerformWordWrap:Boolean;
begin
 if Assigned(Self)
 then Result:=Editor.WordWrap
 else Result:=false;
end;

procedure TFormTextEditDialog.SetPerformWordWrap(aWordWrap:Boolean);
begin
 if Assigned(Self) then Editor.WordWrap:=aWordWrap;
end;

function LineShift(const S:LongString; Shift:Integer):LongString;
begin
 Result:=S;
 if (Shift>0) then Result:=StringOfChar(' ',Shift)+Result;
 if (Shift<0) then Result:=WideToStr(Copy(StrToWide(Result),1-Shift,MaxInt));
end;

function TextShift(const S:LongString; Shift:Integer; TrimEOL:Boolean):LongString;
var i:Integer; List:TStringList;
begin
 Result:=S;
 if (Shift<>0) then
 try
  List:=TStringList.Create;
  try
   List.Text:=S;
   for i:=0 to List.Count-1 do List[i]:=LineShift(List[i],Shift);
   Result:=List.Text;
   if TrimEOL then begin
    if EndsStr(EOL,Result)
    then Result:=Copy(Result,1,Length(Result)-Length(EOL));
   end;
  finally
   Kill(List);
  end;
 except
  on E:Exception do BugReport(E,nil,'TextShift');
 end;
end;

procedure TFormTextEditDialog.TextShiftExecute(Shift:Integer);
var TheStart:Integer; TheBlock:LongString; NeedTrim:Boolean;
begin
 if Assigned(Self) then
 try
  if (PerformSelLength>0) then begin
   TheStart:=PerformSelStart;
   TheBlock:=PerformSelText;
   NeedTrim:=not EndsStr(EOL,TheBlock);
   TheBlock:=TextShift(TheBlock,Shift,NeedTrim);
   PerformSelText:=TheBlock;
   PerformSelStart:=TheStart;
   PerformSelLength:=Length(StrToWide(TheBlock));
  end;
 except
  on E:Exception do BugReport(E,nil,'TextShiftExecute');
 end;
end;

procedure TFormTextEditDialog.FormCreate(Sender: TObject);
begin
 inherited;
 myAnnals:=NewUndoHistory(Editor);
 myAnnals.Master:=@myAnnals;
 myLinePos:=Point(1,1);
 SetStandardFont(Self);
 ActionEditRefresh.Execute;
 Editor.Font.Assign(Self.Font);
 RestoreFont(Editor.Font,DefaultEditDialogFont);
 SetAllButtonsCursor(Self,crHandPoint);
 SmartUpdate(BitBtnOk,mrCaption(mrOk));
 SmartUpdate(BitBtnCancel,mrCaption(mrCancel));
 UpdateMenu(MenuEditReadOnly,
            RusEng('Редактор\запретить','Edit\disable'),
            RusEng('Разрешить\запретить редактирование текста.','Enable\disable edit text.'),
            ShortCut(VK_F4,[]));
 UpdateMenu(MenuEditDelete,
            RusEng('Удалить','Delete'),
            RusEng('Удалить выделенный фрагмент.','Delete selected region.'),
            ShortCut(VK_DELETE,[ssCtrl]));
 UpdateMenu(MenuEditCut,
            RusEng('Вырезать','Cut'),
            RusEng('Вырезать в буфер обмена.','Cut to clipboard.'),
            ShortCut(Word('X'),[ssCtrl]));
 UpdateMenu(MenuEditCopy,
            RusEng('Копировать','Copy'),
            RusEng('Копировать в буфер обмена.','Copy to clipboard.'),
            ShortCut(Word('C'),[ssCtrl]));
 UpdateMenu(MenuEditPaste,
            RusEng('Вставить','Paste'),
            RusEng('Вставить из буфера обмена.','Paste from clipboard.'),
            ShortCut(Word('V'),[ssCtrl]));
 UpdateMenu(MenuEditUndo,
            RusEng('Отменить','Undo'),
            RusEng('Отменить последнюю операцию.','Undo last operation.'),
            ShortCut(Word('Z'),[ssCtrl]));
 UpdateMenu(MenuEditRedo,
            RusEng('Вернуть','Redo'),
            RusEng('Вернуть (повторить) последнюю операцию.','Redo last operation.'),
            ShortCut(Word('Y'),[ssCtrl]));
 UpdateMenu(MenuEditOblivion,
            RusEng('Сброс истории','Reset Undo'),
            RusEng('Сброс (очистка) истории изменений.','Reset (clear) operation history.'),
            0);
 UpdateMenu(MenuEditSelectAll,
            RusEng('Выделить все','Select all'),
            RusEng('Выделить все.','Select all.'),
            ShortCut(Word('A'),[ssCtrl]));
 UpdateMenu(MenuEditSelectNone,
            RusEng('Снять выделение','Select none'),
            RusEng('Отменить выделение блока.','Cancel text selection.'),
            ShortCut(Word('0'),[ssCtrl]));
 UpdateMenu(MenuEditGoToStart,
            RusEng('Иди к началу','Go to start'),
            RusEng('Идти к началу текста.','Go to start of text.'),
            ShortCut(VK_PRIOR,[ssCtrl]));
 UpdateMenu(MenuEditGoToLineN,
            RusEng('Иди к строке №','Go to line №'),
            RusEng('Поиск строки № в тексте.','Find line of text by №.'),
            ShortCut(Word('G'),[ssCtrl]));
 UpdateMenu(MenuEditGoToEnd,
            RusEng('Иди к концу','Go to end'),
            RusEng('Идти к концу текста.','Go to end of text.'),
            ShortCut(VK_NEXT,[ssCtrl]));
 UpdateMenu(MenuEditWordWrap,
            RusEng('Перенос','Word wrap'),
            RusEng('Автоматический перенос слов.','Automatic word wrap.'),
            ShortCut(Word('W'),[ssCtrl]));
 UpdateMenu(MenuEditRefresh,
            RusEng('Обновить','Refresh'),
            RusEng('Обновление (перерисовка) содержимого окна.','Refresh (redraw) window contents.'),
            ShortCut(VK_F5,[]));
 UpdateMenu(MenuEditFont,
            RusEng('Выбрать фонт …','Choose font …'),
            RusEng('Диалог выбора фонта.','Choose font dialog.'),
            0);
 UpdateMenu(MenuEditShiftLeft,
            RusEng('Сдвиг <--','Shift <--'),
            RusEng('Сдвиг фрагмента текста влево <--.','Text selection shift left <--.'),
            ShortCut(VK_HOME,[ssCtrl]));
 UpdateMenu(MenuEditShiftRight,
            RusEng('Сдвиг -->','Shift -->'),
            RusEng('Сдвиг фрагмента текста вправо -->.','Text selection shift right -->.'),
            ShortCut(VK_END,[ssCtrl]));
 PerformReadOnly:=PerformReadOnly; // To update colors
 ZeroLastSelection;
 PerformOblivion;
 BitBtnEditCut.Caption:='';
 BitBtnEditCopy.Caption:='';
 BitBtnEditPaste.Caption:='';
 BitBtnEditUndo.Caption:='';
 BitBtnEditOblivion.Caption:='';
 BitBtnEditRedo.Caption:='';
 BitBtnEditSelectAll.Caption:='';
 BitBtnEditSelectNone.Caption:='';
 BitBtnEditGoToStart.Caption:='';
 BitBtnEditGoToEnd.Caption:='';
 BitBtnEditFont.Caption:='';
 BitBtnEditRefresh.Caption:='';
 BitBtnEditShiftLeft.Caption:='';
 BitBtnEditShiftRight.Caption:='';
 BitBtnEditReadOnly.Caption:='';
 BitBtnEditWordWrap.Caption:='';
 BitBtnEditGoToLineN.Caption:='';
end;

procedure TFormTextEditDialog.FormDestroy(Sender: TObject);
begin
 Kill(myAnnals);
 inherited;
end;

procedure TFormTextEditDialog.TimerUpdateStateTimer(Sender: TObject);
begin
 try
  UpdateState;
 except
  on E:Exception do BugReport(E,Self,'TimerUpdateStateTimer');
 end;
end;

procedure TFormTextEditDialog.ResetParams;
begin
 SetStandardFont(Self);
 PanelEditor.ParentFont:=true;
 Editor.ParentFont:=true;
 RestoreFont(Editor.Font,DefaultEditDialogFont);
end;

 // @set Panel.Font   Name:PT_Mono\Size:10\Color:Black\Style:[Regular]
 // @set Editor.Font  Name:PT_Mono\Size:10\Color:Black\Style:[Regular]
 // @set Form.Left    400  relative "WindowTitle" ComponentName
 // @set Form.Top     400  relative "WindowTitle" ComponentName
 // @set Form.Width   400
 // @set Form.Height  300
 // @set Form.Width   80  relative Screen
 // @set Form.Height  50  relative Desktop
function TFormTextEditDialog.DoApplyParams(const Params:LongString):Integer;
begin
 Result:=Form_ApplyParams_PosSize(Self,Params)
      or Form_ApplyParams_Font(PanelEditor.Font,Params,'Panel.Font',apf_Fonts1st)
      or Form_ApplyParams_Font(Editor.Font,Params,'Editor.Font',apf_Fonts2nd);
end;

procedure TFormTextEditDialog.UpdateState;
var Exposed,HasSelection,CanEdit,CanCut,CanUndo,CanRedo,CanPaste:Boolean;
begin
 if Ok then
 try
  Exposed:=FormIsExposed(Self);
  CanEdit:=Exposed and not PerformReadOnly;
  HasSelection:=Exposed and (PerformSelLength>0);
  CanCut:=Exposed and HasSelection and CanEdit;
  CanUndo:=Exposed and CanEdit and PerformCanUndo;
  CanRedo:=Exposed and CanEdit and PerformCanRedo;
  CanPaste:=Exposed and CanEdit and Clipboard.HasFormat(CF_TEXT);
  ActionEditSelectAll.Enabled:=Exposed;
  ActionEditGoToStart.Enabled:=Exposed;
  ActionEditGoToEnd.Enabled:=Exposed;
  ActionEditRefresh.Enabled:=Exposed;
  ActionEditFont.Enabled:=Exposed;
  ActionEditWordWrap.Enabled:=Exposed;
  ActionEditCopy.Enabled:=HasSelection;
  ActionEditSelectNone.Enabled:=HasSelection;
  ActionEditDelete.Enabled:=CanCut;
  ActionEditCut.Enabled:=CanCut;
  ActionEditUndo.Enabled:=CanUndo;
  ActionEditRedo.Enabled:=CanRedo;
  ActionEditOblivion.Enabled:=HasAnnals and CanUndo or CanRedo;
  ActionEditPaste.Enabled:=CanPaste;
  ActionEditShiftLeft.Enabled:=CanCut;
  ActionEditShiftRight.Enabled:=CanCut;
  ActiveControl:=Editor;
 except
  on E:Exception do BugReport(E,Self,'UpdateState');
 end;
end;

procedure TFormTextEditDialog.ActionEditReadOnlyExecute(Sender: TObject);
begin
 if Ok then
 try
  PerformReadOnly:=not PerformReadOnly;
  PerformSetFocus(0);
 except
  on E:Exception do BugReport(E,Self,'ActionEditReadOnlyExecute');
 end;
end;

procedure TFormTextEditDialog.ActionEditDeleteExecute(Sender: TObject);
begin
 if Ok then PerformClearSelection;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditGoToEndExecute(Sender: TObject);
begin
 if Ok then PerformGoToEnd;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditCutExecute(Sender: TObject);
begin
 if Ok then PerformCutToClipboard;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditCopyExecute(Sender: TObject);
begin
 if Ok then PerformCopyToClipboard;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditPasteExecute(Sender: TObject);
begin
 if Ok then PerformPasteFromClipboard;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditUndoExecute(Sender: TObject);
begin
 if Ok and PerformCanUndo then PerformUndo;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditWordWrapExecute(Sender: TObject);
begin
 if Ok then
 try
  PerformWordWrap:=not PerformWordWrap;
  if (Editor.ScrollBars<>ssAutoBoth) then begin
   if PerformWordWrap
   then Editor.ScrollBars:=ssVertical
   else Editor.ScrollBars:=ssBoth;
  end;
  PerformSetFocus(0);
 except
  on E:Exception do BugReport(E,Self,'ActionEditWordWrapExecute');
 end;
end;

procedure TFormTextEditDialog.ActionEditRedoExecute(Sender: TObject);
begin
 if Ok and PerformCanRedo then PerformRedo;
 PerformSetFocus(0);
end;

function ConfirmOblivion(const par:LongString):Boolean;
var ask:LongString;
begin
 ask:=RusEng('Вы хотите очистить историю изменений редактора?',
             'Do you want to clear editor UnDo/ReDo history?');
 Result:=(YesNo(ask,par)=mrYes);
end;

procedure TFormTextEditDialog.ActionEditOblivionExecute(Sender: TObject);
begin
 if Ok and ConfirmOblivion('') then PerformOblivion;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditSelectAllExecute(Sender: TObject);
begin
 if Ok then PerformSelectAll;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditSelectNoneExecute(Sender: TObject);
begin
 if Ok then PerformSelectNone;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditGoToStartExecute(Sender: TObject);
begin
 if Ok then PerformGoToStart;
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditRefreshExecute(Sender: TObject);
var aModified : Boolean;
begin
 if Ok then
 try
  inherited;
  aModified:=PerformModified;
  RefreshWindow;
  PerformModified:=aModified;
  PerformSetFocus(0);
 except
  on E:Exception do BugReport(E,Self,'ActionEditRefreshExecute');
 end;
end;

procedure TFormTextEditDialog.ActionEditFontExecute(Sender: TObject);
var aModified:Boolean;
begin
 if Ok then
 try
  FontDialog.Font.Assign(Editor.Font);
  if FontDialog.Execute then begin
   aModified:=PerformModified;
   Editor.Font.Assign(FontDialog.Font);
   ActionEditRefreshExecute(Sender);
   PerformModified:=aModified;
   Echo(StdDateTimePrompt+'Font selected:'+EOL
       +Trim(GetFontAsText(Editor.Font)));
   ClipBoard.AsText:=GetFontAsText(Editor.Font);
  end;
  PerformSetFocus(0);
 except
  on E:Exception do BugReport(E,Self,'ActionEditFontExecute');
 end;
end;

procedure TFormTextEditDialog.ActionEditShiftLeftExecute(Sender: TObject);
begin
 if Ok then TextShiftExecute(-1);
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditShiftRightExecute(Sender: TObject);
begin
 if Ok then TextShiftExecute(+1);
 PerformSetFocus(0);
end;

procedure TFormTextEditDialog.ActionEditGoToLineNExecute(Sender: TObject);
var s:LongString; const Delims=JustSpaces+[',',';','=',':','\','/'];
begin
 inherited;
 if Ok then
 try
  s:=Format('%d : %d',[myLinePos.Y,myLinePos.X]);
  try
   if InputQuery(RusEng('Иди на строку № …','Go to line № …'),
                 RusEng('Ввести номер строки:колонки','Enter row:column number'),s)
   then begin
    myLinePos.Y:=StrToIntDef(ExtractWord(1,s,Delims),0);
    myLinePos.X:=StrToIntDef(ExtractWord(2,s,Delims),1);
    if (myLinePos.Y>0) and (myLinePos.X>0) then begin
     GoToRowCol(myLinePos.y,myLinePos.x);
     if (PerformCaretPos.Y+1<>myLinePos.Y)
     then Warning(Format(RusEng('Не могу найти строку "%s".',
                                'Could not find line "%s".'),[s]));
    end else Warning(Format(RusEng('Неверный номер строки "%s".',
                                   'Invalid line number "%s".'),[s]));
   end;
  finally
   s:='';
  end;
  PerformSetFocus(0);
 except
  on E:Exception do BugReport(E,Self,'ActionEditGoToLineNExecute');
 end;
end;

procedure Init_TextEditDialogsPalette;
var s:LongString;
begin
 try
  s:='';
  if SysGlossary.ReadIniAlpha(SysIniFile,SectTextEditorsPalette,'MainBackColor',s)
  then TextEditDialogsPalette.MainBackColor:=StringToColor(s,clWhite);
  if SysGlossary.ReadIniAlpha(SysIniFile,SectTextEditorsPalette,'MainTextColor',s)
  then TextEditDialogsPalette.MainTextColor:=StringToColor(s,clBlack);
  if SysGlossary.ReadIniAlpha(SysIniFile,SectTextEditorsPalette,'ReadOnlyColor',s)
  then TextEditDialogsPalette.ReadOnlyColor:=StringToColor(s,clInfoBk);
 except
  on E:Exception do BugReport(E,nil,'Init_TextEditDialogsPalette');
 end;
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_form_texteditdialog;
begin
 TFormTextEditDialog.DefaultEditorMode:=0;
end;

procedure Free_crw_form_texteditdialog;
begin
end;

initialization

 Init_crw_form_texteditdialog;

finalization

 Free_crw_form_texteditdialog;

end.

//////////////
// END OF FILE
//////////////

