unit uConfirmations;
{$INCLUDE 'DirSync3.inc'}

interface

uses
  SysUtils, Windows, Classes,
  {$IFDEF FAR3} Plugin3, {$ELSE} PluginW, {$ENDIF} FarKeysW, FarColor,
  uSystem, uFAR, uLog,
  uDirSyncConsts, uMessages, uOptions, uSupportFunctions, uCompatibility;

type
  TConfirmationDialogButton = (ocbYes, ocbYesToAll, ocbNo, ocbNoToAll, ocbCancel);
  TWhatToDoWithDifferentFilesDialogButton = (dfbOverwriteLeft, dfbAlwaysOverwriteLeft, dfbOverwriteRight, dfbAlwaysOverwriteRight, dfbSkip, dfbAlwaysSkip, dfbCancel);
  TControlInputInfo = {$IFDEF FAR3_UP} TInputRecord {$ELSE} LONG_PTR {$ENDIF} ;

  TSyncConfirmationDialog = class(TFarDialog)
  private
    fRememberDirectory: string;
    fRememberDirectoryBuffer: TFarCharArray;
    fRememberActive: boolean;
    fGlobalRemember: boolean;
    fOnCompare: TNotifyEvent;
    function GetRememberDirectory: string;
    procedure SetRememberDirectory(const Value: string);
  private
    fCheckRemember: integer;
    fEditRememberDirectory: integer;
    fButtonCompare: integer;
    fButtonCancel: integer;
    fLastFocusedControl: integer;
    fButtons: array of integer;
  protected
    function DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean; override;
    procedure HandleGotFocus(Control: integer); virtual;
    function HandleBtnClick(Control, State: integer): boolean; virtual;
    function HandleControlInput(Control: integer; const Input: TControlInputInfo): boolean; virtual;
    function Execute(out Button: integer): boolean;
    procedure GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer); virtual; abstract;
    function GetFileInfo(MessageID: TMessages; const FileName: string; Width: integer): string;
    function GetHeight: integer;
    function GetContentsHeight: integer; virtual;
    function GetWidth: integer;
    procedure BuildDialog;
    procedure BuildDialogContents(Y: integer); virtual;
    property RememberDirectory: string read GetRememberDirectory write SetRememberDirectory;
    property GlobalRemember: boolean read fGlobalRemember write fGlobalRemember;
    property RememberActive: boolean read fRememberActive;
    property OnCompare: TNotifyEvent read fOnCompare write fOnCompare;
  public
    procedure ClearDialog; override;
  end;

  TConfirmationDialog = class(TSyncConfirmationDialog)
  public
    function Execute: TConfirmationDialogButton;
    property RememberDirectory;
    property GlobalRemember;
  end;

  TOverwriteConfirmationDialog = class(TConfirmationDialog)
  private
    fSourceFileName: string;
    fDestinationFileName: string;
    fFromRightToLeft: boolean;
    fReadOnlyDestination: boolean;
  private
    fTruncatedDestinationFileName: string;
    fSourceFileInfo: string;
    fDestinationFileInfo: string;
  protected
    procedure GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer); override;
    function GetContentsHeight: integer; override;
    procedure BuildDialogContents(Y: integer); override;
  public
    constructor Create;
    property FromRightToLeft: boolean read fFromRightToLeft write fFromRightToLeft;
    property ReadOnlyDestination: boolean read fReadOnlyDestination write fReadOnlyDestination;
    property SourceFileName: string read fSourceFileName write fSourceFileName;
    property DestinationFileName: string read fDestinationFileName write fDestinationFileName;
    property OnCompare;
  end;

  TDeleteConfirmationDialog = class(TConfirmationDialog)
  private
    fFileName: string;
    fFileOnLeft: boolean;
    fReadOnlyFile: boolean;
    fIsDirectory: boolean;
  private
    fTruncatedFileName: string;
    fFileInfo: string;
  protected
    procedure GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer); override;
    function GetContentsHeight: integer; override;
    procedure BuildDialogContents(Y: integer); override;
  public
    constructor Create;
    property FileOnLeft: boolean read fFileOnLeft write fFileOnLeft;
    property ReadOnlyFile: boolean read fReadOnlyFile write fReadOnlyFile;
    property FileName: string read fFileName write fFileName;
    property IsDirectory: boolean read fIsDirectory write fIsDirectory;
  end;

  TRetrySkipCancelDialog = class(TConfirmationDialog)
  private
    fMessage1: TMessages;
    fMessage2: TMessages;
    fFileName1: string;
    fFileName2: string;
  private
    fTruncatedFileName1: string;
    fTruncatedFileName2: string;
  protected
    procedure GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer); override;
    function GetContentsHeight: integer; override;
    procedure BuildDialogContents(Y: integer); override;
  public
    constructor Create;
    property Message1: TMessages read fMessage1 write fMessage1;
    property Message2: TMessages read fMessage2 write fMessage2;
    property FileName1: string read fFileName1 write fFileName1;
    property FileName2: string read fFileName2 write fFileName2;
  end;

  TWhatToDoWithDifferentFilesDialog = class(TSyncConfirmationDialog)
  private
    fLeftFileName: string;
    fRightFileName: string;
  private
    fTruncatedLeftFileName: string;
    fTruncatedRightFileName: string;
  protected
    procedure GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer); override;
    function GetContentsHeight: integer; override;
    procedure BuildDialogContents(Y: integer); override;
  public
    constructor Create;
    function Execute: TWhatToDoWithDifferentFilesDialogButton;
    property LeftFileName: string read fLeftFileName write fLeftFileName;
    property RightFileName: string read fRightFileName write fRightFileName;
    property RememberDirectory;
    property GlobalRemember;
    property OnCompare;
  end;

