lazarus/converter/chgencodingdlg.pas

406 lines
13 KiB
ObjectPascal

{
***************************************************************************
* *
* 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 <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
Dialog and functions to change encodings (e.g. UTF-8) of projects and packages.
}
unit ChgEncodingDlg;
{$mode objfpc}{$H+}
interface
uses
// RTL + FCL
Classes, SysUtils, RegExpr, Laz_AVL_Tree,
// LCL
Forms, Controls, ExtCtrls, StdCtrls, ComCtrls, Buttons,
// CodeTools
CodeCache, CodeToolManager, FileProcs,
// LazUtils
LConvEncoding, LazFileUtils, LazFileCache, LazStringUtils, LazUTF8, AvgLvlTree,
// IDEIntf
IdeIntfStrConsts, IDEWindowIntf, SrcEditorIntf, IDEHelpIntf, IDEImagesIntf,
// IDE
IDEProcs, PackageDefs, PackageSystem, Project, LazarusIDEStrConsts,
EnvironmentOpts, SearchPathProcs;
type
{ TChgEncodingDialog }
TChgEncodingDialog = class(TForm)
ApplyButton: TBitBtn;
HelpButton: TBitBtn;
BtnPanel: TPanel;
CloseButton: TBitBtn;
LabelNoPreview: TLabel;
RegExprErrorLabel: TLabel;
NewEncodingComboBox: TComboBox;
FileFilterCombobox: TComboBox;
NewEncodingLabel: TLabel;
PreviewListView: TListView;
PreviewGroupBox: TGroupBox;
OwnerComboBox: TComboBox;
ScopeGroupBox: TGroupBox;
RegExprCheckBox: TCheckBox;
FileFilterLabel: TLabel;
NonUTF8FilesCheckBox: TCheckBox;
UTF8FilesCheckBox: TCheckBox;
FilesGroupBox: TGroupBox;
procedure ApplyButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure HelpButtonClick(Sender: TObject);
procedure PreviewClick(Sender: TObject);
private
FFiles: TFilenameToStringTree;
function GetFiles: Boolean;
procedure UpdatePreview;
public
end;
function ShowConvertEncodingDlg: TModalResult;
implementation
{$R *.lfm}
function ShowConvertEncodingDlg: TModalResult;
var
ChgEncodingDialog: TChgEncodingDialog;
begin
ChgEncodingDialog:=TChgEncodingDialog.Create(nil);
Result:=ChgEncodingDialog.ShowModal;
ChgEncodingDialog.Free;
end;
{ TChgEncodingDialog }
procedure TChgEncodingDialog.FormCreate(Sender: TObject);
var
List: TStringListUTF8Fast;
Encoding: string;
i: Integer;
begin
IDEDialogLayoutList.ApplyLayout(Self);
Caption:=lisConvertEncoding;
ScopeGroupBox.Caption:=lisConvertProjectOrPackage;
NewEncodingLabel.Caption:=lisNewEncoding;
FilesGroupBox.Caption:=lisFileFilter;
UTF8FilesCheckBox.Caption:=lisFilesInASCIIOrUTF8Encoding;
NonUTF8FilesCheckBox.Caption:=lisFilesNotInASCIINorUTF8Encoding;
FileFilterLabel.Caption:=lisFilter;
RegExprCheckBox.Caption:=lisRegularExpression;
CloseButton.Caption:=lisClose;
ApplyButton.Caption:=lisConvert;
HelpButton.Caption:=lisHelp;
IDEImages.AssignImage(CloseButton, 'btn_close');
IDEImages.AssignImage(ApplyButton, 'btn_ok');
IDEImages.AssignImage(HelpButton, 'btn_help');
PreviewGroupBox.Caption:=dlgWRDPreview;
PreviewListView.Column[0].Caption:=dlgEnvFiles;
PreviewListView.Column[0].Width:=350;
PreviewListView.Column[1].Caption:=uemEncoding;
// get possible encodings
List:=TStringListUTF8Fast.Create;
GetSupportedEncodings(List);
for i:=List.Count-1 downto 0 do begin
Encoding:=List[i];
if (Encoding='') or (SysUtils.CompareText(Encoding,EncodingAnsi)=0) then
List.Delete(i);
end;
List.Sort;
NewEncodingComboBox.Items.Assign(List);
List.Free;
NewEncodingComboBox.Text:='UTF-8';
// get possible filters
List:=TStringListUTF8Fast.Create;
List.Add('*.pas;*.pp;*.p;*.inc;*.lpr;*.lfm;*.lrs;*.txt');
List.Sort;
FileFilterCombobox.Items.Assign(List);
List.Free;
FileFilterCombobox.Text:=FileFilterCombobox.Items[0];
// get possible projects and packages
List:=TStringListUTF8Fast.Create;
for i:=0 to PackageGraph.Count-1 do
if (List.IndexOf(PackageGraph[i].Name)<0)
and (not PackageGraph[i].ReadOnly)
and (not PackageGraph[i].IsVirtual)
then
List.Add(PackageGraph[i].Name);
List.Sort;
if not Project1.IsVirtual then
List.Insert(0,lisEdtDefCurrentProject);
OwnerComboBox.Items.Assign(List);
List.Free;
if OwnerComboBox.Items.Count>0 then
OwnerComboBox.Text:=OwnerComboBox.Items[0]
else
OwnerComboBox.Text:='';
FFiles:=TFilenameToStringTree.Create(FilenamesCaseSensitive);
UpdatePreview;
OwnerComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
NewEncodingComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
FileFilterCombobox.DropDownCount:=EnvironmentOptions.DropDownCount;
end;
procedure TChgEncodingDialog.FormDestroy(Sender: TObject);
begin
IDEDialogLayoutList.SaveLayout(Self);
FFiles.Free;
end;
procedure TChgEncodingDialog.ApplyButtonClick(Sender: TObject);
var
Buf: TCodeBuffer;
SrcEdit: TSourceEditorInterface;
Encoding, OldEncoding, NewEncoding: String;
Node: TAVLTreeNode;
Item: PStringToStringItem;
Filename: String;
HasChanged: boolean;
li, Cur: TListItem;
OldCount, i: Integer;
begin
HasChanged:=False;
NewEncoding:=NormalizeEncoding(NewEncodingComboBox.Text);
PreviewListView.BeginUpdate;
OldCount := PreviewListView.Items.Count;
Node:=FFiles.Tree.FindLowest;
while Node<>nil do begin
Item:=PStringToStringItem(Node.Data);
Filename:=Item^.Name;
Encoding:=Item^.Value;
Cur := PreviewListView.Items.FindCaption(0, Filename, False, True, False);
if Assigned(Cur) and Cur.Checked then
begin
DebugLn(['TChgEncodingDialog.ApplyButtonClick Filename=',Filename,' Encoding=',Encoding]);
Buf:=CodeToolBoss.LoadFile(Filename,true,false);
if (Buf<>nil) and (not Buf.ReadOnly) then begin
OldEncoding:=Buf.DiskEncoding;
SrcEdit:=SourceEditorManagerIntf.SourceEditorIntfWithFilename(Filename);
HasChanged:=true;
if SrcEdit<>nil then begin
DebugLn(['TChgEncodingDialog.ApplyButtonClick changing in source editor: ',Filename]);
Buf.DiskEncoding:=NewEncoding;
SrcEdit.Modified:=true;
end else begin
DebugLn(['TChgEncodingDialog.ApplyButtonClick changing on disk: ',Filename]);
// Buf:=CodeToolBoss.LoadFile(Filename,true,false);
Buf.DiskEncoding:=NewEncoding;
HasChanged:=Buf.Save;
if not HasChanged then
Buf.DiskEncoding:=OldEncoding;
end;
end;
if not HasChanged then begin
li:=PreviewListView.Items.Add;
li.Caption:=Filename;
li.SubItems.Add(Encoding);
li.Checked := True;
end;
end;
Node:=FFiles.Tree.FindSuccessor(Node);
end;
// Now delete all old nodes in PreviewListView
for i := OldCount - 1 downto 0 do PreviewListView.Items.Delete(i);
PreviewListView.EndUpdate;
PreviewGroupBox.Caption:=Format(lisEncodingNumberOfFilesFailed, [PreviewListView.Items.Count]);
end;
procedure TChgEncodingDialog.HelpButtonClick(Sender: TObject);
begin
LazarusHelp.ShowHelpForIDEControl(Self);
end;
procedure TChgEncodingDialog.PreviewClick(Sender: TObject);
begin
UpdatePreview;
end;
function TChgEncodingDialog.GetFiles: Boolean;
// Returns true if some files were found, even if they were not added to list.
var
AProject: TProject;
SearchPath: String;
APackage: TLazPackage;
Dir: String;
FileInfo: TSearchRec;
CurFilename: String;
Buf: TCodeBuffer;
CurEncoding, NewEncoding: String;
IncludeFilterRegExpr: TRegExpr;
CurOwner: TObject;
Expr: String;
ok: Boolean;
p: Integer;
begin
FFiles.Clear;
Result:=False;
// check owner
if OwnerComboBox.Text=lisEdtDefCurrentProject then
CurOwner:=Project1
else
CurOwner:=PackageGraph.FindPackageWithName(OwnerComboBox.Text,nil);
if CurOwner=nil then begin
DebugLn(['TChgEncodingDialog.UpdatePreview package not found: ',OwnerComboBox.Text]);
exit;
end;
// find search paths
if CurOwner is TProject then begin
AProject:=TProject(CurOwner);
SearchPath:=AProject.SourceDirectories.CreateSearchPathFromAllFiles;
SearchPath:=MergeSearchPaths(SearchPath,AProject.CompilerOptions.GetIncludePath(false));
end else begin
APackage:=TLazPackage(CurOwner);
SearchPath:=APackage.SourceDirectories.CreateSearchPathFromAllFiles;
SearchPath:=MergeSearchPaths(SearchPath,APackage.CompilerOptions.GetIncludePath(false));
end;
// find files
IncludeFilterRegExpr:=TRegExpr.Create;
try
Expr:=FileFilterCombobox.Text;
if not RegExprCheckBox.Checked then
Expr:=SimpleSyntaxToRegExpr(Expr);
ok:=false;
try
IncludeFilterRegExpr.Expression:=Expr;
IncludeFilterRegExpr.Compile;
ok:=true;
except
on E: Exception do begin
DebugLn('Invalid Include File Expression ',Expr,' ',E.Message);
RegExprErrorLabel.Caption:=E.Message;
end;
end;
RegExprErrorLabel.Visible := not ok;
if not ok then exit;
NewEncoding:=NormalizeEncoding(NewEncodingComboBox.Text);
p:=1;
while (p<=length(SearchPath)) do begin
Dir:=GetNextDirectoryInSearchPath(SearchPath,p);
if Dir='' then continue;
Dir:=AppendPathDelim(Dir);
DebugLn(['TChgEncodingDialog.GetFiles Dir=',Dir]);
if FindFirstUTF8(Dir+FileMask,faAnyFile,FileInfo)=0 then
try
repeat
// check if special file
if (FileInfo.Name='.') or (FileInfo.Name='..') or (FileInfo.Name='') then
continue;
CurFilename:=Dir+FileInfo.Name;
if FFiles.Contains(CurFilename) then
continue;
if not IncludeFilterRegExpr.Exec(CurFilename) then
continue;
if not FileIsTextCached(CurFilename) then
continue;
if (FileInfo.Attr and faDirectory)>0 then begin
// skip directory
end else begin
Buf:=CodeToolBoss.LoadFile(CurFilename,true,false);
if Buf<>nil then begin
//DebugLn(['TChgEncodingDialog.GetFiles Filename=',CurFilename,' Encoding=',NormalizeEncoding(Buf.DiskEncoding)]);
CurEncoding:=NormalizeEncoding(Buf.DiskEncoding);
Result:=True;
if CurEncoding=NewEncoding then
continue;
if (CurEncoding=EncodingUTF8) and (not UTF8FilesCheckBox.Checked) then
continue;
if (CurEncoding<>EncodingUTF8) and (not NonUTF8FilesCheckBox.Checked) then
continue;
FFiles[CurFilename]:=Buf.DiskEncoding;
end else begin
DebugLn(['TChgEncodingDialog.UpdatePreview read error: ',CurFilename]);
end;
end;
until FindNextUTF8(FileInfo)<>0;
finally
FindCloseUTF8(FileInfo);
end;
end;
finally
IncludeFilterRegExpr.Free;
end;
end;
procedure TChgEncodingDialog.UpdatePreview;
var
Node: TAVLTreeNode;
Item: PStringToStringItem;
Filename: String;
Encoding: String;
li: TListItem;
HasFiles: Boolean;
IsDone: Boolean;
begin
Screen.BeginWaitCursor;
try
HasFiles:=GetFiles;
PreviewListView.Items.Clear;
IsDone:=HasFiles and (FFiles.Tree.Count=0);
PreviewGroupBox.Visible:=not IsDone;
LabelNoPreview.Visible:=IsDone;
LabelNoPreview.Caption:=lisFilesHaveRightEncoding;
ApplyButton.Enabled:=not IsDone;
if IsDone then exit;
PreviewListView.BeginUpdate;
Node:=FFiles.Tree.FindLowest;
while Node<>nil do begin
Item:=PStringToStringItem(Node.Data);
Filename:=Item^.Name;
Encoding:=Item^.Value;
DebugLn(['TChgEncodingDialog.UpdatePreview Filename=',Filename,' Encoding=',Encoding]);
li:=PreviewListView.Items.Add;
li.Caption:=Filename;
li.SubItems.Add(Encoding);
li.Checked := True;
Node:=FFiles.Tree.FindSuccessor(Node);
end;
PreviewListView.EndUpdate;
PreviewGroupBox.Caption:=
Format(lisNumberOfFilesToConvert, [IntToStr(PreviewListView.Items.Count)]);
ApplyButton.Enabled:=True;
finally
Screen.EndWaitCursor;
end;
end;
end.