unit BreakPropertyDlg;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils,
  // LCL
  Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls, ButtonPanel, EditBtn, Spin,
  // LazUtils
  LazUTF8,
  // IdeIntf
  IdeIntfStrConsts, IDEHelpIntf, InputHistoryCopy,
  // DebuggerIntf
  DbgIntfDebuggerBase,
  // IdeConfig
  EnvironmentOpts, RecentListProcs,
  // IdeDebugger
  BreakPropertyDlgGroups, DebuggerDlg, Debugger, BaseDebugManager,
  IdeDebuggerStringConstants;

type

  { TBreakPropertyDlg }

  TBreakPropertyDlg = class(TDebuggerDlg)
    ButtonPanel: TButtonPanel;
    chkTakeSnap: TCheckBox;
    chkLogCallStack: TCheckBox;
    chkEnableGroups: TCheckBox;
    chkDisableGroups: TCheckBox;
    chkEvalExpression: TCheckBox;
    chkLogMessage: TCheckBox;
    chkActionBreak: TCheckBox;
    cmbGroup: TComboBox;
    edtCondition: TComboBox;
    edtEvalExpression: TEdit;
    edtLine: TSpinEdit;
    edtLogMessage: TEdit;
    edtEnableGroups: TEditButton;
    edtDisableGroups: TEditButton;
    edtAutocontinueMS: TEdit;
    edtCounter: TEdit;
    edtFilename: TEdit;
    gbActions: TGroupBox;
    Label1: TLabel;
    lblWatchKind: TLabel;
    lblWatchScope: TLabel;
    lblLogCallStackLimit: TLabel;
    lblMS: TLabel;
    lblFileName: TLabel;
    lblLine: TLabel;
    lblCondition: TLabel;
    lblHitCount: TLabel;
    lblGroup: TLabel;
    lblAutoContinue: TLabel;
    edtLogCallStack: TSpinEdit;
    rbWrite: TRadioButton;
    rbRead: TRadioButton;
    rbReadWrite: TRadioButton;
    rbGlobal: TRadioButton;
    rbLocal: TRadioButton;
    rgWatchKind: TPanel;
    rgWatchScope: TPanel;
    procedure btnHelpClick(Sender: TObject);
    procedure btnOKClick(Sender: TObject);
    procedure BreakPointRemove(const {%H-}ASender: TIDEBreakPoints;
      const ABreakpoint: TIDEBreakPoint);
    procedure BreakPointUpdate(const {%H-}ASender: TIDEBreakPoints;
      const {%H-}ABreakpoint: TIDEBreakPoint);
    procedure chkDisableGroupsChange(Sender: TObject);
    procedure chkEnableGroupsChange(Sender: TObject);
    procedure chkEvalExpressionChange(Sender: TObject);
    procedure chkLogCallStackChange(Sender: TObject);
    procedure chkLogMessageChange(Sender: TObject);
    procedure cmbGroupKeyPress(Sender: TObject; var Key: char);
    procedure edtDisableGroupsButtonClick(Sender: TObject);
    procedure edtEnableGroupsButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    FBreakpointsNotification : TIDEBreakPointsNotification;
    FBreakpoint: TIDEBreakPoint;
    FUpdatingInfo: Boolean;
  protected
    procedure DoEndUpdate; override;
    procedure UpdateInfo;
  public
    constructor Create(AOwner: TComponent; ABreakPoint: TIDEBreakPoint);overload;
    destructor Destroy; override;
  end;

implementation

{$R *.lfm}

{ TBreakPropertyDlg }

procedure TBreakPropertyDlg.BreakPointUpdate(
  const ASender: TIDEBreakPoints; const ABreakpoint: TIDEBreakPoint);
begin
  UpdateInfo;
end;

procedure TBreakPropertyDlg.chkDisableGroupsChange(Sender: TObject);
begin
  edtDisableGroups.Enabled := chkDisableGroups.Checked;
end;

procedure TBreakPropertyDlg.chkEnableGroupsChange(Sender: TObject);
begin
  edtEnableGroups.Enabled := chkEnableGroups.Checked;
end;

procedure TBreakPropertyDlg.chkEvalExpressionChange(Sender: TObject);
begin
  edtEvalExpression.Enabled := chkEvalExpression.Checked;
end;

procedure TBreakPropertyDlg.chkLogCallStackChange(Sender: TObject);
begin
  edtLogCallStack.Enabled := chkLogCallStack.Checked;
end;

procedure TBreakPropertyDlg.chkLogMessageChange(Sender: TObject);
begin
  edtLogMessage.Enabled := chkLogMessage.Checked;
end;