implementation

{ TBaseDialog }

procedure TSyncConfirmationDialog.BuildDialog;
var
  Title: PFarChar;
  Buttons: PFarCharArray;
  i, DefaultButton, ButtonsTop: integer;
begin
  ClearDialog;
  // Get information to build the dialog
  Title := nil;
  SetLength(Buttons, 0);
  DefaultButton := 0;
  GetDialogData(Title, Buttons, DefaultButton);
  SetLength(fButtons, Length(Buttons));
  ButtonsTop := GetHeight-4;
  if not GlobalRemember then
    Dec(ButtonsTop);
  // Title of the dialog
                     AddDoubleBox(  3,  1,            GetWidth+2, GetHeight, GetMsg(MTitle),             0);
                     AddLabel    (  5,  2,            GetWidth-4,            Title,                         DIF_CENTERTEXT);
  // Contents of the dialog
  BuildDialogContents            (      3);
  // The buttons part
                     AddLabel    (  4,  ButtonsTop,   GetWidth-2,            nil,                           DIF_SEPARATOR);
  fCheckRemember :=  AddCheckBox (  5,  ButtonsTop+1,                        GetMsg(MRememberChoiceForDir), False, 0);
  if not GlobalRemember then begin
    fEditRememberDirectory := AddEdit(9,ButtonsTop+2, GetWidth-9,            RememberDirectory,             fRememberDirectoryBuffer, nil, nil, 32768, DIF_DISABLE);
    Inc(ButtonsTop);
  end;
                     AddLabel    (  4,  ButtonsTop+2, GetWidth-2,            nil,                           DIF_SEPARATOR);
  for i := 0 to Pred(Length(Buttons)) do begin
    fButtons[i] :=   AddButton   (  0,  ButtonsTop+3,                        Buttons[i],                    DIF_CENTERGROUP);
    if i = DefaultButton then begin
      {$IFDEF FAR3_UP}
      Items[fButtons[i]].Flags := Items[fButtons[i]].Flags or DIF_DEFAULTBUTTON;
      {$ELSE}
      Items[fButtons[i]].DefaultButton := 1;
      {$ENDIF}
    end;
  end;
  if Assigned(OnCompare) then
    fButtonCompare :=  AddButton (  0,  ButtonsTop+3,                        GetMsg(MCompare),              DIF_CENTERGROUP or DIF_BTNNOCLOSE);
  fButtonCancel :=   AddButton   (  0,  ButtonsTop+3,                        GetMsg(MCancel),               DIF_CENTERGROUP);
end;

