{ *************************************************************************** * * * 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: TAddToPackageDlg is the form for adding files to an open package. } unit AddToPackageDlg; {$mode objfpc}{$H+} interface uses Classes, SysUtils, AVL_Tree, // LCL LCLType, Forms, Controls, Buttons, ExtDlgs, StdCtrls, ExtCtrls, Dialogs, ComCtrls, ButtonPanel, // LazUtils FileUtil, LazFileUtils, LazUtilities, LazUTF8, // BuildIntf ComponentReg, // IDEIntf NewItemIntf, PackageIntf, FormEditingIntf, IDEWindowIntf, IDEDialogs, InputHistory, // IdeUtils IdeUtilsPkgStrConsts, // IdeConfig EnvironmentOpts, // IDE LazarusIDEStrConsts, PackageSystem, PackageDefs, ProjPackChecks; type { TAddToPkgResult } TAddToPkgResult = class public Pkg: TLazPackage; UnitFilename: string; Unit_Name: string; AncestorType: string; NewClassName: string; PageName: string; FileType: TPkgFileType; PkgFileFlags: TPkgFileFlags; UsedUnitname: string; IconNormFile: string; Icon150File: string; Icon200File: string; AutoAddLFMFile: boolean; AutoAddLRSFile: boolean; NewItem: TNewIDEItemTemplate; Next: TAddToPkgResult; procedure Clear; destructor Destroy; override; end; { TIconGuiStuff } TIconGuiStuff = class // Join icon related GUI controls together. Streamlines the code. private Btn: TBitBtn; InfoLabel: TLabel; Title: string; FileName: string; public constructor Create(aBtn: TBitBtn; aInfoLabel: TLabel; aTitle: string); procedure LoadIcon(aLazPackage: TLazPackage; aFileName: string); end; { TAddToPackageDlg } TAddToPackageDlg = class(TForm) AncestorComboBox: TComboBox; AncestorShowAllCheckBox: TCheckBox; AncestorTypeLabel: TLabel; ButtonPanel1: TButtonPanel; ClassNameEdit: TEdit; ClassNameLabel: TLabel; Icon200Label: TLabel; IconNormBitBtn: TBitBtn; Icon150BitBtn: TBitBtn; Icon150InfoLabel: TLabel; Icon200InfoLabel: TLabel; IconNormLabel: TLabel; UnitFilenameExistsLabel: TLabel; UnitDirectoryBrowseButton: TButton; UnitDirectoryEdit: TEdit; UnitDirectoryLabel: TLabel; UnitDirectoryShortenButton: TButton; UnitNameEdit: TEdit; UnitNameLabel: TLabel; Icon200BitBtn: TBitBtn; Icon150Label: TLabel; IconNormInfoLabel: TLabel; UnitFilenameLabel: TLabel; PalettePageCombobox: TComboBox; PalettePageLabel: TLabel; procedure AddToPackageDlgClose(Sender: TObject; var {%H-}CloseAction: TCloseAction); procedure AncestorComboBoxChange(Sender: TObject); procedure AncestorComboBoxCloseUp(Sender: TObject); procedure AncestorShowAllCheckBoxClick(Sender: TObject); procedure ClassNameEditChange(Sender: TObject); procedure IconBitBtnClick(Sender: TObject); procedure UnitDirectoryBrowseButtonClick(Sender: TObject); procedure UnitDirectoryShortenButtonClick(Sender: TObject); procedure UnitDirectoryEditChange(Sender: TObject); procedure UnitNameEditChange(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure NewComponentButtonClick(Sender: TObject); private fLastNewAncestorType: string; fLastNewClassName: string; FLazPackage: TLazPackage; fPkgComponents: TAVLTree;// tree of TPkgComponent fPackages: TAVLTree;// tree of TLazPackage or TPackageLink fParams: TAddToPkgResult; fIconDlg: TOpenPictureDialog; fIconNormGUI: TIconGuiStuff; fIcon150GUI: TIconGuiStuff; fIcon200GUI: TIconGuiStuff; function GenerateUnitFileName: string; function RelatedIconFile(aSuffix: string): string; procedure SetLazPackage(const AValue: TLazPackage); procedure OnIterateComponentClasses(PkgComponent: TPkgComponent); function CheckNewCompOk: Boolean; procedure AutoCompleteNewComponent; procedure AutoCompleteUnitName; procedure UpdateUnitFilename; public procedure UpdateAvailableAncestorTypes; procedure UpdateAvailablePageNames; public property LazPackage: TLazPackage read FLazPackage write SetLazPackage; end; function ShowAddToPackageDlg(Pkg: TLazPackage; out Params: TAddToPkgResult): TModalResult; implementation {$R *.lfm} function ShowAddToPackageDlg(Pkg: TLazPackage; out Params: TAddToPkgResult): TModalResult; var AddDlg: TAddToPackageDlg; begin Params:=nil; AddDlg:=TAddToPackageDlg.Create(nil); try AddDlg.LazPackage:=Pkg; Result:=AddDlg.ShowModal; if Result=mrOk then begin Params:=AddDlg.fParams; AddDlg.fParams:=nil; end; finally AddDlg.Free; end; end; { TIconGuiStuff } constructor TIconGuiStuff.Create(aBtn: TBitBtn; aInfoLabel: TLabel; aTitle: string); begin Btn:=aBtn; InfoLabel:=aInfoLabel; Title:=aTitle; InfoLabel.Caption:=''; end; procedure TIconGuiStuff.LoadIcon(aLazPackage: TLazPackage; aFileName: string); var ShortFN: String; Image: TImage; W, H: Integer; begin Filename:=aFileName; try Image:=TImage.Create(nil); try Image.Picture.LoadFromFile(Filename); W:=Image.Picture.Graphic.Width+6; H:=Image.Picture.Graphic.Height+6; if W > 32 then Btn.Width:=W; if H > 32 then Btn.Height:=H; Btn.Glyph.Assign(Image.Picture.Graphic); ShortFN:=Filename; aLazPackage.ShortenFilename(ShortFN,true); InfoLabel.Caption:=Format('%s (%dx%d)',[ShortFN, Btn.Glyph.Width, Btn.Glyph.Height]); finally Image.Free; end; except on E: Exception do begin IDEMessageDialog(lisCCOErrorCaption, Format(lisErrorLoadingFile2,[FileName]) + LineEnding + E.Message, mtError, [mbCancel]); Btn.Glyph.Clear; InfoLabel.Caption:=lisNoneClickToChooseOne; FileName:=''; end; end; end; { TAddToPackageDlg } procedure TAddToPackageDlg.FormCreate(Sender: TObject); begin Caption:=lisMenuNewComponent; fPkgComponents:=TAVLTree.Create(@CompareIDEComponentByClass); fPackages:=TAVLTree.Create(@CompareLazPackageID); fParams:=TAddToPkgResult.Create; IDEDialogLayoutList.ApplyLayout(Self,700,400); // Setup Components ButtonPanel1.OkButton.Caption:=lisA2PCreateNewComp; ButtonPanel1.OkButton.OnClick:=@NewComponentButtonClick; CheckNewCompOk; AncestorTypeLabel.Caption:=lisA2PAncestorType; AncestorComboBox.Text:=''; AncestorShowAllCheckBox.Caption:=lisA2PShowAll; ClassNameLabel.Caption:=lisA2PNewClassName; ClassNameEdit.Text:=''; PalettePageLabel.Caption:=lisA2PPalettePage; PalettePageCombobox.Text:=''; UnitDirectoryLabel.Caption:=lisA2PDirectoryForUnitFile; UnitDirectoryEdit.Text:=''; UnitFilenameLabel.Caption:=''; UnitFilenameExistsLabel.Caption:=''; with UnitDirectoryBrowseButton do begin Caption:='...'; ShowHint:=true; Hint:=lisChooseDirectory; end; with UnitDirectoryShortenButton do begin Caption:='<>'; ShowHint:=true; Hint:=lisA2PShortenOrExpandFilename; end; UnitNameLabel.Caption:=lisA2PUnitName; UnitNameEdit.Text:=''; IconNormLabel.Caption:=lisA2PIcon24x24; Icon150Label.Caption:=lisA2PIcon36x36; Icon200Label.Caption:=lisA2PIcon48x48; fIconDlg:=TOpenPictureDialog.Create(nil); // Helper objects to join icon related GUI controls together fIconNormGUI:=TIconGuiStuff.Create(IconNormBitBtn, IconNormInfoLabel, lisA2PIcon24x24); fIcon150GUI:=TIconGuiStuff.Create(Icon150BitBtn, Icon150InfoLabel, lisA2PIcon36x36); fIcon200GUI:=TIconGuiStuff.Create(Icon200BitBtn, Icon200InfoLabel, lisA2PIcon48x48); AncestorComboBox.DropDownCount:=EnvironmentOptions.DropDownCount; PalettePageCombobox.DropDownCount:=EnvironmentOptions.DropDownCount; end; procedure TAddToPackageDlg.FormDestroy(Sender: TObject); begin FreeAndNil(fIcon200GUI); FreeAndNil(fIcon150GUI); FreeAndNil(fIconNormGUI); FreeAndNil(fIconDlg); FreeAndNil(fParams); FreeAndNil(fPackages); FreeAndNil(fPkgComponents); end; procedure TAddToPackageDlg.AddToPackageDlgClose(Sender: TObject; var CloseAction: TCloseAction); begin IDEDialogLayoutList.SaveLayout(Self); end; procedure TAddToPackageDlg.AncestorComboBoxChange(Sender: TObject); begin CheckNewCompOk; end; procedure TAddToPackageDlg.AncestorComboBoxCloseUp(Sender: TObject); begin if fLastNewAncestorType<>AncestorComboBox.Text then AutoCompleteNewComponent; end; procedure TAddToPackageDlg.AncestorShowAllCheckBoxClick(Sender: TObject); begin UpdateAvailableAncestorTypes; end; procedure TAddToPackageDlg.ClassNameEditChange(Sender: TObject); begin AutoCompleteUnitName; CheckNewCompOk; end; function TAddToPackageDlg.RelatedIconFile(aSuffix: string): string; var Ext: String; begin Ext := ExtractFileExt(fIconDlg.FileName); Result := ExtractFileNameWithoutExt(fIconDlg.FileName)+ASuffix+Ext; end; procedure TAddToPackageDlg.IconBitBtnClick(Sender: TObject); var Btn: TBitBtn; IconGUI: TIconGuiStuff; OtherIconFile: string; begin Btn:=Sender as TBitBtn; if Btn = IconNormBitBtn then IconGUI:=fIconNormGUI else if Btn = Icon150BitBtn then IconGUI:=fIcon150GUI else if Btn = Icon200BitBtn then IconGUI:=fIcon200GUI; if fIconDlg.InitialDir='' then fIconDlg.InitialDir:=LazPackage.Directory; fIconDlg.Title:=IconGUI.Title; fIconDlg.Options:=fIconDlg.Options+[ofPathMustExist]; fIconDlg.Filter:=Format('%s|*.png|%s|*.bmp|%s|*.xpm|%s|%s', [dlgFilterImagesPng, dlgFilterImagesBitmap, dlgFilterImagesPixmap, dlgFilterAll, GetAllFilesMask]); if fIconDlg.Execute then begin IconGUI.LoadIcon(LazPackage, fIconDlg.FileName); // Load high resolution icons automatically if found. if Btn = IconNormBitBtn then begin // 150% OtherIconFile:=RelatedIconFile('_150'); if FileExists(OtherIconFile) then fIcon150GUI.LoadIcon(LazPackage, OtherIconFile); // 200% OtherIconFile:=RelatedIconFile('_200'); if FileExists(OtherIconFile) then fIcon200GUI.LoadIcon(LazPackage, OtherIconFile); end; end; end; procedure TAddToPackageDlg.UnitDirectoryBrowseButtonClick(Sender: TObject); var DirDialog: TSelectDirectoryDialog; begin DirDialog:=TSelectDirectoryDialog.Create(nil); try InputHistories.ApplyFileDialogSettings(DirDialog); DirDialog.InitialDir:=LazPackage.Directory; DirDialog.Title:=lisA2PDirectoryForUnitFile; //DirDialog.Options:=DirDialog.Options+[ofPathMustExist]; //DirDialog.Filter:=Format('%s|*.pas;*.pp', [dlgFilterPascalFile]); if DirDialog.Execute then begin UnitDirectoryEdit.Text:=DirDialog.Filename; UpdateUnitFilename; end; InputHistories.StoreFileDialogSettings(DirDialog); finally DirDialog.Free; end; end; procedure TAddToPackageDlg.UnitDirectoryShortenButtonClick(Sender: TObject); var S: string; begin Assert(LazPackage.HasDirectory and FilenameIsAbsolute(LazPackage.Directory), 'Unexpected package directory'); S:=UnitDirectoryEdit.Text; if (S='') then S:='.'; // Toggle between absolute and relative paths. if FilenameIsAbsolute(S) then UnitDirectoryEdit.Text:=CreateRelativePath(S,LazPackage.Directory,True) else UnitDirectoryEdit.Text:=CreateAbsolutePath(S,LazPackage.Directory); UpdateUnitFilename; end; procedure TAddToPackageDlg.UnitDirectoryEditChange(Sender: TObject); begin UpdateUnitFilename; if UnitDirectoryEdit.Text<>'' then fIconDlg.InitialDir:=UnitDirectoryEdit.Text; end; procedure TAddToPackageDlg.UnitNameEditChange(Sender: TObject); begin CheckNewCompOk; UpdateUnitFilename; end; procedure TAddToPackageDlg.NewComponentButtonClick(Sender: TObject); var PkgFile: TPkgFile; PkgComponent: TPkgComponent; ARequiredPackage: TLazPackage; NewDependency: TPkgDependency; ThePath: String; begin fParams.Clear; fParams.FileType:=pftUnit; fParams.PkgFileFlags:=[pffHasRegisterProc,pffAddToPkgUsesSection]; fParams.AncestorType:=AncestorComboBox.Text; fParams.NewClassName:=ClassNameEdit.Text; fParams.PageName:=PalettePageCombobox.Text; fParams.Unit_Name:=UnitNameEdit.Text; fParams.UsedUnitname:=''; fParams.IconNormFile:=fIconNormGUI.Filename; fParams.Icon150File:=fIcon150GUI.Filename; fParams.Icon200File:=fIcon200GUI.Filename; // prepend path to unit filename ThePath:=UnitDirectoryEdit.Text; if ThePath='' then ThePath:='.'; ThePath:=CreateAbsolutePath(ThePath,LazPackage.Directory); if not DirectoryExists(ThePath) then if not ForceDirectories(ThePath) then raise Exception.Create('NewComponentButtonClick: Cannot create directory '+ThePath); fParams.UnitFilename:=AppendPathDelim(ThePath)+GenerateUnitFileName; // check if package is readonly if LazPackage.ReadOnly then begin IDEMessageDialog(lisAF2PPackageIsReadOnly, Format(lisAF2PThePackageIsReadOnly, [LazPackage.IDAsString]), mtError,[mbCancel]); exit; end; // check Ancestor Type if not IsValidIdent(fParams.AncestorType) then begin IDEMessageDialog(lisA2PInvalidAncestorType, Format(lisA2PTheAncestorTypeIsNotAValidPascalIdentifier, [fParams.AncestorType]), mtError,[mbCancel]); exit; end; // check pagename if length(fParams.PageName)>100 then begin IDEMessageDialog(lisA2PPageNameTooLong, Format(lisA2PThePageNameIsTooLongMax100Chars, [fParams.PageName]), mtError,[mbCancel]); exit; end; // check classname if not IsValidIdent(fParams.NewClassName) then begin IDEMessageDialog(lisA2PInvalidClassName, Format(lisA2PTheClassNameIsNotAValidPascalIdentifier, [fParams.NewClassName]), mtError,[mbCancel]); exit; end; // check classname<>ancestortype if CompareText(fParams.NewClassName,fParams.AncestorType)=0 then begin IDEMessageDialog(lisA2PInvalidCircularDependency, Format(lisA2PTheClassNameAndAncestorTypeAreTheSame,[fParams.NewClassName,fParams.AncestorType]), mtError,[mbCancel]); exit; end; // check ancestor type is not unitname PkgFile:=PackageGraph.FindUnit(LazPackage,fParams.AncestorType,true,true); if PkgFile<>nil then begin if IDEMessageDialog(lisA2PAmbiguousAncestorType, Format(lisA2PTheAncestorTypeHasTheSameNameAsTheUnit, [fParams.AncestorType, LineEnding, PkgFile.Filename]), mtError,[mbCancel,mbIgnore])<>mrIgnore then exit; end; // check classname does not interfere with an existing unitname PkgFile:=PackageGraph.FindUnit(LazPackage,fParams.NewClassName,true,true); if PkgFile<>nil then begin if IDEMessageDialog(lisA2PAmbiguousClassName, Format(lisA2PTheClassNameHasTheSameNameAsTheUnit, [fParams.AncestorType, LineEnding, PkgFile.Filename]), mtError,[mbCancel,mbIgnore])<>mrIgnore then exit; end; // check if classname already exists PkgComponent:=TPkgComponent(IDEComponentPalette.FindRegComponent(fParams.NewClassname)); if PkgComponent<>nil then begin if IDEMessageDialog(lisA2PClassNameAlreadyExists, Format(lisA2PTheClassNameExistsAlreadyInPackageFile, [fParams.NewClassName, LineEnding, PkgComponent.PkgFile.LazPackage.IDAsString, LineEnding, PkgComponent.PkgFile.Filename]), mtError,[mbCancel,mbIgnore])<>mrIgnore then exit; end; // check if unitname is a componentclass if IDEComponentPalette.FindRegComponent(fParams.Unit_Name)<>nil then begin if IDEMessageDialog(lisA2PAmbiguousUnitName, Format(lisA2PTheUnitNameIsTheSameAsAnRegisteredComponent,[fParams.Unit_Name,LineEnding]), mtWarning,[mbCancel,mbIgnore])<>mrIgnore then exit; end; // create dependency if needed PkgComponent:=TPkgComponent(IDEComponentPalette.FindRegComponent(fParams.AncestorType)); if PkgComponent<>nil then begin fParams.UsedUnitname:=PkgComponent.GetUnitName; ARequiredPackage:=PkgComponent.PkgFile.LazPackage; ARequiredPackage:=TLazPackage(PackageEditingInterface.RedirectPackageDependency(ARequiredPackage)); NewDependency:=TPkgDependency.Create; NewDependency.DependencyType:=pdtLazarus; NewDependency.PackageName:=ARequiredPackage.Name; if TPkgFileCheck.AddingDependency(LazPackage,NewDependency,false)=mrOK then PackageGraph.AddDependencyToPackage(LazPackage, NewDependency) else NewDependency.Free; end; ModalResult:=mrOk; end; procedure TAddToPackageDlg.SetLazPackage(const AValue: TLazPackage); begin if FLazPackage=AValue then exit; FLazPackage:=AValue; fParams.Pkg:=FLazPackage; UpdateAvailableAncestorTypes; UpdateAvailablePageNames; end; function TAddToPackageDlg.CheckNewCompOk: Boolean; begin Result:=(AncestorComboBox.Text<>'') and (ClassNameEdit.Text<>'') and (UnitNameEdit.Text<>''); ButtonPanel1.OKButton.Enabled:=Result; end; procedure TAddToPackageDlg.OnIterateComponentClasses(PkgComponent: TPkgComponent); begin if fPkgComponents.Find(PkgComponent)=nil then fPkgComponents.Add(PkgComponent); end; function TAddToPackageDlg.GenerateUnitFileName: string; begin Result:=UnitNameEdit.Text; if Result='' then Exit; if EnvironmentOptions.CharcaseFileAction in [ccfaAsk, ccfaAutoRename] then Result:=LowerCase(Result); // append pascal file extension Result:=Result+PascalExtension[EnvironmentOptions.PascalFileExtension]; end; procedure TAddToPackageDlg.AutoCompleteNewComponent; var PkgComponent: TPkgComponent; begin fLastNewAncestorType:=AncestorComboBox.Text; if not IsValidIdent(fLastNewAncestorType) then exit; PkgComponent:=TPkgComponent(IDEComponentPalette.FindRegComponent(fLastNewAncestorType)); // create unique classname ClassNameEdit.Text:=IDEComponentPalette.CreateNewClassName(fLastNewAncestorType); // choose the same page name if (PkgComponent<>nil) and (PkgComponent.RealPage<>nil) then PalettePageCombobox.Text:=PkgComponent.RealPage.PageName; // filename AutoCompleteUnitName; ButtonPanel1.OkButton.Enabled:=True; end; procedure TAddToPackageDlg.AutoCompleteUnitName; var CurClassName: String; NewUnitName: String; begin // check if update needed CurClassName:=ClassNameEdit.Text; if fLastNewClassName=CurClassName then exit; fLastNewClassName:=CurClassName; // check classname if not IsValidIdent(CurClassName) then exit; // create unitname NewUnitName:=CurClassName; if NewUnitName[1]='T' then NewUnitName:=copy(NewUnitName,2,length(NewUnitName)-1); NewUnitName:=PackageGraph.CreateUniqueUnitName(NewUnitName); UnitNameEdit.Text:=NewUnitName; // default directory UnitDirectoryEdit.Text:=LazPackage.Directory; end; procedure TAddToPackageDlg.UpdateUnitFilename; begin UnitFilenameLabel.Caption:=AppendPathDelim(UnitDirectoryEdit.Text)+GenerateUnitFileName; if FileExists(UnitFilenameLabel.Caption) then UnitFilenameExistsLabel.Caption:=lisA2PFileAlreadyExists else UnitFilenameExistsLabel.Caption:=''; end; procedure TAddToPackageDlg.UpdateAvailableAncestorTypes; var ANode: TAVLTreeNode; sl: TStringListUTF8Fast; OldAncestorType: String; begin // get all available registered components fPkgComponents.Clear; if AncestorShowAllCheckBox.Checked then begin PackageGraph.IterateAllComponentClasses(@OnIterateComponentClasses); end else begin PackageGraph.IterateComponentClasses(LazPackage,@OnIterateComponentClasses, true,true); end; // put them into a list sl:=TStringListUTF8Fast.Create; ANode:=fPkgComponents.FindLowest; while ANode<>nil do begin sl.Add(TPkgComponent(ANode.Data).ComponentClass.ClassName); ANode:=fPkgComponents.FindSuccessor(ANode); end; // add at least TComponent sl.Add('TComponent'); sl.Sort; // put them into the combobox OldAncestorType:=AncestorComboBox.Text; AncestorComboBox.Items.Assign(sl); AncestorComboBox.Text:=OldAncestorType; sl.Free; end; procedure TAddToPackageDlg.UpdateAvailablePageNames; var i: Integer; APageName: String; sl: TStringListUTF8Fast; begin // get all current pagenames (excluding the hidden page) sl:=TStringListUTF8Fast.Create; for i:=0 to IDEComponentPalette.Pages.Count-1 do begin APageName:=IDEComponentPalette.Pages[i].PageName; if APageName<>'' then sl.Add(APageName); end; sl.Sort; PalettePageCombobox.Items.Assign(sl); sl.Free; end; { TAddToPkgResult } procedure TAddToPkgResult.Clear; begin UnitFilename:=''; Unit_Name:=''; AncestorType:=''; NewClassName:=''; PageName:=''; FileType:=pftUnit; PkgFileFlags:=[]; UsedUnitname:=''; AutoAddLFMFile:=false; AutoAddLRSFile:=false; FreeThenNil(Next); end; destructor TAddToPkgResult.Destroy; begin FreeThenNil(Next); inherited Destroy; end; end.