{
 /***************************************************************************
                            initialsetupdlgs.pas
                            --------------------
       Contains the dialogs to help users setup basic settings.


 ***************************************************************************/

 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner
  
  Abstract:
    Contains the dialogs to help users setup basic settings.
}
unit InitialSetupDlgs;

{$mode objfpc}{$H+}

{off $DEFINE VerboseFPCSrcScanThead}

interface

uses
  // RTL + FCL
  Classes, SysUtils, fgl, pkgglobals, fpmkunit,
  // LCL
  Forms, Controls, Buttons, Dialogs, Graphics, ComCtrls, ExtCtrls, StdCtrls,
  // CodeTools
  FileProcs, CodeToolManager, DefineTemplates,
  // LazUtils
  FPCAdds, FileUtil, LazUTF8, LazFileUtils, LazFileCache, LazLoggerBase,
  // IdeIntf
  IdeIntfStrConsts, MacroDefIntf, IDEDialogs, IDEImagesIntf, IDEUtils,
  // DebuggerIntf
  DbgIntfDebuggerBase,
  // LazDebuggerGdbmi
  GDBMIDebugger, DividerBevel,
  // IdeConfig
  LazConf, EnvironmentOpts, TransferMacros, IDEProcs,
  // IDE
  LazarusIDEStrConsts, IDETranslations, BaseBuildManager, InitialSetupProc,
  GenerateFppkgConfigurationDlg, LazarusPackageIntf, InitialSetupDlgDebuggerFrame, IdeDebuggerOpts;
  