procedure TSyncConfirmationDialog.BuildDialogContents(Y: integer);
begin
end;

procedure TSyncConfirmationDialog.ClearDialog;
begin
  inherited;
  fCheckRemember := -1;
  fEditRememberDirectory := -1;
  fButtonCompare := -1;
  fButtonCancel := -1;
  fLastFocusedControl := -1;
  SetLength(fRememberDirectoryBuffer, 0);
  SetLength(fButtons, 0);
end;

function TSyncConfirmationDialog.DialogProc(Msg, Param1, Param2: TIntPtr; var DlgProcResult: TIntPtr): boolean;
begin
  Result := False;
  case Msg of
    DN_GOTFOCUS:
      HandleGotFocus(Param1);
    DN_BTNCLICK:
      if HandleBtnClick(Param1, Param2) then begin
        DlgProcResult := 1;
        Result := True;
      end;
    {$IFDEF FAR3_UP} DN_CONTROLINPUT {$ELSE} DN_KEY {$ENDIF} :
      if HandleControlInput(Param1, {$IFDEF FAR3_UP} PInputRecord(Param2)^ {$ELSE} Param2 {$ENDIF} ) then begin
        DlgProcResult := 1;
        Result := True;
      end;
  end;
  if not Result then
    Result := inherited DialogProc(Msg, Param1, Param2, DlgProcResult);
end;

function TSyncConfirmationDialog.Execute(out Button: integer): boolean;
var
  i, ButtonID: integer;
begin
  Result := False;
  fRememberActive := False;
  BuildDialog;
  if Build(-1, -1, GetWidth+6, GetHeight+2, FDLG_WARNING, 'Overwrite') then begin
    if inherited Execute(ButtonID) then begin
      fRememberActive := ItemChecked[fCheckRemember];
      if GlobalRemember then
        fRememberDirectory := ''
      else
        fRememberDirectory := ExcludeTrailingPathDelimiter(ItemText[fEditRememberDirectory]);
      for i := 0 to Pred(Length(fButtons)) do
        if fButtons[i] = ButtonID then begin
          Button := i;
          Result := True;
          Break;
        end;
    end;
  end;
end;

function TSyncConfirmationDialog.GetContentsHeight: integer;
begin
  Result := 0;
end;

function TSyncConfirmationDialog.GetFileInfo(MessageID: TMessages; const FileName: string; Width: integer): string;
var
  Attr: DWORD;
  Size: int64;
  CTime, MTime, ATime: TDateTime;
  sSize, sDate: string;
begin
  //Log('GetFileInfo for %s', [FileName]);
  Attr := GetFileAttributes(PChar(FileName));
  //Log('- Attributes = 0x%08.8x', [Attr]);
  if (Attr <> INVALID_FILE_ATTRIBUTES) and ((Attr and FILE_ATTRIBUTE_DIRECTORY) <> 0) then
    sSize := string(GetMsg(MDirectory))
  else begin
    Size := GetFileSize(FileName);
    //Log('- Size = %d', [Size]);
    if Size >= 1000000000000 then
      sSize := FormatCurr('###,###,##0" MiB"', Size div $100000)
    else if Size >= 0 then
      sSize := FormatCurr('###,###,###,##0', Size)
    else
      sSize := '?';
  end;
  if GetFileDateTime(FileName, CTime, MTime, ATime) then
    sDate := FormatDateTime('dd.mm.yyyy hh:nn:ss', MTime)
  else
    sDate := '?';
  Result := Format('%-*.*s   %15.15s %21.21s', [Width-40, Width-40, string(GetMsg(MessageID)), sSize, sDate]);
end;

function TSyncConfirmationDialog.GetHeight: integer;
begin
  Result := 8 + GetContentsHeight;
  if GlobalRemember then
    Dec(Result);
end;

function TSyncConfirmationDialog.GetRememberDirectory: string;
begin
  if GlobalRemember then
    Result := ''
  else
    Result := fRememberDirectory;
end;

function TSyncConfirmationDialog.GetWidth: integer;
begin
  Result := 70;
end;

