{ /***************************************************************************
                    showcompileropts.pas  -  Lazarus IDE unit
                    -----------------------------------------

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

 ***************************************************************************
 *                                                                         *
 *   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:
    Dialog for showing the compiler options as command line parameters.
}
unit ShowCompilerOpts;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, contnrs,
  // LCL
  Forms, Controls, Buttons, StdCtrls, ComCtrls, ExtCtrls,
  // LazUtils
  LazFileUtils, LazUTF8, LazStringUtils,
  // CodeTools
  CodeToolsCfgScript,
  // IdeIntf
  BaseIDEIntf, LazIDEIntf, IDEImagesIntf, CompOptsIntf, ProjectIntf,
  PackageIntf, MacroIntf,
  // IDE
  LazarusIDEStrConsts, Project, PackageDefs,
  CompilerOptions, ModeMatrixOpts, MiscOptions;

type
  TShowCompToolOpts = class
    CompOpts: TCompilationToolOptions;
    Sheet: TTabSheet;
    Memo: TMemo;
    MultiLineCheckBox: TCheckBox;
  end;

  { TShowCompilerOptionsDlg }

  TShowCompilerOptionsDlg = class(TForm)
    CloseButton: TBitBtn;
    CmdLineMemo: TMemo;
    CmdLineParamsTabSheet: TTabSheet;
    InheritedParamsTabSheet: TTabSheet;
    InhItemMemo: TMemo;
    InhSplitter: TSplitter;
    InhTreeView: TTreeView;
    MultilineCheckBox: TCheckBox;
    PageControl1: TPageControl;
    RelativePathsCheckBox: TCheckBox;
    procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure InhTreeViewSelectionChanged(Sender: TObject);
    procedure MultilineCheckBoxChange(Sender: TObject);
    procedure RelativePathsCheckBoxChange(Sender: TObject);
  private
    FCompilerOpts: TBaseCompilerOptions;
    ImageIndexInherited: Integer;
    ImageIndexRequired: Integer;
    ImageIndexPackage: Integer;
    InheritedChildDatas: TFPList; // list of PInheritedNodeData
    FToolOptions: TObjectList; // list of TShowCompToolOpts
    FUpdatingMultiline: boolean;
    procedure ClearInheritedTree;
    procedure SetCompilerOpts(const AValue: TBaseCompilerOptions);
    procedure FillMemo(Memo: TMemo; Params: string);
    procedure UpdateMemo;
    procedure UpdateInheritedTree;
    procedure UpdateExecuteBeforeAfter;
    procedure UpdateToolMemo(Opts: TShowCompToolOpts);
  public
    property CompilerOpts: TBaseCompilerOptions read FCompilerOpts write SetCompilerOpts;
  end;

function ShowCompilerOptionsDialog(OwnerForm: TCustomForm;
  CompilerOpts: TBaseCompilerOptions): TModalResult;

implementation

{$R *.lfm}

type
  TInheritedNodeData = record
    FullText: string;
    Option: TInheritedCompilerOption;
  end;
  PInheritedNodeData = ^TInheritedNodeData;

function ShowCompilerOptionsDialog(OwnerForm: TCustomForm;
  CompilerOpts: TBaseCompilerOptions): TModalResult;
var
  ShowCompilerOptionsDlg: TShowCompilerOptionsDlg;
begin
  Result:=mrOk;
  LazarusIDE.PrepareBuildTarget(false,smsfsBackground);
  ShowCompilerOptionsDlg:=TShowCompilerOptionsDlg.Create(OwnerForm);
  try
    ShowCompilerOptionsDlg.CompilerOpts:=CompilerOpts;
    Result:=ShowCompilerOptionsDlg.ShowModal;
  finally
    ShowCompilerOptionsDlg.Free;
  end;
end;

{ TShowCompilerOptionsDlg }

procedure TShowCompilerOptionsDlg.RelativePathsCheckBoxChange(Sender: TObject);
begin
  UpdateMemo;
end;

procedure TShowCompilerOptionsDlg.ClearInheritedTree;
var
  i: integer;
  ChildData: PInheritedNodeData;
begin
  if InhTreeView = nil then
    exit;
  InhTreeView.BeginUpdate;
  // dispose all child data
  if InheritedChildDatas <> nil then
  begin
    for i := 0 to InheritedChildDatas.Count - 1 do
    begin
      ChildData := PInheritedNodeData(InheritedChildDatas[i]);
      Dispose(ChildData);
    end;
    InheritedChildDatas.Free;
    InheritedChildDatas := nil;
  end;
  InhTreeView.Items.Clear;
  InhTreeView.EndUpdate;
end;

procedure TShowCompilerOptionsDlg.InhTreeViewSelectionChanged(Sender: TObject);
var
  ANode: TTreeNode;
  ChildData: PInheritedNodeData;
  sl: TStrings;
begin
  ANode := InhTreeView.Selected;
  if (ANode = nil) or (ANode.Data = nil) then
  begin
    InhItemMemo.Lines.Text := lisSelectANode;
  end
  else
  begin
    ChildData := PInheritedNodeData(ANode.Data);
    if ChildData^.Option in icoAllSearchPaths then
    begin
      sl := SplitString(ChildData^.FullText, ';');
      InhItemMemo.Lines.Assign(sl);
      sl.Free;
    end
    else
      InhItemMemo.Lines.Text := ChildData^.FullText;
  end;
end;

procedure TShowCompilerOptionsDlg.MultilineCheckBoxChange(Sender: TObject);
var
  CheckBox: TCheckBox;
  Checked: Boolean;
  i: Integer;
  Opts: TShowCompToolOpts;
begin
  if FUpdatingMultiline then exit;
  CheckBox:=Sender as TCheckBox;
  Checked:=CheckBox.Checked;

  FUpdatingMultiline:=true;
  try
    MultilineCheckBox.Checked:=Checked;
    UpdateMemo;

    for i:=0 to FToolOptions.Count-1 do
    begin
      Opts:=TShowCompToolOpts(FToolOptions[i]);
      if Opts.MultiLineCheckBox<>nil then begin
        Opts.MultiLineCheckBox.Checked:=Checked;
        UpdateToolMemo(Opts);
      end;
    end;
  finally
    FUpdatingMultiline:=false;
  end;
end;

procedure TShowCompilerOptionsDlg.FormCreate(Sender: TObject);
begin
  FToolOptions:=TObjectList.Create(true);

  ImageIndexPackage := IDEImages.LoadImage('item_package');
  ImageIndexRequired := IDEImages.LoadImage('pkg_required');
  ImageIndexInherited := IDEImages.LoadImage('pkg_inherited');

  Caption:=dlgCompilerOptions;

  PageControl1.ActivePage:=CmdLineParamsTabSheet;
  CmdLineParamsTabSheet.Caption:=lisCommandLineParameters;
  RelativePathsCheckBox.Caption:=lisShowRelativePaths;
  RelativePathsCheckBox.Checked:=not MiscellaneousOptions.ShowCompOptFullFilenames;
  MultilineCheckBox.Caption:=lisShowMultipleLines;
  MultilineCheckBox.Checked:=MiscellaneousOptions.ShowCompOptMultiLine;

  InheritedParamsTabSheet.Caption:=lisInheritedParameters;
  InhTreeView.Images := IDEImages.Images_16;
  InhItemMemo.Text := lisSelectANode;

  CloseButton.Caption:=lisBtnClose;
end;

procedure TShowCompilerOptionsDlg.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  MiscellaneousOptions.ShowCompOptFullFilenames:=not RelativePathsCheckBox.Checked;
  MiscellaneousOptions.ShowCompOptMultiLine:=MultilineCheckBox.Checked;
  MiscellaneousOptions.Save;

  FreeAndNil(FToolOptions);
end;

procedure TShowCompilerOptionsDlg.FormDestroy(Sender: TObject);
begin
  ClearInheritedTree;
end;

procedure TShowCompilerOptionsDlg.SetCompilerOpts(
  const AValue: TBaseCompilerOptions);
begin
  if FCompilerOpts=AValue then exit;
  FCompilerOpts:=AValue;
  UpdateMemo;
  UpdateInheritedTree;
  UpdateExecuteBeforeAfter;
end;

procedure TShowCompilerOptionsDlg.FillMemo(Memo: TMemo; Params: string);
var
  ParamList: TStringList;
begin
  if Memo=nil then exit;
  if MultilineCheckBox.Checked then begin
    ParamList:=TStringList.Create;
    try
      SplitCmdLineParams(Params,ParamList);
      Memo.Lines.Assign(ParamList);
    finally
      ParamList.Free;
    end;
    Memo.ScrollBars:=ssAutoBoth;
  end else begin
    Memo.ScrollBars:=ssAutoVertical;
    Memo.Lines.Text:=Params;
  end;
end;

procedure TShowCompilerOptionsDlg.UpdateMemo;
var
  Flags: TCompilerCmdLineOptions;
  CurOptions, CompPath: String;
begin
  if CompilerOpts=nil then exit;

  Flags:=CompilerOpts.DefaultMakeOptionsFlags;
  if not RelativePathsCheckBox.Checked then
    Include(Flags,ccloAbsolutePaths);
  CurOptions := CompilerOpts.MakeOptionsString(Flags);
  CompPath:=CompilerOpts.ParsedOpts.GetParsedValue(pcosCompilerPath);
  if Pos(' ',CompPath)>0 then
    CompPath:=QuotedStr(CompPath);
  FillMemo(CmdLineMemo,CompPath+' '+CurOptions);
end;

procedure TShowCompilerOptionsDlg.UpdateInheritedTree;
var
  OptionsList: TFPList;
  i: integer;
  AncestorOptions: TAdditionalCompilerOptions;
  AncestorNode: TTreeNode;
  AncestorBaseOpts: TBaseCompilerOptions;
  Vars: TCTCfgScriptVariables;
  Macro: TLazBuildMacro;
  j: Integer;

  procedure AddChildNode(const NewNodeName, Value: string;
    Option: TInheritedCompilerOption);
  var
    VisibleValue: string;
    ChildNode: TTreeNode;
    ChildData: PInheritedNodeData;
  begin
    if Value = '' then
      exit;
    New(ChildData);
    ChildData^.FullText := Value;
    ChildData^.Option := Option;
    if InheritedChildDatas = nil then
      InheritedChildDatas := TFPList.Create;
    InheritedChildDatas.Add(ChildData);

    if UTF8Length(Value) > 100 then
      VisibleValue := UTF8Copy(Value, 1, 100) + '[...]'
    else
      VisibleValue := Value;
    ChildNode := InhTreeView.Items.AddChildObject(AncestorNode,
      NewNodeName + ' = "' + VisibleValue + '"', ChildData);
    ChildNode.ImageIndex := ImageIndexRequired;
    ChildNode.SelectedIndex := ChildNode.ImageIndex;
  end;

var
  SkippedPkgList: TFPList;
  AProject: TProject;
  Pkg: TLazPackage;
  t: TBuildMatrixGroupType;

  procedure AddMatrixGroupNode(Grp: TBuildMatrixGroupType);
  begin
    if AncestorNode<>nil then exit;
    AncestorNode := InhTreeView.Items.Add(nil, '');
    case Grp of
    bmgtEnvironment: AncestorNode.Text:=dlgGroupEnvironment;
    bmgtProject: AncestorNode.Text:=dlgProject;
    bmgtSession: AncestorNode.Text:=lisProjectSession;
    end;
    AncestorNode.ImageIndex := ImageIndexPackage;
    AncestorNode.SelectedIndex := AncestorNode.ImageIndex;
  end;

  procedure AddMatrixGroup(Grp: TBuildMatrixGroupType);
  var
    CustomOptions: String;
    OutDir: String;
  begin
    AncestorNode := nil;
    CustomOptions:='';
    OnAppendCustomOption(CompilerOpts,CustomOptions,[Grp]);
    if CustomOptions<>'' then begin
      AddMatrixGroupNode(Grp);
      AddChildNode(liscustomOptions, CustomOptions, icoCustomOptions);
    end;
    OutDir:='.*';
    OnGetOutputDirectoryOverride(CompilerOpts,OutDir,[Grp]);
    if OutDir<>'.*' then begin
      AddMatrixGroupNode(Grp);
      AddChildNode('Output directory', OutDir, icoNone);
    end;
    if AncestorNode<>nil then
      AncestorNode.Expand(true);
  end;

begin
  if CompilerOpts=nil then exit;
  OptionsList := nil;
  //debugln(['TCompilerInheritedOptionsFrame.UpdateInheritedTree START CompilerOpts=',DbgSName(CompilerOpts)]);
  CompilerOpts.GetInheritedCompilerOptions(OptionsList);
  SkippedPkgList:=nil;
  try
    if CompilerOpts is TProjectCompilerOptions then begin
      AProject:=TProjectCompilerOptions(CompilerOpts).LazProject;
      AProject.GetAllRequiredPackages(SkippedPkgList);
      if (SkippedPkgList<>nil)
      and (not (pfUseDesignTimePackages in AProject.Flags)) then begin
        // keep design time only packages
        for i:=SkippedPkgList.Count-1 downto 0 do
          if TLazPackage(SkippedPkgList[i]).PackageType<>lptDesignTime then
            SkippedPkgList.Delete(i);
      end;
    end;
    //debugln(['TCompilerInheritedOptionsFrame.UpdateInheritedTree END']);
    InhTreeView.BeginUpdate;
    ClearInheritedTree;
    if OptionsList <> nil then
    begin
      Vars:=GetBuildMacroValues(CompilerOpts,false);
      // add All node
      AncestorNode := InhTreeView.Items.Add(nil, lisAllInheritedOptions);
      AncestorNode.ImageIndex := ImageIndexInherited;
      AncestorNode.SelectedIndex := AncestorNode.ImageIndex;
      with CompilerOpts do
      begin
        AddChildNode(lisunitPath,
          GetInheritedOption(icoUnitPath, True), icoUnitPath);
        AddChildNode(lisincludePath,
          GetInheritedOption(icoIncludePath, True), icoIncludePath);
        AddChildNode(lisobjectPath,
          GetInheritedOption(icoObjectPath, True), icoObjectPath);
        AddChildNode(lislibraryPath,
          GetInheritedOption(icoLibraryPath, True), icoLibraryPath);
        AddChildNode(lislinkerOptions, GetInheritedOption(icoLinkerOptions, True),
          icoLinkerOptions);
        AddChildNode(liscustomOptions, GetInheritedOption(icoCustomOptions, True),
          icoCustomOptions);
      end;
      AncestorNode.Expanded := True;
      // add detail nodes
      for i := 0 to OptionsList.Count - 1 do
      begin
        AncestorOptions := TAdditionalCompilerOptions(OptionsList[i]);
        AncestorNode := InhTreeView.Items.Add(nil, '');
        AncestorNode.Text := AncestorOptions.GetOwnerName;
        AncestorNode.ImageIndex := ImageIndexPackage;
        AncestorNode.SelectedIndex := AncestorNode.ImageIndex;
        AncestorBaseOpts:=AncestorOptions.GetBaseCompilerOptions;
        with AncestorOptions.ParsedOpts do
        begin
          AddChildNode(lisunitPath,
            CreateRelativeSearchPath(GetParsedValue(pcosUnitPath),CompilerOpts.BaseDirectory),
            icoUnitPath);
          AddChildNode(lisincludePath,
            CreateRelativeSearchPath(GetParsedValue(pcosIncludePath),CompilerOpts.BaseDirectory),
            icoIncludePath);
          AddChildNode(lisobjectPath,
            CreateRelativeSearchPath(GetParsedValue(pcosObjectPath),CompilerOpts.BaseDirectory),
            icoObjectPath);
          AddChildNode(lislibraryPath,
            CreateRelativeSearchPath(GetParsedValue(pcosLibraryPath),CompilerOpts.BaseDirectory),
            icoLibraryPath);
          AddChildNode(lislinkerOptions, GetParsedValue(pcosLinkerOptions),
            icoLinkerOptions);
          AddChildNode(liscustomOptions, GetParsedValue(pcosCustomOptions),
            icoCustomOptions);
        end;
        if (AncestorBaseOpts<>nil) and (Vars<>nil) then begin
          for j:=0 to AncestorBaseOpts.BuildMacros.Count-1 do
          begin
            Macro:=AncestorBaseOpts.BuildMacros[j];
            AddChildNode(Macro.Identifier,Vars.Values[Macro.Identifier],icoNone);
          end;
        end;
        AncestorNode.Expanded := True;
      end;
      OptionsList.Free;
    end else
    begin
      InhTreeView.Items.Add(nil, lisNoCompilerOptionsInherited);
    end;
    if SkippedPkgList<>nil then begin
      for i:=0 to SkippedPkgList.Count-1 do begin
        Pkg:=TLazPackage(SkippedPkgList[i]);
        AncestorNode := InhTreeView.Items.Add(nil, '');
        AncestorNode.Text := Format(lisExcludedAtRunTime, [Pkg.Name]);
        AncestorNode.ImageIndex := ImageIndexPackage;
        AncestorNode.SelectedIndex := AncestorNode.ImageIndex;
      end;
    end;

    // add matrix options
    for t:=low(TBuildMatrixGroupType) to high(TBuildMatrixGroupType) do
      AddMatrixGroup(t);

    InhTreeView.EndUpdate;
  finally
    SkippedPkgList.Free;
  end;
end;

procedure TShowCompilerOptionsDlg.UpdateExecuteBeforeAfter;
var
  PgIndex, ToolIndex: integer;

  procedure AddTool(CompOpts: TCompilationToolOptions; aCaption: string);
  const
    Space = 6;
  var
    ShowOpts: TShowCompToolOpts;
    Sheet: TTabSheet;
    Memo: TMemo;
    CheckBox: TCheckBox;
  begin
    if ToolIndex>=FToolOptions.Count then begin
      ShowOpts:=TShowCompToolOpts.Create;
      ShowOpts.CompOpts:=CompOpts;
      FToolOptions.Add(ShowOpts);
    end else
      ShowOpts:=TShowCompToolOpts(FToolOptions[ToolIndex]);
    inc(ToolIndex);

    if CompOpts.CompileReasons=[] then begin
      // this tool is never called -> skip
      if ShowOpts.Sheet<>nil then begin
        ShowOpts.Sheet.Free;
        ShowOpts.Sheet:=nil;
      end;
      exit;
    end;

    if ShowOpts.Sheet=nil then
    begin
      Sheet:=PageControl1.AddTabSheet;
      ShowOpts.Sheet:=Sheet;
      Sheet.Name:='TabSheet_Tool'+IntToStr(ToolIndex);
    end else
      Sheet:=ShowOpts.Sheet;
    inc(PgIndex);
    Sheet.Caption:=aCaption;

    if ShowOpts.Memo=nil then begin
      Memo:=TMemo.Create(Sheet);
      ShowOpts.Memo:=Memo;
      Memo.Name:='Memo_Tool'+IntToStr(ToolIndex);
      Memo.Parent:=Sheet;
    end else
      Memo:=ShowOpts.Memo;

    if ShowOpts.MultiLineCheckBox=nil then begin
      CheckBox:=TCheckBox.Create(Sheet);
      ShowOpts.MultiLineCheckBox:=CheckBox;
      CheckBox.Name:='MulitlineCheckBox_Tool'+IntToStr(ToolIndex);
      CheckBox.Parent:=Sheet;
      CheckBox.Checked:=MultilineCheckBox.Checked;
    end else
      CheckBox:=ShowOpts.MultiLineCheckBox;

    CheckBox.Left:=Space;
    CheckBox.AnchorParallel(akBottom,Space,Sheet);
    CheckBox.Anchors:=[akLeft,akBottom];
    CheckBox.Caption:=MultilineCheckBox.Caption;
    CheckBox.Checked:=MultilineCheckBox.Checked;
    CheckBox.OnChange:=@MultilineCheckBoxChange;

    Memo.WordWrap:=true;
    Memo.Align:=alTop;
    Memo.AnchorToNeighbour(akBottom,Space,CheckBox);

    UpdateToolMemo(ShowOpts);
  end;

var
  OldPageIndex: integer;
begin
  OldPageIndex:=PageControl1.PageIndex;
  PgIndex:=2;
  ToolIndex:=0;
  DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TShowCompilerOptionsDlg.UpdateExecuteBeforeAfter'){$ENDIF};
  try
    AddTool(CompilerOpts.ExecuteBefore,'Execute Before');
    AddTool(CompilerOpts.ExecuteAfter,'Execute After');
    PageControl1.PageIndex:=OldPageIndex;
  finally
    EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TShowCompilerOptionsDlg.UpdateExecuteBeforeAfter'){$ENDIF};
  end;
end;

procedure TShowCompilerOptionsDlg.UpdateToolMemo(Opts: TShowCompToolOpts);
var
  Params: String;
begin
  Params:=Opts.CompOpts.Command;
  IDEMacros.SubstituteMacros(Params);
  FillMemo(Opts.Memo,Params);
end;

end.