type
  TInitialSetupDialog = class;

  TSetupFrameInfo = record
    Node: TTreeNode;
    Page: TTabSheet;
    State: TInitSetupDlgFrameState;
    Action: TInitSetupDlgFrameAction;
    Group: String;
  end;
  TSetupDlgFrameMap = specialize TFPGMap<Pointer{ISetupDlgFrame}, TSetupFrameInfo>;
  TGroupMap = specialize TFPGMap<string, TSetupFrameInfo>;

  { TSearchFpcSourceThread }

  TSearchFpcSourceThread = class(TThread)
  private
    fSetupDialog: TInitialSetupDialog;
    fFPCVer: string;
    fFoundFPCSrc: TSDFileInfo;
    {$IFDEF VerboseFPCSrcScanThead}
    fPath: string;
    fFileInfo: TSearchRec;
    procedure Debug;
    {$ENDIF}
    function CheckFPCSrcDir(Dir: string): TSDFileInfo;
    procedure DoSearch(const APath: String);
    procedure UpdateFPCSrcDir;
    procedure Finishing;
  protected
    procedure Execute; override;
  public
    constructor Create(aSetupDialog: TInitialSetupDialog);
    destructor Destroy; override;
  end;

  { TInitialSetupDialog }

  TInitialSetupDialog = class(TForm, ISetupDlgProvider)
    BtnPanel: TPanel;
    CompilerBrowseButton: TButton;
    CompilerComboBox: TComboBox;
    CompilerLabel: TLabel;
    CompilerMemo: TMemo;
    CompilerTabSheet: TTabSheet;
    DebuggerTabSheet: TTabSheet;
    FPCSourcesTabSheet: TTabSheet;
    FPCSrcDirBrowseButton: TButton;
    FPCSrcDirComboBox: TComboBox;
    FPCSrcDirLabel: TLabel;
    FPCSrcDirMemo: TMemo;
    ImageList1: TImageList;
    LazarusTabSheet: TTabSheet;
    LazDirBrowseButton: TButton;
    LazDirComboBox: TComboBox;
    LazDirLabel: TLabel;
    LazDirMemo: TMemo;
    MakeExeBrowseButton: TButton;
    MakeExeComboBox: TComboBox;
    MakeExeLabel: TLabel;
    MakeExeMemo: TMemo;
    MakeExeTabSheet: TTabSheet;
    PropertiesPageControl: TPageControl;
    PropertiesTreeView: TTreeView;
    ScanLabel: TLabel;
    ScanProgressBar: TProgressBar;
    Splitter1: TSplitter;
    StartIDEBitBtn: TBitBtn;
    StopScanButton: TBitBtn;
    WelcomePaintBox: TPaintBox;
    FppkgTabSheet: TTabSheet;
    FppkgComboBox: TComboBox;
    FppkgLabel: TLabel;
    FppkgBrowseButton: TButton;
    FppkgMemo: TMemo;
    FppkgWriteConfigButton: TButton;
    procedure CompilerBrowseButtonClick(Sender: TObject);
    procedure CompilerComboBoxChange(Sender: TObject);
    procedure CompilerComboBoxExit(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FPCSrcDirBrowseButtonClick(Sender: TObject);
    procedure FPCSrcDirComboBoxChange(Sender: TObject);
    procedure LazDirBrowseButtonClick(Sender: TObject);
    procedure LazDirComboBoxChange(Sender: TObject);
    procedure MakeExeBrowseButtonClick(Sender: TObject);
    procedure MakeExeComboBoxChange(Sender: TObject);
    procedure OnAppActivate(Sender: TObject);
    procedure PropertiesPageControlChange(Sender: TObject);
    procedure PropertiesTreeViewSelectionChanged(Sender: TObject);
    procedure StartIDEBitBtnClick(Sender: TObject);
    procedure StopScanButtonClick(Sender: TObject);
    procedure WelcomePaintBoxPaint(Sender: TObject);
    procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
    procedure FppkgComboBoxChange(Sender: TObject);
    procedure FppkgBrowseButtonClick(Sender: TObject);
    procedure FppkgWriteConfigButtonClick(Sender: TObject);
  private
    FInitDebuggerFrame: TInitDebuggerFrame;
    FSetupDlgFrameMap: TSetupDlgFrameMap;
    FSetupDlgGroupMap: TGroupMap;
    FFlags: TSDFlags;
    FLastParsedLazDir: string;
    fLastParsedCompiler: string;
    fLastParsedFPCSrcDir: string;
    fLastParsedMakeExe: string;
    fLastParsedFppkgConfigFile: string;
    FIdleConnected: boolean;
    ImgIDFatal: LongInt;
    ImgIDError: LongInt;
    ImgIDWarning: LongInt;
    ImgIDNote: LongInt;
    ImgIDHint: LongInt;
    ImgIDInfo: LongInt;
    FHeadGraphic: TPortableNetworkGraphic;
    FSelectingPage: boolean;
    FCandidates: array[TSDFilenameType] of TSDFileInfoList; // list of TSDFileInfo
    fSearchFpcSourceThread: TSearchFpcSourceThread;
    procedure DoDbgFrameStateChanged(AState: TSDFilenameQuality);
    procedure FrameStateChanged(AnSender: ISetupDlgFrame; AState: TInitSetupDlgFrameState; AnAction: TInitSetupDlgFrameAction);
    procedure SetGroupCaption(AGroupId, AName: String);
    procedure UpdateCaptions;
    procedure SelectPage(const ATreeNode: TTreeNode; AnUserSelect: Boolean = False);
    function SelectDirectory(aTitle: string): string;
    function SelectDirectory(aTitle: string; aPathFileName: string;
                             aEnvOptParseType: TEnvOptParseType): string; overload;
    procedure StartFPCSrcThread;
    procedure UpdateLazarusDirCandidates;
    procedure UpdateCompilerFilenameCandidates;
    procedure UpdateFPCSrcDirCandidates;
    procedure UpdateFPCSrcDirCandidate(aFPCSrcDirInfo: TSDFileInfo);
    procedure UpdateMakeExeCandidates(aStopIfFits: boolean = False);
    procedure UpdateFppkgCandidates;
    procedure FillComboboxWithFileInfoList(ABox: TComboBox; List: TSDFileInfoList;
       ItemIndex: integer = 0);
    procedure SetIdleConnected(const AValue: boolean);
    procedure UpdateLazDirNote;
    procedure UpdateCompilerNote(aQuiet: boolean = False);
    procedure UpdateFPCSrcDirNote;
    procedure UpdateMakeExeNote;
    procedure UpdateFppkgNote;
    function FirstErrorNode: TTreeNode;
    function FirstWarningNode: TTreeNode;
    function GetFirstCandidate(Candidates: TSDFileInfoList;
      MinQuality: TSDFilenameQuality = sddqCompatible): TSDFileInfo;
    function QualityToImgIndex(Quality: TSDFilenameQuality): integer;
    procedure ShowHideScanControls(aShow: Boolean);
    procedure ThreadTerminated(Sender: TObject); // called in main thread by fSearchFpcSourceThread.OnTerminate
    procedure TranslateResourceStrings;
  public
    TVNodeLazarus: TTreeNode;
    TVNodeCompiler: TTreeNode;
    TVNodeFPCSources: TTreeNode;
    TVNodeMakeExe: TTreeNode;
    TVNodeDebugger: TTreeNode;
    TVNodeFppkg: TTreeNode;
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    procedure Init; //Check for config errors, find and show alternatives
    property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
  end;


function ShowInitialSetupDialog: TModalResult;

// Debugger
// Search debugger candidates and add them to list, including quality level

implementation

type

  { TSetupMacros }

  TSetupMacros = class(TTransferMacroList)
  protected
    procedure DoSubstitution({%H-}TheMacro: TTransferMacro; const MacroName: string;
      var s: string; const {%H-}Data: PtrInt; var Handled, {%H-}Abort: boolean;
      {%H-}Depth: integer); override;
  public
    FPCVer: string;
    LazarusDir: string;
  end;

function ShowInitialSetupDialog: TModalResult;
var
  InitialSetupDialog: TInitialSetupDialog;
begin
  InitialSetupDialog:=TInitialSetupDialog.Create(nil);
  try
    Application.TaskBarBehavior:=tbMultiButton;
    InitialSetupDialog.Init;
    Result:=InitialSetupDialog.ShowModal;
  finally
    InitialSetupDialog.Free;
    Application.TaskBarBehavior:=tbDefault;
  end;
end;

{ TSearchFpcSourceThread }

function DoCompFrameList(const Item1, Item2: ISetupDlgFrame): Integer;
begin
  Result := Item1.SortOrder - Item2.SortOrder;
  if Result = 0 then begin
    if Item1.UniqueId < Item2.UniqueId then Result := -1;
    if Item1.UniqueId > Item2.UniqueId then Result := 1;
  end;
end;

constructor TSearchFpcSourceThread.Create(aSetupDialog: TInitialSetupDialog);
begin
  inherited Create(True);
  FreeOnTerminate:=True;
  fSetupDialog:=aSetupDialog;
end;

destructor TSearchFpcSourceThread.Destroy;
begin
  inherited Destroy;
end;

procedure TSearchFpcSourceThread.Execute;
var
  RootDir: String;
begin
  // ToDo: RootDir must be changed for Windows and maybe other systems.
  //       GetUserDir returns the user profile dir on Windows.
  RootDir:=GetUserDir;
  // Scan directories under root directory.
  DoSearch(AppendPathDelim(RootDir));
  if Assigned(fFoundFPCSrc) then
    Synchronize(@UpdateFPCSrcDir); // Update GUI in main thread.
  Synchronize(@Finishing);
end;

function TSearchFpcSourceThread.CheckFPCSrcDir(Dir: string): TSDFileInfo;
var
  RealDir: String;
begin
  Result:=Nil;
  RealDir:=TrimFilename(Dir);
  if RealDir='' then exit;
  if not DirPathExistsCached(RealDir) then exit;   // check if exists
  Result:=TSDFileInfo.Create;
  Result.Filename:=RealDir;
  Result.Caption:=Dir;                             // check quality
  Result.Quality:=CheckFPCSrcDirQuality(RealDir, Result.Note, fFPCVer, False);
  if Result.Quality<>sddqCompatible then           // return only exact matches
    FreeAndNil(Result);
end;

procedure TSearchFpcSourceThread.DoSearch(const APath: String);
var
  PathInfo: TSearchRec;
  FPCSrc: TSDFileInfo;
begin
  if FindFirstUTF8(APath+AllDirectoryEntriesMask, faDirectory, PathInfo) = 0 then
  try
    repeat
      if Terminated then Break;
      if (PathInfo.Name='') or (PathInfo.Name[1]='.')
      or ((PathInfo.Attr and faDirectory) = 0) then Continue;
      case lowercase(PathInfo.Name) of
      'pictures',
      'music',
      'photos',
      'movies',
      'videos',
      'calendar',
      'calendars',
      'mail',
      'messages',
      'contacts',
      'cache',
      'caches',
      'trash',
      'containers',
      {$IFDEF darwin}
      'applications',
      {$ENDIF}
      {$IFDEF Linux}
      'proc',
      'sys',
      'dev',
      {$ENDIF}
      'tmp': continue;
      end;
      {$IFDEF VerboseFPCSrcScanThead}
      fPath := APath;
      fFileInfo := PathInfo;
      Synchronize(@Debug);
      {$ENDIF}
      DoSearch(AppendPathDelim(APath+PathInfo.Name));  // Recursive call
      FPCSrc:=CheckFPCSrcDir(APath+PathInfo.Name);
      if Assigned(FPCSrc) then begin
        fFoundFPCSrc:=FPCSrc;                 // An exact match was found.
        Terminate;
      end;
    until (FindNextUTF8(PathInfo) <> 0);
  finally
    FindCloseUTF8(PathInfo);
  end;
end;

{$IFDEF VerboseFPCSrcScanThead}
procedure TSearchFpcSourceThread.Debug;
begin
  DebugLn(['* TSearchFpcSourceThread.Debug: Path=', fPath, ', Name=', fFileInfo.Name]);
end;
{$ENDIF}

procedure TSearchFpcSourceThread.UpdateFPCSrcDir;
begin
  DebugLn(['TSearchFpcSourceThread.UpdateFPCSrcDir']);
  fSetupDialog.UpdateFPCSrcDirCandidate(fFoundFPCSrc);
  fSetupDialog.UpdateFPCSrcDirNote;
end;

procedure TSearchFpcSourceThread.Finishing;
begin
  DebugLn(['TSearchFpcSourceThread.Finishing']);
  fSetupDialog.ShowHideScanControls(False); // Hide scan controls
end;

{ TSetupMacros }

procedure TSetupMacros.DoSubstitution(TheMacro: TTransferMacro;
  const MacroName: string; var s: string; const Data: PtrInt; var Handled,
  Abort: boolean; Depth: integer);
begin
  Handled:=true;
  if CompareText(MacroName,'ENV')=0 then
    s:=GetEnvironmentVariableUTF8(MacroName)
  else if CompareText(MacroName,'PrimaryConfigPath')=0 then
    s:=GetPrimaryConfigPath
  else if CompareText(MacroName,'SecondaryConfigPath')=0 then
    s:=GetSecondaryConfigPath
  else if CompareText(MacroName,'FPCVer')=0 then begin
    if FPCVer<>'' then
      s:=FPCVer
    else
      s:={$I %FPCVERSION%};
  end else if CompareText(MacroName,'LazarusDir')=0 then begin
    if LazarusDir<>'' then
      s:=LazarusDir
    else
      s:='<LazarusDirNotSet>';
  end else if (CompareText(MacroName,'TargetOS')=0) then
    s:=GetCompiledTargetOS
  else if (CompareText(MacroName,'TargetCPU')=0) then
    s:=GetCompiledTargetCPU
  else if (CompareText(MacroName,'SrcOS')=0) then
    s:=GetDefaultSrcOSForTargetOS(GetCompiledTargetOS)
  else
    Handled:=false;
  //debugln(['TSetupMacros.DoSubstitution MacroName=',MacroName,' Value="',s,'"']);
end;

{$R *.lfm}

{ TInitialSetupDialog }

procedure TInitialSetupDialog.FormCreate(Sender: TObject);
var
  FrameList: TSetupDlgFrameList;
  i, GrpIdx: Integer;
  NewNode: TTreeNode;
  NewTab: TTabSheet;
  ScrollBox: TScrollBox;
  NewParent: TWinControl;
  CurFrame: ISetupDlgFrame;
  Info, GrpInfo: TSetupFrameInfo;
  Grp: String;
  NewDiv: TDividerBevel;
begin
  LazarusTabSheet.Caption:='Lazarus';
  CompilerTabSheet.Caption:=lisCompiler;
  FPCSourcesTabSheet.Caption:=lisFPCSources;
  MakeExeTabSheet.Caption:='Make';
  DebuggerTabSheet.Caption:=lisDebugger;
  FppkgTabSheet.Caption := 'Fppkg';

  FHeadGraphic:=TPortableNetworkGraphic.Create;
  FHeadGraphic.LoadFromResourceName(HInstance, 'ide_icon48x48');

  TVNodeLazarus:=PropertiesTreeView.Items.Add(nil,LazarusTabSheet.Caption);
  TVNodeCompiler:=PropertiesTreeView.Items.Add(nil,CompilerTabSheet.Caption);
  TVNodeFPCSources:=PropertiesTreeView.Items.Add(nil,FPCSourcesTabSheet.Caption);
  TVNodeMakeExe:=PropertiesTreeView.Items.Add(nil,MakeExeTabSheet.Caption);
  TVNodeDebugger:=PropertiesTreeView.Items.Add(nil,DebuggerTabSheet.Caption);

  LazarusTabSheet.Tag    := PtrUInt(Pointer(TVNodeLazarus));
  CompilerTabSheet.Tag   := PtrUInt(Pointer(TVNodeCompiler));
  FPCSourcesTabSheet.Tag := PtrUInt(Pointer(TVNodeFPCSources));
  MakeExeTabSheet.Tag    := PtrUInt(Pointer(TVNodeMakeExe));
  DebuggerTabSheet.Tag   := PtrUInt(Pointer(TVNodeDebugger));


  FppkgTabSheet.TabVisible := EnvironmentOptions.FppkgCheck;
  if FppkgTabSheet.TabVisible then begin
    TVNodeFppkg:=PropertiesTreeView.Items.Add(nil,FppkgTabSheet.Caption);
    FppkgTabSheet.Tag      := PtrUInt(Pointer(TVNodeFppkg));
  end;
  ImgIDFatal   := Imagelist1.AddResourceName(HInstance, 'state_fatal');
  ImgIDError   := Imagelist1.AddResourceName(HInstance, 'state_error');
  ImgIDWarning := Imagelist1.AddResourceName(HInstance, 'state_warning');
  ImgIDNote    := Imagelist1.AddResourceName(HInstance, 'state_note');
  ImgIDHint    := Imagelist1.AddResourceName(HInstance, 'state_hint');
  ImgIDInfo    := Imagelist1.AddResourceName(HInstance, 'state_information');

  IDEImages.AssignImage(StopScanButton, 'menu_stop');

  FrameList := SetupDlgFrameList;
  FrameList.Sort(@DoCompFrameList);
  DisableAutoSizing;
  try
    for i := 0 to FrameList.Count - 1 do begin
      CurFrame := FrameList[i];
      Grp    := CurFrame.GroupId;
      GrpIdx := -1;
      NewNode := nil;
      if Grp <> '' then begin
        GrpIdx := FSetupDlgGroupMap.IndexOf(Grp);
        if GrpIdx >= 0 then begin
          GrpInfo := FSetupDlgGroupMap.Data[GrpIdx];
          NewNode := GrpInfo.Node;
          NewTab  := GrpInfo.Page;
          ScrollBox := NewTab.Controls[0] as TScrollBox;
        end;
      end;

      if NewNode = nil then begin
        NewNode := PropertiesTreeView.Items.Add(nil, CurFrame.Caption);

        NewTab := PropertiesPageControl.AddTabSheet;
        NewTab.Caption := CurFrame.Caption;
        NewTab.Tag := PtrUInt(NewNode);

        ScrollBox := TScrollBox.Create(Self);
        ScrollBox.Parent := NewTab;
        ScrollBox.Align := alClient;
        ScrollBox.HorzScrollBar.Visible := False;
        NewParent := ScrollBox;

        if (GrpIdx < 0) and (Grp <> '') then begin
          GrpInfo.Node   := NewNode;
          GrpInfo.Page   := NewTab;
          GrpInfo.State  := issOk;
          GrpInfo.Action := isaReady;
          GrpIdx := FSetupDlgGroupMap.Add(Grp, GrpInfo);
        end;
      end;
      if GrpIdx >= 0 then begin
        NewDiv := TDividerBevel.Create(Self);
        NewDiv.Parent := ScrollBox;
        if ScrollBox.ControlCount > 1 then begin
          NewDiv.AnchorSide[akTop].Side := asrBottom;
          NewDiv.AnchorSide[akTop].Control := ScrollBox.Controls[ScrollBox.ControlCount - 2];
          NewDiv.BorderSpacing.Top := 10;
        end
        else begin
          NewDiv.AnchorSide[akTop].Side := asrTop;
          NewDiv.AnchorSide[akTop].Control := ScrollBox;
        end;
        NewDiv.AnchorSide[akLeft].Side := asrTop;
        NewDiv.AnchorSide[akLeft].Control := ScrollBox;
        NewDiv.AnchorSide[akRight].Side := asrBottom;
        NewDiv.AnchorSide[akRight].Control := ScrollBox;
        NewDiv.Anchors := [akTop, akLeft, akRight];

        NewDiv.Caption := CurFrame.Caption;
        NewParent := TPanel.Create(Self);

        TPanel(NewParent).Parent := ScrollBox;
        TPanel(NewParent).AnchorSide[akTop].Side := asrBottom;
        TPanel(NewParent).AnchorSide[akTop].Control := NewDiv;
        TPanel(NewParent).AnchorSide[akLeft].Side := asrTop;
        TPanel(NewParent).AnchorSide[akLeft].Control := ScrollBox;
        TPanel(NewParent).AnchorSide[akRight].Side := asrBottom;
        TPanel(NewParent).AnchorSide[akRight].Control := ScrollBox;
        TPanel(NewParent).Anchors := [akTop, akLeft, akRight];
        TPanel(NewParent).AutoSize := True;
        TPanel(NewParent).BevelInner := bvNone;
        TPanel(NewParent).BevelOuter := bvNone;
      end;

      Info.Node   := NewNode;
      Info.Page   := NewTab;
      Info.State  := issOk;
      Info.Action := isaReady;
      Info.Group  := Grp;
      FSetupDlgFrameMap.Add(Pointer(CurFrame), Info);

      CurFrame.AddToDialog(Self, NewParent, Self);
    end;
  finally
    EnableAutoSizing;
  end;

  UpdateCaptions;

  Application.AddOnActivateHandler(@OnAppActivate);
end;

procedure TInitialSetupDialog.CompilerComboBoxChange(Sender: TObject);
begin
  UpdateCompilerNote({Quiet} True);
  UpdateFPCSrcDirNote;
end;

procedure TInitialSetupDialog.CompilerComboBoxExit(Sender: TObject);
begin
  UpdateCompilerNote({Quiet} False);
  UpdateFPCSrcDirNote;
end;

procedure TInitialSetupDialog.CompilerBrowseButtonClick(Sender: TObject);
var
  lExpandedName: string; // Expanded name before Dialog
  lDirName, lFileName: string;
  lTitle: string;
  lChanged: boolean=False;
  Dlg: TIDEOpenDialog;
  Filter: String;
begin
  Dlg := IDEOpenDialogClass.Create(nil);
  try
    lTitle := 'fpc'+GetExecutableExt;
    Dlg.Title := SimpleFormat(lisSelectPathTo, [lTitle]);
    lExpandedName := EnvironmentOptions.GetParsedValue(eopCompilerFilename, CompilerComboBox.Text);
    lDirName := GetValidDirectory(lExpandedName, {out} lFileName);
    Dlg.Options := Dlg.Options+[ofFileMustExist];
    if lFileName='' then
      lFileName := lTitle;
    Filter := dlgFilterAll+'|'+GetAllFilesMask;
    if ExtractFileExt(lFileName)<>'' then
      Filter := dlgFilterExecutable+'|*'+ExtractFileExt(lFileName)+'|'+Filter;
    Dlg.Filter := Filter;
    Dlg.InitialDir := lDirName;
    Dlg.FileName := lFileName;
    if not Dlg.Execute then
      exit;
    lFileName := CleanAndExpandFilename(Dlg.Filename);
    lChanged := UpperCase(lExpandedName)<>UpperCase(lFileName);
  finally
    Dlg.Free;
  end;
  if lChanged then begin // Avoid loosing $(macros)
    CompilerComboBox.Text := lFileName;
    UpdateCompilerNote;
  end;
end;

procedure TInitialSetupDialog.FormDestroy(Sender: TObject);
var
  d: TSDFilenameType;
begin
  IdleConnected:=false;
  if Assigned(fSearchFpcSourceThread) then begin
    fSearchFpcSourceThread.Terminate;
    fSearchFpcSourceThread.WaitFor;
  end;
  for d:=low(FCandidates) to high(FCandidates) do
    FreeAndNil(FCandidates[d]);
  FreeAndNil(FHeadGraphic);
end;

procedure TInitialSetupDialog.FPCSrcDirBrowseButtonClick(Sender: TObject);
var
  Dir: String;
begin
  Dir:=SelectDirectory(lisSelectFPCSourceDirectory, FPCSrcDirComboBox.Text,eopFPCSourceDirectory);
  if Dir='' then
    exit;
  FPCSrcDirComboBox.Text:=Dir;
  UpdateFPCSrcDirNote;
end;

procedure TInitialSetupDialog.FPCSrcDirComboBoxChange(Sender: TObject);
begin
  UpdateFPCSrcDirNote;
end;

procedure TInitialSetupDialog.LazDirBrowseButtonClick(Sender: TObject);
var
  Dir: String;
begin
  Dir:=SelectDirectory(lisSelectLazarusSourceDirectory,LazDirComboBox.Text,eopLazarusDirectory);
  if Dir='' then
    exit;
  LazDirComboBox.Text:=Dir;
  UpdateLazDirNote;
end;

procedure TInitialSetupDialog.LazDirComboBoxChange(Sender: TObject);
begin
  UpdateLazDirNote;
end;

procedure TInitialSetupDialog.MakeExeBrowseButtonClick(Sender: TObject);
var
  lExpandedName: string; // Expanded name before Dialog
  lDirName, lFileName: string;
  lTitle: string;
  lChanged: boolean=False;
  Dlg: TIDEOpenDialog;
  Filter: String;

begin
  Dlg := IDEOpenDialogClass.Create(nil);
  try
    lTitle := 'make'+GetExecutableExt;
    Dlg.Title := SimpleFormat(lisSelectPathTo, [lTitle]);
    lExpandedName := EnvironmentOptions.GetParsedValue(eopMakeFilename, MakeExeComboBox.Text);
    lDirName := GetValidDirectory(lExpandedName, {out} lFileName);
    Dlg.Options := Dlg.Options+[ofFileMustExist];
    if lFileName='' then
      lFileName := lTitle;
    Filter := dlgFilterAll+'|'+GetAllFilesMask;
    if ExtractFileExt(lFileName)<>'' then
      Filter := dlgFilterExecutable+'|*'+ExtractFileExt(lFileName)+'|'+Filter;
    Dlg.Filter := Filter;
    Dlg.InitialDir := lDirName;
    Dlg.FileName := lFileName;
    if not Dlg.Execute then
      exit;
    lFileName := CleanAndExpandFilename(Dlg.Filename);
    lChanged := UpperCase(lExpandedName)<>UpperCase(lFileName);
  finally
    Dlg.Free;
  end;
  if lChanged then begin // Avoid loosing $(macros)
    MakeExeComboBox.Text := lFileName;
    UpdateMakeExeNote;
  end;
end;

procedure TInitialSetupDialog.MakeExeComboBoxChange(Sender: TObject);
begin
  UpdateMakeExeNote;
end;

procedure TInitialSetupDialog.OnAppActivate(Sender: TObject);
begin
  // switched back from another application
  InvalidateFileStateCache;
end;

procedure TInitialSetupDialog.PropertiesPageControlChange(Sender: TObject);
var
  i: Integer;
  t: PtrInt;
begin
  if PropertiesPageControl.ActivePage=nil then exit;
  t:=PropertiesPageControl.ActivePage.tag;
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
    if PtrUInt(PropertiesTreeView.Items.TopLvlItems[i])=t then
      SelectPage(PropertiesTreeView.Items.TopLvlItems[i], True);
end;

procedure TInitialSetupDialog.PropertiesTreeViewSelectionChanged(Sender: TObject);
begin
  if PropertiesTreeView.Selected=nil then
    SelectPage(TVNodeLazarus)
  else
    SelectPage(PropertiesTreeView.Selected, True);
end;

procedure TInitialSetupDialog.StartIDEBitBtnClick(Sender: TObject);
var
  Node: TTreeNode;
  s: String;
  MsgResult: TModalResult;
  i: Integer;
begin
  Node:=FirstErrorNode;
  s:='';
  if Node=TVNodeLazarus then
    s:=lisWithoutAProperLazarusDirectoryYouWillGetALotOfWarn
  else if Node=TVNodeCompiler then
    s:=lisWithoutAProperCompilerTheCodeBrowsingAndCompilingW
  else if Node=TVNodeFPCSources then
    s:=lisWithoutTheProperFPCSourcesCodeBrowsingAndCompletio
  else if Node=TVNodeMakeExe then
    s:=lisWithoutAProperMakeExecutableTheCompilingOfTheIDEIs
  else if Node=TVNodeDebugger then
    s:=lisWithoutAProperDebuggerDebuggingWillBeDisappointing;
  if s<>'' then begin
    MsgResult:=MessageDlg(lisCCOWarningCaption, s, mtWarning, [mbIgnore,
      mbCancel], 0);
    if MsgResult<>mrIgnore then exit;
  end;

  s:=LazDirComboBox.Text;
  if s<>'' then
    EnvironmentOptions.LazarusDirectory:=s;
  s:=CompilerComboBox.Text;
  if s<>'' then
    EnvironmentOptions.CompilerFilename:=s;
  s:=FPCSrcDirComboBox.Text;
  if s<>'' then
    EnvironmentOptions.FPCSourceDirectory:=s;
  s:=MakeExeComboBox.Text;
  if s<>'' then
    EnvironmentOptions.MakeFilename:=s;
  FInitDebuggerFrame.ApplySelection;
  for i := 0 to SetupDlgFrameList.Count - 1 do
    SetupDlgFrameList[i].ApplySelection;
  ModalResult:=mrOk;
end;

procedure TInitialSetupDialog.StopScanButtonClick(Sender: TObject);
begin
  if fSearchFpcSourceThread<>nil then
    fSearchFpcSourceThread.Terminate;
end;

procedure TInitialSetupDialog.WelcomePaintBoxPaint(Sender: TObject);
begin
  with WelcomePaintBox.Canvas do begin
    GradientFill(WelcomePaintBox.ClientRect,$854b32,$c88e60,gdHorizontal);
    Draw(0,WelcomePaintBox.ClientHeight-FHeadGraphic.Height,FHeadGraphic);
    Font.Color:=clWhite;
    Font.Height:=30;
    Brush.Style:=bsClear;
    TextOut(FHeadGraphic.Width+15, 5, lisConfigureLazarusIDE);
  end;
end;

procedure TInitialSetupDialog.OnIdle(Sender: TObject; var Done: Boolean);
var
  f: Boolean;
  i: Integer;
begin
  f := FFlags <> [];
  if sdfCompilerFilenameNeedsUpdate in FFlags then begin
    UpdateCompilerFilenameCandidates;
    UpdateCompilerNote;
  end else if sdfFPCSrcDirNeedsUpdate in FFlags then begin
    UpdateFPCSrcDirCandidates;
    UpdateFPCSrcDirNote;
  end else if sdfMakeExeFilenameNeedsUpdate in FFlags then begin
    UpdateMakeExeCandidates;
    UpdateMakeExeNote;
  end else if sdfDebuggerFilenameNeedsUpdate in FFlags then begin
    DoDbgFrameStateChanged(FInitDebuggerFrame.UpdateState);
    Exclude(FFlags,sdfDebuggerFilenameNeedsUpdate);
    IdleConnected:=True;
  end else if sdfFppkgConfigFileNeedsUpdate in FFlags then begin
    fLastParsedFppkgConfigFile := ' ';
    UpdateFppkgCandidates;
    UpdateFppkgNote;
  end else
    IdleConnected:=false;

  if f then begin
    for i := 0 to SetupDlgFrameList.Count - 1 do
      SetupDlgFrameList[i].UpdateState;
  end;
end;

procedure TInitialSetupDialog.UpdateCaptions;
begin
  Caption:=SimpleFormat(lisWelcomeToLazarusIDE, [LazarusVersionStr]);

  StartIDEBitBtn.Caption:=lisStartIDE;

  LazarusTabSheet.Caption:='Lazarus';
  CompilerTabSheet.Caption:=lisCompiler;
  FPCSourcesTabSheet.Caption:=lisFPCSources;
  MakeExeTabSheet.Caption:='Make';
  DebuggerTabSheet.Caption:=lisDebugger;
  FppkgTabSheet.Caption:='Fppkg';

  TVNodeLazarus.Text:=LazarusTabSheet.Caption;
  TVNodeCompiler.Text:=CompilerTabSheet.Caption;
  TVNodeFPCSources.Text:=FPCSourcesTabSheet.Caption;
  TVNodeMakeExe.Text:=MakeExeTabSheet.Caption;
  TVNodeDebugger.Text:=DebuggerTabSheet.Caption;
  if TVNodeFppkg<>nil then
    TVNodeFppkg.Text:=FppkgTabSheet.Caption;

  LazDirBrowseButton.Caption:=lisPathEditBrowse;
  LazDirLabel.Caption:=SimpleFormat(
    lisTheLazarusDirectoryContainsTheSourcesOfTheIDEAndTh, [PathDelim]);

  FppkgLabel.Caption:=lisFppkgConfiguration;
  FppkgBrowseButton.Caption:=lisPathEditBrowse;
  FppkgWriteConfigButton.Caption:=lisCreateFppkgConfig;

  CompilerBrowseButton.Caption:=lisPathEditBrowse;
  CompilerLabel.Caption:=SimpleFormat(lisTheFreePascalCompilerExecutableTypicallyHasTheName,
    [DefineTemplates.GetDefaultCompilerFilename,
     DefineTemplates.GetDefaultCompilerFilename(GetCompiledTargetCPU)]);

  FPCSrcDirBrowseButton.Caption:=lisPathEditBrowse;
  FPCSrcDirLabel.Caption:=SimpleFormat(lisTheSourcesOfTheFreePascalPackagesAreRequiredForBro,
    [GetForcedPathDelims('rtl/linux/system.pp')]);
  ScanLabel.Caption := lisScanning;
  StopScanButton.Caption:=lisStop;

  MakeExeBrowseButton.Caption:=lisPathEditBrowse;
  MakeExeLabel.Caption:=SimpleFormat(
    lisTheMakeExecutableTypicallyHasTheName, ['make'+GetExecutableExt('')]);
end;

procedure TInitialSetupDialog.DoDbgFrameStateChanged(AState: TSDFilenameQuality
  );
var
  ImageIndex: Integer;
begin
  ImageIndex:=QualityToImgIndex(AState);
  TVNodeDebugger.ImageIndex:=ImageIndex;
  TVNodeDebugger.SelectedIndex:=ImageIndex;
end;

procedure TInitialSetupDialog.FrameStateChanged(AnSender: ISetupDlgFrame;
  AState: TInitSetupDlgFrameState; AnAction: TInitSetupDlgFrameAction);
var
  ImageIndex, i, j: Integer;
  nd: TTreeNode;
  info, info2: TSetupFrameInfo;
begin
  i := FSetupDlgFrameMap.IndexOf(Pointer(AnSender));
  if i < 0 then exit;
  info := FSetupDlgFrameMap.Data[i];
  nd := info.Node;

  info.State := AState;
  info.Action := AnAction;
  FSetupDlgFrameMap.Data[i] := info;

  if info.Group <> '' then begin
    for j := 0 to FSetupDlgFrameMap.Count - 1 do begin
      info2 := FSetupDlgFrameMap.Data[j];
      if (info2.Group = info.Group) then begin
        if (info2.State > AState) then AState := info2.State;
      end;
    end;
  end;

  ImageIndex := -1;
  case AState of
    issOk:      ImageIndex := -1;
    issInfo:    ImageIndex := ImgIDInfo;
    issHint:    ImageIndex := ImgIDHint;
    issNote:    ImageIndex := ImgIDNote;
    issWarning: ImageIndex := ImgIDWarning;
    issError:   ImageIndex := ImgIDError;
    issFatal:   ImageIndex := ImgIDFatal;
  end;


  nd.ImageIndex:=ImageIndex;
  nd.SelectedIndex:=ImageIndex;
end;

procedure TInitialSetupDialog.SetGroupCaption(AGroupId, AName: String);
var
  GrpIdx: Integer;
  GrpInfo: TSetupFrameInfo;
begin
  GrpIdx := FSetupDlgGroupMap.IndexOf(AGroupId);
  if GrpIdx >= 0 then begin
    GrpInfo := FSetupDlgGroupMap.Data[GrpIdx];
    GrpInfo.Page.Caption := AName;
    GrpInfo.Node.Text := AName;
  end;
end;

procedure TInitialSetupDialog.SelectPage(const ATreeNode: TTreeNode; AnUserSelect: Boolean);
var
  i: Integer;
  Node: TTreeNode;
  info: TSetupFrameInfo;
begin
  if FSelectingPage then exit;
  FSelectingPage:=true;
  try
    for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do begin
      Node:=PropertiesTreeView.Items.TopLvlItems[i];
      if Node=ATreeNode then begin
        PropertiesTreeView.Selected:=Node;
        break;
      end;
    end;
    for i:=0 to PropertiesPageControl.PageCount-1 do begin
      if PropertiesPageControl.Pages[i].Tag=PtrUInt(ATreeNode) then begin
        PropertiesPageControl.ActivePageIndex:=i;
        break;
      end;
    end;

    for i := 0 to FSetupDlgFrameMap.Count - 1 do begin
      info := FSetupDlgFrameMap.Data[i];
      if info.Node = ATreeNode then
        ISetupDlgFrame(FSetupDlgFrameMap.Keys[i]).PageSelected(AnUserSelect);
    end;
  finally
    FSelectingPage:=false;
  end;
end;

function TInitialSetupDialog.SelectDirectory(aTitle: string): string;
var
  DirDlg: TSelectDirectoryDialog;
begin
  Result:='';
  DirDlg:=TSelectDirectoryDialog.Create(nil);
  try
    DirDlg.Title:=aTitle;
    DirDlg.Options:=DirDlg.Options+[ofPathMustExist,ofFileMustExist];
    if not DirDlg.Execute then exit;
    Result:=DirDlg.FileName;
  finally
    DirDlg.Free;
  end;
end;

function TInitialSetupDialog.SelectDirectory(aTitle: string;
  aPathFileName: string; aEnvOptParseType: TEnvOptParseType): string;
var
  DirDlg: TSelectDirectoryDialog;
  lCurDirName: string;
  lDirPath: string;
  lDirName: string;
begin
  Result := '';
  if aPathFileName='' then
    case aEnvOptParseType of
      eopLazarusDirectory: lDirPath := EnvironmentOptions.GetParsedLazarusDirectory;
      eopFPCSourceDirectory: lDirPath := EnvironmentOptions.GetParsedFPCSourceDirectory;
    end
  else
    lDirPath := EnvironmentOptions.GetParsedValue(eopLazarusDirectory, aPathFileName);
  lCurDirName := CleanAndExpandFilename(ExcludeTrailingBackSlash(lDirPath));
  lDirPath := GetValidDirectoryAndFilename(lCurDirName, {out} lDirName);
  lDirPath := ExcludeTrailingBackSlash(lDirPath);
  DirDlg := TSelectDirectoryDialog.Create(nil);
  try
    DirDlg.Title := aTitle;
    DirDlg.InitialDir := lDirPath;
    DirDlg.FileName := lDirName;
    DirDlg.Options := DirDlg.Options + [ofExtensionDifferent, ofPathMustExist];
    if DirDlg.Execute then begin
      lDirName := CleanAndExpandFilename(DirDlg.FileName);
      if UpperCase(lCurDirName)<>UpperCase(lDirName) then
        Result := lDirName;
    end;
  finally
    DirDlg.Free;
  end;
end;

procedure TInitialSetupDialog.StartFPCSrcThread;
begin
  fSearchFpcSourceThread:=TSearchFpcSourceThread.Create(Self);
  fSearchFpcSourceThread.OnTerminate:=@ThreadTerminated;
  fSearchFpcSourceThread.fFPCVer:=GetFPCVer;
  ShowHideScanControls(True); // Show scan controls while thread is running
  fSearchFpcSourceThread.Start;
end;

procedure TInitialSetupDialog.UpdateLazarusDirCandidates;
var
  Dirs: TSDFileInfoList;
begin
  Dirs:=SearchLazarusDirectoryCandidates(false);
  FreeAndNil(FCandidates[sddtLazarusSrcDir]);
  FCandidates[sddtLazarusSrcDir]:=Dirs;
  FillComboboxWithFileInfoList(LazDirComboBox,Dirs);
end;

procedure TInitialSetupDialog.UpdateCompilerFilenameCandidates;
var
  Files: TSDFileInfoList;
begin
  Exclude(FFlags,sdfCompilerFilenameNeedsUpdate);
  Files:=SearchFPCExeCandidates(false,CodeToolBoss.CompilerDefinesCache.TestFilename);
  FreeAndNil(FCandidates[sddtCompilerFilename]);
  FCandidates[sddtCompilerFilename]:=Files;
  FillComboboxWithFileInfoList(CompilerComboBox,Files);
end;

procedure TInitialSetupDialog.UpdateFPCSrcDirCandidates;
var
  Dirs: TSDFileInfoList;
begin
  Exclude(FFlags,sdfFPCSrcDirNeedsUpdate);
  Dirs:=SearchFPCSrcDirCandidates(false,GetFPCVer);
  FreeAndNil(FCandidates[sddtFPCSrcDir]);
  FCandidates[sddtFPCSrcDir]:=Dirs;
  FillComboboxWithFileInfoList(FPCSrcDirComboBox,Dirs);
end;

procedure TInitialSetupDialog.UpdateFPCSrcDirCandidate(aFPCSrcDirInfo: TSDFileInfo);
var
  Dirs: TSDFileInfoList;
begin
  Exclude(FFlags,sdfFPCSrcDirNeedsUpdate);
  FreeAndNil(FCandidates[sddtFPCSrcDir]);
  Dirs:=TSDFileInfoList.Create;
  Dirs.Add(aFPCSrcDirInfo);
  FCandidates[sddtFPCSrcDir]:=Dirs;
  FillComboboxWithFileInfoList(FPCSrcDirComboBox,Dirs);
end;

procedure TInitialSetupDialog.UpdateMakeExeCandidates(aStopIfFits: boolean);
var
  Files: TSDFileInfoList;
begin
  Exclude(FFlags,sdfMakeExeFilenameNeedsUpdate);
  Files:=SearchMakeExeCandidates(aStopIfFits);
  FreeAndNil(FCandidates[sddtMakeExeFileName]);
  FCandidates[sddtMakeExeFileName]:=Files;
  FillComboboxWithFileInfoList(MakeExeComboBox,Files);
end;

procedure TInitialSetupDialog.FillComboboxWithFileInfoList(ABox: TComboBox;
  List: TSDFileInfoList; ItemIndex: integer);
var
  sl: TStringList;
  i: Integer;
begin
  sl:=TStringList.Create;
  try
    if List<>nil then
      for i:=0 to List.Count-1 do
        sl.Add(TSDFileInfo(List[i]).Caption);
    ABox.Items.Assign(sl);
    if (ItemIndex>=0) and (ItemIndex<sl.Count) then
      ABox.Text:=sl[ItemIndex]
    else if ABox.Text=ABox.Name then
      ABox.Text:='';
  finally
    sl.Free;
  end;
end;

procedure TInitialSetupDialog.SetIdleConnected(const AValue: boolean);
begin
  if FIdleConnected=AValue then exit;
  FIdleConnected:=AValue;
  if IdleConnected then
    Application.AddOnIdleHandler(@OnIdle)
  else
    Application.RemoveOnIdleHandler(@OnIdle);
end;

procedure TInitialSetupDialog.UpdateLazDirNote;
var
  CurCaption: String;
  Note: string;
  Quality: TSDFilenameQuality;
  s: String;
  ImageIndex: Integer;
begin
  if csDestroying in ComponentState then exit;
  CurCaption:=LazDirComboBox.Text;
  CurCaption:=ChompPathDelim(CurCaption);
  EnvironmentOptions.LazarusDirectory:=CurCaption;
  if FLastParsedLazDir=EnvironmentOptions.GetParsedLazarusDirectory then exit;
  FLastParsedLazDir:=EnvironmentOptions.GetParsedLazarusDirectory;
  //debugln(['TInitialSetupDialog.UpdateLazDirNote ',FLastParsedLazDir]);
  Quality:=CheckLazarusDirectoryQuality(FLastParsedLazDir,Note);
  case Quality of
  sddqInvalid: s:=lisError;
  sddqCompatible: s:='';
  else s:=lisWarning;
  end;
  if EnvironmentOptions.LazarusDirectory<>EnvironmentOptions.GetParsedLazarusDirectory
  then
    s:=lisDirectory+EnvironmentOptions.GetParsedLazarusDirectory+LineEnding+
      LineEnding+s;
  LazDirMemo.Text:=s+Note;

  ImageIndex:=QualityToImgIndex(Quality);
  TVNodeLazarus.ImageIndex:=ImageIndex;
  TVNodeLazarus.SelectedIndex:=ImageIndex;

  FFlags:=FFlags+[sdfCompilerFilenameNeedsUpdate,sdfFPCSrcDirNeedsUpdate,
                  sdfMakeExeFilenameNeedsUpdate,sdfDebuggerFilenameNeedsUpdate];
  IdleConnected:=true;
end;

procedure TInitialSetupDialog.UpdateCompilerNote(aQuiet: boolean);
var
  CurCaption, ParsedC, Note, s: String;
  Quality: TSDFilenameQuality;
  ImageIndex: Integer;
  CfgCache: TPCTargetConfigCache;
begin
  if csDestroying in ComponentState then exit;
  CurCaption:=CompilerComboBox.Text;
  EnvironmentOptions.CompilerFilename:=CurCaption;
  ParsedC:=EnvironmentOptions.GetParsedCompilerFilename;
  if fLastParsedCompiler=ParsedC then exit;
  fLastParsedCompiler:=ParsedC;
  Quality:=CheckFPCExeQuality(fLastParsedCompiler,Note,
                              CodeToolBoss.CompilerDefinesCache.TestFilename);
  if Quality=sddqCompatible then
  begin
    // check compiler again
    CfgCache:=CodeToolBoss.CompilerDefinesCache.ConfigCaches.Find(
                                               fLastParsedCompiler,true);
    CfgCache.CompilerDate:=0; // force update
    if CfgCache.NeedsUpdate then
      CfgCache.Update(CodeToolBoss.CompilerDefinesCache.TestFilename);
    BuildBoss.SetBuildTargetIDE(aQuiet);
  end;
  case Quality of
  sddqInvalid: s:=lisError;
  sddqCompatible: s:='';
  else s:=lisWarning;
  end;
  ParsedC:=EnvironmentOptions.GetParsedCompilerFilename;
  if EnvironmentOptions.CompilerFilename<>ParsedC then
    s:=lisFile2+ParsedC+LineEnding+LineEnding+s;
  CompilerMemo.Text:=s+Note;

  ImageIndex:=QualityToImgIndex(Quality);
  TVNodeCompiler.ImageIndex:=ImageIndex;
  TVNodeCompiler.SelectedIndex:=ImageIndex;

  FFlags:=FFlags+[sdfFPCSrcDirNeedsUpdate,
                  sdfMakeExeFilenameNeedsUpdate,sdfDebuggerFilenameNeedsUpdate];
  if FppkgTabSheet.TabVisible then
    Include(FFlags,sdfFppkgConfigFileNeedsUpdate);
  IdleConnected:=true;
end;

procedure TInitialSetupDialog.UpdateFPCSrcDirNote;
var
  CurCaption: String;
  Note: string;
  Quality: TSDFilenameQuality;
  s: String;
  ImageIndex: Integer;
begin
  if csDestroying in ComponentState then exit;
  CurCaption:=FPCSrcDirComboBox.Text;
  CurCaption:=ChompPathDelim(CurCaption);
  EnvironmentOptions.FPCSourceDirectory:=CurCaption;
  if fLastParsedFPCSrcDir=EnvironmentOptions.GetParsedFPCSourceDirectory then exit;
  fLastParsedFPCSrcDir:=EnvironmentOptions.GetParsedFPCSourceDirectory;
  //debugln(['TInitialSetupDialog.UpdateFPCSrcDirNote ',fLastParsedFPCSrcDir]);
  Quality:=CheckFPCSrcDirQuality(fLastParsedFPCSrcDir,Note,GetFPCVer);
  case Quality of
  sddqInvalid: s:=lisError;
  sddqCompatible: s:='';
  else s:=lisWarning;
  end;
  if EnvironmentOptions.FPCSourceDirectory<>EnvironmentOptions.GetParsedFPCSourceDirectory
  then
    s:=lisDirectory+EnvironmentOptions.GetParsedFPCSourceDirectory+LineEnding+
      LineEnding+s;
  s+=Note;
  if Quality<>sddqCompatible then
    s+=#13+lisYouCanDownloadFPCAndTheFPCSourcesFromHttpSourcefor;
  FPCSrcDirMemo.Text:=s;

  ImageIndex:=QualityToImgIndex(Quality);
  TVNodeFPCSources.ImageIndex:=ImageIndex;
  TVNodeFPCSources.SelectedIndex:=ImageIndex;
end;

procedure TInitialSetupDialog.UpdateMakeExeNote;
var
  CurCaption: String;
  Note: string;
  Quality: TSDFilenameQuality;
  s: String;
  ImageIndex: Integer;
begin
  if csDestroying in ComponentState then exit;
  CurCaption:=MakeExeComboBox.Text;
  EnvironmentOptions.MakeFilename:=CurCaption;
  if fLastParsedMakeExe=EnvironmentOptions.GetParsedMakeFilename then exit;
  fLastParsedMakeExe:=EnvironmentOptions.GetParsedMakeFilename;
  //debugln(['TInitialSetupDialog.UpdateMakeExeNote ',fLastParsedMakeExe]);
  Quality:=CheckMakeExeQuality(fLastParsedMakeExe,Note);

  case Quality of
  sddqInvalid: s:=lisError;
  sddqCompatible: s:='';
  else s:=lisWarning;
  end;
  if EnvironmentOptions.MakeFilename<>EnvironmentOptions.GetParsedMakeFilename
  then
    s:=lisFile2+EnvironmentOptions.GetParsedMakeFilename+LineEnding+
      LineEnding+s;
  MakeExeMemo.Text:=s+Note;

  ImageIndex:=QualityToImgIndex(Quality);
  TVNodeMakeExe.ImageIndex:=ImageIndex;
  TVNodeMakeExe.SelectedIndex:=ImageIndex;

  IdleConnected:=true;
end;

function TInitialSetupDialog.FirstErrorNode: TTreeNode;
var
  i: Integer;
begin
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
  begin
    Result:=PropertiesTreeView.Items.TopLvlItems[i];
    if Result.ImageIndex=ImgIDFatal then exit;
  end;
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
  begin
    Result:=PropertiesTreeView.Items.TopLvlItems[i];
    if Result.ImageIndex=ImgIDError then exit;
  end;
  Result:=FirstWarningNode;
end;

function TInitialSetupDialog.FirstWarningNode: TTreeNode;
var
  i: Integer;
begin
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
  begin
    Result:=PropertiesTreeView.Items.TopLvlItems[i];
    if Result.ImageIndex=ImgIDWarning then
      exit;
  end;
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
  begin
    Result:=PropertiesTreeView.Items.TopLvlItems[i];
    if Result.ImageIndex=ImgIDNote then
      exit;
  end;
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
  begin
    Result:=PropertiesTreeView.Items.TopLvlItems[i];
    if Result.ImageIndex=ImgIDHint then
      exit;
  end;
  for i:=0 to PropertiesTreeView.Items.TopLvlCount-1 do
  begin
    Result:=PropertiesTreeView.Items.TopLvlItems[i];
    if Result.ImageIndex=ImgIDInfo then
      exit;
  end;
  Result:=nil;
end;

function TInitialSetupDialog.GetFirstCandidate(Candidates: TSDFileInfoList;
  MinQuality: TSDFilenameQuality): TSDFileInfo;
var
  i: Integer;
begin
  if Candidates<>nil then
    for i:=0 to Candidates.Count-1 do begin
      Result:=TSDFileInfo(Candidates[i]);
      if Result.Quality>=MinQuality then
        exit;
    end;
  Result:=nil;
end;

function TInitialSetupDialog.QualityToImgIndex(Quality: TSDFilenameQuality): integer;
begin
  if Quality=sddqCompatible then
    Result:=-1
  else if Quality=sddqWrongMinorVersion then
    Result:=ImgIDWarning
  else if Quality=sddqIncomplete then
    Result:=ImgIDWarning
  else if Quality=sddqMakeNotWithFpc then
    Result:=ImgIDWarning
  else if Quality=sddqHint then
    Result:=ImgIDHint
  else
    Result:=ImgIDError;
end;

procedure TInitialSetupDialog.ShowHideScanControls(aShow: Boolean);
begin
  // Show ProgressBar and Stop button durin scanning
  ScanLabel.Visible:=aShow;
  ScanProgressBar.Visible:=aShow;
  StopScanButton.Visible:=aShow;
  // At the same time disable other GUI controls so a user can not mess with it
  StartIDEBitBtn.Enabled:=not aShow;
  FPCSrcDirBrowseButton.Enabled:=not aShow;
  FPCSrcDirComboBox.Enabled:=not aShow;
//  FPCSrcDirMemo.Enabled:=not aShow;
end;

procedure TInitialSetupDialog.ThreadTerminated(Sender: TObject);
begin
  debugln(['TInitialSetupDialog.ThreadTerminated ']);
  fSearchFpcSourceThread:=nil; // Thread will free itself. Make the variable nil, too.
  ShowHideScanControls(false);
end;

procedure TInitialSetupDialog.TranslateResourceStrings;
begin
  IDETranslations.TranslateResourceStrings(
                           EnvironmentOptions.GetParsedLazarusDirectory,
                           EnvironmentOptions.LanguageID);
  UpdateCaptions;
end;

constructor TInitialSetupDialog.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  FSetupDlgFrameMap := TSetupDlgFrameMap.Create;
  FSetupDlgGroupMap := TGroupMap.Create;

  FInitDebuggerFrame := TInitDebuggerFrame.Create(Self);
  FInitDebuggerFrame.Parent := DebuggerTabSheet;
  FInitDebuggerFrame.Align := alClient;
  FInitDebuggerFrame.OnStateChanged := @DoDbgFrameStateChanged;
  FInitDebuggerFrame.Init;
end;

destructor TInitialSetupDialog.Destroy;
var
  i: Integer;
begin
  for i := 0 to SetupDlgFrameList.Count - 1 do
    SetupDlgFrameList[i].Done;

  inherited Destroy;
  FSetupDlgFrameMap.Free;
  FSetupDlgGroupMap.Free;
end;

procedure TInitialSetupDialog.Init;
var
  Node: TTreeNode;
  Candidate: TSDFileInfo;
  IsFirstStart: Boolean;
  PrimaryFilename: String;
  SecondaryFilename: String;
  PrimaryEnvs: TStringList;
  SecondaryEnvs: TStringList;
  i: Integer;
begin
  IsFirstStart:=not FileExistsCached(EnvironmentOptions.Filename);
  if not IsFirstStart then begin
    IsFirstStart:= False;
    PrimaryFilename:=EnvironmentOptions.Filename;
    SecondaryFilename:=AppendPathDelim(GetSecondaryConfigPath)+ExtractFilename(PrimaryFilename);
    if FileExistsUTF8(PrimaryFilename)
    and FileExistsUTF8(SecondaryFilename) then begin
      // compare content of primary and secondary config
      PrimaryEnvs:=TStringList.Create;
      SecondaryEnvs:=TStringList.Create;
      try
        PrimaryEnvs.LoadFromFile(PrimaryFilename);
      except
        on E: Exception do
          debugln(['TInitialSetupDialog.Init unable to read "'+PrimaryFilename+'": '+E.Message]);
      end;
      try
        SecondaryEnvs.LoadFromFile(SecondaryFilename);
      except
        on E: Exception do
          debugln(['TInitialSetupDialog.Init unable to read "'+SecondaryFilename+'": '+E.Message]);
      end;
      // IsFirstStart:=PrimaryEnvs.Text=SecondaryEnvs.Text;
      PrimaryEnvs.Free;
      SecondaryEnvs.Free;
    end;
  end
  else
    IsFirstStart := True;
  //debugln(['TInitialSetupDialog.Init IsFirstStart=',IsFirstStart,' ',EnvironmentOptions.Filename]);

  // Lazarus directory
  UpdateLazarusDirCandidates;
  if IsFirstStart
  or (not FileExistsCached(EnvironmentOptions.GetParsedLazarusDirectory))
  then begin
    // first start => choose first best candidate
    Candidate:=GetFirstCandidate(FCandidates[sddtLazarusSrcDir]);
    if Candidate<>nil then
    begin
      EnvironmentOptions.LazarusDirectory:=Candidate.Caption;
      if Candidate.Quality=sddqCompatible then
        TranslateResourceStrings;
    end;
  end;
  LazDirComboBox.Text:=EnvironmentOptions.LazarusDirectory;
  FLastParsedLazDir:='. .';
  UpdateLazDirNote;

  // compiler filename
  UpdateCompilerFilenameCandidates;
  if IsFirstStart
  or (EnvironmentOptions.CompilerFilename='')
  or (not FileExistsCached(EnvironmentOptions.GetParsedCompilerFilename))
  then begin
    // first start => choose first best candidate
    Candidate:=GetFirstCandidate(FCandidates[sddtCompilerFilename]);
    if Candidate<>nil then
      EnvironmentOptions.CompilerFilename:=Candidate.Caption;
  end;
  CompilerComboBox.Text:=EnvironmentOptions.CompilerFilename;
  fLastParsedCompiler:='. .';
  UpdateCompilerNote;

  // FPC source directory
  UpdateFPCSrcDirCandidates;
  {$IFDEF DebugSearchFPCSrcThread}
  IsFirstStart:=true;
  {$ENDIF}
  if IsFirstStart or (EnvironmentOptions.FPCSourceDirectory='')
  or (not FileExistsCached(EnvironmentOptions.GetParsedFPCSourceDirectory))
  then begin
    // first start => choose first best candidate
    {$IFDEF DebugSearchFPCSrcThread}
    Candidate:=nil;
    {$ELSE}
    Candidate:=GetFirstCandidate(FCandidates[sddtFPCSrcDir]);
    {$ENDIF}
    if Candidate<>nil then begin
      EnvironmentOptions.FPCSourceDirectory:=Candidate.Caption;
    end
    else begin
      // No candidates found => start a thread to scan the file system.
      {$IFNDEF LCLCarbon}
      // carbon interface does not support Synchronize outside Application.Run
      StartFPCSrcThread;
      SelectPage(TVNodeFPCSources);
      {$ENDIF}
    end;
  end;
  ShowHideScanControls(fSearchFpcSourceThread<>nil);
  FPCSrcDirComboBox.Text:=EnvironmentOptions.FPCSourceDirectory;
  fLastParsedFPCSrcDir:='. .';
  UpdateFPCSrcDirNote;

  // Make executable
  UpdateMakeExeCandidates({aStopIfFits} True);
  if IsFirstStart
  or (EnvironmentOptions.MakeFilename='')
  or (not FileExistsCached(EnvironmentOptions.GetParsedMakeFilename)) then
  begin
    // first start => choose first best candidate
    Candidate:=GetFirstCandidate(FCandidates[sddtMakeExeFilename]);
    if Candidate<>nil then
      EnvironmentOptions.MakeFilename:=Candidate.Caption
    else begin // second chance => better an incomplete instead of none (especially for windows)
      Candidate:=GetFirstCandidate(FCandidates[sddtMakeExeFilename], sddqIncomplete);
      if Candidate<>nil then
        EnvironmentOptions.MakeFilename:=Candidate.Caption;
    end;
  end;
  MakeExeComboBox.Text:=EnvironmentOptions.MakeFilename;
  fLastParsedMakeExe:='. .';
  UpdateMakeExeNote;

  // Debugger
  DoDbgFrameStateChanged(FInitDebuggerFrame.UpdateState);

  // Fppkg
  fLastParsedFppkgConfigFile := ' ';
  if FppkgTabSheet.TabVisible then
  begin
    FppkgComboBox.Text := EnvironmentOptions.FppkgConfigFile;
    UpdateFppkgCandidates;
    UpdateFppkgNote;
  end;

  for i := 0 to SetupDlgFrameList.Count - 1 do
    SetupDlgFrameList[i].Init;
  for i := 0 to SetupDlgFrameList.Count - 1 do
    SetupDlgFrameList[i].UpdateState;

  // select first error
  Node:=FirstErrorNode;
  if Node=nil then
    Node:=TVNodeLazarus;
  SelectPage(Node);
end;

procedure TInitialSetupDialog.UpdateFppkgNote;
var
  CurCaption: String;
  FppkgMsg, Note: string;
  Quality: TSDFilenameQuality;
  ConfigFile: string;
  ImageIndex: Integer;
begin
  if csDestroying in ComponentState then exit;
  if not FppkgTabSheet.TabVisible then exit;
  CurCaption:=FppkgComboBox.Text;
  EnvironmentOptions.FppkgConfigFile:=CurCaption;
  if fLastParsedFppkgConfigFile=EnvironmentOptions.GetParsedFppkgConfig then exit;
  fLastParsedFppkgConfigFile:=EnvironmentOptions.GetParsedFppkgConfig;

  ConfigFile := fLastParsedFppkgConfigFile;
  Quality := CheckFppkgConfigFile(ConfigFile, Note);
  if Quality<>sddqCompatible then
  begin
    Note := lisError + Note + LineEnding;
    FppkgWriteConfigButton.Enabled := (Quality=sddqIncomplete);
  end
  else
  begin
    Quality := CheckFppkgConfiguration(ConfigFile, FppkgMsg);

    if Quality=sddqCompatible then
    begin
      Note := lisOk;
      FppkgWriteConfigButton.Enabled := False;
    end
    else
    begin
      Note := lisError + Format(lisIncorrectFppkgConfiguration, [FppkgMsg]) + LineEnding;
      Note := Note + LineEnding + lisFppkgFixConfiguration;
      FppkgWriteConfigButton.Enabled := True;
    end;
  end;

  FppkgMemo.Text := lisFile2 + ConfigFile + LineEnding + LineEnding + Note;

  ImageIndex:=QualityToImgIndex(Quality);
  TVNodeFppkg.ImageIndex:=ImageIndex;
  TVNodeFppkg.SelectedIndex:=ImageIndex;

  IdleConnected:=true;
end;

procedure TInitialSetupDialog.FppkgComboBoxChange(Sender: TObject);
begin
  UpdateFppkgNote;
end;

procedure TInitialSetupDialog.UpdateFppkgCandidates;

  function SearchFppkgFpcPrefixCandidates: TSDFileInfoList;

    function CheckFile(AFile: string; var List: TSDFileInfoList): boolean;
    var
      Item: TSDFileInfo;
    begin
      Result:=false;
      if AFile='' then exit;
      ForcePathDelims(AFile);
      // check if already checked
      if Assigned(List) and List.CaptionExists(AFile) then exit;

      if FileExistsUTF8(AFile) then
      begin
        if List=nil then
          List:=TSDFileInfoList.create(true);
        Item:=List.AddNewItem(AFile, AFile);
        Item.Quality:=sddqCompatible;
        Result := True;
      end;
    end;
  begin
    Result:=nil;

    CheckFile(GetFppkgConfigFile(False, False), Result);
    CheckFile(GetFppkgConfigFile(False, True), Result);
    CheckFile(GetFppkgConfigFile(True, False), Result);
    CheckFile(GetFppkgConfigFile(True, True), Result);
  end;

var
  Files: TSDFileInfoList;
begin
  Exclude(FFlags,sdfFppkgConfigFileNeedsUpdate);
  if not FppkgTabSheet.TabVisible then exit;
  Files:=SearchFppkgFpcPrefixCandidates;
  FreeAndNil(FCandidates[sddtFppkgFpcPrefix]);
  FCandidates[sddtFppkgFpcPrefix]:=Files;
  FillComboboxWithFileInfoList(FppkgComboBox,Files,-1);
end;

procedure TInitialSetupDialog.FppkgBrowseButtonClick(Sender: TObject);
var
  lExpandedName: string; // Expanded name before Dialog
  lDirName, lFileName: string;
  lTitle: string;
  lChanged: boolean=False;
  Dlg: TIDEOpenDialog;
  Filter: String;
begin
  if not FppkgTabSheet.TabVisible then exit;
  Dlg:=IDEOpenDialogClass.Create(nil);
  try
    lTitle:='fppkg.cfg';
    Dlg.Title:=SimpleFormat(lisSelectPathTo, [lTitle]);
    lExpandedName:=EnvironmentOptions.GetParsedValue(eopFppkgConfigFile, FppkgComboBox.Text);
    lDirName := GetValidDirectory(lExpandedName, {out} lFileName);
    Dlg.Options:=Dlg.Options+[ofFileMustExist];
    if lFileName='' then
      lFileName:=lTitle;
    Filter:=dlgFilterAll+'|'+GetAllFilesMask;
    if ExtractFileExt(lFileName)<>'' then
      Filter:=dlgFilterExecutable+'|*'+ExtractFileExt(lFileName)+'|'+Filter;
    Dlg.Filter:=Filter;
    Dlg.InitialDir:=lDirName;
    Dlg.FileName:=lFileName;
    if not Dlg.Execute then
      exit;
    lFileName:=CleanAndExpandFilename(Dlg.Filename);
    lChanged := UpperCase(lExpandedName)<>UpperCase(lFileName);
  finally
    Dlg.Free;
  end;
  if lChanged then begin // Avoid loosing $(macros)
    FppkgComboBox.Text:=lFileName;
    UpdateFppkgNote;
  end;
end;

procedure TInitialSetupDialog.FppkgWriteConfigButtonClick(Sender: TObject);
var
  Dialog: TGenerateFppkgConfigurationDialog;
begin
  if not FppkgTabSheet.TabVisible then exit;
  Dialog := TGenerateFppkgConfigurationDialog.Create(Self);
  try
    Dialog.Compiler := fLastParsedCompiler;
    if fLastParsedFppkgConfigFile='' then
      Dialog.FppkgCfgFilename := GetFppkgConfigFile(False, False)
    else
      Dialog.FppkgCfgFilename := fLastParsedFppkgConfigFile;
    Dialog.ShowModal;
  finally
    Dialog.Free;
  end;

  fLastParsedFppkgConfigFile := ' ';
  UpdateFppkgNote;
end;

end.