function TSyncConfirmationDialog.HandleBtnClick(Control, State: integer): boolean;
begin
  Result := False;
  if (fCheckRemember >= 0) and (Control = fCheckRemember) and (fEditRememberDirectory >= 0) then begin
    ItemEnabled[fEditRememberDirectory] := State <> 0;
  end
  else if (fButtonCompare >= 0) and (Control = fButtonCompare) then begin
    if Assigned(OnCompare) then
      OnCompare(Self);
    if fLastFocusedControl >= 0 then
      SendDlgMessage(DM_SETFOCUS, fLastFocusedControl, nil)
    else
      SendDlgMessage(DM_SETFOCUS, fCheckRemember, nil);
    Result := True;
  end;
end;

function TSyncConfirmationDialog.HandleControlInput(Control: integer; const Input: TControlInputInfo): boolean;
var
  Key, ShiftFlags: DWORD;
begin
  Result := False;
  if (fCheckRemember >= 0) and (Control = fCheckRemember) and (fEditRememberDirectory >= 0) and (ItemEnabled[fEditRememberDirectory]) then begin
    {$IFDEF FAR3_UP}
    if Input.EventType = KEY_EVENT then
      if Input.Event.KeyEvent.bKeyDown then begin
        Key := Input.Event.KeyEvent.wVirtualKeyCode;
        ShiftFlags := Input.Event.KeyEvent.dwControlKeyState and (LEFT_ALT_PRESSED or LEFT_CTRL_PRESSED or RIGHT_ALT_PRESSED or RIGHT_CTRL_PRESSED or SHIFT_PRESSED);
    {$ELSE}
        ShiftFlags := Input and (KEY_ALT or KEY_CTRL or KEY_RALT or KEY_RCTRL or KEY_SHIFT);
        Key := Input and (not KEY_CTRLMASK);
    {$ENDIF}
        if (ShiftFlags = 0) then begin
          if Key = VK_BACK then begin
            ItemText[fEditRememberDirectory] := RemoveLastPathComponent(ItemText[fEditRememberDirectory]);
            Result := True;
          end
          else if Key = VK_DELETE then begin
            ItemText[fEditRememberDirectory] := '';
            Result := True;
          end
          else if Key = VK_F5 then begin
            ItemText[fEditRememberDirectory] := RememberDirectory;
            Result := True;
          end
        end;
    {$IFDEF FAR3_UP}
      end;
    {$ENDIF}
  end;
end;

procedure TSyncConfirmationDialog.HandleGotFocus(Control: integer);
begin
  if (Control <> fButtonCompare) then
    fLastFocusedControl := Control;
end;

procedure TSyncConfirmationDialog.SetRememberDirectory(const Value: string);
begin
  fRememberDirectory := ExcludeTrailingPathDelimiter(Value);
end;

{ TConfirmationDialog }

function TConfirmationDialog.Execute: TConfirmationDialogButton;
var
  Button: integer;
  Remember: boolean;
begin
  Result := ocbCancel;
  if inherited Execute(Button) then begin
    Remember := Self.RememberActive;
    //Log('Execute -> Button = %d, Remember = %d', [Button, Integer(Remember)]);
    if Button = 0 then
      if Remember then
        Result := ocbYesToAll
      else
        Result := ocbYes
    else if Button = 1 then
      if Remember then
        Result := ocbNoToAll
      else
        Result := ocbNo;
  end;
end;

{ TOverwriteConfirmationDialog }

procedure TOverwriteConfirmationDialog.BuildDialogContents(Y: integer);
begin
  inherited;
  //Log('TOverwriteConfirmationDialog.BuildDialogContents - Source="%s", Destination="%s"', [SourceFileName, DestinationFileName]);
  fTruncatedDestinationFileName := TFarUtils.TruncateString(RemoveNTSchematicsFromFileName(DestinationFileName), GetWidth-4, ' ');
  fSourceFileInfo := GetFileInfo(MNew, SourceFileName, GetWidth-4);
  fDestinationFileInfo := GetFileInfo(MExisting, DestinationFileName, GetWidth-4);
  AddLabel    (  5,  Y,   GetWidth-4,     PChar(fTruncatedDestinationFileName), DIF_SHOWAMPERSAND);
  AddLabel    (  4,  Y+1, GetWidth-2,     nil,                                  DIF_SEPARATOR);
  AddLabel    (  5,  Y+2, GetWidth-4,     PChar(fSourceFileInfo),               DIF_SHOWAMPERSAND);
  AddLabel    (  5,  Y+3, GetWidth-4,     PChar(fDestinationFileInfo),          DIF_SHOWAMPERSAND);
