unit folderdiff;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, ComCtrls, ToolWin, Grids, constants, hashmap, ImgList;

type
  PFolderRecord = ^TFolderRecord;
  TFolderRecord = record
    name: xString;
    bDir: Boolean;
    size: Integer;
    modified: TDatetime;
    diffres: Integer;
    diffcount: Integer;
    dir: xString;
  end;

  TFolderDiffForm = class(TForm)
    Panel1: TPanel;
    Panel2: TPanel;
    ToolBar1: TToolBar;
    StatusBar1: TStatusBar;
    Splitter1: TSplitter;
    StringGrid1: TStringGrid;
    StringGrid2: TStringGrid;
    Panel1Top: TPanel;
    Panel2Top: TPanel;
    ResetLayoutTB: TToolButton;
    ToolButton1: TToolButton;
    CompareDirsTB: TToolButton;
    SynchronizedScrollBarsTB: TToolButton;
    ToolButton2: TToolButton;
    DiffCompareDirsTB: TToolButton;
    HaltTB: TToolButton;
    OpenDir1TB: TToolButton;
    OpenDir2TB: TToolButton;
    ToolButton3: TToolButton;
    RecursiveFoldersTB: TToolButton;
    ImageList1: TImageList;
    procedure FormDeactivate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure RecursiveFoldersTBClick(Sender: TObject);
    procedure OpenDir2TBClick(Sender: TObject);
    procedure OpenDir1TBClick(Sender: TObject);
    procedure HaltTBClick(Sender: TObject);
    procedure StringGrid1TopLeftChanged(Sender: TObject);
    procedure Panel1TopDblClick(Sender: TObject);
    procedure StringGrid1DblClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure DiffCompareDirsTBClick(Sender: TObject);
    procedure StringGrid2MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure StringGrid2SelectCell(Sender: TObject; ACol, ARow: Integer;
      var CanSelect: Boolean);
    procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
      var CanSelect: Boolean);
    procedure SynchronizedScrollBarsTBClick(Sender: TObject);
    procedure CompareDirsTBClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
    procedure ResetLayoutTBClick(Sender: TObject);
  private
    procedure RefreshAllSGCells(l: Integer = 1; h: Integer = 2);
    procedure ClearFolderList(X: TStringList);
    procedure UpdateStatusBar(bBlank: Boolean = false);
    { Private declarations }
  public
    { Public declarations }
    FFolder1StringList: TStringList;
    FFolder2StringList: TStringList;
//    FFolder1FileNameSet: TStringIntHashMap;
//    FFolder2FileNameSet: TStringIntHashMap;
    SynchronizedScrollBars: Boolean;
    RecursiveFolders: Boolean;
    SynchroBool: Boolean;
    FCompared: Boolean;
    FHalt: Boolean;
    FDestroying: Boolean;
    FNumOfFiles1,FNumOfFiles2: Integer;
    FNumOfDirs1,FNumOfDirs2: Integer;
    FTotalSize1,FTotalSize2: Int64;//Integer;
    FDeleted,FInserted,FChanged,FMatched,FNotCompared: Integer;

    FBaseDir1: xString;
    FBaseDir2: xString;
    procedure ResetLayout;
    procedure LoadDirectory(path: xString; isDir1: Boolean; bRecursive: Boolean; var sg: TStringGrid; bSGRedraw: Boolean = false);
    procedure CompareFolders(bDiff: Boolean = false);
  end;

  TSGSortType = (celltypeString,celltypeInteger,celltypeDouble);

  procedure GridSort(SG: TStringGrid; ByColNumber,FromRow,ToRow: Integer; SortType: TSGSortType = celltypeString; SG2: TStringGrid = nil);  //!WARNING! SG2 must be of the same size as SG1 else crash
var
  FolderDiffForm: TFolderDiffForm;

implementation

{$R *.dfm}

uses langres,simple,crc32,LCS,tables,filediff,opendir, mainframe;


procedure GridSort(SG: TStringGrid; ByColNumber,FromRow,ToRow: Integer; SortType: TSGSortType = celltypeString; SG2: TStringGrid = nil);  //!WARNING! SG2 must be of the same size as SG1 else crash
var Temp : TStringList;

  function SortStr(const s: xString) : xString;
  var i,j: Integer;
      len: Integer;
      stemp: xString;
  begin
    result := '';
    case SortType of
       celltypeString: result := s;
      celltypeInteger: begin
                         len := Pos(' ',s);
                         if len > 0 then begin
                           SetLength(stemp,len);
                           j := 1;
                           for i := 1 to Pred(len) do begin
                             if s[i] in ['0'..'9'] then begin
                               stemp[j] := s[i];
                               j := j + 1;
                             end;
                           end;
                           SetLength(stemp,j-1);
//                           stemp := Copy(s,1,len-1);
//                           stemp  := StringReplace(stemp,' ', '',[rfReplaceAll, rfIgnoreCase]);
                           result := FormatFloat('000000000',StrToIntDef(stemp,0));
                         end
                         else if len = 0 then begin
                           result := FormatFloat('000000000',StrToIntDef(s,-1));
                         end;
                       end;
       celltypeDouble: try
                         result := FormatFloat('000000000.000000',StrToFloat(trim(s)));
                       except
                         result  := '0.00';
                       end;
    end;
  end;

  procedure QuickSort(Lo,Hi: Integer; CC: TStrings);
    procedure sort(l,r: Integer);
    var  i,j: Integer;
         s: xString;
    begin
      i := l; j := r;
      s := SortStr(CC[(l+r) shr 1]);
      repeat
        while SortStr(CC[i]) < s do inc(i);
        while s < SortStr(CC[j]) do dec(j);
        if i <= j then begin
          Temp.Assign(SG.Rows[j]);
          SG.Rows[j].Assign(SG.Rows[i]);
          SG.Rows[i].Assign(Temp);
          if SG2 <> nil then begin
            Temp.Assign(SG2.Rows[j]);
            SG2.Rows[j].Assign(SG2.Rows[i]);
            SG2.Rows[i].Assign(Temp);
          end;
          inc(i); dec(j);
        end;
      until i > j;
      if l < j then sort(l,j);
      if i < r then sort(i,r);
    end;
  begin
    Sort(Lo,Hi);
  end;

  procedure RevQuickSort(Lo,Hi: Integer; CC: TStrings);
    procedure sort(l,r: Integer);
    var  i,j: Integer;
         s: xString;
    begin
      i := l; j := r;
      s := SortStr(CC[(l+r) shr 1]);
      repeat
        while SortStr(CC[i]) > s do inc(i);
        while s > SortStr(CC[j]) do dec(j);
        if i <= j then begin
          Temp.Assign(SG.Rows[j]);
          SG.Rows[j].Assign(SG.Rows[i]);
          SG.Rows[i].Assign(Temp);
          if SG2 <> nil then begin
            Temp.Assign(SG2.Rows[j]);
            SG2.Rows[j].Assign(SG2.Rows[i]);
            SG2.Rows[i].Assign(Temp);
          end;
          inc(i); dec(j);
        end;
      until i > j;
      if l < j then sort(l,j);
      if i < r then sort(i,r);
    end;
  begin
    Sort(Lo,Hi);
  end;

