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