1 {
2  /***************************************************************************
3                               diffdialog.pas
4                               --------------
5 
6 
7  ***************************************************************************/
8 
9  ***************************************************************************
10  *                                                                         *
11  *   This source is free software; you can redistribute it and/or modify   *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  *   This code is distributed in the hope that it will be useful, but      *
17  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
19  *   General Public License for more details.                              *
20  *                                                                         *
21  *   A copy of the GNU General Public License is available on the World    *
22  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
23  *   obtain it by writing to the Free Software Foundation,                 *
24  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
25  *                                                                         *
26  ***************************************************************************
27 
28   Author: Mattias Gaertner
29 
30   Abstract:
31     The TDiffDlg is a dialog for showing differences between two files.
32 
33 }
34 
35 unit DiffDialog;
36 
37 {$mode objfpc}{$H+}
38 
39 interface
40 
41 uses
42   Classes, SysUtils,
43   // LCL
44   Forms, Controls, Buttons, StdCtrls, ExtCtrls, Dialogs, ComCtrls, LCLType,
45   // LazUtils
46   FileUtil,
47   // SynEdit
48   SynEdit, SynHighlighterDiff,
49   // IdeIntf
50   IDEWindowIntf, IDEHelpIntf, IDEImagesIntf,
51   // IDE
52   LazarusIDEStrConsts, EditorOptions, InputHistory, DiffPatch, SourceEditor, EnvironmentOpts;
53 
54 type
55 
56   { TAvailableDiffFile }
57 
58   TAvailableDiffFile = class
59   private
60     Name: string;
61     Editor: TSourceEditor;
62     SelectionAvailable: boolean;
63   public
64     constructor Create(const NewName: string; NewEditor: TSourceEditor;
65       NewSelectionAvailable: boolean);
66   end;
67 
68   { TAvailableDiffFiles }
69 
70   TAvailableDiffFiles = class(TList)
71   private
GetItemsnull72     function GetItems(Index: integer): TAvailableDiffFile;
73     procedure SetItems(Index: integer; const AValue: TAvailableDiffFile);
74   public
75     procedure Clear; override;
Addnull76     function Add(DiffFile: TAvailableDiffFile): integer;
IndexOfNamenull77     function IndexOfName(const Name: string): integer;
78   public
79     property Items[Index: integer]: TAvailableDiffFile read GetItems write SetItems; default;
80   end;
81 
82   TDiffDlg = class;
83 
84   { TSelectedDiffFile }
85 
86   TSelectedDiffFile = class
87   private
88     fOwner: TDiffDlg;
89     fFile: TAvailableDiffFile;        // Selected File
90     fCombobox: TComboBox;             // Reference for the user selection GUI.
91     fOnlySelCheckBox: TCheckBox;
TextContentsnull92     function TextContents: string;
93     procedure SetIndex(NewIndex: integer);
94     procedure SetFileName(aFileName: string);
95     procedure UpdateIndex;
96   public
97     constructor Create(aOwner: TDiffDlg; aCombobox: TComboBox; aOnlySelCheckBox: TCheckBox);
98   end;
99 
100   { TDiffDlg }
101 
102   TDiffDlg = class(TForm)
103     HelpButton: TBitBtn;
104     CloseButton: TBitBtn;
105     DiffSynEdit: TSynEdit;
106     OpenInEditorButton: TBitBtn;
107     ProgressBar1: TProgressBar;
108     SynDiffSyn1: TSynDiffSyn;
109     Text1FileOpenButton: TButton;
110     CancelScanningButton: TBitBtn;
111     dlgOpen: TOpenDialog;
112 
113     Text2FileOpenButton: TButton;
114 
115     // text 1
116     Text1GroupBox: TGroupBox;
117     Text1Combobox: TComboBox;
118     Text1OnlySelectionCheckBox: TCheckBox;
119 
120     // text 2
121     Text2GroupBox: TGroupBox;
122     Text2Combobox: TComboBox;
123     Text2OnlySelectionCheckBox: TCheckBox;
124 
125     // options
126     OptionsGroupBox: TCheckGroup;
127 
128     procedure CancelScanningButtonClick(Sender: TObject);
129     procedure FileOpenClick(Sender: TObject);
130     procedure FormCreate(Sender: TObject);
131     procedure HelpButtonClick(Sender: TObject);
132     procedure OnChangeFlag(Sender: TObject);
133     procedure Text1ComboboxChange(Sender: TObject);
134     procedure Text2ComboboxChange(Sender: TObject);
135   private
136     fUpdating: Boolean;
137     fIdleConnected: boolean;
138     fCancelled: boolean;
139     fAvailableFiles: TAvailableDiffFiles;
140     fSelectedFile1: TSelectedDiffFile;
141     fSelectedFile2: TSelectedDiffFile;
142     procedure SetupComponents;
143     procedure UpdateDiff;
144     procedure SetIdleConnected(const AValue: boolean);
145     procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
146     procedure UpdateProgress(aPosition: Integer);
147   public
148     constructor Create(TheOwner: TComponent); override;
149     destructor Destroy; override;
150     procedure Init;
151     procedure FillTextComboBoxes;
152     procedure SaveSettings;
153     procedure SetDiffOptions(NewOptions: TTextDiffFlags);
GetDiffOptionsnull154     function GetDiffOptions: TTextDiffFlags;
155 
156     property IdleConnected: boolean read fIdleConnected write SetIdleConnected;
157   end;
158 
ShowDiffDialognull159 function ShowDiffDialog(Text1Index: integer; out Diff: string): TModalResult;
160 
161 const
162   IgnoreCaseCheckBox = 0;
163   IgnoreEmptyLineChangesCheckBox = 1;
164   IgnoreHeadingSpacesCheckBox = 2;
165   IgnoreLineEndsCheckBox = 3;
166   IgnoreSpaceCharAmountCheckBox = 4;
167   IgnoreSpaceCharsCheckBox = 5;
168   IgnoreTrailingSpacesCheckBox = 6;
169 
170 implementation
171 
172 {$R *.lfm}
173 
ShowDiffDialognull174 function ShowDiffDialog(Text1Index: integer; out Diff: string): TModalResult;
175 var
176   DiffDlg: TDiffDlg;
177   Files: TAvailableDiffFiles;
178   i: Integer;
179   SrcEdit: TSourceEditor;
180 begin
181   DiffDlg := TDiffDlg.Create(nil);
182   Files := TAvailableDiffFiles.Create;
183   try
184     // Get available files
185     for i:=0 to SourceEditorManager.SourceEditorCount - 1 do begin
186       SrcEdit := SourceEditorManager.SourceEditors[i]; // FindSourceEditorWithPageIndex(i);
187       Files.Add(TAvailableDiffFile.Create(SrcEdit.PageName, SrcEdit, SrcEdit.SelectionAvailable));
188     end;
189     DiffDlg.fAvailableFiles := Files;
190     DiffDlg.fSelectedFile1.SetIndex(Text1Index);
191     DiffDlg.Init;
192     Result := DiffDlg.ShowModal;
193     DiffDlg.SaveSettings;
194     // "Open in editor" button returns mrYes.
195     if Result=mrYes then
196       Diff := DiffDlg.DiffSynEdit.Text;
197   finally
198     Files.Free;
199     DiffDlg.Free;
200   end;
201 end;
202 
203 { TSelectedDiffFile }
204 
205 constructor TSelectedDiffFile.Create(aOwner: TDiffDlg; aCombobox: TComboBox;
206   aOnlySelCheckBox: TCheckBox);
207 begin
208   inherited Create;
209   fOwner := aOwner;
210   fCombobox := aCombobox;
211   fOnlySelCheckBox := aOnlySelCheckBox;
212 end;
213 
TextContentsnull214 function TSelectedDiffFile.TextContents: string;
215 var
216   dat: TStringList;
217 begin
218   if fFile = nil then Exit('');
219   if fFile.Editor = nil then
220   begin
221     dat := TStringList.Create;
222     try
223       dat.LoadFromFile(fFile.Name);
224       Result := dat.Text;
225     finally
226       dat.Free;
227     end;
228   end
229   else begin
230     if (fFile.SelectionAvailable and fOnlySelCheckBox.Checked) then
231       Result := fFile.Editor.EditorComponent.SelText
232     else
233       Result := fFile.Editor.EditorComponent.Lines.Text;
234   end;
235 end;
236 
237 procedure TSelectedDiffFile.SetIndex(NewIndex: integer);
238 var
239   OldFile: TAvailableDiffFile;
240 begin
241   OldFile:=fFile;
242   if (NewIndex>=0) and (NewIndex<fOwner.fAvailableFiles.Count) then begin
243     fFile:=fOwner.fAvailableFiles[NewIndex];
244     fCombobox.Text:=fFile.Name;
245     fOnlySelCheckBox.Enabled:=fFile.SelectionAvailable;
246   end else begin
247     fFile:=nil;
248     fCombobox.Text:='';
249     fOnlySelCheckBox.Enabled:=false;
250   end;
251   if fFile<>OldFile then fOwner.UpdateDiff;
252 end;
253 
254 procedure TSelectedDiffFile.SetFileName(aFileName: string);
255 // Assumes that aFileName is already in fCombobox.Items.
256 begin
257   fCombobox.ItemIndex := fCombobox.Items.IndexOf(aFileName);
258   SetIndex(fCombobox.Items.IndexOf(aFileName));
259 end;
260 
261 procedure TSelectedDiffFile.UpdateIndex;
262 begin
263   SetIndex(fCombobox.Items.IndexOf(fCombobox.Text));
264 end;
265 
266 { TDiffDlg }
267 
268 constructor TDiffDlg.Create(TheOwner: TComponent);
269 begin
270   inherited Create(TheOwner);
271   fUpdating := False;
272   fCancelled := False;
273   fSelectedFile1 := TSelectedDiffFile.Create(Self, Text1Combobox, Text1OnlySelectionCheckBox);
274   fSelectedFile2 := TSelectedDiffFile.Create(Self, Text2Combobox, Text2OnlySelectionCheckBox);
275   Caption := lisCaptionCompareFiles;
276   IDEDialogLayoutList.ApplyLayout(Self,600,500);
277   SetupComponents;
278 end;
279 
280 destructor TDiffDlg.Destroy;
281 begin
282   fSelectedFile2.Free;
283   fSelectedFile1.Free;
284   inherited Destroy;
285 end;
286 
287 procedure TDiffDlg.OnChangeFlag(Sender: TObject);
288 begin
289   UpdateDiff;
290 end;
291 
292 procedure TDiffDlg.FileOpenClick(Sender: TObject);
293 begin
294   if dlgOpen.Execute then
295   begin
296     //only add new files
297     if Text1ComboBox.Items.IndexOf(dlgOpen.FileName) = -1 then
298     begin
299       fAvailableFiles.Add(TAvailableDiffFile.Create(dlgOpen.FileName,nil,False));
300       Text1ComboBox.Items.Add(dlgOpen.FileName);
301       Text2ComboBox.Items.Add(dlgOpen.FileName);
302     end;
303     //set the combobox and make the diff
304     if TButton(Sender) = Text1FileOpenButton then
305       fSelectedFile1.SetFileName(dlgOpen.FileName)
306     else
307       fSelectedFile2.SetFileName(dlgOpen.FileName);
308   end;
309 end;
310 
311 procedure TDiffDlg.CancelScanningButtonClick(Sender: TObject);
312 begin
313   fCancelled := True;
314 end;
315 
316 procedure TDiffDlg.HelpButtonClick(Sender: TObject);
317 begin
318   LazarusHelp.ShowHelpForIDEControl(Self);
319 end;
320 
321 procedure TDiffDlg.Text1ComboboxChange(Sender: TObject);
322 begin
323   fSelectedFile1.UpdateIndex;
324 end;
325 
326 procedure TDiffDlg.Text2ComboboxChange(Sender: TObject);
327 begin
328   fSelectedFile2.UpdateIndex;
329 end;
330 
331 procedure TDiffDlg.SetupComponents;
332 begin
333   // text 1
334   Text1GroupBox.Caption:=lisDiffDlgFile1;
335   Text1OnlySelectionCheckBox.Caption:=lisDiffDlgOnlySelection;
336   Text1FileOpenButton.Caption:='...';
337 
338   // text 2
339   Text2GroupBox.Caption:=lisDiffDlgFile2;
340   Text2OnlySelectionCheckBox.Caption:=lisDiffDlgOnlySelection;
341   Text2FileOpenButton.Caption:='...';
342 
343   // options
344   with OptionsGroupBox do
345   begin
346     Caption:=lisOptions;
347     Items.Add(lisDiffDlgCaseInsensitive);
348     Items.Add(lisDiffDlgIgnoreIfEmptyLinesWereAdd);
349     Items.Add(lisDiffDlgIgnoreSpacesAtStartOfLine);
350     Items.Add(lisDiffDlgIgnoreSpacesAtEndOfLine);
351     Items.Add(lisDiffDlgIgnoreIfLineEndCharsDiffe);
352     Items.Add(lisDiffDlgIgnoreIfSpaceCharsWereAdd);
353     Items.Add(lisDiffDlgIgnoreSpaces);
354   end;
355 
356   // buttons
357   IDEImages.AssignImage(CancelScanningButton, 'btn_cancel');
358   CloseButton.Caption:=lisClose;
359   OpenInEditorButton.Caption:=lisDiffDlgOpenDiffInEditor;
360   HelpButton.Caption:=lisMenuHelp;
361 
362   OpenInEditorButton.LoadGlyphFromStock(idButtonOpen);
363   if OpenInEditorButton.Glyph.Empty then
364     IDEImages.AssignImage(OpenInEditorButton, 'laz_open');
365 
366   // dialogs
367   dlgOpen.Title:=lisOpenExistingFile;
368   dlgOpen.Filter:=dlgFilterAll+' ('+GetAllFilesMask+')|'+GetAllFilesMask
369                  +'|'+dlgFilterLazarusUnit+' (*.pas;*.pp)|*.pas;*.pp'
370                  +'|'+dlgFilterLazarusProject+' (*.lpi)|*.lpi'
371                  +'|'+dlgFilterLazarusForm+' (*.lfm;*.dfm)|*.lfm;*.dfm'
372                  +'|'+dlgFilterLazarusPackage+' (*.lpk)|*.lpk'
373                  +'|'+dlgFilterLazarusProjectSource+' (*.lpr)|*.lpr';
374 
375   // diff
376   EditorOpts.GetSynEditSettings(DiffSynEdit);
377 end;
378 
379 procedure TDiffDlg.UpdateDiff;
380 begin
381   IdleConnected:=True;
382 end;
383 
384 procedure TDiffDlg.UpdateProgress(aPosition: Integer);
385 begin
386   ProgressBar1.Position := aPosition;
387   Application.ProcessMessages;
388 end;
389 
390 procedure TDiffDlg.SetIdleConnected(const AValue: boolean);
391 begin
392   if fIdleConnected=AValue then exit;
393   fIdleConnected:=AValue;
394   if fIdleConnected then
395     Application.AddOnIdleHandler(@OnIdle)
396   else
397     Application.RemoveOnIdleHandler(@OnIdle);
398 end;
399 
400 procedure TDiffDlg.OnIdle(Sender: TObject; var Done: Boolean);
401 var
402   Text1Src, Text2Src: string;
403   DiffOutput: TDiffOutput;
404 begin
405   IdleConnected := false;
406   if fUpdating then Exit;
407   fUpdating := True;
408   DiffSynEdit.Lines.Text := '';
409   Text1Src := fSelectedFile1.TextContents;
410   Text2Src := fSelectedFile2.TextContents;
411   if (Text1Src <> '') and (Text2Src <> '') then
412   begin
413     Text1GroupBox.Enabled := False;
414     Text2GroupBox.Enabled := False;
415     OpenInEditorButton.Enabled := False;
416     //CancelScanningButton.Enabled := True;
417 
418     DiffOutput := TDiffOutput.Create(Text1Src, Text2Src, GetDiffOptions);
419     try
420       ProgressBar1.Max := DiffOutput.GetProgressMax;
421       DiffOutput.OnProgressPos := @UpdateProgress;
422       DiffSynEdit.Lines.Text := DiffOutput.CreateTextDiff;
423     finally
424       DiffOutput.Free;
425     end;
426 
427     //CancelScanningButton.Enabled := False;
428     OpenInEditorButton.Enabled := True;
429     Text2GroupBox.Enabled := True;
430     Text1GroupBox.Enabled := True;
431   end;
432   fUpdating:=False;
433 end;
434 
435 procedure TDiffDlg.Init;
436 var
437   LastText2Name: String;
438   i: Integer;
439 begin
440   // fill all diff file names
441   FillTextComboBoxes;
442 
443   // get recent Text 2
444   i:=0;
445   LastText2Name:=InputHistories.DiffText2;
446   if LastText2Name<>'' then
447     i:=fAvailableFiles.IndexOfName(LastText2Name);
448   if i<0 then i:=0;
449   if i=fAvailableFiles.IndexOf(fSelectedFile2.fFile) then inc(i);
450   fSelectedFile2.SetIndex(i);
451 
452   // set recent options
453   SetDiffOptions(InputHistories.DiffFlags);
454 
455   // and action ...
456   UpdateDiff;
457 end;
458 
459 procedure TDiffDlg.FillTextComboBoxes;
460 var
461   i: Integer;
462 begin
463   // Text 1
464   Text1Combobox.Items.BeginUpdate;
465   Text1Combobox.Items.Clear;
466   for i:=0 to fAvailableFiles.Count-1 do
467     Text1Combobox.Items.Add(fAvailableFiles[i].Name);
468   Text1Combobox.Items.EndUpdate;
469 
470   // Text 2
471   Text2Combobox.Items.BeginUpdate;
472   Text2Combobox.Items.Clear;
473   for i:=0 to fAvailableFiles.Count-1 do
474     Text2Combobox.Items.Add(fAvailableFiles[i].Name);
475   Text2Combobox.Items.EndUpdate;
476 end;
477 
478 procedure TDiffDlg.FormCreate(Sender: TObject);
479 begin
480   Text1Combobox.DropDownCount:=EnvironmentOptions.DropDownCount;
481   Text2Combobox.DropDownCount:=EnvironmentOptions.DropDownCount;
482 end;
483 
484 procedure TDiffDlg.SaveSettings;
485 begin
486   InputHistories.DiffFlags:=GetDiffOptions;
487   if (fSelectedFile2<>nil) and (fSelectedFile2.fFile<>nil) then begin
488     InputHistories.DiffText2:=fSelectedFile2.fFile.Name;
489     InputHistories.DiffText2OnlySelection:=Text2OnlySelectionCheckBox.Checked;
490   end else begin
491     InputHistories.DiffText2:='';
492     InputHistories.DiffText2OnlySelection:=false;
493   end;
494   IDEDialogLayoutList.SaveLayout(Self);
495 end;
496 
497 procedure TDiffDlg.SetDiffOptions(NewOptions: TTextDiffFlags);
498 begin
499   OptionsGroupBox.Checked[IgnoreCaseCheckBox]:=tdfIgnoreCase in NewOptions;
500   OptionsGroupBox.Checked[IgnoreEmptyLineChangesCheckBox]:=tdfIgnoreEmptyLineChanges in NewOptions;
501   OptionsGroupBox.Checked[IgnoreHeadingSpacesCheckBox]:=tdfIgnoreHeadingSpaces in NewOptions;
502   OptionsGroupBox.Checked[IgnoreLineEndsCheckBox]:=tdfIgnoreLineEnds in NewOptions;
503   OptionsGroupBox.Checked[IgnoreSpaceCharAmountCheckBox]:=tdfIgnoreSpaceCharAmount in NewOptions;
504   OptionsGroupBox.Checked[IgnoreSpaceCharsCheckBox]:=tdfIgnoreSpaceChars in NewOptions;
505   OptionsGroupBox.Checked[IgnoreTrailingSpacesCheckBox]:=tdfIgnoreTrailingSpaces in NewOptions;
506 end;
507 
TDiffDlg.GetDiffOptionsnull508 function TDiffDlg.GetDiffOptions: TTextDiffFlags;
509 begin
510   Result:=[];
511   if OptionsGroupBox.Checked[IgnoreCaseCheckBox] then
512     Include(Result,tdfIgnoreCase);
513   if OptionsGroupBox.Checked[IgnoreEmptyLineChangesCheckBox] then
514     Include(Result,tdfIgnoreEmptyLineChanges);
515   if OptionsGroupBox.Checked[IgnoreHeadingSpacesCheckBox] then
516     Include(Result,tdfIgnoreHeadingSpaces);
517   if OptionsGroupBox.Checked[IgnoreLineEndsCheckBox] then
518     Include(Result,tdfIgnoreLineEnds);
519   if OptionsGroupBox.Checked[IgnoreSpaceCharAmountCheckBox] then
520     Include(Result,tdfIgnoreSpaceCharAmount);
521   if OptionsGroupBox.Checked[IgnoreSpaceCharsCheckBox] then
522     Include(Result,tdfIgnoreSpaceChars);
523   if OptionsGroupBox.Checked[IgnoreTrailingSpacesCheckBox] then
524     Include(Result,tdfIgnoreTrailingSpaces);
525 end;
526 
527 { TAvailableDiffFile }
528 
529 constructor TAvailableDiffFile.Create(const NewName: string; NewEditor: TSourceEditor;
530   NewSelectionAvailable: boolean);
531 begin
532   Name:=NewName;
533   Editor:=NewEditor;
534   SelectionAvailable:=NewSelectionAvailable;
535 end;
536 
537 { TAvailableDiffFiles }
538 
TAvailableDiffFiles.GetItemsnull539 function TAvailableDiffFiles.GetItems(Index: integer): TAvailableDiffFile;
540 begin
541   Result:=TAvailableDiffFile(inherited Items[Index]);
542 end;
543 
544 procedure TAvailableDiffFiles.SetItems(Index: integer; const AValue: TAvailableDiffFile);
545 begin
546   inherited Items[Index]:=AValue;
547 end;
548 
549 procedure TAvailableDiffFiles.Clear;
550 var
551   i: Integer;
552 begin
553   for i:=0 to Count-1 do
554     Items[i].Free;
555   inherited Clear;
556 end;
557 
Addnull558 function TAvailableDiffFiles.Add(DiffFile: TAvailableDiffFile): integer;
559 begin
560   Result:=inherited Add(DiffFile);
561 end;
562 
IndexOfNamenull563 function TAvailableDiffFiles.IndexOfName(const Name: string): integer;
564 begin
565   Result:=Count-1;
566   while (Result>=0) and (Items[Result].Name<>Name) do dec(Result);
567 end;
568 
569 end.
570 
571