begin
  if SG.Cols[ByColNumber].Count > 1 then begin
    Temp := TStringList.Create;
    if ByColNumber <> SG.Tag then begin
      QuickSort(FromRow,ToRow,SG.Cols[ByColNumber]);
      SG.Tag := ByColNumber;
    end
    else begin
      RevQuickSort(FromRow,ToRow,SG.Cols[ByColNumber]);
      SG.Tag := -1;
    end;
    Temp.Free;
  end;
end;

procedure TFolderDiffForm.ResetLayout;
var R: TRect;
begin
  if FDestroying then
    Exit;

  Panel2.Width := (ClientWidth-Splitter1.Width) shr 1;
  Panel1.Width := (ClientWidth-Splitter1.Width) shr 1;

(*  Panel1.SetBounds(0,Panel1.Top,(ClientWidth-Splitter1.Width) div 2,Panel1.Height);
  Panel2.SetBounds((ClientWidth-Splitter1.Width) div 2 + Splitter1.Width,Panel2.Top,(ClientWidth-Splitter1.Width) div 2,Panel2.Height);

  StringGrid1.Align := alClient;
  StringGrid2.Align := alClient;*)
end;

procedure TFolderDiffForm.ResetLayoutTBClick(Sender: TObject);
begin
  ResetLayout;
end;

procedure TFolderDiffForm.FormCreate(Sender: TObject);
begin
  FDestroying := false;
  FFolder1StringList := TStringList.Create;
  FFolder2StringList := TStringList.Create;

  FFolder1StringList.Duplicates := dupAccept;
  FFolder2StringList.Duplicates := dupAccept;