end;

constructor TOverwriteConfirmationDialog.Create;
begin
  inherited Create( {$IFDEF FAR3_UP} PLUGIN_GUID, DIALOG_OVERWRITE_GUID {$ELSE} FarApi.ModuleNumber {$ENDIF} );
  fFromRightToLeft := False;
  fReadOnlyDestination := False;
end;

function TOverwriteConfirmationDialog.GetContentsHeight: integer;
begin
  Result := inherited + 4;
end;

procedure TOverwriteConfirmationDialog.GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer);
begin
  if ReadOnlyDestination then
    if FromRightToLeft then
      Title := GetMsg(MLeftFileReadonly)
    else
      Title := GetMsg(MRightFileReadonly)
  else
    if FromRightToLeft then
      Title := GetMsg(MLeftFileAlreadyExists)
    else
      Title := GetMsg(MRightFileAlreadyExists);
  SetLength(Buttons, 2);
  Buttons[0] := GetMsg(MOverwrite);
  Buttons[1] := GetMsg(MSkip);
  DefaultButton := 0;
end;

{ TDeleteConfirmationDialog }

procedure TDeleteConfirmationDialog.BuildDialogContents(Y: integer);
begin
  inherited;
  //Log('TDeleteConfirmationDialog.BuildDialogContents');
  fTruncatedFileName := TFarUtils.TruncateString(RemoveNTSchematicsFromFileName(FileName), GetWidth-4, ' ');
  //Log(FN);
  if IsDirectory then
    fFileInfo := GetFileInfo(MEraseDir, FileName, GetWidth-4)
  else
    fFileInfo := GetFileInfo(MEraseFile, FileName, GetWidth-4);
  //Log(FileInfo);
  AddLabel    (  5,  Y,   GetWidth-4,     PChar(fTruncatedFileName), DIF_SHOWAMPERSAND);
  AddLabel    (  4,  Y+1, GetWidth-2,     nil,                       DIF_SEPARATOR);
  AddLabel    (  5,  Y+2, GetWidth-4,     PChar(fFileInfo),          DIF_SHOWAMPERSAND);
end;

constructor TDeleteConfirmationDialog.Create;
begin
  inherited Create( {$IFDEF FAR3_UP} PLUGIN_GUID, DIALOG_DELETE_GUID {$ELSE} FarApi.ModuleNumber {$ENDIF} );
  fFileOnLeft := False;
  fReadOnlyFile := False;
  fIsDirectory := False;
end;

function TDeleteConfirmationDialog.GetContentsHeight: integer;
begin
  Result := inherited + 3;
end;

procedure TDeleteConfirmationDialog.GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer);
begin
  if ReadOnlyFile then
    if FileOnLeft then
      if IsDirectory then
        Title := GetMsg(MLeftDirReadonly)
      else
        Title := GetMsg(MLeftFileReadonly)
    else
      if IsDirectory then
        Title := GetMsg(MRightDirReadonly)
      else
        Title := GetMsg(MRightFileReadonly)
  else
    if FileOnLeft then
      if IsDirectory then
        Title := GetMsg(MLeftDirErase)
      else
        Title := GetMsg(MLeftFileErase)
    else
      if IsDirectory then
        Title := GetMsg(MRightDirErase)
      else
        Title := GetMsg(MRightFileErase);
  SetLength(Buttons, 2);
  Buttons[0] := GetMsg(MErase);
  Buttons[1] := GetMsg(MSkip);
  DefaultButton := 0;
end;

{ TWhatToDoWithDifferentFilesDialog }

