{ *************************************************************************** * * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner } unit CleanPkgDeps; {$mode objfpc}{$H+} interface uses Classes, SysUtils, contnrs, FileUtil, AvgLvlTree, LazLogger, LvlGraphCtrl, Forms, Controls, Graphics, Dialogs, ButtonPanel, ComCtrls, ExtCtrls, StdCtrls, Buttons, LazarusIDEStrConsts, Project, PackageDefs, IDEImagesIntf; const CPDProjectName = '-Project-'; type { TCPDNodeInfo } TCPDNodeInfo = class public Owner: string; // CPDProjectName or package name Dependency: string; // required package name end; { TCleanPkgDepsDlg } TCleanPkgDepsDlg = class(TForm) CancelBitBtn: TBitBtn; DeleteSelectedBitBtn: TBitBtn; BtnPanel: TPanel; SelectAllBitBtn: TBitBtn; SelectNoneBitBtn: TBitBtn; TransitivityGroupBox: TGroupBox; TransitivityLabel: TLabel; TransitivityTreeView: TTreeView; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure SelectAllBitBtnClick(Sender: TObject); procedure SelectNoneBitBtnClick(Sender: TObject); procedure TransitivityTreeViewMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private function GetTVNodeChecked(TVNode: TTreeNode): boolean; procedure SetTVNodeChecked(TVNode: TTreeNode; AValue: boolean); private FOwners: TFPList; ImgIndexProject: integer; ImgIndexPackage: integer; ImgIndexDelete: integer; ImgIndexKeep: integer; procedure SetOwners(AValue: TFPList); procedure ClearTreeData; procedure UpdateTransitivityTree; procedure UpdateButtons; procedure AddTransitivities(DepOwner: TObject; ImgIndex: integer; FirstDependency: TPkgDependency); function FindAlternativeRoute(Dependency, StartDependency: TPkgDependency): TFPList; property TVNodeChecked[TVNode: TTreeNode]: boolean read GetTVNodeChecked write SetTVNodeChecked; function GetDepOwnerName(DepOwner: TObject; WithVersion: boolean): string; public property Owners: TFPList read FOwners write SetOwners; function FetchDeletes: TObjectList; // list of TCPDNodeInfo end; var CleanPkgDepsDlg: TCleanPkgDepsDlg; function ShowCleanPkgDepDlg(Pkg: TLazPackage; out ListOfNodeInfos: TObjectList): TModalResult; function ShowCleanPkgDepDlg(AProject: TProject; out ListOfNodeInfos: TObjectList): TModalResult; function ShowCleanPkgDepDlg(Owners: TFPList; FreeOwners: boolean; out ListOfNodeInfos: TObjectList): TModalResult; implementation function ShowCleanPkgDepDlg(Pkg: TLazPackage; out ListOfNodeInfos: TObjectList): TModalResult; var Owners: TFPList; begin Owners:=TFPList.Create; Owners.Add(Pkg); Result:=ShowCleanPkgDepDlg(Owners,true,ListOfNodeInfos); end; function ShowCleanPkgDepDlg(AProject: TProject; out ListOfNodeInfos: TObjectList): TModalResult; var Owners: TFPList; begin Owners:=TFPList.Create; Owners.Add(AProject); Result:=ShowCleanPkgDepDlg(Owners,true,ListOfNodeInfos); end; function ShowCleanPkgDepDlg(Owners: TFPList; FreeOwners: boolean; out ListOfNodeInfos: TObjectList): TModalResult; var Dlg: TCleanPkgDepsDlg; begin ListOfNodeInfos:=nil; Dlg:=TCleanPkgDepsDlg.Create(nil); try Dlg.Owners:=Owners; Result:=Dlg.ShowModal; if Result=mrOk then ListOfNodeInfos:=Dlg.FetchDeletes; finally if FreeOwners then Owners.Free; Dlg.Free; end; end; {$R *.lfm} { TCleanPkgDepsDlg } procedure TCleanPkgDepsDlg.FormCreate(Sender: TObject); begin ImgIndexProject := IDEImages.LoadImage(16, 'item_project'); ImgIndexPackage := IDEImages.LoadImage(16, 'item_package'); ImgIndexDelete := IDEImages.LoadImage(16, 'laz_delete'); ImgIndexKeep := IDEImages.LoadImage(16, 'menu_run'); Caption:=lisPkgCleanUpPackageDependencies; TransitivityGroupBox.Caption:=lisPkgTransitivity; TransitivityLabel.Caption:= lisPkgTheFollowingDependenciesAreNotNeededBecauseOfTheAu; TransitivityTreeView.Images:=IDEImages.Images_16; SelectAllBitBtn.Caption:=lisMenuSelectAll; SelectNoneBitBtn.Caption:=lisPkgClearSelection; DeleteSelectedBitBtn.Caption:=lisPkgDeleteDependencies; end; procedure TCleanPkgDepsDlg.FormDestroy(Sender: TObject); begin ClearTreeData; end; procedure TCleanPkgDepsDlg.SelectAllBitBtnClick(Sender: TObject); var i: Integer; begin for i:=0 to TransitivityTreeView.Items.Count-1 do TVNodeChecked[TransitivityTreeView.Items[i]]:=true; end; procedure TCleanPkgDepsDlg.SelectNoneBitBtnClick(Sender: TObject); var i: Integer; begin for i:=0 to TransitivityTreeView.Items.Count-1 do TVNodeChecked[TransitivityTreeView.Items[i]]:=false; end; procedure TCleanPkgDepsDlg.TransitivityTreeViewMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var TVNode: TTreeNode; begin TVNode:=TransitivityTreeView.GetNodeAt(X,Y); if TVNode=nil then exit; if X>=TVNode.DisplayIconLeft then begin TVNodeChecked[TVNode]:=not TVNodeChecked[TVNode]; end; end; function TCleanPkgDepsDlg.GetTVNodeChecked(TVNode: TTreeNode): boolean; begin Result:=(TVNode<>nil) and (TVNode.Data<>nil) and (TVNode.ImageIndex=ImgIndexDelete); end; procedure TCleanPkgDepsDlg.SetTVNodeChecked(TVNode: TTreeNode; AValue: boolean); begin if TVNode.Data=nil then exit; if TVNodeChecked[TVNode]=AValue then exit; if AValue then TVNode.ImageIndex:=ImgIndexDelete else TVNode.ImageIndex:=ImgIndexKeep; TVNode.SelectedIndex:=TVNode.ImageIndex; UpdateButtons; end; procedure TCleanPkgDepsDlg.SetOwners(AValue: TFPList); begin if FOwners=AValue then Exit; FOwners:=AValue; UpdateTransitivityTree; UpdateButtons; end; procedure TCleanPkgDepsDlg.ClearTreeData; var i: Integer; TVNode: TTreeNode; begin for i:=0 to TransitivityTreeView.Items.Count-1 do begin TVNode:=TransitivityTreeView.Items[i]; if TVNode.Data<>nil then begin TObject(TVNode.Data).Free; TVNode.Data:=nil; end; end; end; procedure TCleanPkgDepsDlg.UpdateTransitivityTree; var i: Integer; CurOwner: TObject; AProject: TProject; APackage: TLazPackage; begin TransitivityTreeView.BeginUpdate; ClearTreeData; TransitivityTreeView.Items.Clear; for i:=0 to Owners.Count-1 do begin CurOwner:=TObject(Owners[i]); if CurOwner is TProject then begin AProject:=TProject(CurOwner); AddTransitivities(AProject,ImgIndexProject,AProject.FirstRequiredDependency); end else if CurOwner is TLazPackage then begin APackage:=TLazPackage(CurOwner); AddTransitivities(APackage,ImgIndexPackage,APackage.FirstRequiredDependency); end; end; TransitivityTreeView.EndUpdate; end; procedure TCleanPkgDepsDlg.UpdateButtons; var i: Integer; TVNode: TTreeNode; CheckCnt: Integer; begin CheckCnt:=0; for i:=0 to TransitivityTreeView.Items.Count-1 do begin TVNode:=TransitivityTreeView.Items[i]; if TVNodeChecked[TVNode] then CheckCnt+=1; end; DeleteSelectedBitBtn.Enabled:=CheckCnt>0; end; procedure TCleanPkgDepsDlg.AddTransitivities(DepOwner: TObject; ImgIndex: integer; FirstDependency: TPkgDependency); var Dependency: TPkgDependency; AltRoute: TFPList; MainTVNode: TTreeNode; TVNode: TTreeNode; Info: TCPDNodeInfo; s: String; i: Integer; begin MainTVNode:=nil; Dependency:=FirstDependency; while Dependency<>nil do begin AltRoute:=FindAlternativeRoute(Dependency,FirstDependency); if AltRoute<>nil then begin if MainTVNode=nil then begin MainTVNode:=TransitivityTreeView.Items.Add(nil,GetDepOwnerName(DepOwner,true)); MainTVNode.ImageIndex:=ImgIndex; MainTVNode.SelectedIndex:=MainTVNode.ImageIndex; end; s:=Dependency.AsString+' = '; for i:=0 to AltRoute.Count-1 do begin if i>0 then s+='-'; s+=TLazPackage(AltRoute[i]).Name; end; TVNode:=TransitivityTreeView.Items.AddChild(MainTVNode,s); TVNode.ImageIndex:=ImgIndexDelete; TVNode.SelectedIndex:=TVNode.ImageIndex; Info:=TCPDNodeInfo.Create; TVNode.Data:=Info; Info.Owner:=GetDepOwnerName(DepOwner,false); Info.Dependency:=Dependency.RequiredPackage.Name; MainTVNode.Expand(true); AltRoute.Free; end; Dependency:=Dependency.NextRequiresDependency; end; end; function TCleanPkgDepsDlg.FindAlternativeRoute(Dependency, StartDependency: TPkgDependency): TFPList; var Visited: TAvgLvlTree; function Search(Pkg: TLazPackage; Level: integer; var AltRoute: TFPList): boolean; var CurDependency: TPkgDependency; begin Result:=false; if Pkg=nil then exit; if Pkg=Dependency.Owner then exit; // cycle detected if (Level>0) and (Pkg=Dependency.RequiredPackage) then begin // alternative route found AltRoute:=TFPList.Create; AltRoute.Add(Pkg); exit(true); end; if Visited.Find(Pkg)<>nil then exit; Visited.Add(Pkg); CurDependency:=Pkg.FirstRequiredDependency; while CurDependency<>nil do begin if Search(CurDependency.RequiredPackage,Level+1,AltRoute) then begin AltRoute.Insert(0,Pkg); exit(true); end; CurDependency:=CurDependency.NextRequiresDependency; end; end; var CurDependency: TPkgDependency; begin Result:=nil; if Dependency=nil then exit; if Dependency.RequiredPackage=nil then exit; Visited:=TAvgLvlTree.Create; try CurDependency:=StartDependency; while CurDependency<>nil do begin if CurDependency<>Dependency then if Search(CurDependency.RequiredPackage,0,Result) then exit; CurDependency:=CurDependency.NextRequiresDependency; end; finally Visited.Free; end; end; function TCleanPkgDepsDlg.GetDepOwnerName(DepOwner: TObject; WithVersion: boolean ): string; begin if DepOwner is TProject then Result:=CPDProjectName else if DepOwner is TLazPackage then begin if WithVersion then Result:=TLazPackage(DepOwner).IDAsString else Result:=TLazPackage(DepOwner).Name; end; end; function TCleanPkgDepsDlg.FetchDeletes: TObjectList; var i: Integer; TVNode: TTreeNode; Info: TCPDNodeInfo; begin Result:=TObjectList.Create(true); for i:=0 to TransitivityTreeView.Items.Count-1 do begin TVNode:=TransitivityTreeView.Items[i]; if TVNodeChecked[TVNode] and (TObject(TVNode.Data) is TCPDNodeInfo) then begin Info:=TCPDNodeInfo(TVNode.Data); TVNode.Data:=nil; Result.Add(Info); end; end; end; end.