//  FFolder1FileNameSet := TStringIntHashMap.create;
//  FFolder2FileNameSet := TStringIntHashMap.create;
  RecursiveFoldersTB.down := FXRecursiveFolders;
  SynchronizedScrollBarsTB.down := FXSynchronizedScrollBars;


  SynchronizedScrollBars := SynchronizedScrollBarsTB.down;
  RecursiveFolders := RecursiveFoldersTB.down;
  SynchroBool := false;
  FCompared := false;

  with StringGrid1 do begin
    cells[0,0] := GetLangString(STR_NAME);
    cells[1,0] := GetLangString(STR_SIZE);
    cells[2,0] := GetLangString(STR_MODIFIED);
    cells[3,0] := GetLangString(STR_DIFF_COUNT);
    cells[4,0] := GetLangString(STR_REL_FOLDER);

    ColWidths[1] := Canvas.TextWidth(format('    %1.0n KB',[1024 * 99000/1]));
    ColWidths[2] := Canvas.TextWidth(formatDatetime(shortDateFormat + '        '+ ShortTimeFormat, 0));
    ColWidths[4] := Canvas.TextWidth('C:\test\test\test\test\test\test\test');

    Tag := -1;
  end;

  with StringGrid2 do begin
    cells[0,0] := GetLangString(STR_NAME);
    cells[1,0] := GetLangString(STR_SIZE);
    cells[2,0] := GetLangString(STR_MODIFIED);
    cells[3,0] := GetLangString(STR_DIFF_COUNT);
    cells[4,0] := GetLangString(STR_REL_FOLDER);

    ColWidths[1] := Canvas.TextWidth(format('    %1.0n KB',[1024 * 99000/1]));
    ColWidths[2] := Canvas.TextWidth(formatDatetime(shortDateFormat + '        '+ ShortTimeFormat, 0));
    ColWidths[4] := Canvas.TextWidth('C:\test\test\test\test\test\test\test');

    Tag := -1;
  end;

  LoadDirectory(FCurrentDirectory + 'test\' + 'x\',true,RecursiveFolders,StringGrid1,true);
  LoadDirectory(FCurrentDirectory + 'test\' + 'y\',false,RecursiveFolders,StringGrid2,true);
end;

function FileTime2DateTime(FileTime: TFileTime): TDateTime;
var LocalFileTime: TFileTime;
    SystemTime: TSystemTime;
begin
  FileTimeToLocalFileTime(FileTime,LocalFileTime);
  FileTimeToSystemTime(LocalFileTime,SystemTime);
  result := SystemTimeToDateTime(SystemTime);
end;

procedure TFolderDiffForm.LoadDirectory(path: xString; isDir1: Boolean; bRecursive: Boolean; var sg: TStringGrid; bSGRedraw: Boolean = false); //!TEXT!
var i,j: Integer;
    FolderStringList: TStringList;
    numoffiles,numofdirs: Integer;
    totalsize: Int64;

  procedure loaddir(const s: xString);
  var  res: integer;
       FolderRecord: PFolderRecord;
       sr: TSearchRec;
       k: Integer;
       str: xString;
  begin
//    SynchronizedScrollBarsTBClick(SynchronizedScrollBarsTB);

    if isDir1 then begin
      FolderStringList := FFolder1StringList;
      str := FBaseDir1 + appendSlash(s);
      panel1top.caption := path + ' (' + IntToStr(numoffiles) + ' files in ' + IntToStr(numofdirs) + ' subdirs - size: ' + IntToStr(totalsize) + ')';
    end
    else begin
      FolderStringList := FFolder2StringList;
      str := FBaseDir2 + appendSlash(s);
      panel2top.caption := path + ' (' + IntToStr(numoffiles) + ' files in ' + IntToStr(numofdirs) + ' subdirs - size: ' + IntToStr(totalsize) + ')';
    end;
    panel1top.Hint :=  panel1top.Caption;
    panel2top.Hint := panel2top.Hint;

    Application.ProcessMessages;

    if (FHalt) or (not DirectoryExists(str)) then exit;

//    SynchronizedScrollBars := false;
//    SynchronizedScrollBarsTB.Down := false;

//      ClearFolderSL(FolderStringList);
(*    for k := 0 to FolderStringList.Count - 1 do begin
      if FolderStringList.Objects[k] <> nil then
        dispose(PFolderRecord(FolderStringList.Objects[k]));
    end;
    FolderStringList.Clear;*)

    res := FindFirst(str + '*.*',faAnyFile, sr);
    while res = 0 do
    begin
      if (sr.Name[1] <> '.') or ((sr.Name <> '.') and (sr.Name <> '..')) then begin
        New(FolderRecord);
        FolderRecord.bDir := (sr.Attr and faDirectory = faDirectory);
        if FolderRecord.bDir then begin
          numofdirs := numofdirs + 1;
//          FolderStringList.AddObject('D'+uppercase(sr.Name),pointer(FolderRecord))
          dispose(FolderRecord);
          if bRecursive then begin
            str := s+AnsiUpperCase(sr.Name);
            loaddir(appendSlash(str));
          end;
        end
        else begin
          numoffiles := numoffiles + 1;
          totalsize := totalsize + sr.Size;
//          FolderStringList.AddObject('F'+uppercase(sr.Name),pointer(FolderRecord));
          if s = '' then begin
            str := ('B'+AnsiUpperCase(sr.Name));  //!WARNING! case insensitive filenames => Win32 only
          end
          else begin
            str := ('A'+s+AnsiUpperCase(sr.Name));
          end;
          i := FolderStringList.AddObject(str,pointer(FolderRecord));
(*          if isDir1 then begin
            FFolder1FileNameSet.add(str,i);
          end
          else begin
            FFolder2FileNameSet.add(str,i);
          end;*)
          FolderRecord.name := sr.Name;
          FolderRecord.size := sr.Size;
          FolderRecord.modified := FileTime2DateTime(sr.FindData.ftLastWriteTime);
          FolderRecord.diffres := ES_NOT_COMPARED;
          FolderRecord.diffcount := -1;
          FolderRecord.dir := s;
        end;


//        if FolderRecord.bDir then begin
//        end;
      end;
      res := FindNext(sr);
    end;
  end;
begin
  if path = '' then
    Exit;

  if isDir1 then begin
    FBaseDir1 := appendSlash(AnsiUpperCase(path));
  end
  else begin
    FBaseDir2 := appendSlash(AnsiUpperCase(path));
  end;
  //path is scrammbled sometimes from now on?

  if isDir1 then begin
    FolderStringList := FFolder1StringList;
  end
  else begin
    FolderStringList := FFolder2StringList;
  end;
  if FolderStringList <> nil then
    ClearFolderList(FolderStringList);
  numoffiles := 0;
  numofdirs := 0;
  totalsize := 0;
  StatusBar1.Panels[4].Text := 'Running...';
  FHalt := false;
  loaddir('');
  if FHalt then
    StatusBar1.Panels[4].Text := 'Halted!'
  else
    StatusBar1.Panels[4].Text := 'Ended.';
  if FolderStringList <> nil then
    FolderStringList.Sort;

  UpdateStatusBar(true);
  FCompared := false;
(*
  if sg <> nil then begin
    for i := 1 to Pred(sg.RowCount) do begin
      sg.Rows[i].Clear;
    end;
  end;

  sg.RowCount := FolderStringList.Count + sg.FixedRows;
  for i := 0 to Pred(FolderStringList.Count) do begin
    if (PFolderRecord(FolderStringList.Objects[i]).name <> '') then begin//and (not PFolderRecord(FolderStringList.Objects[i]).bDir) then begin
      sg.Cells[0,i+1] := PFolderRecord(FolderStringList.Objects[i]).name;
      sg.Cells[1,i+1] := format('%1.0n KB',[(PFolderRecord(FolderStringList.Objects[i]).size + 512)/1024]);
      sg.Cells[2,i+1] := formatDatetime(shortDateFormat + '  '+ ShortTimeFormat, PFolderRecord(FolderStringList.Objects[i]).modified);
      if PFolderRecord(FolderStringList.Objects[i]).diffcount <> -1 then
        sg.Cells[3,i+1] := IntToStr(PFolderRecord(FolderStringList.Objects[i]).diffcount)
      else
        sg.Cells[3,i+1] := '';
    end
  end;
//    DisplayFolderList(bDir);
*)

  if isDir1 then begin
    FNumOfFiles1 := numoffiles;
    FNumOfDirs1 := numofdirs;
    FTotalSize1 := totalsize;
    panel1top.caption := path + ' (' + IntToStr(numoffiles) + ' files in ' + IntToStr(numofdirs) + ' subdirs - size: ' + IntToStr(totalsize) + ')';
    if bSGRedraw then
      RefreshAllSGCells(1,1);
  end
  else begin
    FNumOfFiles2 := numoffiles;
    FNumOfDirs2 := numofdirs;
    FTotalSize2 := totalsize;
    panel2top.caption := path + ' (' + IntToStr(numoffiles) + ' files in ' + IntToStr(numofdirs) + ' subdirs - size: ' + IntToStr(totalsize) + ')';
    if bSGRedraw then
      RefreshAllSGCells(2,2);
  end;
  panel1top.Hint := panel1top.Caption;
  panel2top.Hint := panel2top.Caption;

(*    with MainForm do
    begin
      mnuCompare.enabled :=
      (fFolder1StringList.Count > 0) and (fFolder2StringList.Count > 0);
      tbCompare.enabled := mnuCompare.enabled;
      mnuCompareFiles.Enabled := false;
    end;*)
end;

procedure TFolderDiffForm.StringGrid1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var ACol,ARow: Integer;
    mpos: TPoint;
begin
  mpos := Mouse.CursorPos;
  X := StringGrid1.ScreenToClient(mpos).X;
  Y := StringGrid1.ScreenToClient(mpos).Y;

  StringGrid1.MouseToCell(X, Y, ACol, ARow);
//  Form4.Caption := IntToStr(ACol) + ' , ' + IntToStr(ARow);
(*  if ARow = 0 then begin
    if (ACol = 1) or (ACol = 3) then begin
      if FCompared then
        GridSort(StringGrid1,ACol,1,StringGrid1.RowCount-1,celltypeInteger,StringGrid2)
      else
        GridSort(StringGrid1,ACol,1,StringGrid1.RowCount-1,celltypeInteger);
    end
    else begin
      if FCompared then
        GridSort(StringGrid1,ACol,1,StringGrid1.RowCount-1,celltypeString,StringGrid2)
      else
        GridSort(StringGrid1,ACol,1,StringGrid1.RowCount-1,celltypeString);
    end;
  end;*)
end;

procedure TFolderDiffForm.StringGrid2MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var ACol,ARow: Integer;
    mpos: TPoint;
begin
  mpos := Mouse.CursorPos;
  X := StringGrid2.ScreenToClient(mpos).X;
  Y := StringGrid2.ScreenToClient(mpos).Y;

  StringGrid2.MouseToCell(X, Y, ACol, ARow);
(*  if ARow = 0 then begin
    if (ACol = 1) or (ACol = 3) then begin
      if FCompared then
        GridSort(StringGrid2,ACol,1,StringGrid2.RowCount-1,celltypeInteger,StringGrid1)
      else
        GridSort(StringGrid2,ACol,1,StringGrid2.RowCount-1,celltypeInteger);
    end
    else begin
      if FCompared then
        GridSort(StringGrid2,ACol,1,StringGrid2.RowCount-1,celltypeString,StringGrid1)
      else
        GridSort(StringGrid2,ACol,1,StringGrid2.RowCount-1,celltypeString);
    end;
  end;*)
end;

procedure TFolderDiffForm.ClearFolderList(X: TStringList);
var I: Integer;
begin
  for I := 0 to Pred(X.Count) do begin
    if PFolderRecord(X.Objects[I]) <> nil then
      dispose(PFolderRecord(X.Objects[I]));
  end;
  X.clear;
end;

procedure TFolderDiffForm.FormDestroy(Sender: TObject);
var I: Integer;
begin
(*  for I := 0 to Pred(FFolder1StringList.Count) do begin
    if FFolder1StringList.Objects[I] <> nil then
      dispose(PFolderRecord(FFolder1StringList.Objects[I]));
  end;
  for I := 0 to Pred(FFolder2StringList.Count) do begin
    if FFolder2StringList.Objects[I] <> nil then
      dispose(PFolderRecord(FFolder2StringList.Objects[I]));
  end;*)
  FDestroying := true;

  ClearFolderList(FFolder1StringList);
  ClearFolderList(FFolder2StringList);

  FFolder1StringList.Free;
  FFolder2StringList.Free;

//  FFolder1FileNameSet.Free;
//  FFolder2FileNameSet.Free;
end;

procedure TFolderDiffForm.RefreshAllSGCells(l: Integer = 1; h: Integer = 2);
var FolderStringList: TStringList;
    sg: TStringGrid;
    i,j: Integer;
begin
  for j := l to h do begin
    if j = 1 then begin
      FolderStringList := FFolder1StringList;
      sg := StringGrid1;
    end
    else begin
      FolderStringList := FFolder2StringList;
      sg := StringGrid2;
    end;

(*    if sg <> nil then begin
      for i := 1 to Pred(sg.RowCount) do begin
        sg.Rows[i].Clear;
      end;
    end;*)

    sg.RowCount := FolderStringList.Count + sg.FixedRows;
    for i := 0 to Pred(FolderStringList.Count) do begin
      if (PFolderRecord(FolderStringList.Objects[i]).name <> '') then begin//and (not PFolderRecord(FolderStringList.Objects[i]).bDir) then begin
        sg.Cells[0,i+1] := PFolderRecord(FolderStringList.Objects[i]).name;
        sg.Cells[1,i+1] := format('%1.0n kB',[(PFolderRecord(FolderStringList.Objects[i]).size + 512)/1024]);
//        sg.Cells[2,i+1] := formatDatetime(shortDateFormat + '  '+ ShortTimeFormat, PFolderRecord(FolderStringList.Objects[i]).modified);
        sg.Cells[2,i+1] := formatDatetime('yyyy-mm-dd' + '  '+ 'hh:mm:ss', PFolderRecord(FolderStringList.Objects[i]).modified);

        if PFolderRecord(FolderStringList.Objects[i]).diffcount <> -1 then
          sg.Cells[3,i+1] := IntToStr(PFolderRecord(FolderStringList.Objects[i]).diffcount)
        else
          sg.Cells[3,i+1] := '';
        sg.Cells[4,i+1] := PFolderRecord(FolderStringList.Objects[i]).dir;
//        sg.Cells[4,i+1] := IntToStr(PFolderRecord(FolderStringList.Objects[i]).diffres);
        sg.Objects[0,i+1] := TObject(PFolderRecord(FolderStringList.Objects[i]).diffres);
      end
      else begin
        sg.Cells[0,i+1] := '';
        sg.Cells[1,i+1] := '';
        sg.Cells[2,i+1] := '';
        sg.Cells[3,i+1] := '';
        sg.Cells[4,i+1] := PFolderRecord(FolderStringList.Objects[i]).dir;
//        sg.Cells[4,i+1] := IntToStr(PFolderRecord(FolderStringList.Objects[i]).diffres);
        sg.Objects[0,i+1] := TObject(PFolderRecord(FolderStringList.Objects[i]).diffres);
      end;
    end;
  end;
end;


function areFilesEqual(const file1, file2: xString): Boolean;
var m1,m2: TMemoryStream;
begin
  result := false;
  m1 := TMemoryStream.create;
  try
    m1.LoadFromFile(file1);
    m2 := TMemoryStream.create;
    try
      m2.LoadFromFile(file2);
      If m1.size = m2.size Then
        result := CompareMem(m1.memory, m2.memory, m1.size);
    finally
      m2.free;
    end;
  finally
    m1.free;
  end
end;

procedure TFolderDiffForm.UpdateStatusBar(bBlank: Boolean = false);  //!TEXT!
begin
  if bBlank then begin
    StatusBar1.Panels[0].Text := '';
    StatusBar1.Panels[1].Text := '';
    StatusBar1.Panels[2].Text := '';
    StatusBar1.Panels[3].Text := '';
  end
  else begin
    StatusBar1.Panels[0].Text := IntToStr(FChanged) + ' changed';
    StatusBar1.Panels[1].Text := IntToStr(FDeleted) + ' deleted';
    StatusBar1.Panels[2].Text := IntToStr(FInserted) + ' inserted';
    StatusBar1.Panels[3].Text := IntToStr(FMatched) + ' matched';
  end;
end;

function filepath_equal(key1, key2: Integer): Boolean;
var I: Integer;
begin
  result := false;
  if FolderDiffForm.FFolder1StringList[key1] <> FolderDiffForm.FFolder2StringList[key2] then
    Exit;
  result := true;
end;

procedure TFolderDiffForm.CompareFolders(bDiff: Boolean = false);  //!TEXT!
var i,j,k,l: Integer;
    b: Boolean;
    p,r: PFolderRecord;
    NewStringList1: TStringList;
    NewStringHashMap1: TStringIntHashMap;
    NewStringList2: TStringList;
    NewStringHashMap2: TStringIntHashMap;
    ft: _FILETIME;
    max1,max2: Integer;
    h,d: Integer;
    a1: intarray;
    a2: intarray;
    CC1,CC2: TStringList;
    len1,len2: Integer;
    chset: TCharSet;
    es: TEditScript;
    op,index,count: Integer;
begin
  if FCompared = true then begin
    LoadDirectory(FBaseDir1,true,RecursiveFolders,StringGrid1,false);
    LoadDirectory(FBaseDir2,false,RecursiveFolders,StringGrid2,false);
  end;
  FDeleted := 0;
  FInserted := 0;
  FChanged := 0;
  FMatched := 0;
  FNotCompared := 0;
  max1 := Pred(FFolder1StringList.count);
  max2 := Pred(FFolder2StringList.count);
//  h := max(max1,max2);
  StatusBar1.Panels[4].Text := 'Comparing...';

  SetLength(a1,max1+1);
  SetLength(a2,max2+1);

  for i := 0 to max1 do
    a1[i] := i;
  for i := 0 to max2 do
    a2[i] := i;

  d := calcDiff_comp_func(a1,max1+1,a2,max2+1,es,filepath_equal);
{$IFDEF DEBUG1}
  writeln(es.toString);
{$ENDIF}

  a1 := nil;
  a2 := nil;

  i := 0;
  j := 0;
  es.getNextReset;
  while (not FHalt) and es.getNext(op,index,count) do begin
    if op = ES_DELETE then begin
      for l := 1 to count do begin
        new(p);
        p.name := '';//PFolderRecord(FFolder1StringList.Objects[i]).name;
        p.size := 0;
        ft.dwLowDateTime := 0;
        ft.dwHighDateTime := 0;
        p.modified := FileTime2DateTime(ft);
        p.diffres := ES_DELETE;
        p.diffcount := -1;
        p.dir := PFolderRecord(FFolder1StringList.Objects[i]).dir;
        FFolder2StringList.insertObject(j,p.dir,TObject(p));
        PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_DELETE;
        i := i + 1;
        j := j + 1;
        FDeleted := FDeleted + 1;
        UpdateStatusBar;
        Application.ProcessMessages;
        if FHalt then
          break;
      end;
    end
    else if op = ES_INSERT then begin
      for l := 1 to count do begin
        new(p);
        p.name := '';
        p.size := 0;
        ft.dwLowDateTime := 0;
        ft.dwHighDateTime := 0;
        p.modified := FileTime2DateTime(ft);
        p.diffres := ES_INSERT;
        p.diffcount := -1;
        p.dir := PFolderRecord(FFolder2StringList.Objects[j]).dir;
        FFolder1StringList.insertObject(i,p.dir,TObject(p));
        PFolderRecord(FFolder2StringList.Objects[j]).diffres := ES_INSERT;
        i := i + 1;
        j := j + 1;
        FInserted := FInserted + 1;
        UpdateStatusBar;
        Application.ProcessMessages;
        if FHalt then
          break;
      end;
    end
    else if op = ES_MATCH then begin
      for l := 1 to count do begin
        if not bDiff then begin
          p := PFolderRecord(FFolder1StringList.Objects[i]);
          r := PFolderRecord(FFolder2StringList.Objects[j]);
          if areFilesEqual(FBaseDir1 + p.dir + p.name,FBaseDir2 + r.dir + r.name) then begin
            PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_MATCH;
            PFolderRecord(FFolder2StringList.Objects[j]).diffres := ES_MATCH;
            FMatched := FMatched + 1;
          end
          else begin
            PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_CHANGE;
            PFolderRecord(FFolder2StringList.Objects[j]).diffres := ES_CHANGE;
            FChanged := FChanged + 1;
          end;
        end
        else begin
          //calcDist
          CC1 := TStringList.create;
          CC2 := TStringList.create;
          chset := TCharSet.create('diffWS');
          chset.addString(DIFF_WS);

          p := PFolderRecord(FFolder1StringList.Objects[i]);
          r := PFolderRecord(FFolder2StringList.Objects[j]);
          CC1.LoadFromFile(FBaseDir1 + p.dir + p.name);
          CC2.LoadFromFile(FBaseDir2 + r.dir + p.name);

          len1 := CC1.count;
          len2 := CC2.count;
          SetLength(a1,len1);
          SetLength(a2,len2);
          for k := 0 to Pred(len1) do begin
            a1[k] := StringToCRC32(CC1[k],chset,DIFF_CASE_SENSITIVE);
          end;
          for k := 0 to Pred(len2) do begin
            a2[k] := StringToCRC32(CC2[k],chset,DIFF_CASE_SENSITIVE);
          end;

          k := calcDist_fast(a1,len1,a2,len2);

          if k > 0 then begin
            p.diffres := ES_CHANGE;
            r.diffres := ES_CHANGE;
            FChanged := FChanged + 1;
          end
          else begin
            //match, aj ked pri zhodnych hashoch pre nerovnake riadky mozu byt !=, takze pre istotu check
            //hmm to skor potom vymenit vetvy !WARNING! pomale!
            if areFilesEqual(FBaseDir1 + p.dir + p.name,FBaseDir2 + r.dir + p.name) then begin
              p.diffres := ES_MATCH;
              r.diffres := ES_MATCH;
              FMatched := FMatched + 1;
            end
            else begin
              p.diffres := ES_CHANGE;
              r.diffres := ES_CHANGE;
              FChanged := FChanged + 1;
            end;
  (*          p.diffres := ES_MATCH;
            r.diffres := ES_MATCH;*)
          end;
          p.diffcount := k;
          r.diffcount := k;

          a1 := nil;
          a2 := nil;

          chset.Free;
          CC1.Free;
          CC2.Free;
        end;
        i := i + 1;
        j := j + 1;
        UpdateStatusBar;
        Application.ProcessMessages;
        if FHalt then
          break;
      end;
    end;
//    UpdateStatusBar;
//    Application.ProcessMessages;
  end;
  es.Free;
(*  i := 0;
  j := 0;
  StatusBar1.Panels[4].Text := 'Comparing...';
  FHalt := false;
  FHalt := true;
  while (not FHalt) and ((i <= max1) or (j <= max2)) do begin
    if (j > max2) or ((i <= max1) and (FFolder1StringList[i] < FFolder2StringList[j])) then begin
      new(p);
      p.name := '';//PFolderRecord(FFolder1StringList.Objects[i]).name;
      p.size := 0;
      ft.dwLowDateTime := 0;
      ft.dwHighDateTime := 0;
      p.modified := FileTime2DateTime(ft);
      p.diffres := ES_DELETE;
      p.diffcount := -1;
      p.dir := PFolderRecord(FFolder1StringList.Objects[i]).dir;
      FFolder2StringList.insertObject(j,p.dir,TObject(p));
      PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_DELETE;
      i := i + 1;
      j := j + 1;
      max2 := max2 + 1;
      FDeleted := FDeleted + 1;
    end
    else if (i <= max1) and (j <= max2) and (FFolder1StringList[i] = FFolder2StringList[j]) then begin
      if not bDiff then begin
        p := PFolderRecord(FFolder1StringList.Objects[i]);
        r := PFolderRecord(FFolder2StringList.Objects[j]);
        if areFilesEqual(FBaseDir1 + p.dir + p.name,FBaseDir2 + r.dir + p.name) then begin
          PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_MATCH;
          PFolderRecord(FFolder2StringList.Objects[j]).diffres := ES_MATCH;
          FMatched := FMatched + 1;
        end
        else begin
          PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_CHANGE;
          PFolderRecord(FFolder2StringList.Objects[j]).diffres := ES_CHANGE;
          FChanged := FChanged + 1;
        end;
      end
      else begin
        //calcDist
        CC1 := TStringList.create;
        CC2 := TStringList.create;
        chset := TCharSet.create('diffWS');
        chset.addString(DIFF_WS);

        p := PFolderRecord(FFolder1StringList.Objects[i]);
        r := PFolderRecord(FFolder2StringList.Objects[j]);
        CC1.LoadFromFile(FBaseDir1 + p.dir + p.name);
        CC2.LoadFromFile(FBaseDir2 + r.dir + p.name);

        len1 := CC1.count;
        len2 := CC2.count;
        SetLength(a1,len1);
        SetLength(a2,len2);
        for k := 0 to Pred(len1) do begin
          a1[k] := StringToCRC32(CC1[k],chset,DIFF_CASE_SENSITIVE);
        end;
        for k := 0 to Pred(len2) do begin
          a2[k] := StringToCRC32(CC2[k],chset,DIFF_CASE_SENSITIVE);
        end;

        k := calcDist(a1,len1,a2,len2);

        if k > 0 then begin
          p.diffres := ES_CHANGE;
          r.diffres := ES_CHANGE;
          FChanged := FChanged + 1;
        end
        else begin
          //match, aj ked pri zhodnych hashoch pre nerovnake riadky mozu byt !=, takze pre istotu check
          //hmm to skor potom vymenit vetvy !WARNING! pomale!
          if areFilesEqual(FBaseDir1 + p.dir + p.name,FBaseDir2 + r.dir + p.name) then begin
            p.diffres := ES_MATCH;
            r.diffres := ES_MATCH;
            FMatched := FMatched + 1;
          end
          else begin
            p.diffres := ES_CHANGE;
            r.diffres := ES_CHANGE;
            FChanged := FChanged + 1;
          end;
//          p.diffres := ES_MATCH;
//          r.diffres := ES_MATCH;
        end;
        p.diffcount := k;
        r.diffcount := k;

        chset.Free;
        CC1.Free;
        CC2.Free;
      end;
      i := i + 1;
      j := j + 1;
    end
    else if (i > max1) or ((j <= max2) and (FFolder1StringList[i] > FFolder2StringList[j])) then begin
      new(p);
      p.name := '';
      p.size := 0;
      ft.dwLowDateTime := 0;
      ft.dwHighDateTime := 0;
      p.modified := FileTime2DateTime(ft);
      p.diffres := ES_INSERT;
      p.diffcount := -1;
      p.dir := PFolderRecord(FFolder2StringList.Objects[j]).dir;
      FFolder1StringList.insertObject(i,p.dir,TObject(p));
      PFolderRecord(FFolder2StringList.Objects[j]).diffres := ES_INSERT;
      i := i + 1;
      j := j + 1;
      max1 := max1 + 1;
      FInserted := FInserted + 1;
    end;
    UpdateStatusBar;
    Application.ProcessMessages;
  end;
*)
(*
  NewStringList2 := TStringList.create;
  NewStringHashMap2 := TStringIntHashMap.create;
  for i := 0 to Pred(FFolder1StringList.count) do begin
    index := FFolder2FileNameSet.findByKey(FFolder1StringList[i],b);
    if b then begin
      PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_MATCH;
      r := PFolderRecord(FFolder2StringList.Objects[index]);
      j := NewStringList2.AddObject(FFolder2StringList[index],TObject(r));
      r.diffres := ES_MATCH;
    end
    else begin
      PFolderRecord(FFolder1StringList.Objects[i]).diffres := ES_DELETE;
      r := PFolderRecord(FFolder1StringList.Objects[i]);
      new(p);
      p.name := '';//PFolderRecord(FFolder1StringList.Objects[i]).name;
      p.size := 0;
      ft.dwLowDateTime := 0;
      ft.dwHighDateTime := 0;
      p.modified := FileTime2DateTime(ft);
//      p.diffres := ES_NOT_COMPARED;
      p.diffcount := -1;
      p.dir := PFolderRecord(FFolder1StringList.Objects[i]).dir;
//      index := FFolder2StringList.InsertObject(j,'',TObject(p));
      j := NewStringList2.AddObject('',TObject(p));
      NewStringHashMap2.add(p.dir + appendSlash(p.name),j);
      p.diffres := ES_DELETE;
    end;
  end;

  j := 0;
  NewStringList1 := TStringList.create;
  NewStringHashMap1 := TStringIntHashMap.create;

  for i := 0 to Pred(FFolder2StringList.count) do begin
    if PFolderRecord(FFolder2StringList.Objects[i]).diffres = ES_NOT_COMPARED then begin
      index := FFolder1FileNameSet.findByKey(FFolder2StringList[i],b);
      if b then begin
        PFolderRecord(FFolder2StringList.Objects[i]).diffres := ES_MATCH;
        r := PFolderRecord(FFolder1StringList.Objects[index]);
        j := NewStringList1.AddObject(FFolder1StringList[index],TObject(r));
        r.diffres := ES_MATCH;
      end
      else begin
        PFolderRecord(FFolder2StringList.Objects[i]).diffres := ES_INSERT;
        r := PFolderRecord(FFolder2StringList.Objects[i]);
        new(p);
        p.name := '';//PFolderRecord(FFolder1StringList.Objects[i]).name;
        p.size := 0;
        ft.dwLowDateTime := 0;
        ft.dwHighDateTime := 0;
        p.modified := FileTime2DateTime(ft);
//        p.diffres := ES_NOT_COMPARED;
        p.diffcount := -1;
        p.dir := PFolderRecord(FFolder2StringList.Objects[i]).dir;
//        index := FFolder2StringList.InsertObject(j,'',TObject(p));
        j := NewStringList1.AddObject('',TObject(p));
        NewStringHashMap1.add(p.dir + appendSlash(p.name),j);
        p.diffres := ES_INSERT;
      end;
    end;
  end;

  FFolder1StringList.Free;
  FFolder1StringList := NewStringList1;
  FFolder1FileNameSet.Free;
  FFolder1FileNameSet := NewStringHashMap1;

  FFolder2StringList.Free;
  FFolder2StringList := NewStringList2;
  FFolder2FileNameSet.Free;
  FFolder2FileNameSet := NewStringHashMap2;
*)
  FCompared := true;
  if FHalt then
    StatusBar1.Panels[4].Text := 'Halted!'
  else
    StatusBar1.Panels[4].Text := 'Ended.';
  UpdateStatusBar;
  RefreshAllSGCells;
end;

procedure TFolderDiffForm.CompareDirsTBClick(Sender: TObject);
begin
  CompareFolders;
end;

procedure TFolderDiffForm.SynchronizedScrollBarsTBClick(Sender: TObject);
begin
  SynchronizedScrollBars := SynchronizedScrollBarsTB.down;
end;

procedure TFolderDiffForm.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
begin
  if SynchronizedScrollBars and (ARow > 0) then begin
    if not SynchroBool then begin
      SynchroBool := true;
      if ARow < StringGrid2.RowCount then
        StringGrid2.Row := ARow
      else
        StringGrid2.Row := Pred(StringGrid2.RowCount);
      if ACol < StringGrid2.ColCount then
        StringGrid2.Col := ACol
      else
        StringGrid2.Col := Pred(StringGrid2.ColCount);
    end;
    SynchroBool := false;
  end;
end;

procedure TFolderDiffForm.StringGrid2SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
begin
  if SynchronizedScrollBars and (ARow > 0) then begin
    if not SynchroBool then begin
      SynchroBool := true;
      if ARow < StringGrid1.RowCount then
        StringGrid1.Row := ARow
      else
        StringGrid1.Row := Pred(StringGrid1.RowCount);
      if ACol < StringGrid1.ColCount then
        StringGrid1.Col := ACol
      else
        StringGrid1.Col := Pred(StringGrid1.ColCount);
    end;
    SynchroBool := false;
  end;
end;

procedure TFolderDiffForm.DiffCompareDirsTBClick(Sender: TObject);
begin
  CompareFolders(true);
end;

procedure TFolderDiffForm.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var SG: TStringGrid;
    FolderList: TStringList;
    c: TColor;
    r,g,b: Integer;
    textWidth: Integer;
begin
  if not (Sender is TStringGrid) then
    Exit;

  SG := TStringGrid(Sender);
  if SG = StringGrid1 then
    FolderList := FFolder1StringList
  else
    FolderList := FFolder2StringList;

  SG.Canvas.Font.Color := clBlack;
  if ARow > FolderList.count then begin
    SG.Canvas.Brush.Color := COLOR_TEXTBACKGROUND;
    SG.Canvas.FillRect(Rect);
  end
  else if ARow = 0 then begin  //fixer rows
    SG.Canvas.Brush.Color := clBtnFace;
  end
  else begin  //normal rows
(*    if gdSelected in State then begin
      SG.Canvas.Font.Color := clWhite;
      SG.Canvas.Brush.Color := clBlack;
    end
    else begin*)
      SG.Canvas.Pen.Color := clBlack;
      case Integer(SG.Objects[0,ARow]) of
        ES_INSERT: c := COLOR_INSERT;
        ES_DELETE: c := COLOR_DELETE;
        ES_CHANGE: c := COLOR_CHANGE;
        ES_NOT_COMPARED: c := COLOR_NOT_COMPARED;
        ES_MATCH: c := COLOR_MATCH;
      else
        c := COLOR_MATCH;
      end;
      if gdSelected in State then begin
        c := BRIGHTER_COLOR(c);
      end;
      SG.Canvas.Brush.Color := c;
//    end;
  end;

  if (ACol = 1) or (ACol = 3) then begin
    textwidth := SG.Canvas.TextExtent(SG.Cells[ACol,ARow]).cx;
    SG.Canvas.TextRect(Rect, Rect.right - textwidth - 2,Rect.top + 2, SG.Cells[ACol,ARow])
  end
  else begin
      SG.Canvas.TextRect(Rect,Rect.left + 2,Rect.top+2,SG.Cells[ACol,ARow]);
  end;

  if (gdFixed in State) then begin
    SG.Canvas.Pen.Color := clBtnHighlight;
    SG.Canvas.moveto(rect.Left,rect.Bottom-1);
    SG.Canvas.LineTo(rect.Left,rect.Top);
    SG.Canvas.LineTo(rect.Right-1,rect.Top);
    SG.Canvas.pen.Color := clBtnShadow;
    SG.Canvas.LineTo(rect.Right-1,rect.Bottom-1);
    SG.Canvas.LineTo(rect.Left,rect.Bottom-1);
  end
  else if (gdSelected in State) then begin
    SG.Canvas.pen.Color := clBtnShadow;
    if ACol = 0 then
    begin
      SG.Canvas.MoveTo(rect.Left,rect.Bottom-1);
      SG.Canvas.LineTo(rect.Left,rect.Top);
    end
    else begin
      SG.Canvas.MoveTo(rect.Left,rect.Top);
    end;
    SG.Canvas.LineTo(rect.Right,rect.Top);

    if ACol = 2 then
    begin
      SG.Canvas.MoveTo(rect.Right-1,rect.Top);
      SG.Canvas.LineTo(rect.Right-1,rect.Bottom-1);
    end
    else begin
      SG.Canvas.MoveTo(rect.Right,rect.Bottom-1);
    end;
    SG.Canvas.LineTo(rect.Left-1,rect.Bottom-1);
  end;
//  if SG = StringGrid1 then begin
//  writeln('##',ACol,' , ',ARow);
//  Canvas.TextOut(0,0,StringGrid1.Cells[ACol,ARow]);
//  writeln(Rect.Left,' , ',Rect.Top);
//  StringGrid1.Canvas.FillRect(Rect);
end;

procedure TFolderDiffForm.FormResize(Sender: TObject);
begin
  ResetLayout;
end;

procedure TFolderDiffForm.StringGrid1DblClick(Sender: TObject);
var SG,SG2: TStringGrid;
    s: xString;
    s2: xString;
    sname,s2name: xString;
    mpos: TPoint;
    X,Y: Integer;
    ACol,ARow: Integer;
begin
  if not (Sender is TStringGrid) then
    Exit;
  SG := TStringGrid(Sender);

  mpos := Mouse.CursorPos;
  X := SG.ScreenToClient(mpos).X;
  Y := SG.ScreenToClient(mpos).Y;
  SG.MouseToCell(X, Y, ACol, ARow);

  if ARow = 0 then begin
    if SG = StringGrid1 then begin
      SG2 := StringGrid2;
    end
    else begin
      SG2 := StringGrid1;
    end;
    if (ACol = 1) or (ACol = 3) then begin
      if FCompared then
        GridSort(SG,ACol,1,SG.RowCount-1,celltypeInteger,SG2)
      else
        GridSort(SG,ACol,1,SG.RowCount-1,celltypeInteger);
    end
    else begin
      if FCompared then
        GridSort(SG,ACol,1,SG.RowCount-1,celltypeString,SG2)
      else
        GridSort(SG,ACol,1,SG.RowCount-1,celltypeString);
    end;
    Exit;
  end;

  if SynchronizedScrollBars then begin
    if SG = StringGrid1 then begin
      sname := SG.Cells[0,SG.Row];
      s := FBaseDir1 + SG.Cells[4,SG.Row] + sname;
      s2name := StringGrid2.Cells[0,StringGrid2.Row];
      s2 := FBaseDir2 + StringGrid2.Cells[4,StringGrid2.Row] + s2name;
    end
    else begin
      sname := SG.Cells[0,SG.Row];
      s := FBaseDir2 + SG.Cells[4,SG.Row] + sname;
      s2name := StringGrid1.Cells[0,StringGrid1.Row];
      s2 := FBaseDir1 + StringGrid1.Cells[4,StringGrid1.Row] + s2name;
    end;

    if sname <> '' then
      FileDiffForm.MyEdit1.LoadFromFile(s)
    else
      FileDiffForm.MyEdit1.LoadFromFile(sname);
    if s2name <> '' then
      FileDiffForm.MyEdit2.LoadFromFile(s2)
    else
      FileDiffForm.MyEdit2.LoadFromFile(s2name);
  end
  else begin
    if SG = StringGrid1 then begin
      sname := SG.Cells[0,SG.Row];
      s := FBaseDir1 + SG.Cells[4,SG.Row] + sname;
      if sname <> '' then
        FileDiffForm.MyEdit1.LoadFromFile(s)
      else
        FileDiffForm.MyEdit1.LoadFromFile(sname);
    end
    else begin
      sname := SG.Cells[0,SG.Row];
      s := FBaseDir2 + SG.Cells[4,SG.Row] + sname;
      if sname <> '' then
        FileDiffForm.MyEdit2.LoadFromFile(s)
      else
        FileDiffForm.MyEdit2.LoadFromFile(sname);
    end;

  end;

  MainForm.ShowFileDiffTBClick(Sender);
//  FileDiffForm.Show;
end;

procedure TFolderDiffForm.Panel1TopDblClick(Sender: TObject);
var I: Integer;
    isDir1: Boolean;
    SG: TStringGrid;
begin
  if Sender is TPanel then begin
    if Sender = Panel1Top then begin
      isDir1 := true;
      SG := StringGrid1;
    end
    else begin
      isDir1 := false;
      SG := StringGrid2;
    end;
    I := OpenDirForm.ShowModal;
    if I = mrOK then
      LoadDirectory(OpenDirForm.DirectoryListBox1.Directory,isDir1,RecursiveFolders,SG,true);
  end;
end;

procedure TFolderDiffForm.StringGrid1TopLeftChanged(Sender: TObject);
var SG: TStringGrid;
begin
  if SynchronizedScrollBars then begin
    if Sender is TStringGrid then begin
      SG := TStringGrid(Sender);
      if SG = StringGrid1 then
        StringGrid2.TopRow := min(SG.TopRow,StringGrid2.RowCount - 1)
      else
        StringGrid1.TopRow := min(SG.TopRow,StringGrid1.RowCount - 1);
    end;
  end;
end;

procedure TFolderDiffForm.HaltTBClick(Sender: TObject);
begin
  FHalt := true;
end;

procedure TFolderDiffForm.OpenDir1TBClick(Sender: TObject);
begin
  Panel1TopDblClick(Panel1Top);
end;

procedure TFolderDiffForm.OpenDir2TBClick(Sender: TObject);
begin
  Panel1TopDblClick(Panel2Top);
end;

procedure TFolderDiffForm.RecursiveFoldersTBClick(Sender: TObject);
begin
  RecursiveFolders := RecursiveFoldersTB.down;
end;

procedure TFolderDiffForm.FormActivate(Sender: TObject);
begin
  MainForm.FolderDiff1.Visible := true;
end;

procedure TFolderDiffForm.FormDeactivate(Sender: TObject);
begin
  MainForm.FolderDiff1.Visible := false;
end;

end.
