{ *************************************************************************** }
{                                                                             }
{ EControl Syntax Editor (MDI Application)                                    }
{                                                                             }
{ Copyright (c) 2004 - 2006 EControl Ltd., Zaharov Michael                    }
{     www.econtrol.ru                                                         }
{     support@econtrol.ru                                                     }
{                                                                             }
{ *************************************************************************** }
unit main;

interface

{To compile without RX Library uncomment next UNDEF}
{.$UNDEF USE_RX_LIB}
{$IFNDEF WIN32}
 {$UNDEF USE_RX_LIB}
{$ENDIF}
{$IFNDEF VER140}
 {$UNDEF USE_RX_LIB}
{$ENDIF}


uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Mask, ExtCtrls, ImgList, ShellApi, StdActns,
  ComCtrls, ActnList, Buttons, ToolWin, Menus,
  {$IFNDEF WIN32} Variants, System.ComponentModel, {$ENDIF}
  {$IFDEF USE_RX_LIB} MRUList, Placemnt, {$ENDIF}
  ecSyntTree, ecKeyMap, ecActns, ecExports, ecPopupCtrl, ecSyntDlg,
  ecSyntMemo, ecSyntAnal, ecPrint, ecStylesCombo, ecPropManager, ecAutoReplace,
  ecSpell, AppEvnts, ecExtHighlight;