procedure TBreakPropertyDlg.cmbGroupKeyPress(Sender: TObject; var Key: char);
begin
  if Key = ';' then Key := #0;
end;

procedure TBreakPropertyDlg.edtDisableGroupsButtonClick(Sender: TObject);
var
  s: TCaption;
begin
  if FBreakpoint = nil then Exit;
  s := edtDisableGroups.Text;
  if ExecuteBreakPointGroupDlg(FBreakpoint, s, DebugBoss.BreakPointGroups, bgaDisable) = mrok
  then edtDisableGroups.Text := s;
end;

procedure TBreakPropertyDlg.edtEnableGroupsButtonClick(Sender: TObject);
var
  s: TCaption;
begin
  if FBreakpoint = nil then Exit;
  s := edtEnableGroups.Text;
  if ExecuteBreakPointGroupDlg(FBreakpoint, s, DebugBoss.BreakPointGroups, bgaEnable) = mrok
  then edtEnableGroups.Text := s;
end;

procedure TBreakPropertyDlg.FormCreate(Sender: TObject);
begin
  edtCondition.DropDownCount := EnvironmentOptions.DropDownCount;
  cmbGroup.DropDownCount := EnvironmentOptions.DropDownCount;
end;

procedure TBreakPropertyDlg.btnHelpClick(Sender: TObject);
begin
  LazarusHelp.ShowHelpForIDEControl(Self);
end;

procedure TBreakPropertyDlg.BreakPointRemove(
  const ASender: TIDEBreakPoints; const ABreakpoint: TIDEBreakPoint);
begin
  if ABreakpoint = FBreakpoint
  then ModalResult := mrCancel;
end;

procedure TBreakPropertyDlg.btnOKClick(Sender: TObject);
  function CheckGroupList(Alist: TStringList): Boolean;
  var
    i: Integer;
    r: TModalResult;
    s: String;
    NewGroup: TIDEBreakPointGroup;
  begin
    Result := True;
    s := '';
  for i := 0 to Alist.Count-1 do begin
      if Alist[i] = '' then continue;
      if DebugBoss.BreakPointGroups.GetGroupByName(Alist[i]) = nil then
        s := s + ', ' + Alist[i];
    end;
    if s = '' then exit;
    delete(s, 1, 2);
    r := MessageDlg(Format(dbgBreakPropertyGroupNotFound, [LineEnding, s]),
                    mtConfirmation, [mbYes, mbIgnore, mbCancel], 0);
    if r = mrCancel then exit(False);
    if r = mrYes then begin
      for i := 0 to Alist.Count-1 do begin
        if Alist[i] = '' then continue;
        if DebugBoss.BreakPointGroups.GetGroupByName(Alist[i]) = nil then begin
          NewGroup := TIDEBreakPointGroup(DebugBoss.BreakPointGroups.Add);
          NewGroup.Name := Alist[i];
        end;
      end;
    end;
  end;
var
  Actions: TIDEBreakPointActions;
  GroupName: String;
  NewGroup: TIDEBreakPointGroup;
  ws: TDBGWatchPointScope;
  wk: TDBGWatchPointKind;
  i: SizeInt;
  EnableGroupList, DisableGroupList: TStringListUTF8Fast;