procedure TWhatToDoWithDifferentFilesDialog.BuildDialogContents(Y: integer);
begin
  inherited;
  fTruncatedLeftFileName := TFarUtils.TruncateString(RemoveNTSchematicsFromFileName(LeftFileName), GetWidth-4, ' ');
  fTruncatedRightFileName := TFarUtils.TruncateString(RemoveNTSchematicsFromFileName(RightFileName), GetWidth-4, ' ');
  AddLabel    (4, 3, GetWidth-2, nil, DIF_SEPARATOR);
  AddLabel    (5, 4, GetMsg(MLeftFileName), 0);
  AddLabel    (5, 5, PFarChar(fTruncatedLeftFileName), 0);
  AddLabel    (5, 6, GetMsg(MRightFileName), 0);
  AddLabel    (5, 7, PFarChar(fTruncatedRightFileName), 0);
end;

constructor TWhatToDoWithDifferentFilesDialog.Create;
begin
  inherited Create( {$IFDEF FAR3_UP} PLUGIN_GUID, DIALOG_DIFFERENTFILES_GUID {$ELSE} FarApi.ModuleNumber {$ENDIF} );
end;

function TWhatToDoWithDifferentFilesDialog.Execute: TWhatToDoWithDifferentFilesDialogButton;
var
  Button: integer;
  Remember: boolean;
begin
  Result := dfbCancel;
  if inherited Execute(Button) then begin
    Remember := Self.RememberActive;
    //Log('Execute -> Button = %d, Remember = %d', [Button, Integer(Remember)]);
    if Button = 0 then
      if Remember then
        Result := dfbAlwaysOverwriteLeft
      else
        Result := dfbOverwriteLeft
    else if Button = 1 then
      if Remember then
        Result := dfbAlwaysOverwriteRight
      else
        Result := dfbOverwriteRight
    else if Button = 2 then
      if Remember then
        Result := dfbAlwaysSkip
      else
        Result := dfbSkip;
  end;
end;

function TWhatToDoWithDifferentFilesDialog.GetContentsHeight: integer;
begin
  Result := 5;
end;

procedure TWhatToDoWithDifferentFilesDialog.GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer);
begin
  Title := GetMsg(MFilesAreDifferent);
  SetLength(Buttons, 3);
  Buttons[0] := GetMsg(MOverwriteLeft);
  Buttons[1] := GetMsg(MOverwriteRight);
  Buttons[2] := GetMsg(MSkip);
  DefaultButton := 2;
end;

{ TRetrySkipCancelDialog }

procedure TRetrySkipCancelDialog.BuildDialogContents(Y: integer);
begin
  inherited;
  fTruncatedFileName1 := TFarUtils.TruncateString(RemoveNTSchematicsFromFileName(FileName1), GetWidth-4, ' ');
  fTruncatedFileName2 := TFarUtils.TruncateString(RemoveNTSchematicsFromFileName(FileName2), GetWidth-4, ' ');
  AddLabel    (  5,  Y,   GetWidth-4,     PChar(fTruncatedFileName1), DIF_SHOWAMPERSAND);
  if FileName2 <> '' then begin
    AddLabel  (  5,  Y+1, GetWidth-4,     GetMsg(Message2),           0);
    AddLabel  (  5,  Y+2, GetWidth-4,     PChar(fTruncatedFileName2), DIF_SHOWAMPERSAND);
  end;
end;

constructor TRetrySkipCancelDialog.Create;
begin
  inherited Create( {$IFDEF FAR3_UP} PLUGIN_GUID, DIALOG_RETRYSKIPCANCEL_GUID {$ELSE} FarApi.ModuleNumber {$ENDIF} );
  fMessage1 := MNoLngStringDefined;
  fMessage2 := MNoLngStringDefined;
  fFileName1 := '';
  fFileName2 := '';
end;

function TRetrySkipCancelDialog.GetContentsHeight: integer;
begin
  Result := inherited + 1;
  if FileName2 <> '' then
    Inc(Result, 2);
end;

procedure TRetrySkipCancelDialog.GetDialogData(var Title: PFarChar; var Buttons: PFarCharArray; var DefaultButton: integer);
begin
  Title := GetMsg(Message1);
  SetLength(Buttons, 2);
  Buttons[0] := GetMsg(MRetry);
  Buttons[1] := GetMsg(MSkip);
  DefaultButton := 0;
end;

end.