type
  TFileEntry = class
  private
    FSource: TSyntTextSource;
    FState: TecMemoStateStorage;
    FFileName: string;
    FPrevMod: Boolean;
    FInitState: Boolean;
  private
    function IsTheFile(const AFileName: string): Boolean;
    function GetModified: Boolean;
    function IsNewFile: Boolean;
    function GetTitle: string;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Load(const FileName: string);
    procedure Save(const FileName: string);

    property Source: TSyntTextSource read FSource;
    property State: TecMemoStateStorage read FState;
    property FileName: string read FFileName;
    property Modified: Boolean read GetModified;
    property Title: string read GetTitle;
  end;

  TSDIEditMain = class(TForm)
    ActionList1: TActionList;
    SB: TStatusBar;
    SyntFindDialog1: TSyntFindDialog;
    actOpen: TAction;
    actSave: TAction;
    OD: TOpenDialog;
    SD: TSaveDialog;
    SyntReplaceDialog1: TSyntReplaceDialog;
    MainMenu1: TMainMenu;
    actSaveAs: TAction;
    File1: TMenuItem;
    Edit1: TMenuItem;
    New1: TMenuItem;
    Open1: TMenuItem;
    Save1: TMenuItem;
    Saveas1: TMenuItem;
    N1: TMenuItem;
    Exit1: TMenuItem;
    Copy1: TMenuItem;
    Cut1: TMenuItem;
    Paste1: TMenuItem;
    Delete1: TMenuItem;
    N3: TMenuItem;
    Undo1: TMenuItem;
    Redo1: TMenuItem;
    Findnext1: TMenuItem;
    Replace1: TMenuItem;
    Options1: TMenuItem;
    actCustomize1: TMenuItem;
    N4: TMenuItem;
    SelectAll1: TMenuItem;
    EditorProperties1: TMenuItem;
    About1: TMenuItem;
    SyntaxHighlignting2: TMenuItem;
    LexerLibrary1: TMenuItem;
    SelLexMenu: TMenuItem;
    View1: TMenuItem;
    FullExpand1: TMenuItem;
    FullCollapse1: TMenuItem;
    SyntaxManager1: TSyntaxManager;
    Print1: TMenuItem;
    N9: TMenuItem;
    PrintSetup1: TMenuItem;
    actShowNonPrinted1: TMenuItem;
    N10: TMenuItem;
    ExportRTF1: TMenuItem;
    CopyasRTF1: TMenuItem;
    PrinterSetupDialog1: TPrinterSetupDialog;
    Splitter1: TSplitter;
    ExportHTML1: TMenuItem;
    ImageList1: TImageList;
    SyntKeyMapping1: TSyntKeyMapping;
    KeyMapping1: TMenuItem;
    Action11: TMenuItem;
    N12: TMenuItem;
    SynchronizeTree1: TMenuItem;
    ecCommandRedo: TecCommandAction;
    ecCommandAction1: TecCommandAction;
    ecCommandAction2: TecCommandAction;
    ecFullCollapse1: TecFullCollapse;
    ecFullExpand1: TecFullExpand;
    ecCopyAsRTF1: TecCopyAsRTF;
    Reopen1: TMenuItem;
    N8: TMenuItem;
    ecCommandAction3: TecCommandAction;
    CollapseSelection1: TMenuItem;
    HelpContents1: THelpContents;
    Contents1: TMenuItem;
    N5: TMenuItem;
    ecSearch1: TecSearch;
    ecSearchAgain1: TecSearchAgain;
    ecReplace1: TecReplace;
    SyntStyles1: TSyntStyles;
    Userstyles1: TMenuItem;
    ecUserRangeDelete1: TecUserRangeDelete;
    ecUserRangeCollapsable1: TecUserRangeCollapsable;
    ecUserRangeTopLine1: TecUserRangeTopLine;
    ecUserRangeBottomLine1: TecUserRangeBottomLine;
    ecUserRangeLineHighlight1: TecUserRangeLineHighlight;
    TemplatePopup1: TTemplatePopup;
    Wordwrap1: TMenuItem;
    ecSyntPrinter1: TecSyntPrinter;
    N11: TMenuItem;
    actPrintPreview1: TMenuItem;
    ecPrintAction1: TecPrintAction;
    ecPreviewAction1: TecPreviewAction;
    ecExportRTFAction1: TecExportRTFAction;
    ecExportHTMLAction1: TecExportHTMLAction;
    ecCustomizeEditorOptionsAction1: TecCustomizeEditorOptionsAction;
    ecCustomizeSyntaxHighlightingAction1: TecCustomizeSyntaxHighlightingAction;
    ecCustomizeLexerPropertiesAction1: TecCustomizeLexerPropertiesAction;
    ecCustomizeKeyMappingAction1: TecCustomizeKeyMappingAction;
    ecCustomizeUserStylesAction1: TecCustomizeUserStylesAction;
    PageSetup1: TMenuItem;
    ecToggleNonPrinted1: TecToggleNonPrinted;
    ecToggleWordWrap1: TecToggleWordWrap;
    PropsManager1: TPropsManager;
    SyntAutoReplace1: TSyntAutoReplace;
    ecCustomizeLexerLib1: TecCustomizeLexerLib;
    ecToggleSyntTreeSync1: TecToggleSyntTreeSync;
    ecCustomizeAutoReplace1: TecCustomizeAutoReplace;
    CustomizeAutoReplace1: TMenuItem;
    PropsManager2: TPropsManager;
    ecIncrementalSearch1: TecIncrementalSearch;
    IncrementalSearch1: TMenuItem;
    ecPageSetupAction1: TecPageSetupAction;
    Language1: TMenuItem;
    ecFindCurrentWordNextAction1: TecFindCurrentWordNextAction;
    ecFindCurrentWordPriorAction1: TecFindCurrentWordPriorAction;
    N14: TMenuItem;
    Search1: TMenuItem;
    FindCurrentWordNext1: TMenuItem;
    FindCurrentWordPrior1: TMenuItem;
    PrinterSetupDialog2: TPrinterSetupDialog;
    PrintSetupAction: TAction;
    FileExitAction: TAction;
    SharedStyles: TSyntStyles;
    Sharedstyles1: TMenuItem;
    N2: TMenuItem;
    EncodingMenu: TMenuItem;
    FormatMenu: TMenuItem;
    ANSI1: TMenuItem;
    Unicode1: TMenuItem;
    SwapedUnicode1: TMenuItem;
    UTF81: TMenuItem;
    Default1: TMenuItem;
    Windows1: TMenuItem;
    Unix1: TMenuItem;
    Macintosh1: TMenuItem;
    N6: TMenuItem;
    actSaveAll: TAction;
    actClose: TAction;
    actCloseAll: TAction;
    SaveAll1: TMenuItem;
    Close1: TMenuItem;
    CloseAll1: TMenuItem;
    ControlBar1: TControlBar;
    ToolBar1: TToolBar;
    ToolButton7: TToolButton;
    ToolButton8: TToolButton;
    ToolButton9: TToolButton;
    ToolButton3: TToolButton;
    ToolButton4: TToolButton;
    ToolButton6: TToolButton;
    ToolButton10: TToolButton;
    ToolButton12: TToolButton;
    ToolButton19: TToolButton;
    ToolButton20: TToolButton;
    ToolButton21: TToolButton;
    ToolButton32: TToolButton;
    ToolButton17: TToolButton;
    ToolBar2: TToolBar;
    ToolButton36: TToolButton;
    ToolButton37: TToolButton;
    ToolButton38: TToolButton;
    ToolButton39: TToolButton;
    ToolButton40: TToolButton;
    ToolButton41: TToolButton;
    ToolBar3: TToolBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    ToolButton5: TToolButton;
    ToolButton18: TToolButton;
    UserStylesCombo1: TUserStylesCombo;
    ToolButton33: TToolButton;
    ToolButton42: TToolButton;
    ToolBar4: TToolBar;
    ToolButton24: TToolButton;
    ToolButton25: TToolButton;
    ToolButton26: TToolButton;
    ToolButton22: TToolButton;
    ToolButton23: TToolButton;
    ToolButton27: TToolButton;
    ToolButton11: TToolButton;
    ToolButton14: TToolButton;
    ToolbarPopup: TPopupMenu;
    WindowMenu: TMenuItem;
    Cascade1: TMenuItem;
    ileHorizontally1: TMenuItem;
    ileVertically1: TMenuItem;
    MinimizeAll1: TMenuItem;
    Arrange1: TMenuItem;
    ToolButton13: TToolButton;
    ToolButton15: TToolButton;
    ToolButton16: TToolButton;
    PopupMenu1: TPopupMenu;
    Close2: TMenuItem;
    N13: TMenuItem;
    Save2: TMenuItem;
    Saveas2: TMenuItem;
    actSortTree: TAction;
    Sortedtree1: TMenuItem;
    ecCopy1: TecCopy;
    ecPaste1: TecPaste;
    ecCut1: TecCut;
    ecClear1: TecClear;
    ecUndo1: TecUndo;
    ecSelectAll1: TecSelectAll;
    ecToggleCollapse1: TecToggleCollapse;
    oggleCollapse1: TMenuItem;
    ecDictionary1: TecDictionary;
    actDictionaries: TAction;
    Dictionaries1: TMenuItem;
    SyntaxTreeView1: TSyntaxTreeView;
    actShowHyperlinks: TAction;
    Showhyperlinks1: TMenuItem;
    ToolButton28: TToolButton;
    ToolButton29: TToolButton;
    ecSyncEditMode1: TecSyncEditMode;
    TreeImageList: TImageList;
    ecCommentLines1: TecCommentLines;
    ecUnCommentLines1: TecUnCommentLines;
    ToolButton30: TToolButton;
    ToolButton31: TToolButton;
    ToolButton34: TToolButton;
    DisableFolding1: TMenuItem;
    ecInSelCollapse1: TecInSelCollapse;
    ecInSelExpand1: TecInSelExpand;
    Collapseinselection1: TMenuItem;
    Expandinselection1: TMenuItem;
    ecToggleFolding1: TecToggleFolding;
    N15: TMenuItem;
    ToolButton35: TToolButton;
    ToolButton43: TToolButton;
    ApplicationEvents1: TApplicationEvents;
    TabControl1: TTabControl;
    Editor: TSyntaxMemo;
    ecSpellChecker1: TecSpellChecker;
    HyperlinkHighlighter1: THyperlinkHighlighter;
    PopupMenu2: TPopupMenu;
    MenuItem1: TMenuItem;
    MenuItem2: TMenuItem;
    MenuItem3: TMenuItem;
    Copy2: TMenuItem;
    Cut2: TMenuItem;
    Paste2: TMenuItem;
    Delete2: TMenuItem;
    MenuItem4: TMenuItem;
    MenuItem5: TMenuItem;
    MenuItem6: TMenuItem;
    MenuItem7: TMenuItem;
    Indent1: TMenuItem;
    Unindent1: TMenuItem;
    N7: TMenuItem;
    ogglebookmarks1: TMenuItem;
    Bookmark01: TMenuItem;
    Bookmark11: TMenuItem;
    Bookmark21: TMenuItem;
    Bookmark31: TMenuItem;
    Bookmark41: TMenuItem;
    Bookmark51: TMenuItem;
    Bookmark61: TMenuItem;
    Bookmark71: TMenuItem;
    Bookmark81: TMenuItem;
    Bookmark91: TMenuItem;
    Gotobookmarks1: TMenuItem;
    Bookmark02: TMenuItem;
    Bookmark12: TMenuItem;
    Bookmark22: TMenuItem;
    Bookmark32: TMenuItem;
    Bookmark42: TMenuItem;
    Bookmark52: TMenuItem;
    Bookmark62: TMenuItem;
    Bookmark72: TMenuItem;
    Bookmark82: TMenuItem;
    Bookmark92: TMenuItem;
    procedure UpdateStatusBar(Sender: TObject);
    procedure actSaveUpdate(Sender: TObject);
    procedure actOpenExecute(Sender: TObject);
    procedure actSaveExecute(Sender: TObject);
    procedure actNewFileExecute(Sender: TObject);
    procedure actSaveAsExecute(Sender: TObject);
    procedure About1Click(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure SyntaxMemo1IncSearchChange(Sender: TObject;
      State: TIncSearchState);
    procedure SyntaxManager1LexerChanged(Sender: TObject);
    procedure LocalizeClick(Sender: TObject);
    procedure ecCustomizeEditorOptionsAction1ExecuteOK(Sender: TObject);
    procedure PrintSetupActionExecute(Sender: TObject);
    procedure FileExitActionExecute(Sender: TObject);
    procedure ecCustomizeLexerPropertiesAction1ExecuteOK(Sender: TObject);
    procedure Sharedstyles1Click(Sender: TObject);
    procedure ActionList1Update(Action: TBasicAction;
      var Handled: Boolean);
    procedure SetEncoding(Sender: TObject);
    procedure SetFormat(Sender: TObject);
    procedure SplitterMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormShow(Sender: TObject);
    procedure actSaveAllUpdate(Sender: TObject);
    procedure actCloseUpdate(Sender: TObject);
    procedure actCloseAllUpdate(Sender: TObject);
    procedure actSaveAllExecute(Sender: TObject);
    procedure actCloseExecute(Sender: TObject);
    procedure actCloseAllExecute(Sender: TObject);
    procedure ToolbarPopupPopup(Sender: TObject);
    procedure ShowToolbar(Sender: TObject);
    procedure actSortTreeUpdate(Sender: TObject);
    procedure actSortTreeExecute(Sender: TObject);
    procedure ToolButton28Click(Sender: TObject);
    procedure actDictionariesExecute(Sender: TObject);
    procedure Splitter1Moved(Sender: TObject);
    procedure actShowHyperlinksUpdate(Sender: TObject);
    procedure actShowHyperlinksExecute(Sender: TObject);
    procedure ApplicationEvents1Activate(Sender: TObject);
    procedure GotoBookmark(Sender: TObject);
    procedure ToggleBookmark(Sender: TObject);
    procedure AddToDictClick(Sender: TObject);
    procedure SkipErrorClick(Sender: TObject);
    procedure EditorGetGutterImage(Sender: TObject; const Line: Integer;
      List: TList);
    procedure EditorGetLineNumberStr(Sender: TObject; Line: Integer;
      var NumberStr: String);
    procedure EditorGetTokenHint(Sender: TObject; TokenIndex: Integer;
      var HintText: String);
    procedure EditorSetBookmark(Snder: TObject; Bookmark: TBookmark;
      var Accept: Boolean);
    procedure TabControl1Change(Sender: TObject);
  private
    FLocalizer: TecLocalizer;
    procedure SaveFrame(FileIdx: integer; PromtDialog: Boolean = False);
    procedure LoadLanguages;
    function GetLanguage: string;
    procedure SetLanguage(Value: string);
    procedure ApplySharedStyles;
    function GetModifiedCount: integer;
    function PromtToSave(const FileName: string; AllowAll: Boolean): TModalResult;
    procedure CloseAll(DoPromt: Boolean);

    function GetFile(Index: integer): TFileEntry;
    function GetFileCount: integer;
    function GetCurFile: integer;
    procedure SetCurFile(const Value: integer);
    procedure CloseFile(FileIdx: integer);
    procedure NewFile;
    procedure SyncEditClick(Sender: TObject; Line: integer;
      Shift: TShiftState);
  private
    FFileList: TList;
    FCurFile: TFileEntry;
    {$IFDEF USE_RX_LIB}
    FormPlacement: TFormPlacement;
    MRU: TMRUManager;
    procedure MRUClick(Sender: TObject; const RecentName, Caption: String;
      UserData: Integer);
    {$ENDIF}
    procedure LoadTreeImages;
  protected
    procedure WndProc(var Message: TMessage); override;
  public
    constructor Create(AOwner: TComponent); override;
    function OpenFile(const AFileName: string): integer;

    property FileCount: integer read GetFileCount;
    property Files[Index: integer]: TFileEntry read GetFile;
    property CurrentFile: integer read GetCurFile write SetCurFile;
  published
    property Language: string read GetLanguage write SetLanguage;
  end;

var
  SDIEditMain: TSDIEditMain;

implementation

uses ecEditorAbout, ecStrUtils, SingleInst, inifiles, Contnrs;

{$R *.dfm}
type
  TControlHack = class(TControl);

{ TFileEntry }

constructor TFileEntry.Create;
begin
  FSource := TSyntTextSource.Create(nil);
  FState := TecMemoStateStorage.Create(nil);
end;

destructor TFileEntry.Destroy;
begin
  FSource.Free;
  FState.Free;
  inherited;
end;

procedure TFileEntry.Load(const FileName: string);
begin
  FSource.Lines.LoadFromFile(FileName);
  FFileName := FileName;
end;

procedure TFileEntry.Save(const FileName: string);
var attr: integer;
begin
  with FSource do
    begin
      {$IFDEF EC_UNICODE}
      if (Lines.TextCoding = tcAnsi) and  Lines.ContainUnicode and
         (MessageBox(Handle, 'Text contains Unicode characters. Would you like to save text in Unicode format?',
          'Warning', MB_ICONWARNING or MB_YESNO) = IDYES) then
        Lines.TextCoding := tcUnicode;
      {$ENDIF}
      if FileExists(FileName) then
        begin
          attr := FileGetAttr(FileName);
          if (attr and faReadOnly) <> 0 then
            if MessageBox(0, 'Do you want to overwrite read only file?',
                 'Warning', MB_ICONWARNING or MB_YESNO ) = IDNO then
               Exit
            else
               FileSetAttr(FileName, attr and (not faReadOnly));
        end;
      FFileName := FileName;
      Lines.SaveToFile(FFileName);
      Lines.UndoList.Clear;
      Lines.RedoCount := 0;
    end;
end;

function TFileEntry.IsTheFile(const AFileName: string): Boolean;
var s1, s2: string;
begin
  // To use MS CharUpper
  Result := (Length(FFileName) = Length(AFileName)) and
            (Length(FFileName) > 0);
  if Result then
    begin
      s1 := Copy(FFileName, 1, Length(FFileName));
      s2 := Copy(AFileName, 1, Length(AFileName));
      Result := not IsNewFile and
                (string(CharUpper(PChar(s1))) =
                 string(CharUpper(PChar(s2))));
    end;
end;

function TFileEntry.IsNewFile: Boolean;
begin
  Result := FFileName = '';
end;

function TFileEntry.GetModified: Boolean;
begin
  Result := Source.Lines.UndoList.Count > 0;
end;

function TFileEntry.GetTitle: string;
const
  SUntitledFile = 'Untitled';
begin
  if FFileName = '' then
    Result := SUntitledFile
  else
    Result := ExtractFileName(FFileName);
  if Modified then
    Result := Result + '*';
  FPrevMod := Modified;
end;

{ TSyntEditMain }

constructor TSDIEditMain.Create(AOwner: TComponent);
begin
  inherited;
  FFileList := TObjectList.Create;
  TControlHack(Splitter1).OnMouseDown := SplitterMouseDown;
{$IFDEF USE_RX_LIB}
  FormPlacement := TFormPlacement.Create(Self);
  FormPlacement.Name := 'FormPlacement';
  FormPlacement.IniFileName := ChangeFileExt(Application.ExeName, '.ini');
  FormPlacement.IniSection := 'MainForm';
  MRU := TMRUManager.Create(Self);
  MRU.Name := 'MRU';
  MRU.AccelDelimiter := adSpace;
  MRU.Capacity := 9;
  MRU.RecentMenu := Reopen1;
  MRU.OnClick := MRUClick;
  MRU.IniStorage := FormPlacement;
  Reopen1.Visible := True;
  FormPlacement.RestoreFormPlacement;
{$ENDIF}
end;

procedure TSDIEditMain.LoadTreeImages;
var s: string;
    bmp: TBitmap;
    c: TColor;
begin
  // Loading tree images
  s := ExtractFilePath(Application.ExeName) + 'ecTreeImg.bmp';
  if FileExists(s) then
   try
     bmp := TBitmap.Create;
     try
       bmp.LoadFromFile(s);
     except
       bmp.Free;
       Exit;
     end;
     if bmp.Empty then Exit;
     c := bmp.Canvas.Pixels[0,0];
     bmp.Free;
     if TreeImageList.GetInstRes(0, rtBitmap, s, 16, [lrFromFile], c) then
       SyntaxTreeView1.Images := TreeImageList;
    except
    end;
end;

procedure TSDIEditMain.FormShow(Sender: TObject);
var s: string;

  procedure LoadFile(Comp: TLoadableComponent; const AFileName: string);
  begin
    if FileExists(AFileName) then
      try
        Comp.LoadFromFile(AFileName);
      except
        Application.HandleException(Self);
      end;
  end;
begin
  DragAcceptFiles(Handle, True);
  Caption := 'EControl syntax' + {$IFDEF EC_UNICODE}' (unicode)' + {$ENDIF}' editor';

  LoadLanguages;
  PropsManager1.Add(Editor);
  PropsManager1.LoadProps;
  PropsManager1.UpdateAll;
  PropsManager2.LoadProps;
  LoadFile(SyntaxManager1, ExtractFilePath(Application.ExeName) + 'LexLib.LXL');
  LoadFile(SyntStyles1, ChangeFileExt(Application.ExeName, '.ecs'));
  LoadTreeImages;
//  LoadFile(SharedStyles, ChangeFileExt(Application.ExeName, '.ess'));
  s := ChangeFileExt(Application.ExeName, '.ari');
  if FileExists(s) then
   SyntAutoReplace1.Items.LoadFromFile(s);

  Editor.ReadOnly := (ParamCount > 1) and SameText(ParamStr(2), 'r');
  if Editor.ReadOnly then FileExitAction.ShortCut := VK_ESCAPE;

  ecDictionary1.LoadCfg(ChangeFileExt(Application.ExeName, '.ini'));

  if ParamCount > 0 then  OpenFile(ParamStr(1))
    else NewFile;
end;

procedure TSDIEditMain.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
{$IFDEF USE_RX_LIB}
  FormPlacement.SaveFormPlacement;
{$ENDIF}
  PropsManager1.SaveProps;
  PropsManager2.SaveProps;
  SyntStyles1.SaveToFile(ChangeFileExt(Application.ExeName, '.ecs'));
//  SharedStyles.SaveToFile(ChangeFileExt(Application.ExeName, '.ess'));
  SyntAutoReplace1.Items.SaveToFile(ChangeFileExt(Application.ExeName, '.ari'));
  ecDictionary1.SaveCfg(ChangeFileExt(Application.ExeName, '.ini'));
  FreeAndNil(FFileList);
end;


function TSDIEditMain.GetFile(Index: integer): TFileEntry;
begin
  Result := TFileEntry(FFileList[Index]);
end;

function TSDIEditMain.GetFileCount: integer;
begin
  Result := FFileList.Count;
end;

function TSDIEditMain.GetCurFile: integer;
begin
  Result := TabControl1.TabIndex;
end;

procedure TSDIEditMain.SetCurFile(const Value: integer);
var i: integer;
    Img: Boolean;
begin
  if FCurFile <> nil then
   with FCurFile do
    begin
      State.Assign(Editor);
      FInitState := True;
    end;

  TabControl1.TabIndex := Value;
  FCurFile := Files[Value];
  with FCurFile do
   begin
    Editor.TextSource := Source;
    if FInitState then
      Editor.Assign(State);
    SyntaxManager1.CurrentLexer := Source.SyntaxAnalyzer;
    ecSyntPrinter1.Title := Title;
    Application.Title := Title;
    Img := False;
    if (TreeImageList.Count > 0) and (Source.SyntaxAnalyzer <> nil) then
     with Source.SyntaxAnalyzer do
      for i := 0 to BlockRules.Count - 1 do
       if (BlockRules[i].TreeItemImage <> -1) or
          (BlockRules[i].TreeGroupImage <> -1) then
        begin
          Img := True;
          Break;
        end;
    if Img then SyntaxTreeView1.Images := TreeImageList
      else SyntaxTreeView1.Images := nil;
    UpdateStatusBar(nil);
  end;
end;

function TSDIEditMain.OpenFile(const AFileName: string): integer;
var i: integer;
    fe: TFileEntry;
begin
  // Check, if the file is opened
  for i := 0 to FileCount - 1 do
   if Files[i].IsTheFile(AFileName) then
    begin
      Result := i;
      CurrentFile := i;
      Exit;
    end;
  // Create new frame and load file
  if (FileCount = 1) and Files[0].IsNewFile and not Files[0].Modified then
    begin
      fe := Files[0];
      Result := 0;
    end
  else
    begin
      fe := TFileEntry.Create;
      Result := FileCount;
      FFileList.Add(fe);
      TabControl1.Tabs.Add('');
    end;

  fe.Load(AFileName);
  fe.Source.SyntaxAnalyzer := SyntaxManager1.AnalyzerForFile(AFileName);
  Editor.SetFocus;
  TabControl1.Tabs[Result] := fe.Title;
  CurrentFile := Result;
{$IFDEF USE_RX_LIB}
  MRU.Add(AFileName, 0);
{$ENDIF}
end;

// Save specified frame
procedure TSDIEditMain.SaveFrame(FileIdx: integer; PromtDialog: Boolean);
begin
  with Files[FileIdx] do
   begin
    if not PromtDialog then PromtDialog := IsNewFile;
    if PromtDialog then
      begin
        SD.Filter := SyntaxManager1.GetFilesFilter;
        SD.FileName := FileName;
        if SD.Execute then
         begin
          Save(SD.FileName);
      {$IFDEF USE_RX_LIB}
          if not IsTheFile(SD.FileName) then
            MRU.Add(SD.FileName, 0);
      {$ENDIF}
         end;
      end
    else Save(FileName);
    TabControl1.Tabs[FileIdx] := Title;
   end;
end;

procedure TSDIEditMain.NewFile;
var fe: TFileEntry;
begin
  fe := TFileEntry.Create;
  FFileList.Add(fe);
  TabControl1.Tabs.Add(fe.Title);
  CurrentFile := FileCount - 1;
end;

procedure TSDIEditMain.CloseFile(FileIdx: integer);
begin
  if FCurFile = FFileList[FileIdx] then
    FCurFile := nil;
  FFileList.Delete(FileIdx);
  TabControl1.Tabs.Delete(FileIdx);
  if (FileCount = 0) and not (csDestroying in ComponentState) then
    NewFile;
end;

procedure TSDIEditMain.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
var i, ModifiedCount, Res: integer;
begin
  ModifiedCount := GetModifiedCount;
  if ModifiedCount > 0 then
    begin
      res := IDYES;
      for i := 0 to FileCount - 1 do
        if Files[i].Modified then
          begin
            if res <> mrAll then
              res := PromtToSave(Files[i].FileName, ModifiedCount > 1);
            case res of
              mrYes, mrAll: SaveFrame(i, False);
              mrCancel: begin
                         CanClose := False;
                         Exit;
                        end;
            end;
            Dec(ModifiedCount);
          end;
    end;

  if SyntaxManager1.Modified then
   case MessageDlg('Save changes to LexLib.lxl?', mtConfirmation, [mbYes, mbNo, mbCancel], 0) of
    mrYes:  SyntaxManager1.SaveToFile(ExtractFilePath(Application.ExeName) + 'LexLib.LXL');
    mrCancel: begin
               CanClose := False;
               Exit;
              end;
   end;
end;

function TSDIEditMain.PromtToSave(const FileName: string; AllowAll: Boolean): TModalResult;
var Buttons: TMsgDlgButtons;
begin
  Buttons := [mbYes, mbNo, mbCancel];
  if AllowAll then
    Include(Buttons, mbAll);
  Result := MessageDlg('Save changes to ' + ExtractFileName(FileName) + '?',
     mtConfirmation, Buttons, 0);
end;

function TSDIEditMain.GetModifiedCount: integer;
var i: integer;
begin
  Result := 0;
  for i := 0 to FileCount - 1 do
    if Files[i].Modified then
      Inc(Result);
end;

{$IFDEF USE_RX_LIB}
procedure TSDIEditMain.MRUClick(Sender: TObject; const RecentName,
  Caption: String; UserData: Integer);
begin
  OpenFile(RecentName);
end;
{$ENDIF}

procedure TSDIEditMain.actSaveUpdate(Sender: TObject);
begin
  actSave.Enabled := Files[CurrentFile].Modified;
end;

procedure TSDIEditMain.actNewFileExecute(Sender: TObject);
begin
  NewFile;
end;

procedure TSDIEditMain.actOpenExecute(Sender: TObject);
begin
  OD.Filter := SyntaxManager1.GetFilesFilter;
  if OD.Execute then
    OpenFile(OD.FileName);
end;

procedure TSDIEditMain.actSaveExecute(Sender: TObject);
begin
  SaveFrame(CurrentFile, False);
end;

procedure TSDIEditMain.actSaveAsExecute(Sender: TObject);
begin
  SaveFrame(CurrentFile, True);
end;

procedure TSDIEditMain.About1Click(Sender: TObject);
begin
  TForm1.Create(Self).ShowModal;
end;

procedure TSDIEditMain.PrintSetupActionExecute(Sender: TObject);
begin
  PrinterSetupDialog1.Execute;
end;

procedure TSDIEditMain.FileExitActionExecute(Sender: TObject);
begin
  {$IFNDEF VER130}
  FileExitAction.ActionComponent := nil;
  {$ENDIF}
  Close;
end;

procedure TSDIEditMain.actSaveAllUpdate(Sender: TObject);
begin
  actSaveAll.Enabled := GetModifiedCount > 0;
end;

procedure TSDIEditMain.actCloseUpdate(Sender: TObject);
begin
  actClose.Enabled := (FileCount > 1) or not Files[CurrentFile].IsNewFile;
end;

procedure TSDIEditMain.actCloseAllUpdate(Sender: TObject);
begin
  actCloseAll.Enabled := FileCount > 0;
end;

procedure TSDIEditMain.actSaveAllExecute(Sender: TObject);
var i: integer;
begin
  for i := 0 to FileCount - 1 do
    if Files[i].Modified then
      SaveFrame(i, False);
end;

procedure TSDIEditMain.actCloseExecute(Sender: TObject);
begin
  if Files[CurrentFile].Modified then
   if PromtToSave(Files[CurrentFile].FileName, False) = mrYes then
     SaveFrame(CurrentFile, False);
  CloseFile(CurrentFile);
end;

procedure TSDIEditMain.CloseAll(DoPromt: Boolean);
var i, ModifiedCount, Res: integer;
begin
  ModifiedCount := GetModifiedCount;
  res := IDYES;
  for i := FileCount - 1 downto 0 do
    begin
      if Files[i].Modified then
        begin
          if (res <> mrAll) or not DoPromt then
            res := PromtToSave(Files[i].FileName, ModifiedCount > 1);
          case res of
            mrYes, mrAll: SaveFrame(i, False);
            mrCancel: Exit;
          end;
          Dec(ModifiedCount);
        end;
      CloseFile(i);
    end;
end;

procedure TSDIEditMain.actCloseAllExecute(Sender: TObject);
begin
  CloseAll(True);
end;

procedure TSDIEditMain.TabControl1Change(Sender: TObject);
begin
  CurrentFile := CurrentFile;
end;

//==============================================================================
// Localization
//==============================================================================
// Loads *.lng files
procedure TSDIEditMain.LoadLanguages;
var F: TSearchRec;
    R: integer;
    Dir, S: string;
    mi: TMenuItem;
begin
 {$IFDEF WIN32}
  Dir := ExtractFileDir(Application.ExeName);
  try
    R := FindFirst(Dir + '\*.lng', faAnyFile, F);
    while R = 0 do
     begin
       S := ExtractFileName(F.Name);
       R := Pos('.', S);
       if R > 1 then S := Copy(S, 1, R - 1);
       mi := TMenuItem.Create(Self);
       mi.Caption := '&' + S;
       mi.RadioItem := True;
       mi.OnClick := LocalizeClick;
       Language1.Add(mi);

       R := FindNext(F);
     end;
  finally
    FindClose(F);
  end;
 {$ENDIF}
end;


procedure TSDIEditMain.LocalizeClick(Sender: TObject);
var i: integer;
    fn: string;
begin
  if FLocalizer = nil then
    FLocalizer := TecLocalizer.Create(Self);

  fn := ExtractFilePath(Application.ExeName) +
        RemoveAmpersand((Sender as TMenuItem).Caption) + '.lng';
  FLocalizer.Properties.LoadFromFile(fn);
  if FLocalizer.Properties.Count > 0 then
   try
     FLocalizer.Charset := StrToInt(FLocalizer.Properties[0]);
   except
   end;

  for i := 0 to Language1.Count - 1 do
    Language1.Items[i].Checked := Language1.Items[i] = Sender;
end;

procedure TSDIEditMain.ecCustomizeLexerPropertiesAction1ExecuteOK(
  Sender: TObject);
begin
  SyntaxManager1.Modified := True;
end;

function TSDIEditMain.GetLanguage: string;
var i: integer;
begin
  for i := 0 to Language1.Count - 1 do
   with Language1.Items[i] do
    if Checked then
     begin
      Result := RemoveAmpersand(Caption);
      Exit;
     end;
  Result := '';
end;

procedure TSDIEditMain.SetLanguage(Value: string);
var i: integer;
begin
  Value := RemoveAmpersand(Value);
  for i := 0 to Language1.Count - 1 do
   with Language1.Items[i] do
    if SameText(RemoveAmpersand(Caption), Value) then
     begin
      LocalizeClick(Language1.Items[i]);
      Exit;
     end;
end;

//==============================================================================
// Common editor's event handlers
//==============================================================================
procedure TSDIEditMain.UpdateStatusBar(Sender: TObject);
begin
  with Editor do
   begin
     SB.Panels[0].Text := Format('  %d: %d', [CaretPos.X, CaretPos.Y]);
     if Modified then  SB.Panels[1].Text := 'Modified'
                 else  SB.Panels[1].Text := '';
     if ReplaceMode then SB.Panels[2].Text := 'Overwrite'
                    else SB.Panels[2].Text := 'Insert';
   end;
end;

procedure TSDIEditMain.SyntaxMemo1IncSearchChange(Sender: TObject;
  State: TIncSearchState);
begin
  if State = isStart then
   with Sender as TSyntaxMemo do
     begin
      IncSearchBack := ftBackward in SyntFindDialog1.Flags;
      IncSearchIgnoreCase := not (ftCaseSensitive in SyntFindDialog1.Flags);
     end;
  if State <> isStop then
   SB.SimpleText := 'Search for: ' + (Sender as TSyntaxMemo).IncSearchStr
  else
   SB.SimpleText := '';
  SB.SimplePanel := State <> isStop;
end;

//==============================================================================
// Other event handlers
//==============================================================================
procedure TSDIEditMain.SyntaxManager1LexerChanged(Sender: TObject);
begin
  Files[CurrentFile].Source.SyntaxAnalyzer := SyntaxManager1.CurrentLexer;
end;

procedure TSDIEditMain.Sharedstyles1Click(Sender: TObject);
begin
  if SharedStyles.Customize then
   ApplySharedStyles;
end;

procedure TSDIEditMain.ApplySharedStyles;
var i: integer;
begin
  for i := 0 to SyntaxManager1.AnalyzerCount - 1 do
   SyntaxManager1.Analyzers[i].Formats.Synchronize(SharedStyles.Styles);
end;

procedure TSDIEditMain.ActionList1Update(Action: TBasicAction;
  var Handled: Boolean);
var i : integer;
    en: Boolean;
begin
  if Action = FileExitAction then
   begin
      with Files[CurrentFile].Source do
      begin
        for i := 0 to FormatMenu.Count - 1 do
          FormatMenu.Items[i].Checked :=
            integer(Lines.TextFormat) = FormatMenu.Items[i].Tag;
        for i := 0 to EncodingMenu.Count - 1 do
          EncodingMenu.Items[i].Checked :=
            integer(Lines.TextCoding) = EncodingMenu.Items[i].Tag;
      end;
   end;
end;

procedure TSDIEditMain.SetEncoding(Sender: TObject);
var te: TTextCoding;
begin
  te := TTextCoding((Sender as TMenuItem).Tag);
   with Files[CurrentFile] do
    if te <> Source.Lines.TextCoding then
      begin
        Source.Lines.TextCoding := te;
//        Modified := True;
      end;
end;

procedure TSDIEditMain.SetFormat(Sender: TObject);
var tf: TTextFormat;
begin
  tf := TTextFormat((Sender as TMenuItem).Tag);
   with Files[CurrentFile] do
    if tf <> Source.Lines.TextFormat then
      begin
        Source.Lines.TextFormat := tf;
//        Modified := True;
      end;
end;

procedure TSDIEditMain.SplitterMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if not SyntaxTreeView1.Visible or (SyntaxTreeView1.Width = 0) then
   begin
    SyntaxTreeView1.Left := 0;
    Splitter1.Left := 1;
   end;
end;

procedure TSDIEditMain.ecCustomizeEditorOptionsAction1ExecuteOK(
  Sender: TObject);
begin
  PropsManager1.UpdateAll;
end;

procedure TSDIEditMain.ToolbarPopupPopup(Sender: TObject);
var i, N: integer;
    mi: TMenuItem;
begin
  ToolbarPopup.Items.Clear;
  N := ComponentCount;
  for i := 0 to N - 1 do
   if Components[i] is TToolBar then
    with Components[i] as TToolBar do
    begin
      mi := TMenuItem.Create(Self);
      mi.Caption := Caption;
      mi.Checked := Visible;
      mi.Tag := integer(Self.Components[i]);
      mi.OnClick := ShowToolbar;
      ToolbarPopup.Items.Add(mi);
    end;
end;

procedure TSDIEditMain.ShowToolbar(Sender: TObject);
begin
  with TToolbar((Sender as TComponent).Tag) do
    Visible := not Visible;
end;

procedure TSDIEditMain.actSortTreeUpdate(Sender: TObject);
begin
  actSortTree.Checked := SyntaxTreeView1.SortType = stText;
end;

procedure TSDIEditMain.actSortTreeExecute(Sender: TObject);
begin
  if SyntaxTreeView1.SortType = stText then
    begin
      SyntaxTreeView1.SortType := stNone;
      SyntaxTreeView1.UpdateTree;
    end
  else
    SyntaxTreeView1.SortType := stText;
end;

procedure TSDIEditMain.ToolButton28Click(Sender: TObject);
begin
  if Editor <> nil then
   with Editor.InTextImages.Add do
    begin
     Editor.InTextImageList := ImageList1;
     ImageIndex := 51;
     Position := Editor.CaretStrPos;
     Editor.Invalidate;
     Hint := 'In-text image hint (testing)';
    end;
end;

// Processing single instance message
procedure TSDIEditMain.WndProc(var Message: TMessage);
var PC: array[0..MAX_PATH] of Char;
    N, i: integer;
begin
  case Message.Msg of
    WM_TESTMAININSTANCE:
       begin
        Message.Result := 1;
        GlobalGetAtomName(Message.wParam, PC, MAX_PATH);
        Application.Restore;
        BringWindowToTop(Handle);
        try
          OpenFile(PC);
        except
          Application.HandleException(Self);
        end;
       end;
    WM_DROPFILES:
       begin
         N := DragQueryFile(Message.wParam, $FFFFFFFF, nil, 0);
         for i := 0 to N - 1 do
          if DragQueryFile(Message.wParam, i, PC, MAX_PATH) > 0 then
           try
             OpenFile(PC);
           except
           end;
         DragFinish(Message.wParam);
       end;
    else inherited;
  end;
end;

procedure TSDIEditMain.actDictionariesExecute(Sender: TObject);
begin
  ecDictionary1.Customize;
end;

procedure TSDIEditMain.Splitter1Moved(Sender: TObject);
begin
  if (SyntaxTreeView1.Width = 0) or (Editor = nil) then
    SyntaxTreeView1.SyntaxMemo := nil else
   SyntaxTreeView1.SyntaxMemo := Editor;
end;

procedure TSDIEditMain.actShowHyperlinksUpdate(Sender: TObject);
begin
  actShowHyperlinks.Checked := HyperlinkHighlighter1.Active;
end;

procedure TSDIEditMain.actShowHyperlinksExecute(Sender: TObject);
begin
  HyperlinkHighlighter1.Active := not HyperlinkHighlighter1.Active;
end;

procedure TSDIEditMain.ApplicationEvents1Activate(Sender: TObject);
begin
  if Editor <> nil then
    Editor.SetFocus;
end;
// =============================================================================
procedure TSDIEditMain.ToggleBookmark(Sender: TObject);
begin
  Editor.ToggleBookmark((Sender as TComponent).Tag);
end;

procedure TSDIEditMain.GotoBookmark(Sender: TObject);
begin
  Editor.GotoBookmark((Sender as TComponent).Tag);
end;

procedure TSDIEditMain.SkipErrorClick(Sender: TObject);
begin
  if ecSpellChecker1.AddToDictionary(ecSpellChecker1.HltRangeAt(Editor.CaretStrPos),
         TecDictionaryWords(SKIP_DICTIONARY)) then
     Editor.Invalidate;
end;

procedure TSDIEditMain.AddToDictClick(Sender: TObject);
begin
  if ecSpellChecker1.AddToDictionary(ecSpellChecker1.HltRangeAt(Editor.CaretStrPos),
         TecDictionaryWords(USER_DICTIONARY)) then
     Editor.Invalidate;
end;

procedure TSDIEditMain.SyncEditClick(Sender: TObject; Line: integer;
  Shift: TShiftState);
var idx: integer;
begin
  if Editor.SelLength > 0 then
    Editor.SyncEditing.AddCurSelection
  else
   begin
    idx := Editor.SyncEditing.RangeEndAtLine(Line);
    if idx <> -1 then
      Editor.SyncEditing.Delete(idx);
   end;
end;

procedure TSDIEditMain.EditorGetGutterImage(Sender: TObject;
  const Line: Integer; List: TList);
var gi: TGutterObject;
begin
  if (Editor.SelLength > 0) and
     (Line = Editor.StrPosToCaretPos(Editor.SelStart + Editor.SelLength).Y) then
   begin
    gi := TGutterObject.Create(nil);
    gi.OnClick := SyncEditClick;
    gi.ImageIndex := 58;
    gi.Hint := 'Create sync range';
    List.Add(gi);
    Exit;
   end;
  if Editor.SyncEditing.RangeEndAtLine(Line) <> -1 then
   begin
    gi := TGutterObject.Create(nil);
    gi.ImageIndex := 59;
    gi.Hint := 'Remove sync range';
    gi.OnClick := SyncEditClick;
    List.Add(gi);
    Exit;
   end;
end;

procedure TSDIEditMain.EditorGetLineNumberStr(Sender: TObject;
  Line: Integer; var NumberStr: String);
begin
  if ((Line + 1) mod 10 = 0) or
     (Editor.Collapsed.GetCollapsedIndex(Line) <> -1) or
     (Line = Editor.CaretPos.Y) then
    NumberStr := IntToStr(Line + 1)
  else
  if (Line + 1) mod 5 = 0 then
    NumberStr := '-'
  else
    NumberStr := '.';
end;

procedure TSDIEditMain.EditorGetTokenHint(Sender: TObject;
  TokenIndex: Integer; var HintText: String);
var p: TPoint;
begin
 try
// This example show how to get token information and use it for hint showing
  if (Sender as TSyntaxMemo).SyntObj <> nil then
  with (Sender as TSyntaxMemo).SyntObj do
   begin
    HintText := 'Token:      ' + TagStr[TokenIndex] + sLineBreak;
    if Length(HintText) > 100 then HintText := '';
    p := TagPos[TokenIndex];
    HintText := HintText +
               'Rule:       ' + Tags[TokenIndex].Rule.DisplayName + sLineBreak +
               'Token pos:  ' + Format('(X: %d; Y: %d)', [p.X + 1, p.Y + 1]) + sLineBreak +
               'Position: ' + IntToStr(TokenIndex);
   end;
 except
  on E: Exception do
   HintText := E.Message;
 end;
end;

procedure TSDIEditMain.EditorSetBookmark(Snder: TObject;
  Bookmark: TBookmark; var Accept: Boolean);
begin
  with Bookmark do
   if (BmIndex >= 0) and (BmIndex < 10) then
    begin
     ImageIndex := 13 + BmIndex;
     Hint := 'Bookmark ' + IntToStr(BmIndex);
    end;
end;

end.
