{ /*************************************************************************** installpkgsetdlg.pas -------------------- ***************************************************************************/ *************************************************************************** * * * 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: Dialog to edit the package set installed in the IDE. } unit InstallPkgSetDlg; {$mode objfpc}{$H+} interface uses Classes, SysUtils, contnrs, Laz_AVL_Tree, // LCL LCLType, LCLProc, Forms, Controls, Graphics, Dialogs, StdCtrls, Buttons, ExtCtrls, ComCtrls, ImgList, // LazControls TreeFilterEdit, // Codetools BasicCodeTools, // LazUtils LazFileUtils, Laz2_XMLCfg, LazUTF8, // IdeIntf PackageDependencyIntf, PackageIntf, IDEImagesIntf, IDEHelpIntf, IDEDialogs, IDEWindowIntf, PackageLinkIntf, // IDE LazarusIDEStrConsts, InputHistory, LazConf, PackageDefs, PackageSystem, LPKCache, PackageLinks; type TOnCheckInstallPackageList = procedure(PkgIDs: TObjectList; RemoveConflicts: boolean; out Ok: boolean) of object; { TInstallPkgSetDialog } TInstallPkgSetDialog = class(TForm) AddToInstallButton: TBitBtn; AvailableTreeView: TTreeView; AvailablePkgGroupBox: TGroupBox; MiddleBevel: TBevel; HelpButton: TBitBtn; CancelButton: TBitBtn; ExportButton: TButton; BtnPanel: TPanel; InstallTreeView: TTreeView; AvailableFilterEdit: TTreeFilterEdit; LPKParsingTimer: TTimer; NoteLabel: TLabel; Panel1: TPanel; Panel2: TPanel; PkgInfoMemo: TMemo; PkgInfoGroupBox: TGroupBox; ImportButton: TButton; PkgInfoMemoLicense: TMemo; SaveAndExitButton: TBitBtn; InstallPkgGroupBox: TGroupBox; SaveAndRebuildButton: TBitBtn; InstalledFilterEdit: TTreeFilterEdit; Splitter1: TSplitter; Splitter2: TSplitter; UninstallButton: TBitBtn; procedure AddToInstallButtonClick(Sender: TObject); function FilterEditGetImageIndex({%H-}Str: String; {%H-}Data: TObject; var {%H-}AIsEnabled: Boolean): Integer; procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction); procedure InstallTreeViewKeyPress(Sender: TObject; var Key: char); procedure LPKParsingTimerTimer(Sender: TObject); procedure OnAllLPKParsed(Sender: TObject); procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean); procedure TreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; {%H-}State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages, {%H-}DefaultDraw: Boolean); procedure AvailableTreeViewDblClick(Sender: TObject); procedure AvailableTreeViewKeyPress(Sender: TObject; var Key: char); procedure AvailableTreeViewSelectionChanged(Sender: TObject); procedure ExportButtonClick(Sender: TObject); procedure HelpButtonClick(Sender: TObject); procedure ImportButtonClick(Sender: TObject); procedure SaveAndRebuildButtonClick(Sender: TObject); procedure InstallTreeViewDblClick(Sender: TObject); procedure InstallPkgSetDialogCreate(Sender: TObject); procedure InstallPkgSetDialogDestroy(Sender: TObject); procedure InstallPkgSetDialogShow(Sender: TObject); procedure InstallPkgSetDialogResize(Sender: TObject); procedure InstallTreeViewSelectionChanged(Sender: TObject); procedure SaveAndExitButtonClick(Sender: TObject); procedure UninstallButtonClick(Sender: TObject); private FIdleConnected: boolean; FNewInstalledPackages: TObjectList; // list of TLazPackageID (not TLazPackage) FOldInstalledPackages: TPkgDependency; FOnCheckInstallPackageList: TOnCheckInstallPackageList; FRebuildIDE: boolean; FSelectedPkgState: TLPKInfoState; FSelectedPkgID: string; fAvailablePkgsNeedUpdate: boolean; ImgIndexPackage: integer; ImgIndexInstallPackage: integer; ImgIndexInstalledPackage: integer; ImgIndexUninstallPackage: integer; ImgIndexCirclePackage: integer; ImgIndexMissingPackage: integer; ImgIndexAvailableOnline: integer; ImgIndexOverlayUnknown: integer; ImgIndexOverlayBasePackage: integer; ImgIndexOverlayFPCPackage: integer; ImgIndexOverlayLazarusPackage: integer; ImgIndexOverlayDesigntimePackage: integer; ImgIndexOverlayRuntimePackage: integer; procedure SetIdleConnected(AValue: boolean); procedure SetOldInstalledPackages(const AValue: TPkgDependency); procedure AssignOldInstalledPackagesToList; function PackageInInstallList(PkgName: string): boolean; function GetPkgImgIndex(Installed: TPackageInstallType; InInstallList, IsOnline: boolean): integer; procedure UpdateAvailablePackages(Immediately: boolean = false); procedure UpdateNewInstalledPackages; function DependencyToStr(Dependency: TPkgDependency): string; procedure ClearNewInstalledPackages; function CheckSelection: boolean; procedure UpdateButtonStates; procedure UpdatePackageInfo(Tree: TTreeView); function NewInstalledPackagesContains(APackageID: TLazPackageID): boolean; function IndexOfNewInstalledPackageID(APackageID: TLazPackageID): integer; function IndexOfNewInstalledPkgByName(const APackageName: string): integer; procedure SavePackageListToFile(const AFilename: string); procedure LoadPackageListFromFile(const AFilename: string); function ExtractNameFromPkgID(ID: string): string; procedure AddToInstall; procedure AddToUninstall; procedure PkgInfosChanged; procedure ChangePkgVersion(PkgInfo: TLPKInfo; NewVersion: TPkgVersion); function FindOnlinePackageLink(const AName: String): TPackageLink; public function GetNewInstalledPackages: TObjectList; property OldInstalledPackages: TPkgDependency read FOldInstalledPackages write SetOldInstalledPackages; property NewInstalledPackages: TObjectList read FNewInstalledPackages; // list of TLazPackageID property RebuildIDE: boolean read FRebuildIDE write FRebuildIDE; property OnCheckInstallPackageList: TOnCheckInstallPackageList read FOnCheckInstallPackageList write FOnCheckInstallPackageList; property IdleConnected: boolean read FIdleConnected write SetIdleConnected; end; function ShowEditInstallPkgsDialog(OldInstalledPackages: TPkgDependency; CheckInstallPackageList: TOnCheckInstallPackageList; var NewInstalledPackages: TObjectList; // list of TLazPackageID (must be freed) var RebuildIDE: boolean): TModalResult; implementation {$R *.lfm} procedure SetControlsWidthOnMax(AControls: array of TControl); var i, MaxWidth: Integer; begin MaxWidth:=0; for i:=Low(AControls) to High(AControls) do if AControls[i].Width>MaxWidth then MaxWidth:=AControls[i].Width; for i:=Low(AControls) to High(AControls) do AControls[i].Constraints.MinWidth:=MaxWidth; // AutoSize=True end; function ShowEditInstallPkgsDialog(OldInstalledPackages: TPkgDependency; CheckInstallPackageList: TOnCheckInstallPackageList; var NewInstalledPackages: TObjectList; // list of TLazPackageID var RebuildIDE: boolean): TModalResult; var InstallPkgSetDialog: TInstallPkgSetDialog; begin NewInstalledPackages:=nil; InstallPkgSetDialog:=TInstallPkgSetDialog.Create(nil); try InstallPkgSetDialog.OldInstalledPackages:=OldInstalledPackages; InstallPkgSetDialog.UpdateButtonStates; InstallPkgSetDialog.OnCheckInstallPackageList:=CheckInstallPackageList; Result:=InstallPkgSetDialog.ShowModal; NewInstalledPackages:=InstallPkgSetDialog.GetNewInstalledPackages; RebuildIDE:=InstallPkgSetDialog.RebuildIDE; finally InstallPkgSetDialog.Free; end; end; { TInstallPkgSetDialog } procedure TInstallPkgSetDialog.InstallPkgSetDialogCreate(Sender: TObject); begin IDEDialogLayoutList.ApplyLayout(Self); InstallTreeView.Images := IDEImages.Images_16; AvailableTreeView.Images := IDEImages.Images_16; ImgIndexPackage := IDEImages.LoadImage('item_package'); ImgIndexInstalledPackage := IDEImages.LoadImage('pkg_installed'); ImgIndexInstallPackage := IDEImages.LoadImage('pkg_package_autoinstall'); ImgIndexUninstallPackage := IDEImages.LoadImage('pkg_package_uninstall'); ImgIndexCirclePackage := IDEImages.LoadImage('pkg_package_circle'); ImgIndexMissingPackage := IDEImages.LoadImage('pkg_conflict'); ImgIndexAvailableOnline := IDEImages.LoadImage('pkg_install'); ImgIndexOverlayUnknown := IDEImages.LoadImage('state_unknown'); ImgIndexOverlayBasePackage := IDEImages.LoadImage('pkg_core_overlay'); ImgIndexOverlayFPCPackage := IDEImages.LoadImage('pkg_fpc_overlay'); ImgIndexOverlayLazarusPackage := IDEImages.LoadImage('pkg_lazarus_overlay'); ImgIndexOverlayDesignTimePackage := IDEImages.LoadImage('pkg_design_overlay'); ImgIndexOverlayRunTimePackage := IDEImages.LoadImage('pkg_runtime_overlay'); Caption:=lisInstallUninstallPackages; NoteLabel.Caption:=lisIDECompileAndRestart; AvailablePkgGroupBox.Caption:=lisAvailableForInstallation; ExportButton.Caption:=lisExportList; ImportButton.Caption:=lisImportList; UninstallButton.Caption:=lisUninstallSelection; IDEImages.AssignImage(UninstallButton, 'arrow__darkred_right'); InstallPkgGroupBox.Caption:=lisPckEditInstall; AddToInstallButton.Caption:=lisInstallSelection; IDEImages.AssignImage(AddToInstallButton, 'arrow__darkgreen_left'); PkgInfoGroupBox.Caption := lisPackageInfo; SaveAndRebuildButton.Caption:=lisSaveAndRebuildIDE; SaveAndExitButton.Caption:=lisSaveAndExitDialog; HelpButton.Caption:=lisMenuHelp; CancelButton.Caption:=lisCancel; FNewInstalledPackages:=TObjectList.Create(true); PkgInfoMemo.Clear; PkgInfoMemoLicense.Clear; LPKInfoCache.AddOnQueueEmpty(@OnAllLPKParsed); LPKInfoCache.StartLPKReaderWithAllAvailable; UpdateButtonStates; end; procedure TInstallPkgSetDialog.InstallPkgSetDialogDestroy(Sender: TObject); begin LPKInfoCache.EndLPKReader; LPKInfoCache.RemoveOnQueueEmpty(@OnAllLPKParsed); ClearNewInstalledPackages; FreeAndNil(FNewInstalledPackages); IdleConnected:=false; end; procedure TInstallPkgSetDialog.InstallPkgSetDialogShow(Sender: TObject); begin InstalledFilterEdit.Filter:=''; // (filter) - text is shown after this. AvailableFilterEdit.Filter:=''; SetControlsWidthOnMax([UninstallButton, AddToInstallButton]); SetControlsWidthOnMax([ImportButton, ExportButton]); end; procedure TInstallPkgSetDialog.SaveAndRebuildButtonClick(Sender: TObject); begin if not CheckSelection then exit; RebuildIDE:=true; ModalResult:=mrOk; end; procedure TInstallPkgSetDialog.InstallTreeViewDblClick(Sender: TObject); begin AddToUninstall; end; procedure TInstallPkgSetDialog.AvailableTreeViewSelectionChanged(Sender: TObject); begin UpdateButtonStates; UpdatePackageInfo(AvailableTreeView); end; procedure TInstallPkgSetDialog.ExportButtonClick(Sender: TObject); var SaveDialog: TSaveDialog; AFilename: string; begin SaveDialog:=TSaveDialog.Create(nil); try InputHistories.ApplyFileDialogSettings(SaveDialog); SaveDialog.InitialDir:=GetPrimaryConfigPath; SaveDialog.Title:=lisExportPackageListXml; SaveDialog.Options:=SaveDialog.Options+[ofPathMustExist]; if SaveDialog.Execute then begin AFilename:=CleanAndExpandFilename(SaveDialog.Filename); if ExtractFileExt(AFilename)='' then AFilename:=AFilename+'.xml'; SavePackageListToFile(AFilename); end; InputHistories.StoreFileDialogSettings(SaveDialog); finally SaveDialog.Free; end; end; procedure TInstallPkgSetDialog.HelpButtonClick(Sender: TObject); begin LazarusHelp.ShowHelpForIDEControl(Self); end; procedure TInstallPkgSetDialog.ImportButtonClick(Sender: TObject); var OpenDialog: TOpenDialog; AFilename: string; begin OpenDialog:=TOpenDialog.Create(nil); try InputHistories.ApplyFileDialogSettings(OpenDialog); OpenDialog.InitialDir:=GetPrimaryConfigPath; OpenDialog.Title:=lisImportPackageListXml; OpenDialog.Options:=OpenDialog.Options+[ofPathMustExist,ofFileMustExist]; if OpenDialog.Execute then begin AFilename:=CleanAndExpandFilename(OpenDialog.Filename); LoadPackageListFromFile(AFilename); end; InputHistories.StoreFileDialogSettings(OpenDialog); finally OpenDialog.Free; end; end; procedure TInstallPkgSetDialog.AddToInstallButtonClick(Sender: TObject); begin AddToInstall; end; function TInstallPkgSetDialog.FilterEditGetImageIndex(Str: String; Data: TObject; var AIsEnabled: Boolean): Integer; begin Result:=0; end; procedure TInstallPkgSetDialog.FormClose(Sender: TObject; var CloseAction: TCloseAction); begin IDEDialogLayoutList.SaveLayout(Self); end; procedure TInstallPkgSetDialog.InstallTreeViewKeyPress(Sender: TObject; var Key: char); begin if Key = char(VK_RETURN) then AddToUninstall; end; procedure TInstallPkgSetDialog.LPKParsingTimerTimer(Sender: TObject); begin UpdateNewInstalledPackages; UpdateAvailablePackages; end; procedure TInstallPkgSetDialog.OnAllLPKParsed(Sender: TObject); begin LPKParsingTimer.Enabled:=false; UpdateNewInstalledPackages; UpdateAvailablePackages; end; procedure TInstallPkgSetDialog.OnIdle(Sender: TObject; var Done: Boolean); begin if fAvailablePkgsNeedUpdate then UpdateAvailablePackages(true); IdleConnected:=false; end; procedure TInstallPkgSetDialog.TreeViewAdvancedCustomDrawItem( Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages, DefaultDraw: Boolean); var Info: TLPKInfo; NodeRect: TRect; x: Integer; Images: TCustomImageList; CurCanvas: TCanvas; y: Integer; Tree: TTreeView; InLazSrc: Boolean; IsBase: Boolean; PkgType: TLazPackageType; Installed: TPackageInstallType; PkgName: String; ImgIndex: Integer; Unknown: Boolean; PackageLink: TPackageLink; ImagesRes: TScaledImageListResolution; begin Tree:=Sender as TTreeView; if Stage=cdPostPaint then begin LPKInfoCache.EnterCritSection; try Info:=LPKInfoCache.FindPkgInfoWithIDAsString(Node.Text); if Info=nil then exit; PkgName:=Info.ID.Name; Unknown:=not (Info.LPKParsed in [lpkiParsed,lpkiParsedError]); InLazSrc:=Info.InLazSrc; IsBase:=Info.Base; PkgType:=Info.PkgType; Installed:=Info.Installed; finally LPKInfoCache.LeaveCritSection; end; if Sender = InstallTreeView then PackageLink := nil else PackageLink := FindOnlinePackageLink(Info.ID.Name); Images:=Tree.Images; if Images = nil then exit; ImagesRes := Images.ResolutionForPPI[Tree.ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor]; CurCanvas:=Tree.Canvas; NodeRect:=Node.DisplayRect(False); x:=Node.DisplayIconLeft+1; y:=(NodeRect.Top+NodeRect.Bottom-ImagesRes.Height) div 2; // draw image ImgIndex:=GetPkgImgIndex(Installed,PackageInInstallList(PkgName), PackageLink <> nil); ImagesRes.Draw(CurCanvas,x,y,ImgIndex); // draw overlays if InLazSrc then ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayLazarusPackage); if IsBase then ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayBasePackage); if PkgType=lptRunTimeOnly then ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayRuntimePackage); if PkgType=lptDesignTime then ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayDesigntimePackage); if Unknown then ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayUnknown); end; PaintImages:=false; end; procedure TInstallPkgSetDialog.AvailableTreeViewDblClick(Sender: TObject); begin AddToInstall; end; procedure TInstallPkgSetDialog.AvailableTreeViewKeyPress(Sender: TObject; var Key: char); begin if Key = char(VK_RETURN) then AddToInstall; end; procedure TInstallPkgSetDialog.InstallPkgSetDialogResize(Sender: TObject); var w: Integer; begin w:=ClientWidth div 2-InstallPkgGroupBox.BorderSpacing.Left*3; if w<1 then w:=1; InstallPkgGroupBox.Width:=w; end; procedure TInstallPkgSetDialog.InstallTreeViewSelectionChanged(Sender: TObject); begin UpdateButtonStates; UpdatePackageInfo(InstallTreeView); end; procedure TInstallPkgSetDialog.SaveAndExitButtonClick(Sender: TObject); begin if not CheckSelection then exit; RebuildIDE:=false; ModalResult:=mrOk; end; procedure TInstallPkgSetDialog.UninstallButtonClick(Sender: TObject); begin AddToUninstall; end; procedure TInstallPkgSetDialog.SetOldInstalledPackages(const AValue: TPkgDependency); begin if FOldInstalledPackages=AValue then exit; FOldInstalledPackages:=AValue; AssignOldInstalledPackagesToList; end; procedure TInstallPkgSetDialog.SetIdleConnected(AValue: boolean); begin if FIdleConnected=AValue then Exit; FIdleConnected:=AValue; if IdleConnected then Application.AddOnIdleHandler(@OnIdle) else Application.RemoveOnIdleHandler(@OnIdle); end; procedure TInstallPkgSetDialog.AssignOldInstalledPackagesToList; var Dependency: TPkgDependency; Cnt: Integer; NewPackageID: TLazPackageID; begin ClearNewInstalledPackages; Cnt:=0; Dependency:=OldInstalledPackages; while Dependency<>nil do begin NewPackageID:=TLazPackageID.Create; if (Dependency.LoadPackageResult=lprSuccess) and (Dependency.RequiredPackage<>nil) then begin // packages can be freed while the dialog runs => use packageid instead NewPackageID.AssignID(Dependency.RequiredPackage); end else begin NewPackageID.Name:=Dependency.PackageName; end; FNewInstalledPackages.Add(NewPackageID); Dependency:=Dependency.NextRequiresDependency; inc(Cnt); end; UpdateNewInstalledPackages; UpdateAvailablePackages; end; function TInstallPkgSetDialog.PackageInInstallList(PkgName: string): boolean; var i: Integer; begin for i:=0 to NewInstalledPackages.Count-1 do if SysUtils.CompareText(TLazPackageID(NewInstalledPackages[i]).Name,PkgName)=0 then exit(true); Result:=false; end; function TInstallPkgSetDialog.GetPkgImgIndex(Installed: TPackageInstallType; InInstallList, IsOnline: boolean): integer; begin if Installed<>pitNope then begin // is not currently installed if InInstallList then begin // is installed and will be installed Result:=ImgIndexPackage; end else begin // is installed and will be uninstalled Result:=ImgIndexUninstallPackage; end; end else begin // is currently installed if InInstallList then begin // is not installed and will be installed Result:=ImgIndexInstallPackage; end else begin // is not installed and will be not be installed if IsOnline then Result := ImgIndexAvailableOnline else Result:=ImgIndexPackage; end; end; end; function TInstallPkgSetDialog.FindOnlinePackageLink(const AName: String): TPackageLink; var PackageLink: TPackageLink; PkgName: String; P: Integer; begin Result := nil; Exit; if OPMInterface = nil then Exit; PkgName := Trim(AName); P := Pos(' ', PkgName); if P > 0 then PkgName := Copy(PkgName, 1, P - 1); PackageLink := OPMInterface.FindOnlineLink(PkgName); if PackageLink <> nil then Result := PackageLink; end; procedure TInstallPkgSetDialog.UpdateAvailablePackages(Immediately: boolean); var ANode: TAvlTreeNode; FilteredBranch: TTreeFilterBranch; Info: TLPKInfo; List: TStringList; i: Integer; PackageLink: TPackageLink; begin if not Immediately then begin fAvailablePkgsNeedUpdate:=true; IdleConnected:=true; exit; end; fAvailablePkgsNeedUpdate:=false; List:=TStringList.Create; try // collect available packages, not yet installed LPKInfoCache.EnterCritSection; try ANode:=LPKInfoCache.LPKByID.FindLowest; while ANode<>nil do begin Info:=TLPKInfo(ANode.Data); ANode:=LPKInfoCache.LPKByID.FindSuccessor(ANode); PackageLink := FindOnlinePackageLink(Info.ID.Name); if (PackageLink <> nil) and (PackageLink.PackageType in [lptDesignTime,lptRunAndDesignTime]) then begin if (not PackageInInstallList(Info.ID.Name)) then begin Info.PkgType := PackageLink.PackageType; Info.ID.Version.Assign(PackageLink.Version); List.Add(Info.ID.IDAsString); Continue; end; end; if Info.LPKParsed=lpkiParsedError then continue; if (Info.LPKParsed in [lpkiNotParsed,lpkiParsing]) or (Info.PkgType in [lptDesignTime,lptRunAndDesignTime]) then begin if (not PackageInInstallList(Info.ID.Name)) then List.Add(Info.ID.IDAsString); end; end; finally LPKInfoCache.LeaveCritSection; end; // fill tree view through FilterEdit FilteredBranch := AvailableFilterEdit.GetCleanBranch(Nil); // All items are top level. for i:=0 to List.Count-1 do FilteredBranch.AddNodeData(List[i],nil); finally List.Free; end; AvailableFilterEdit.InvalidateFilter; end; procedure TInstallPkgSetDialog.UpdateNewInstalledPackages; var NewPackageID: TLazPackageID; APackage: TLazPackage; FilteredBranch: TTreeFilterBranch; List: TStringListUTF8Fast; i: Integer; begin List:=TStringListUTF8Fast.Create; try for i:=0 to FNewInstalledPackages.Count-1 do begin NewPackageID:=TLazPackageID(FNewInstalledPackages[i]); APackage:=PackageGraph.FindPackageWithName(NewPackageID.Name,nil); if APackage<>nil then NewPackageID:=APackage; List.Add(NewPackageID.IDAsString); end; List.Sort; // fill tree view through FilterEdit FilteredBranch := InstalledFilterEdit.GetCleanBranch(Nil); // All items are top level. for i:=0 to List.Count-1 do FilteredBranch.AddNodeData(List[i],nil); finally List.Free; end; InstalledFilterEdit.InvalidateFilter; end; procedure TInstallPkgSetDialog.PkgInfosChanged; // called in mainthread after package parser helper thread finished begin UpdateAvailablePackages; end; procedure TInstallPkgSetDialog.ChangePkgVersion(PkgInfo: TLPKInfo; NewVersion: TPkgVersion); // called by LPKInfoCache when a lpk has a different version than the IDE list var OldID, NewID: String; procedure ChangeTV(TV: TTreeView); var i: Integer; Node: TTreeNode; begin for i:=0 to TV.Items.TopLvlCount-1 do begin Node:=TV.Items.TopLvlItems[i]; if Node.Text=OldID then Node.Text:=NewID; end; end; begin OldID:=PkgInfo.ID.IDAsString; NewID:=PkgInfo.ID.Name+' '+NewVersion.AsString; ChangeTV(AvailableTreeView); ChangeTV(InstallTreeView); end; function TInstallPkgSetDialog.DependencyToStr(Dependency: TPkgDependency): string; begin Result:=''; if Dependency=nil then exit; if (Dependency.LoadPackageResult=lprSuccess) and (Dependency.RequiredPackage<>nil) then Result:=Dependency.RequiredPackage.IDAsString else Result:=Dependency.PackageName; end; procedure TInstallPkgSetDialog.ClearNewInstalledPackages; begin FNewInstalledPackages.Clear; end; function TInstallPkgSetDialog.CheckSelection: boolean; begin OnCheckInstallPackageList(FNewInstalledPackages,true,Result); end; procedure TInstallPkgSetDialog.UpdateButtonStates; var Cnt: Integer; Dependency: TPkgDependency; s: String; ListChanged: Boolean; begin UninstallButton.Enabled:=InstallTreeView.Selected<>nil; AddToInstallButton.Enabled:=AvailableTreeView.Selected<>nil; // check for changes ListChanged:=false; Cnt:=0; Dependency:=OldInstalledPackages; while Dependency<>nil do begin s:=Dependency.PackageName; if not PackageInInstallList(s) then begin ListChanged:=true; break; end; Dependency:=Dependency.NextRequiresDependency; inc(Cnt); end; if InstallTreeView.Items.TopLvlCount<>Cnt then ListChanged:=true; SaveAndExitButton.Enabled:=ListChanged; SaveAndRebuildButton.Enabled:=ListChanged; end; procedure TInstallPkgSetDialog.UpdatePackageInfo(Tree: TTreeView); var InfoStr: string; procedure AddState(const NewState: string); begin if (InfoStr<>'') and (InfoStr[length(InfoStr)]<>' ') then InfoStr:=InfoStr+', '; InfoStr:=InfoStr+NewState; end; var PkgID: String; Info: TLPKInfo; PackageLink: TPackageLink; Author, Description, License: String; begin if Tree = nil then Exit; PkgID := ''; if Tree.Selected <> nil then PkgID := Tree.Selected.Text; if PkgID = '' then Exit; if Tree = InstallTreeView then PackageLink := nil else PackageLink := FindOnlinePackageLink(PkgID); LPKInfoCache.EnterCritSection; try Info:=LPKInfoCache.FindPkgInfoWithIDAsString(PkgID); if ((Info=nil) and (FSelectedPkgID='')) or ((Info<>nil) and (Info.ID.IDAsString=FSelectedPkgID) and (Info.LPKParsed=FSelectedPkgState)) then exit; // no change PkgInfoMemo.Clear; PkgInfoMemoLicense.Clear; if (Info=nil) then begin FSelectedPkgID:=''; exit; end; FSelectedPkgID:=PkgID; if Info.LPKParsed=lpkiNotParsed then begin LPKInfoCache.ParseLPKInfoInMainThread(Info); if FSelectedPkgID='' then begin // version has changed // => has already triggered an update exit; end; end; if PackageLink = nil then begin Author := Info.Author; Description := Trim(Info.Description); License := Info.License; end else begin Author := PackageLink.Author; Description := Trim(PackageLink.Description); License := PackageLink.License; end; if Description<>'' then // Description is the most interesting piece. PkgInfoMemo.Lines.Add(Description); // Put it first. PkgInfoMemo.Lines.Add(''); if Author<>'' then PkgInfoMemo.Lines.Add(lisPckOptsAuthor + ': ' + Author); // Author PkgInfoMemo.Lines.Add(Format(lisOIPFilename, [Info.LPKFilename])); // Pkg name if License<>'' then // License has its own memo. PkgInfoMemoLicense.Lines.Add(lisPckOptsLicense + ': ' + License); InfoStr:=lisCurrentState; if Info.Installed<>pitNope then begin if PackageInInstallList(Info.ID.Name)=false then AddState(lisSelectedForUninstallation); AddState(lisInstalled); end else begin if PackageInInstallList(Info.ID.Name)=true then AddState(lisSelectedForInstallation); AddState(lisNotInstalled); if PackageLink <> nil then AddState(lisOnlinePackage); end; if Info.Base then AddState(lisPckExplBase); AddState(LazPackageTypeIdents[Info.PkgType]); PkgInfoMemo.Lines.Add(InfoStr); PkgInfoMemo.SelStart := 1; PkgInfoMemoLicense.SelStart := 1; finally LPKInfoCache.LeaveCritSection; end; end; function TInstallPkgSetDialog.NewInstalledPackagesContains( APackageID: TLazPackageID): boolean; begin Result:=IndexOfNewInstalledPackageID(APackageID)>=0; end; function TInstallPkgSetDialog.IndexOfNewInstalledPackageID( APackageID: TLazPackageID): integer; begin Result:=FNewInstalledPackages.Count-1; while (Result>=0) and (TLazPackageID(FNewInstalledPackages[Result]).Compare(APackageID)<>0) do dec(Result); end; function TInstallPkgSetDialog.IndexOfNewInstalledPkgByName( const APackageName: string): integer; begin Result:=FNewInstalledPackages.Count-1; while (Result>=0) and (CompareText(TLazPackageID(FNewInstalledPackages[Result]).Name, APackageName)<>0) do dec(Result); end; procedure TInstallPkgSetDialog.SavePackageListToFile(const AFilename: string); var XMLConfig: TXMLConfig; i: Integer; LazPackageID: TLazPackageID; begin try XMLConfig:=TXMLConfig.CreateClean(AFilename); try XMLConfig.SetDeleteValue('Packages/Count',FNewInstalledPackages.Count,0); for i:=0 to FNewInstalledPackages.Count-1 do begin LazPackageID:=TLazPackageID(FNewInstalledPackages[i]); XMLConfig.SetDeleteValue('Packages/Item'+IntToStr(i)+'/ID', LazPackageID.IDAsString,''); end; InvalidateFileStateCache; XMLConfig.Flush; finally XMLConfig.Free; end; except on E: Exception do begin MessageDlg(lisCodeToolsDefsWriteError, Format(lisErrorWritingPackageListToFile,[LineEnding,AFilename,LineEnding,E.Message]), mtError, [mbCancel], 0); end; end; end; procedure TInstallPkgSetDialog.LoadPackageListFromFile(const AFilename: string); function PkgNameExists(List: TObjectList; ID: TLazPackageID): boolean; var i: Integer; LazPackageID: TLazPackageID; begin if List<>nil then for i:=0 to List.Count-1 do begin LazPackageID:=TLazPackageID(List[i]); if CompareText(LazPackageID.Name,ID.Name)=0 then begin Result:=true; exit; end; end; Result:=false; end; var XMLConfig: TXMLConfig; i: Integer; LazPackageID: TLazPackageID; NewCount: LongInt; NewList: TObjectList; ID: String; begin NewList:=nil; LazPackageID:=nil; try XMLConfig:=TXMLConfig.Create(AFilename); try NewCount:=XMLConfig.GetValue('Packages/Count',0); LazPackageID:=TLazPackageID.Create; for i:=0 to NewCount-1 do begin // get ID ID:=XMLConfig.GetValue('Packages/Item'+IntToStr(i)+'/ID',''); if ID='' then continue; // parse ID if not LazPackageID.StringToID(ID) then continue; // ignore doubles if PkgNameExists(NewList,LazPackageID) then continue; // add if NewList=nil then NewList:=TObjectList.Create(true); NewList.Add(LazPackageID); LazPackageID:=TLazPackageID.Create; end; // clean up old list ClearNewInstalledPackages; FNewInstalledPackages.Free; // assign new list FNewInstalledPackages:=NewList; NewList:=nil; UpdateNewInstalledPackages; UpdateAvailablePackages; UpdateButtonStates; finally XMLConfig.Free; LazPackageID.Free; NewList.Free; end; except on E: Exception do begin MessageDlg(lisCodeToolsDefsReadError, Format(lisErrorReadingPackageListFromFile,[LineEnding,AFilename,LineEnding,E.Message]), mtError, [mbCancel], 0); end; end; end; function TInstallPkgSetDialog.ExtractNameFromPkgID(ID: string): string; begin if ID='' then Result:='' else Result:=GetIdentifier(PChar(ID)); end; procedure TInstallPkgSetDialog.AddToInstall; function SelectionOk(aPackageID: TLazPackageID): Boolean; var APackage: TLazPackage; ConflictDep: TPkgDependency; i: Integer; begin // check if already in list if NewInstalledPackagesContains(aPackageID) then begin MessageDlg(lisDuplicate, Format(lisThePackageIsAlreadyInTheList, [aPackageID.Name]), mtError, [mbCancel],0); exit(false); end; // check if a package with same name is already in the list i:=IndexOfNewInstalledPkgByName(aPackageID.Name); if i>=0 then begin MessageDlg(lisConflict, Format(lisThereIsAlreadyAPackageInTheList, [aPackageID.Name]), mtError,[mbCancel],0); exit(false); end; // check if package is loaded and has some attributes that prevents // installation in the IDE APackage:=PackageGraph.FindPackageWithID(aPackageID); if APackage<>nil then begin if APackage.PackageType in [lptRunTime,lptRunTimeOnly] then begin IDEMessageDialog(lisNotADesigntimePackage, Format(lisThePackageIsNotADesignTimePackageItCanNotBeInstall, [APackage.IDAsString]), mtError, [mbCancel]); exit(false); end; ConflictDep:=PackageGraph.FindRuntimePkgOnlyRecursively( APackage.FirstRequiredDependency); if ConflictDep<>nil then begin IDEMessageDialog(lisNotADesigntimePackage, Format(lisThePackageCanNotBeInstalledBecauseItRequiresWhichI, [APackage.Name, ConflictDep.AsString]), mtError, [mbCancel]); exit(false); end; end; Result:=true; end; var i, j: Integer; NewSelectedIndex, LastNonSelectedIndex: Integer; NewPackageID: TLazPackageID; Additions: TObjectList; AddedPkgNames: TStringList; TVNode: TTreeNode; PkgName: String; FilteredBranch: TTreeFilterBranch; PkgLinks: TList; PkgLinksStr: String; PkgLink: TPackageLink; begin NewSelectedIndex:=-1; LastNonSelectedIndex:=-1; Additions:=TObjectList.Create(false); AddedPkgNames:=TStringList.Create; PkgLinks := TList.Create; NewPackageID:=TLazPackageID.Create; FilteredBranch := AvailableFilterEdit.GetExistingBranch(Nil); // All items are top level. try for i:=0 to AvailableTreeView.Items.TopLvlCount-1 do begin TVNode:=AvailableTreeView.Items.TopLvlItems[i]; if not TVNode.MultiSelected then begin LastNonSelectedIndex:=i; continue; end; NewSelectedIndex:=i+1; // Will have the next index after the selected one. PkgName:=TVNode.Text; // Convert package name to ID and check it if not NewPackageID.StringToID(PkgName) then begin TVNode.Selected:=false; DebugLn('TInstallPkgSetDialog.AddToInstall invalid ID: ', PkgName); continue; end; if not SelectionOk(NewPackageID) then begin TVNode.Selected:=false; exit; end; // ok => add to list PkgLink := FindOnlinePackageLink(NewPackageID.Name); if PkgLink <> nil then begin if not FileExists(PkgLink.OPMFileName) then PkgLinks.Add(PkgLink) else begin PkgLink := LazPackageLinks.AddUserLink(PkgLink.OPMFileName, PkgLink.Name); if PkgLink <> nil then LazPackageLinks.SaveUserLinks; Additions.Add(NewPackageID); NewPackageID:=TLazPackageID.Create; AddedPkgNames.Add(PkgName); end; end else begin Additions.Add(NewPackageID); NewPackageID:=TLazPackageID.Create; AddedPkgNames.Add(PkgName); end; end; //download online packages if (OPMInterface <> nil) and (PkgLinks.Count > 0) then begin PkgLinksStr := ''; for I := 0 to PkgLinks.Count - 1 do begin if PkgLinksStr = '' then PkgLinksStr := '"' + TPackageLink(PkgLinks.Items[I]).Name + '"' else PkgLinksStr := PkgLinksStr + ', ' + '"' + TPackageLink(PkgLinks.Items[I]).Name + '"'; end; if IDEMessageDialog(lisDownload, Format(lisDonwloadOnlinePackages, [PkgLinksStr]), mtConfirmation, [mbYes, mbNo]) = mrYes then begin if OPMInterface.DownloadPackages(PkgLinks) = mrOK then begin for I := PkgLinks.Count - 1 downto 0 do begin if OPMInterface.IsPackageAvailable(TPackageLink(PkgLinks.Items[I]), 1) then begin Additions.Add(NewPackageID); NewPackageID:=TLazPackageID.Create; AddedPkgNames.Add(PkgName); PkgLink := LazPackageLinks.AddUserLink(TPackageLink(PkgLinks.Items[I]).OPMFileName, TPackageLink(PkgLinks.Items[I]).Name); if PkgLink <> nil then LazPackageLinks.SaveUserLinks; end; end; end else Dec(NewSelectedIndex); end else Dec(NewSelectedIndex); end; // all ok => add to installed packages for i:=0 to Additions.Count-1 do FNewInstalledPackages.Add(Additions[i]); for i:=0 to AddedPkgNames.Count-1 do begin j:=FilteredBranch.Items.IndexOf(AddedPkgNames[i]); Assert(j<>-1, 'TInstallPkgSetDialog.AddToInstall: '+AddedPkgNames[i]+' not found in Filter items.'); FilteredBranch.Items.Delete(j); end; // Don't call UpdateAvailablePackages here, only the selected nodes were removed. UpdateNewInstalledPackages; UpdateButtonStates; if ((NewSelectedIndex=-1) or (NewSelectedIndex=AvailableTreeView.Items.TopLvlCount)) then NewSelectedIndex:=LastNonSelectedIndex; if NewSelectedIndex<>-1 then AvailableTreeView.Items.TopLvlItems[NewSelectedIndex].Selected:=True; AvailableFilterEdit.InvalidateFilter; finally NewPackageID.Free; AddedPkgNames.Free; Additions.Free; PkgLinks.Free; end; end; procedure TInstallPkgSetDialog.AddToUninstall; function SelectionOk(aPackageID: TLazPackageID): Boolean; var APackage: TLazPackage; begin APackage:=PackageGraph.FindPackageWithID(aPackageID); if APackage<>nil then begin // check if package is a base package if PackageGraph.IsStaticBasePackage(APackage.Name) then begin MessageDlg(lisUninstallImpossible, Format(lisThePackageCanNotBeUninstalledBecauseItIsNeededByTh, [ APackage.Name]), mtError, [mbCancel], 0); exit(false); end; end; Result:=true; end; var i, j: Integer; NewSelectedIndex, LastNonSelectedIndex: Integer; DelPackageID, PackID: TLazPackageID; Deletions: TObjectList; DeletedPkgNames: TStringList; TVNode: TTreeNode; PkgName: String; FilteredBranch: TTreeFilterBranch; begin NewSelectedIndex:=-1; LastNonSelectedIndex:=-1; Deletions:=TObjectList.Create(true); DeletedPkgNames:=TStringList.Create; DelPackageID:=TLazPackageID.Create; FilteredBranch := InstalledFilterEdit.GetExistingBranch(Nil); // All items are top level. try for i:=0 to InstallTreeView.Items.TopLvlCount-1 do begin TVNode:=InstallTreeView.Items.TopLvlItems[i]; if not TVNode.MultiSelected then begin LastNonSelectedIndex:=i; continue; end; NewSelectedIndex:=i+1; // Will have the next index after the selected one. PkgName:=TVNode.Text; if not DelPackageID.StringToID(PkgName) then begin TVNode.Selected:=false; debugln('TInstallPkgSetDialog.AddToUninstall invalid ID: ', PkgName); continue; end; if not SelectionOk(DelPackageID) then begin TVNode.Selected:=false; exit; end; // ok => add to deletions Deletions.Add(DelPackageID); DelPackageID:=TLazPackageID.Create; DeletedPkgNames.Add(PkgName); end; // ok => remove from installed packages InstallTreeView.Selected:=nil; for i:=0 to Deletions.Count-1 do begin PackID:=TLazPackageID(Deletions[i]); j:=IndexOfNewInstalledPackageID(PackID); FNewInstalledPackages.Delete(j); end; for i:=0 to DeletedPkgNames.Count-1 do begin j:=FilteredBranch.Items.IndexOf(DeletedPkgNames[i]); Assert(j<>-1, 'TInstallPkgSetDialog.AddToUninstall: '+DeletedPkgNames[i]+' not found in Filter items.'); FilteredBranch.Items.Delete(j); end; // Don't call UpdateNewInstalledPackages here, only the selected nodes were removed. UpdateAvailablePackages; UpdateButtonStates; if ((NewSelectedIndex=-1) or (NewSelectedIndex=InstallTreeView.Items.TopLvlCount)) then NewSelectedIndex:=LastNonSelectedIndex; if NewSelectedIndex<>-1 then InstallTreeView.Items.TopLvlItems[NewSelectedIndex].Selected:=True; InstalledFilterEdit.InvalidateFilter; finally DelPackageID.Free; DeletedPkgNames.Free; Deletions.Free; end; end; function TInstallPkgSetDialog.GetNewInstalledPackages: TObjectList; var i: Integer; NewPackageID: TLazPackageID; begin Result:=TObjectList.Create(true); for i:=0 to FNewInstalledPackages.Count-1 do begin NewPackageID:=TLazPackageID.Create; NewPackageID.AssignID(TLazPackageID(FNewInstalledPackages[i])); Result.Add(NewPackageID); end; end; end.