begin
  if FBreakpoint = nil then Exit;

  EnableGroupList := TStringListUTF8Fast.Create;
  DisableGroupList := TStringListUTF8Fast.Create;

  try
    EnableGroupList.Delimiter := ';';
    DisableGroupList.Delimiter := ';';

    EnableGroupList.DelimitedText := edtEnableGroups.Text+';'+edtDisableGroups.Text;
    if not CheckGroupList(EnableGroupList) then begin
      ModalResult :=  mrNone;
      exit;
    end;

    EnableGroupList.DelimitedText := edtEnableGroups.Text;
    DisableGroupList.DelimitedText := edtDisableGroups.Text;

    FBreakpointsNotification.OnUpdate := nil;
    case FBreakpoint.Kind of
      bpkSource:
        begin
          // filename + line
          FBreakpoint.SetLocation(edtFilename.Text, edtLine.Value);
        end;
      bpkAddress:
        begin
          FBreakpoint.SetAddress(StrToQWordDef(edtFilename.Text, 0));
        end;
      bpkData:
        begin
          if rbGlobal.Checked
          then ws := wpsGlobal
          else ws := wpsLocal;
          wk := wpkWrite;
          if rbRead.Checked
          then wk := wpkRead;
          if rbReadWrite.Checked
          then wk := wpkReadWrite;
          FBreakpoint.SetWatch(edtFilename.Text, ws, wk);
        end;
    end;
    // expression
    FBreakpoint.Expression := edtCondition.Text;
    // hitcount
    FBreakpoint.BreakHitCount := StrToIntDef(edtCounter.Text, FBreakpoint.HitCount);
    //auto continue
    FBreakpoint.AutoContinueTime := StrToIntDef(edtAutocontinueMS.Text, FBreakpoint.AutoContinueTime);
    // group
    GroupName := cmbGroup.Text;
    NewGroup := DebugBoss.BreakPointGroups.GetGroupByName(GroupName);
    if not Assigned(NewGroup) and (GroupName <> '') then
    begin
      NewGroup := TIDEBreakPointGroup(DebugBoss.BreakPointGroups.Add);
      NewGroup.Name := GroupName;
    end;
    FBreakpoint.Group := NewGroup;
    // enable groups
    for i := 0 to DebugBoss.BreakPointGroups.Count-1 do begin
      NewGroup := DebugBoss.BreakPointGroups[i];
      if EnableGroupList.IndexOf(NewGroup.Name) >= 0
      then FBreakpoint.EnableGroupList.Add(NewGroup)
      else FBreakpoint.EnableGroupList.Remove(NewGroup);
    end;
    // disable groups
    for i := 0 to DebugBoss.BreakPointGroups.Count-1 do begin
      NewGroup := DebugBoss.BreakPointGroups[i];
      if DisableGroupList.IndexOf(NewGroup.Name) >= 0
      then FBreakpoint.DisableGroupList.Add(NewGroup)
      else FBreakpoint.DisableGroupList.Remove(NewGroup);
    end;
    // actions
    Actions := [];
    if chkActionBreak.Checked then Include(Actions, bpaStop);
    if chkDisableGroups.Checked then Include(Actions, bpaDisableGroup);
    if chkEnableGroups.Checked then Include(Actions, bpaEnableGroup);
    if chkEvalExpression.Checked then Include(Actions, bpaEValExpression);
    if chkLogMessage.Checked then Include(Actions, bpaLogMessage);
    if chkLogCallStack.Checked then Include(Actions, bpaLogCallStack);
    if chkTakeSnap.Checked then include(Actions, bpaTakeSnapshot);
    FBreakpoint.Actions := Actions;
    FBreakpoint.LogEvalExpression := edtEvalExpression.Text;
    FBreakpoint.LogMessage := edtLogMessage.Text;
    FBreakpoint.LogCallStackLimit := edtLogCallStack.Value;

    if edtCondition.Text <> '' then
      with InputHistories.HistoryLists.GetList('BreakPointExpression',
        True, rltCaseSensitive) do
      begin
        i := IndexOf(edtCondition.Text);
        if i <> -1 then Delete(i);
        Insert(0, edtCondition.Text);
      end;

  finally
    EnableGroupList.Free;
    DisableGroupList.Free;
  end;
end;

procedure TBreakPropertyDlg.DoEndUpdate;
begin
  inherited DoEndUpdate;
  UpdateInfo;
end;

procedure TBreakPropertyDlg.UpdateInfo;
var
  Actions: TIDEBreakPointActions;
  I: Integer;
  s: String;
begin
  FUpdatingInfo := True;
  if FBreakpoint = nil then Exit;
  case FBreakpoint.Kind of
    bpkSource:
      begin
        // filename
        edtFilename.Text := FBreakpoint.Source;
        // line
        if FBreakpoint.Line > 0
        then edtLine.Value := FBreakpoint.Line
        else edtLine.Value := 0;
      end;
    bpkAddress:
      begin
        edtFilename.Text := '$' + IntToHex(FBreakpoint.Address, 8); // todo: 8/16 depends on platform
      end;
    bpkData:
      begin
        edtFilename.Text := FBreakpoint.WatchData;
        rbGlobal.Checked := FBreakpoint.WatchScope = wpsGlobal;
        rbLocal.Checked := FBreakpoint.WatchScope = wpsLocal;
        rbWrite.Checked := FBreakpoint.WatchKind = wpkWrite;
        rbRead.Checked := FBreakpoint.WatchKind = wpkRead;
        rbReadWrite.Checked := FBreakpoint.WatchKind = wpkReadWrite;
      end;
  end;
  // expression
  edtCondition.Text := FBreakpoint.Expression;
  // hitcount
  edtCounter.Text := IntToStr(FBreakpoint.BreakHitCount);
  // auto continue
  edtAutocontinueMS.Text := IntToStr(FBreakpoint.AutoContinueTime);
  // group
  for I := 0 to DebugBoss.BreakPointGroups.Count - 1 do
    cmbGroup.Items.Add(DebugBoss.BreakPointGroups[I].Name);
  if FBreakpoint.Group = nil
  then cmbGroup.Text := ''
  else cmbGroup.Text := FBreakpoint.Group.Name;
  // enable groups
  s := '';
  for i := 0 to FBreakpoint.EnableGroupList.Count - 1 do begin
    if s <> '' then s := s + ';';
    s := s + FBreakpoint.EnableGroupList[i].Name;
  end;
  edtEnableGroups.Text := s;
  // disable groups
  s := '';
  for i := 0 to FBreakpoint.DisableGroupList.Count - 1 do begin
    if s <> '' then s := s + ';';
    s := s + FBreakpoint.DisableGroupList[i].Name;
  end;
  edtDisableGroups.Text := s;

  // actions
  Actions := FBreakpoint.Actions;
  chkActionBreak.Checked := bpaStop in Actions;
  chkDisableGroups.Checked := bpaDisableGroup in Actions;
  chkEnableGroups.Checked := bpaEnableGroup in Actions;
  chkEvalExpression.Checked := bpaEValExpression in Actions;
  chkLogMessage.Checked := bpaLogMessage in Actions;
  edtLogMessage.Text := FBreakpoint.LogMessage;
  edtEvalExpression.Text := FBreakpoint.LogEvalExpression;
  chkLogCallStack.Checked := bpaLogCallStack in Actions;
  edtLogCallStack.Value := FBreakpoint.LogCallStackLimit;
  chkTakeSnap.Checked := bpaTakeSnapshot in Actions;
  FUpdatingInfo := False;
