{ $Id$ } { /*************************************************************************** diffdialog.pp ------------- ***************************************************************************/ *************************************************************************** * * * 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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: The TDiffDialog is the dialog for showing the differences between two files. } unit DiffDialog; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Math, Forms, Controls, Buttons, StdCtrls, SynEdit, LResources, LazarusIDEStrConsts, EditorOptions, IDEOptionDefs, InputHistory, DiffPatch; type TOnGetDiffFile = procedure(TextID: integer; OnlySelection: boolean; var Source: string) of object; { TDiffFile } TDiffFile = class public Name: string; ID: integer; SelectionAvailable: boolean; constructor Create(const NewName: string; NewID: integer; NewSelectionAvailable: boolean); end; { TDiffFiles } TDiffFiles = class(TList) private function GetItems(Index: integer): TDiffFile; procedure SetItems(Index: integer; const AValue: TDiffFile); public procedure Clear; override; function Add(DiffFile: TDiffFile): integer; function IndexOfName(const Name: string): integer; public property Items[Index: integer]: TDiffFile read GetItems write SetItems; default; end; { TDiffDialog } TDiffDialog = class(TForm) // text 1 Text1GroupBox: TGroupBox; Text1Combobox: TComboBox; Text1OnlySelectionCheckBox: TCheckBox; // text 2 Text2GroupBox: TGroupBox; Text2Combobox: TComboBox; Text2OnlySelectionCheckBox: TCheckBox; // diff preview DiffGroupbox: TGroupBox; DiffSynEdit: TSynEdit; // options OptionsGroupBox: TGroupBox; IgnoreCaseCheckBox: TCheckBox; IgnoreEmptyLineChangesCheckBox: TCheckBox; IgnoreHeadingSpacesCheckBox: TCheckBox; IgnoreLineEndsCheckBox: TCheckBox; IgnoreSpaceCharAmountCheckBox: TCheckBox; IgnoreSpaceCharsCheckBox: TCheckBox; IgnoreTrailingSpacesCheckBox: TCheckBox; // buttons CloseButton: TButton; OpenInEditorButton: TButton; procedure CloseButtonClick(Sender: TObject); procedure DiffDialogResize(Sender: TObject); procedure OnChangeFlag(Sender: TObject); procedure OpenInEditorButtonClick(Sender: TObject); procedure OptionsGroupBoxResize(Sender: TObject); procedure Text1ComboboxChange(Sender: TObject); procedure Text1GroupBoxResize(Sender: TObject); procedure Text2ComboboxChange(Sender: TObject); procedure Text2GroupBoxResize(Sender: TObject); private FOnGetDiffFile: TOnGetDiffFile; fDiffNeedsUpdate: boolean; FLockCount: integer; procedure SetupComponents; procedure UpdateDiff; public Files: TDiffFiles; Text1: TDiffFile; Text2: TDiffFile; constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure Init; procedure FillTextComboBoxes; procedure SetText1Index(NewIndex: integer); procedure SetText2Index(NewIndex: integer); procedure SaveSettings; procedure SetDiffOptions(NewOptions: TTextDiffFlags); function GetDiffOptions: TTextDiffFlags; procedure BeginUpdate; procedure EndUpdate; public property OnGetDiffFile: TOnGetDiffFile read FOnGetDiffFile write FOnGetDiffFile; end; function ShowDiffDialog(Files: TDiffFiles; Text1Index: integer; OnGetDiffFile: TOnGetDiffFile; var OpenDiffInEditor: boolean; var Diff: string): TModalResult; implementation function ShowDiffDialog(Files: TDiffFiles; Text1Index: integer; OnGetDiffFile: TOnGetDiffFile; var OpenDiffInEditor: boolean; var Diff: string): TModalResult; var DiffDlg: TDiffDialog; begin OpenDiffInEditor:=false; DiffDlg:=TDiffDialog.Create(nil); DiffDlg.BeginUpdate; DiffDlg.OnGetDiffFile:=OnGetDiffFile; DiffDlg.Files:=Files; DiffDlg.SetText1Index(Text1Index); DiffDlg.Init; DiffDlg.EndUpdate; Result:=DiffDlg.ShowModal; DiffDlg.SaveSettings; if Result=mrYes then begin OpenDiffInEditor:=true; Diff:=DiffDlg.DiffSynEdit.Text; Result:=mrOk; end; DiffDlg.Free; end; { TDiffDialog } procedure TDiffDialog.DiffDialogResize(Sender: TObject); begin // text 1 with Text1GroupBox do begin SetBounds(3,3,(Parent.ClientWidth-3*3) div 2,70); end; // text 2 with Text2GroupBox do begin SetBounds(Text1GroupBox.Left+Text1GroupBox.Width+3,Text1GroupBox.Top, Text1GroupBox.Width,Text1GroupBox.Height); end; // diff preview with DiffGroupbox do begin SetBounds(Text1GroupBox.Left,Text1GroupBox.Top+Text1GroupBox.Height+5, Parent.ClientWidth-2*Text1GroupBox.Left, Max(30,Parent.ClientHeight-Text1GroupBox.Height-Text1GroupBox.Top -170)); end; // options with OptionsGroupBox do begin SetBounds(Text1GroupBox.Left,DiffGroupbox.Top+DiffGroupbox.Height+5, DiffGroupbox.Width,116); end; // buttons with CloseButton do begin SetBounds(Parent.ClientWidth-300,Parent.ClientHeight-32,80,Height); end; with OpenInEditorButton do begin SetBounds(CloseButton.Left+CloseButton.Width+10,CloseButton.Top, 200,CloseButton.Height); end; end; procedure TDiffDialog.OnChangeFlag(Sender: TObject); begin UpdateDiff; end; procedure TDiffDialog.OpenInEditorButtonClick(Sender: TObject); begin ModalResult:=mrYes; end; procedure TDiffDialog.CloseButtonClick(Sender: TObject); begin ModalResult:=mrOk; end; procedure TDiffDialog.OptionsGroupBoxResize(Sender: TObject); var y: Integer; x: Integer; W: Integer; begin y:=0; x:=4; W:=(OptionsGroupBox.ClientWidth div 2)-8; with IgnoreCaseCheckBox do begin SetBounds(x,y,w,Height); inc(y,Height+2); end; with IgnoreEmptyLineChangesCheckBox do begin SetBounds(x,y,w,Height); inc(y,Height+2); end; with IgnoreHeadingSpacesCheckBox do begin SetBounds(x,y,w,Height); inc(y,Height+2); end; with IgnoreLineEndsCheckBox do begin SetBounds(x,y,w+w,Height); inc(y,Height+2); end; x:=(OptionsGroupBox.ClientWidth div 2)+4; y:=2; with IgnoreSpaceCharAmountCheckBox do begin SetBounds(x,y,w,Height); inc(y,Height+2); end; with IgnoreSpaceCharsCheckBox do begin SetBounds(x,y,w,Height); inc(y,Height+2); end; with IgnoreTrailingSpacesCheckBox do begin SetBounds(x,y,w,Height); end; end; procedure TDiffDialog.Text1ComboboxChange(Sender: TObject); begin SetText1Index(Text1Combobox.Items.IndexOf(Text1Combobox.Text)); end; procedure TDiffDialog.Text1GroupBoxResize(Sender: TObject); begin with Text1Combobox do begin SetBounds(0,0,Parent.ClientWidth,Height); end; with Text1OnlySelectionCheckBox do begin SetBounds(10,Text1Combobox.Top+Text1Combobox.Height+1, Parent.ClientWidth-20,Height); end; end; procedure TDiffDialog.Text2ComboboxChange(Sender: TObject); begin SetText2Index(Text2Combobox.Items.IndexOf(Text2Combobox.Text)); end; procedure TDiffDialog.Text2GroupBoxResize(Sender: TObject); begin with Text2Combobox do begin SetBounds(0,0,Parent.ClientWidth,Height); end; with Text2OnlySelectionCheckBox do begin SetBounds(10,Text1Combobox.Top+Text1Combobox.Height+1, Parent.ClientWidth-20,Height); end; end; procedure TDiffDialog.SetupComponents; begin // text 1 Text1GroupBox:=TGroupBox.Create(Self); with Text1GroupBox do begin Name:='Text1GroupBox'; Parent:=Self; Caption:=lisDiffDlgText1; OnResize:=@Text1GroupBoxResize; end; Text1Combobox:=TComboBox.Create(Self); with Text1Combobox do begin Name:='Text1Combobox'; Parent:=Text1GroupBox; OnChange:=@Text1ComboboxChange; end; Text1OnlySelectionCheckBox:=TCheckBox.Create(Self); with Text1OnlySelectionCheckBox do begin Name:='Text1OnlySelectionCheckBox'; Parent:=Text1GroupBox; Caption:=lisDiffDlgOnlySelection; OnClick:=@OnChangeFlag; end; // text 2 Text2GroupBox:=TGroupBox.Create(Self); with Text2GroupBox do begin Name:='Text2GroupBox'; Parent:=Self; Caption:=lisDiffDlgText2; OnResize:=@Text2GroupBoxResize; end; Text2Combobox:=TComboBox.Create(Self); with Text2Combobox do begin Name:='Text2Combobox'; Parent:=Text2GroupBox; OnChange:=@Text2ComboboxChange; end; Text2OnlySelectionCheckBox:=TCheckBox.Create(Self); with Text2OnlySelectionCheckBox do begin Name:='Text2OnlySelectionCheckBox'; Parent:=Text2GroupBox; Caption:=lisDiffDlgOnlySelection; OnClick:=@OnChangeFlag; end; // diff preview DiffGroupbox:=TGroupBox.Create(Self); with DiffGroupbox do begin Name:='DiffGroupbox'; Parent:=Self; Caption:=lisMenuDiff; end; DiffSynEdit:=TSynEdit.Create(Self); with DiffSynEdit do begin Name:='DiffSynEdit'; Parent:=DiffGroupbox; Gutter.Visible:=false; Align:=alClient; end; // options OptionsGroupBox:=TGroupBox.Create(Self); with OptionsGroupBox do begin Name:='OptionsGroupBox'; Parent:=Self; Caption:=dlgFROpts; OnResize:=@OptionsGroupBoxResize; end; IgnoreCaseCheckBox:=TCheckBox.Create(Self); with IgnoreCaseCheckBox do begin Name:='IgnoreCaseCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgCaseInsensitive; OnClick:=@OnChangeFlag; end; IgnoreEmptyLineChangesCheckBox:=TCheckBox.Create(Self); with IgnoreEmptyLineChangesCheckBox do begin Name:='IgnoreEmptyLineChangesCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgIgnoreIfEmptyLinesWereAdd; OnClick:=@OnChangeFlag; end; IgnoreHeadingSpacesCheckBox:=TCheckBox.Create(Self); with IgnoreHeadingSpacesCheckBox do begin Name:='IgnoreHeadingSpacesCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgIgnoreSpacesAtStartOfLine; OnClick:=@OnChangeFlag; end; IgnoreTrailingSpacesCheckBox:=TCheckBox.Create(Self); with IgnoreTrailingSpacesCheckBox do begin Name:='IgnoreTrailingSpacesCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgIgnoreSpacesAtEndOfLine; OnClick:=@OnChangeFlag; end; IgnoreLineEndsCheckBox:=TCheckBox.Create(Self); with IgnoreLineEndsCheckBox do begin Name:='IgnoreLineEndsCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgIgnoreIfLineEndCharsDiffe; OnClick:=@OnChangeFlag; end; IgnoreSpaceCharAmountCheckBox:=TCheckBox.Create(Self); with IgnoreSpaceCharAmountCheckBox do begin Name:='IgnoreSpaceCharAmountCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgIgnoreIfSpaceCharsWereAdd; OnClick:=@OnChangeFlag; end; IgnoreSpaceCharsCheckBox:=TCheckBox.Create(Self); with IgnoreSpaceCharsCheckBox do begin Name:='IgnoreSpaceCharsCheckBox'; Parent:=OptionsGroupBox; Caption:=lisDiffDlgIgnoreSpaces; OnClick:=@OnChangeFlag; end; // buttons CloseButton:=TButton.Create(Self); with CloseButton do begin Name:='CloseButton'; Parent:=Self; Caption:=lisMenuClose; OnClick:=@CloseButtonClick; end; OpenInEditorButton:=TButton.Create(Self); with OpenInEditorButton do begin Name:='OpenInEditorButton'; Parent:=Self; Caption:=lisDiffDlgOpenDiffInEditor; OnClick:=@OpenInEditorButtonClick; end; end; procedure TDiffDialog.UpdateDiff; var Text1Src, Text2Src: string; DiffTxt: String; begin if FLockCount>0 then begin fDiffNeedsUpdate:=true; exit; end; fDiffNeedsUpdate:=false; if (Text1=nil) or (Text2=nil) then begin DiffSynEdit.Lines.Text:=''; end else begin OnGetDiffFile(Text1.ID, Text1.SelectionAvailable and Text1OnlySelectionCheckBox.Checked, Text1Src); OnGetDiffFile(Text2.ID, Text2.SelectionAvailable and Text2OnlySelectionCheckBox.Checked, Text2Src); DiffTxt:=CreateTextDiff(Text1Src,Text2Src,GetDiffOptions,tdoContext); DiffSynEdit.Lines.Text:=DiffTxt; end; end; constructor TDiffDialog.Create(TheOwner: TComponent); begin inherited Create(TheOwner); Name:='DiffDialog'; Caption := lisMenuDiff; Width:=600; Height:=400; Position:=poScreenCenter; IDEDialogLayoutList.ApplyLayout(Self,600,400); OnResize:=@DiffDialogResize; SetupComponents; OnResize(nil); end; destructor TDiffDialog.Destroy; begin inherited Destroy; end; procedure TDiffDialog.Init; var LastText2Name: String; i: Integer; begin // fill all diff file names FillTextComboBoxes; // get recent Text 2 LastText2Name:=InputHistories.DiffText2; if LastText2Name<>'' then i:=Files.IndexOfName(LastText2Name); if i<0 then i:=0; if i=Files.IndexOf(Text2) then inc(i); SetText2Index(i); // set recent options SetDiffOptions(InputHistories.DiffFlags); // and action ... UpdateDiff; end; procedure TDiffDialog.FillTextComboBoxes; var i: Integer; begin // Text 1 Text1Combobox.Items.BeginUpdate; Text1Combobox.Items.Clear; for i:=0 to Files.Count-1 do Text1Combobox.Items.Add(Files[i].Name); Text1Combobox.Items.EndUpdate; // Text 2 Text2Combobox.Items.BeginUpdate; Text2Combobox.Items.Clear; for i:=0 to Files.Count-1 do Text2Combobox.Items.Add(Files[i].Name); Text2Combobox.Items.EndUpdate; end; procedure TDiffDialog.SetText1Index(NewIndex: integer); var OldText1: TDiffFile; begin OldText1:=Text1; if (NewIndex>=0) and (NewIndexOldText1 then UpdateDiff; end; procedure TDiffDialog.SetText2Index(NewIndex: integer); var OldText2: TDiffFile; begin OldText2:=Text2; if (NewIndex>=0) and (NewIndexOldText2 then UpdateDiff; end; procedure TDiffDialog.SaveSettings; begin InputHistories.DiffFlags:=GetDiffOptions; if Text2<>nil then begin InputHistories.DiffText2:=Text2.Name; InputHistories.DiffText2OnlySelection:=Text2OnlySelectionCheckBox.Checked; end else begin InputHistories.DiffText2:=''; InputHistories.DiffText2OnlySelection:=false; end; IDEDialogLayoutList.SaveLayout(Self); end; procedure TDiffDialog.SetDiffOptions(NewOptions: TTextDiffFlags); begin IgnoreCaseCheckBox.Checked:=tdfIgnoreCase in NewOptions; IgnoreEmptyLineChangesCheckBox.Checked:=tdfIgnoreEmptyLineChanges in NewOptions; IgnoreHeadingSpacesCheckBox.Checked:=tdfIgnoreHeadingSpaces in NewOptions; IgnoreLineEndsCheckBox.Checked:=tdfIgnoreLineEnds in NewOptions; IgnoreSpaceCharAmountCheckBox.Checked:=tdfIgnoreSpaceCharAmount in NewOptions; IgnoreSpaceCharsCheckBox.Checked:=tdfIgnoreSpaceChars in NewOptions; IgnoreTrailingSpacesCheckBox.Checked:=tdfIgnoreTrailingSpaces in NewOptions; end; function TDiffDialog.GetDiffOptions: TTextDiffFlags; begin Result:=[]; if IgnoreCaseCheckBox.Checked then Include(Result,tdfIgnoreCase); if IgnoreEmptyLineChangesCheckBox.Checked then Include(Result,tdfIgnoreEmptyLineChanges); if IgnoreHeadingSpacesCheckBox.Checked then Include(Result,tdfIgnoreHeadingSpaces); if IgnoreLineEndsCheckBox.Checked then Include(Result,tdfIgnoreLineEnds); if IgnoreSpaceCharAmountCheckBox.Checked then Include(Result,tdfIgnoreSpaceCharAmount); if IgnoreSpaceCharsCheckBox.Checked then Include(Result,tdfIgnoreSpaceChars); if IgnoreTrailingSpacesCheckBox.Checked then Include(Result,tdfIgnoreTrailingSpaces); end; procedure TDiffDialog.BeginUpdate; begin inc(FLockCount); end; procedure TDiffDialog.EndUpdate; begin dec(FLockCount); if (FLockCount=0) and fDiffNeedsUpdate then UpdateDiff; end; { TDiffFile } constructor TDiffFile.Create(const NewName: string; NewID: integer; NewSelectionAvailable: boolean); begin Name:=NewName; ID:=NewID; SelectionAvailable:=NewSelectionAvailable; end; { TDiffFiles } function TDiffFiles.GetItems(Index: integer): TDiffFile; begin Result:=TDiffFile(inherited Items[Index]); end; procedure TDiffFiles.SetItems(Index: integer; const AValue: TDiffFile); begin inherited Items[Index]:=AValue; end; procedure TDiffFiles.Clear; var i: Integer; begin for i:=0 to Count-1 do Items[i].Free; inherited Clear; end; function TDiffFiles.Add(DiffFile: TDiffFile): integer; begin Result:=inherited Add(DiffFile); end; function TDiffFiles.IndexOfName(const Name: string): integer; begin Result:=Count-1; while (Result>=0) and (Items[Result].Name<>Name) do dec(Result); end; end.