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, lazutf8classes,
47 // SynEdit
48 SynEdit, SynHighlighterDiff,
49 // IdeIntf
50 IDEWindowIntf, IDEHelpIntf, IDEImagesIntf,
51 // IDE
52 LazarusIDEStrConsts, EditorOptions, InputHistory, DiffPatch, SourceEditor;
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 HelpButtonClick(Sender: TObject);
131 procedure OnChangeFlag(Sender: TObject);
132 procedure Text1ComboboxChange(Sender: TObject);
133 procedure Text2ComboboxChange(Sender: TObject);
134 private
135 fUpdating: Boolean;
136 fIdleConnected: boolean;
137 fCancelled: boolean;
138 fAvailableFiles: TAvailableDiffFiles;
139 fSelectedFile1: TSelectedDiffFile;
140 fSelectedFile2: TSelectedDiffFile;
141 procedure SetupComponents;
142 procedure UpdateDiff;
143 procedure SetIdleConnected(const AValue: boolean);
144 procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
145 procedure UpdateProgress(aPosition: Integer);
146 public
147 constructor Create(TheOwner: TComponent); override;
148 destructor Destroy; override;
149 procedure Init;
150 procedure FillTextComboBoxes;
151 procedure SaveSettings;
152 procedure SetDiffOptions(NewOptions: TTextDiffFlags);
GetDiffOptionsnull153 function GetDiffOptions: TTextDiffFlags;
154
155 property IdleConnected: boolean read fIdleConnected write SetIdleConnected;
156 end;
157
ShowDiffDialognull158 function ShowDiffDialog(Text1Index: integer; out Diff: string): TModalResult;
159
160 const
161 IgnoreCaseCheckBox = 0;
162 IgnoreEmptyLineChangesCheckBox = 1;
163 IgnoreHeadingSpacesCheckBox = 2;
164 IgnoreLineEndsCheckBox = 3;
165 IgnoreSpaceCharAmountCheckBox = 4;
166 IgnoreSpaceCharsCheckBox = 5;
167 IgnoreTrailingSpacesCheckBox = 6;
168
169 implementation
170
171 {$R *.lfm}
172
ShowDiffDialognull173 function ShowDiffDialog(Text1Index: integer; out Diff: string): TModalResult;
174 var
175 DiffDlg: TDiffDlg;
176 Files: TAvailableDiffFiles;
177 i: Integer;
178 SrcEdit: TSourceEditor;
179 begin
180 DiffDlg := TDiffDlg.Create(nil);
181 Files := TAvailableDiffFiles.Create;
182 try
183 // Get available files
184 for i:=0 to SourceEditorManager.SourceEditorCount - 1 do begin
185 SrcEdit := SourceEditorManager.SourceEditors[i]; // FindSourceEditorWithPageIndex(i);
186 Files.Add(TAvailableDiffFile.Create(SrcEdit.PageName, SrcEdit, SrcEdit.SelectionAvailable));
187 end;
188 DiffDlg.fAvailableFiles := Files;
189 DiffDlg.fSelectedFile1.SetIndex(Text1Index);
190 DiffDlg.Init;
191 Result := DiffDlg.ShowModal;
192 DiffDlg.SaveSettings;
193 // "Open in editor" button returns mrYes.
194 if Result=mrYes then
195 Diff := DiffDlg.DiffSynEdit.Text;
196 finally
197 Files.Free;
198 DiffDlg.Free;
199 end;
200 end;
201
202 { TSelectedDiffFile }
203
204 constructor TSelectedDiffFile.Create(aOwner: TDiffDlg; aCombobox: TComboBox;
205 aOnlySelCheckBox: TCheckBox);
206 begin
207 inherited Create;
208 fOwner := aOwner;
209 fCombobox := aCombobox;
210 fOnlySelCheckBox := aOnlySelCheckBox;
211 end;
212
TextContentsnull213 function TSelectedDiffFile.TextContents: string;
214 var
215 dat: TStringListUTF8;
216 begin
217 if fFile = nil then Exit('');
218 if fFile.Editor = nil then
219 begin
220 dat := TStringListUTF8.Create;
221 try
222 dat.LoadFromFile(fFile.Name);
223 Result := dat.Text;
224 finally
225 dat.Free;
226 end;
227 end
228 else begin
229 if (fFile.SelectionAvailable and fOnlySelCheckBox.Checked) then
230 Result := fFile.Editor.EditorComponent.SelText
231 else
232 Result := fFile.Editor.EditorComponent.Lines.Text;
233 end;
234 end;
235
236 procedure TSelectedDiffFile.SetIndex(NewIndex: integer);
237 var
238 OldFile: TAvailableDiffFile;
239 begin
240 OldFile:=fFile;
241 if (NewIndex>=0) and (NewIndex<fOwner.fAvailableFiles.Count) then begin
242 fFile:=fOwner.fAvailableFiles[NewIndex];
243 fCombobox.Text:=fFile.Name;
244 fOnlySelCheckBox.Enabled:=fFile.SelectionAvailable;
245 end else begin
246 fFile:=nil;
247 fCombobox.Text:='';
248 fOnlySelCheckBox.Enabled:=false;
249 end;
250 if fFile<>OldFile then fOwner.UpdateDiff;
251 end;
252
253 procedure TSelectedDiffFile.SetFileName(aFileName: string);
254 // Assumes that aFileName is already in fCombobox.Items.
255 begin
256 fCombobox.ItemIndex := fCombobox.Items.IndexOf(aFileName);
257 SetIndex(fCombobox.Items.IndexOf(aFileName));
258 end;
259
260 procedure TSelectedDiffFile.UpdateIndex;
261 begin
262 SetIndex(fCombobox.Items.IndexOf(fCombobox.Text));
263 end;
264
265 { TDiffDlg }
266
267 constructor TDiffDlg.Create(TheOwner: TComponent);
268 begin
269 inherited Create(TheOwner);
270 fUpdating := False;
271 fCancelled := False;
272 fSelectedFile1 := TSelectedDiffFile.Create(Self, Text1Combobox, Text1OnlySelectionCheckBox);
273 fSelectedFile2 := TSelectedDiffFile.Create(Self, Text2Combobox, Text2OnlySelectionCheckBox);
274 Caption := lisCaptionCompareFiles;
275 IDEDialogLayoutList.ApplyLayout(Self,600,500);
276 SetupComponents;
277 end;
278
279 destructor TDiffDlg.Destroy;
280 begin
281 fSelectedFile2.Free;
282 fSelectedFile1.Free;
283 inherited Destroy;
284 end;
285
286 procedure TDiffDlg.OnChangeFlag(Sender: TObject);
287 begin
288 UpdateDiff;
289 end;
290
291 procedure TDiffDlg.FileOpenClick(Sender: TObject);
292 begin
293 if dlgOpen.Execute then
294 begin
295 //only add new files
296 if Text1ComboBox.Items.IndexOf(dlgOpen.FileName) = -1 then
297 begin
298 fAvailableFiles.Add(TAvailableDiffFile.Create(dlgOpen.FileName,nil,False));
299 Text1ComboBox.Items.Add(dlgOpen.FileName);
300 Text2ComboBox.Items.Add(dlgOpen.FileName);
301 end;
302 //set the combobox and make the diff
303 if TButton(Sender) = Text1FileOpenButton then
304 fSelectedFile1.SetFileName(dlgOpen.FileName)
305 else
306 fSelectedFile2.SetFileName(dlgOpen.FileName);
307 end;
308 end;
309
310 procedure TDiffDlg.CancelScanningButtonClick(Sender: TObject);
311 begin
312 fCancelled := True;
313 end;
314
315 procedure TDiffDlg.HelpButtonClick(Sender: TObject);
316 begin
317 LazarusHelp.ShowHelpForIDEControl(Self);
318 end;
319
320 procedure TDiffDlg.Text1ComboboxChange(Sender: TObject);
321 begin
322 fSelectedFile1.UpdateIndex;
323 end;
324
325 procedure TDiffDlg.Text2ComboboxChange(Sender: TObject);
326 begin
327 fSelectedFile2.UpdateIndex;
328 end;
329
330 procedure TDiffDlg.SetupComponents;
331 begin
332 // text 1
333 Text1GroupBox.Caption:=lisDiffDlgFile1;
334 Text1OnlySelectionCheckBox.Caption:=lisDiffDlgOnlySelection;
335 Text1FileOpenButton.Caption:='...';
336
337 // text 2
338 Text2GroupBox.Caption:=lisDiffDlgFile2;
339 Text2OnlySelectionCheckBox.Caption:=lisDiffDlgOnlySelection;
340 Text2FileOpenButton.Caption:='...';
341
342 // options
343 with OptionsGroupBox do
344 begin
345 Caption:=lisOptions;
346 Items.Add(lisDiffDlgCaseInsensitive);
347 Items.Add(lisDiffDlgIgnoreIfEmptyLinesWereAdd);
348 Items.Add(lisDiffDlgIgnoreSpacesAtStartOfLine);
349 Items.Add(lisDiffDlgIgnoreSpacesAtEndOfLine);
350 Items.Add(lisDiffDlgIgnoreIfLineEndCharsDiffe);
351 Items.Add(lisDiffDlgIgnoreIfSpaceCharsWereAdd);
352 Items.Add(lisDiffDlgIgnoreSpaces);
353 end;
354
355 // buttons
356 IDEImages.AssignImage(CancelScanningButton, 'btn_cancel');
357 CloseButton.Caption:=lisClose;
358 OpenInEditorButton.Caption:=lisDiffDlgOpenDiffInEditor;
359 HelpButton.Caption:=lisMenuHelp;
360
361 OpenInEditorButton.LoadGlyphFromStock(idButtonOpen);
362 if OpenInEditorButton.Glyph.Empty then
363 IDEImages.AssignImage(OpenInEditorButton, 'laz_open');
364
365 // dialogs
366 dlgOpen.Title:=lisOpenExistingFile;
367 dlgOpen.Filter:=dlgFilterAll+' ('+GetAllFilesMask+')|'+GetAllFilesMask
368 +'|'+dlgFilterLazarusUnit+' (*.pas;*.pp)|*.pas;*.pp'
369 +'|'+dlgFilterLazarusProject+' (*.lpi)|*.lpi'
370 +'|'+dlgFilterLazarusForm+' (*.lfm;*.dfm)|*.lfm;*.dfm'
371 +'|'+dlgFilterLazarusPackage+' (*.lpk)|*.lpk'
372 +'|'+dlgFilterLazarusProjectSource+' (*.lpr)|*.lpr';
373
374 // diff
375 EditorOpts.GetSynEditSettings(DiffSynEdit);
376 end;
377
378 procedure TDiffDlg.UpdateDiff;
379 begin
380 IdleConnected:=True;
381 end;
382
383 procedure TDiffDlg.UpdateProgress(aPosition: Integer);
384 begin
385 ProgressBar1.Position := aPosition;
386 Application.ProcessMessages;
387 end;
388
389 procedure TDiffDlg.SetIdleConnected(const AValue: boolean);
390 begin
391 if fIdleConnected=AValue then exit;
392 fIdleConnected:=AValue;
393 if fIdleConnected then
394 Application.AddOnIdleHandler(@OnIdle)
395 else
396 Application.RemoveOnIdleHandler(@OnIdle);
397 end;
398
399 procedure TDiffDlg.OnIdle(Sender: TObject; var Done: Boolean);
400 var
401 Text1Src, Text2Src: string;
402 DiffOutput: TDiffOutput;
403 begin
404 IdleConnected := false;
405 if fUpdating then Exit;
406 fUpdating := True;
407 DiffSynEdit.Lines.Text := '';
408 Text1Src := fSelectedFile1.TextContents;
409 Text2Src := fSelectedFile2.TextContents;
410 if (Text1Src <> '') and (Text2Src <> '') then
411 begin
412 Text1GroupBox.Enabled := False;
413 Text2GroupBox.Enabled := False;
414 OpenInEditorButton.Enabled := False;
415 //CancelScanningButton.Enabled := True;
416
417 DiffOutput := TDiffOutput.Create(Text1Src, Text2Src, GetDiffOptions);
418 try
419 ProgressBar1.Max := DiffOutput.GetProgressMax;
420 DiffOutput.OnProgressPos := @UpdateProgress;
421 DiffSynEdit.Lines.Text := DiffOutput.CreateTextDiff;
422 finally
423 DiffOutput.Free;
424 end;
425
426 //CancelScanningButton.Enabled := False;
427 OpenInEditorButton.Enabled := True;
428 Text2GroupBox.Enabled := True;
429 Text1GroupBox.Enabled := True;
430 end;
431 fUpdating:=False;
432 end;
433
434 procedure TDiffDlg.Init;
435 var
436 LastText2Name: String;
437 i: Integer;
438 begin
439 // fill all diff file names
440 FillTextComboBoxes;
441
442 // get recent Text 2
443 i:=0;
444 LastText2Name:=InputHistories.DiffText2;
445 if LastText2Name<>'' then
446 i:=fAvailableFiles.IndexOfName(LastText2Name);
447 if i<0 then i:=0;
448 if i=fAvailableFiles.IndexOf(fSelectedFile2.fFile) then inc(i);
449 fSelectedFile2.SetIndex(i);
450
451 // set recent options
452 SetDiffOptions(InputHistories.DiffFlags);
453
454 // and action ...
455 UpdateDiff;
456 end;
457
458 procedure TDiffDlg.FillTextComboBoxes;
459 var
460 i: Integer;
461 begin
462 // Text 1
463 Text1Combobox.Items.BeginUpdate;
464 Text1Combobox.Items.Clear;
465 for i:=0 to fAvailableFiles.Count-1 do
466 Text1Combobox.Items.Add(fAvailableFiles[i].Name);
467 Text1Combobox.Items.EndUpdate;
468
469 // Text 2
470 Text2Combobox.Items.BeginUpdate;
471 Text2Combobox.Items.Clear;
472 for i:=0 to fAvailableFiles.Count-1 do
473 Text2Combobox.Items.Add(fAvailableFiles[i].Name);
474 Text2Combobox.Items.EndUpdate;
475 end;
476
477 procedure TDiffDlg.SaveSettings;
478 begin
479 InputHistories.DiffFlags:=GetDiffOptions;
480 if (fSelectedFile2<>nil) and (fSelectedFile2.fFile<>nil) then begin
481 InputHistories.DiffText2:=fSelectedFile2.fFile.Name;
482 InputHistories.DiffText2OnlySelection:=Text2OnlySelectionCheckBox.Checked;
483 end else begin
484 InputHistories.DiffText2:='';
485 InputHistories.DiffText2OnlySelection:=false;
486 end;
487 IDEDialogLayoutList.SaveLayout(Self);
488 end;
489
490 procedure TDiffDlg.SetDiffOptions(NewOptions: TTextDiffFlags);
491 begin
492 OptionsGroupBox.Checked[IgnoreCaseCheckBox]:=tdfIgnoreCase in NewOptions;
493 OptionsGroupBox.Checked[IgnoreEmptyLineChangesCheckBox]:=tdfIgnoreEmptyLineChanges in NewOptions;
494 OptionsGroupBox.Checked[IgnoreHeadingSpacesCheckBox]:=tdfIgnoreHeadingSpaces in NewOptions;
495 OptionsGroupBox.Checked[IgnoreLineEndsCheckBox]:=tdfIgnoreLineEnds in NewOptions;
496 OptionsGroupBox.Checked[IgnoreSpaceCharAmountCheckBox]:=tdfIgnoreSpaceCharAmount in NewOptions;
497 OptionsGroupBox.Checked[IgnoreSpaceCharsCheckBox]:=tdfIgnoreSpaceChars in NewOptions;
498 OptionsGroupBox.Checked[IgnoreTrailingSpacesCheckBox]:=tdfIgnoreTrailingSpaces in NewOptions;
499 end;
500
TDiffDlg.GetDiffOptionsnull501 function TDiffDlg.GetDiffOptions: TTextDiffFlags;
502 begin
503 Result:=[];
504 if OptionsGroupBox.Checked[IgnoreCaseCheckBox] then
505 Include(Result,tdfIgnoreCase);
506 if OptionsGroupBox.Checked[IgnoreEmptyLineChangesCheckBox] then
507 Include(Result,tdfIgnoreEmptyLineChanges);
508 if OptionsGroupBox.Checked[IgnoreHeadingSpacesCheckBox] then
509 Include(Result,tdfIgnoreHeadingSpaces);
510 if OptionsGroupBox.Checked[IgnoreLineEndsCheckBox] then
511 Include(Result,tdfIgnoreLineEnds);
512 if OptionsGroupBox.Checked[IgnoreSpaceCharAmountCheckBox] then
513 Include(Result,tdfIgnoreSpaceCharAmount);
514 if OptionsGroupBox.Checked[IgnoreSpaceCharsCheckBox] then
515 Include(Result,tdfIgnoreSpaceChars);
516 if OptionsGroupBox.Checked[IgnoreTrailingSpacesCheckBox] then
517 Include(Result,tdfIgnoreTrailingSpaces);
518 end;
519
520 { TAvailableDiffFile }
521
522 constructor TAvailableDiffFile.Create(const NewName: string; NewEditor: TSourceEditor;
523 NewSelectionAvailable: boolean);
524 begin
525 Name:=NewName;
526 Editor:=NewEditor;
527 SelectionAvailable:=NewSelectionAvailable;
528 end;
529
530 { TAvailableDiffFiles }
531
TAvailableDiffFiles.GetItemsnull532 function TAvailableDiffFiles.GetItems(Index: integer): TAvailableDiffFile;
533 begin
534 Result:=TAvailableDiffFile(inherited Items[Index]);
535 end;
536
537 procedure TAvailableDiffFiles.SetItems(Index: integer; const AValue: TAvailableDiffFile);
538 begin
539 inherited Items[Index]:=AValue;
540 end;
541
542 procedure TAvailableDiffFiles.Clear;
543 var
544 i: Integer;
545 begin
546 for i:=0 to Count-1 do
547 Items[i].Free;
548 inherited Clear;
549 end;
550
Addnull551 function TAvailableDiffFiles.Add(DiffFile: TAvailableDiffFile): integer;
552 begin
553 Result:=inherited Add(DiffFile);
554 end;
555
IndexOfNamenull556 function TAvailableDiffFiles.IndexOfName(const Name: string): integer;
557 begin
558 Result:=Count-1;
559 while (Result>=0) and (Items[Result].Name<>Name) do dec(Result);
560 end;
561
562 end.
563
564