end;

constructor TBreakPropertyDlg.Create(AOwner: TComponent; ABreakPoint: TIDEBreakPoint);
begin
  inherited Create(AOwner);

  Caption := lisBreakPointProperties;
  case ABreakPoint.Kind of
    bpkSource:
      begin
        lblFileName.Caption := lisPEFilename;
        lblLine.Caption := lisLine;
      end;
    bpkAddress:
      begin
        lblFileName.Caption := lisAddress;
        lblLine.Visible := False;
        edtLine.Visible := False;
        edtFilename.ReadOnly := False;
        edtFilename.Color := clDefault;
      end;
    bpkData:
      begin
        lblFileName.Caption := lisWatchData;
        lblLine.Visible := False;
        edtLine.Visible := False;
        edtFilename.ReadOnly := False;
        edtFilename.Color := clDefault;
        lblWatchKind.Visible := True;
        lblWatchScope.Visible := True;
        rgWatchKind.Visible := True;
        rgWatchScope.Visible := True;
        lblWatchScope.Caption := lisWatchScope;
        lblWatchKind.Caption := lisWatchKind;
        rbGlobal.Caption := lisWatchScopeGlobal;
        rbLocal.Caption := lisWatchScopeLocal;
        rbWrite.Caption := lisWatchKindWrite;
        rbRead.Caption := lisWatchKindRead;
        rbReadWrite.Caption := lisWatchKindReadWrite;
      end;
  end;
  lblCondition.Caption := lisCondition + ':';
  lblHitCount.Caption := lisHitCount + ':';
  lblAutoContinue.Caption := lisAutoContinueAfter;
  lblMS.Caption := lisMS;
  lblGroup.Caption := lisGroup + ':';
  gbActions.Caption := lisActions;
  chkActionBreak.Caption := lisBreak;
  chkEnableGroups.Caption := lisEnableGroups;
  chkDisableGroups.Caption := lisDisableGroups;
  chkEvalExpression.Caption := lisEvalExpression;
  chkLogMessage.Caption := lisLogMessage;
  chkLogCallStack.Caption := lisLogCallStack;
  lblLogCallStackLimit.Caption := lisLogCallStackLimit;
  chkTakeSnap.Caption := lisTakeSnapshot;
  edtCondition.Items.Assign(InputHistories.HistoryLists.GetList(
    'BreakPointExpression', True,rltCaseSensitive));

  FBreakpoint := ABreakPoint;
  FBreakpointsNotification := TIDEBreakPointsNotification.Create;
  FBreakpointsNotification.AddReference;
  FBreakpointsNotification.OnUpdate := @BreakPointUpdate;
  FBreakpointsNotification.OnRemove := @BreakPointRemove;
  UpdateInfo;

  ButtonPanel.OKButton.Caption:=lisBtnOk;
  ButtonPanel.HelpButton.Caption:=lisMenuHelp;
  ButtonPanel.CancelButton.Caption:=lisCancel;
end;

destructor TBreakPropertyDlg.Destroy;
begin
  FBreakpointsNotification.OnUpdate := nil;
  FBreakpointsNotification.OnRemove := nil;
  FBreakpointsNotification.ReleaseReference;
  FBreakpointsNotification := nil;
  inherited Destroy;
end;

end.

