lazarus/components/codetools/ide/codyunitdepwnd.pas
mattias c97895ac60 cody: unit deps: all units: group nodes
git-svn-id: trunk@41743 -
2013-06-17 13:17:46 +00:00

1070 lines
32 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
IDE Window showing dependencies of units and packages.
ToDo:
- add refresh button to rescan
- delay update pages when not visible
- update pages when becoming visible
- additional files as start units
- view:
- flag show nodes for project/package
- flag show nodes for directories
- flag allow multiselect
- filter units
- text search with highlight, next, previous
- double click: open one unit
- selected units
- show owner units as tree structure
- show connected units: used via interface, via implementation, used by interface, used by implementation
- expand node: show connected units
- collapse node: free child nodes
- text search with highlight, next, previous
- double click: open one unit
- resourcestrings
}
unit CodyUnitDepWnd;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, AVL_Tree, LazLogger, LazFileUtils, LazUTF8, Forms,
Controls, ExtCtrls, ComCtrls, StdCtrls, Buttons, Dialogs, LvlGraphCtrl,
LazIDEIntf, ProjectIntf, IDEWindowIntf, PackageIntf, SrcEditorIntf,
IDEDialogs, CodeToolManager, DefineTemplates, CodeToolsStructs, CTUnitGraph,
CTUnitGroupGraph, FileProcs;
const
GroupPrefixProject = '-Project-';
GroupPrefixFPCSrc = 'FPC:';
GroupNone = '-None-';
type
TUDNodeType = (
udnNone,
udnGroup,
udnDirectory,
udnInterface,
udnImplementation,
udnUsedByInterface,
udnUsedByImplementation,
udnUnit
);
TUDNodeTypes = set of TUDNodeType;
{ TUDBaseNode }
TUDBaseNode = class
public
TVNode: TTreeNode;
NodeText: string;
Typ: TUDNodeType;
Identifier: string; // GroupName, Directory, Filename
Group: string;
end;
{ TUDNode }
TUDNode = class(TUDBaseNode)
public
Parent: TUDNode;
ChildNodes: TAVLTree; // tree of TUDNode sorted for Typ and NodeText
constructor Create;
destructor Destroy; override;
procedure Clear;
function GetNode(aTyp: TUDNodeType; const ANodeText: string;
CreateIfNotExists: boolean = false): TUDNode;
function Count: integer;
end;
TUDWFlag = (
udwParsing,
udwNeedUpdateGroupsLvlGraph,
udwNeedUpdateUnitsLvlGraph,
udwNeedUpdateAllUnitsTreeView
);
TUDWFlags = set of TUDWFlag;
{ TUnitDependenciesWindow }
TUnitDependenciesWindow = class(TForm)
AllUnitsFilterEdit: TEdit;
AllUnitsMultiselectSpeedButton: TSpeedButton;
AllUnitsSearchEdit: TEdit;
AllUnitsSearchNextSpeedButton: TSpeedButton;
AllUnitsSearchPrevSpeedButton: TSpeedButton;
AllUnitsGroupBox: TGroupBox;
AllUnitsShowDirsSpeedButton: TSpeedButton;
AllUnitsShowGroupNodesSpeedButton: TSpeedButton;
AllUnitsTreeView: TTreeView; // Node.Data is TUDNode
BtnPanel: TPanel;
MainPageControl: TPageControl;
ProgressBar1: TProgressBar;
GroupsTabSheet: TTabSheet;
GroupsSplitter: TSplitter;
SearchPkgsCheckBox: TCheckBox;
SearchSrcEditCheckBox: TCheckBox;
SelectedUnitsGroupBox: TGroupBox;
SelUnitsSearchEdit: TEdit;
SelUnitsSearchNextSpeedButton: TSpeedButton;
SelUnitsSearchPrevSpeedButton: TSpeedButton;
SelUnitsTreeView: TTreeView;
SearchCustomFilesBrowseButton: TButton;
SearchCustomFilesCheckBox: TCheckBox;
ScopePanel: TPanel;
SearchCustomFilesComboBox: TComboBox;
UnitsSplitter: TSplitter;
UnitsTabSheet: TTabSheet;
Timer1: TTimer;
procedure AllUnitsFilterEditChange(Sender: TObject);
procedure AllUnitsMultiselectSpeedButtonClick(Sender: TObject);
procedure AllUnitsShowDirsSpeedButtonClick(Sender: TObject);
procedure AllUnitsShowGroupNodesSpeedButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure GroupsLvlGraphSelectionChanged(Sender: TObject);
procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
procedure SearchPkgsCheckBoxChange(Sender: TObject);
procedure SearchSrcEditCheckBoxChange(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure SearchCustomFilesBrowseButtonClick(Sender: TObject);
procedure SearchCustomFilesCheckBoxChange(Sender: TObject);
procedure SearchCustomFilesComboBoxChange(Sender: TObject);
private
FAllUnitsMultiSelect: boolean;
FCurrentUnit: TUGUnit;
FIdleConnected: boolean;
FUsesGraph: TUsesGraph;
FGroups: TUGGroups; // referenced by Nodes.Data of GroupsLvlGraph
FAllUnitsRootUDNode: TUDNode;
FFlags: TUDWFlags;
function CreateAllUnitsTree: TUDNode;
procedure SetAllUnitsMultiSelect(AValue: boolean);
procedure SetCurrentUnit(AValue: TUGUnit);
procedure SetIdleConnected(AValue: boolean);
procedure CreateGroups;
function CreateProjectGroup(AProject: TLazProject): TUGGroup;
function CreatePackageGroup(APackage: TIDEPackage): TUGGroup;
procedure CreateFPCSrcGroups;
procedure GuessGroupOfUnits;
procedure AddStartAndTargetUnits;
procedure AddAdditionalFilesAsStartUnits;
procedure SetupGroupsTabSheet;
procedure SetupUnitsTabSheet;
procedure UpdateUnitsButtons;
procedure UpdateAll;
procedure UpdateGroupsLvlGraph;
procedure UpdateUnitsLvlGraph;
procedure UpdateAllUnitsTreeView;
function NodeTextToUnit(NodeText: string): TUGUnit;
function UGUnitToNodeText(UGUnit: TUGUnit): string;
function GetFPCSrcDir: string;
function IsFPCSrcGroup(Group: TUGGroup): boolean;
function IsProjectGroup(Group: TUGGroup): boolean;
function GetAllUnitsFilter: string;
public
GroupsLvlGraph: TLvlGraphControl; // Nodes.Data are TUGGroup of Groups
UnitsLvlGraph: TLvlGraphControl; // Nodes.Data are Units in Groups
property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
property UsesGraph: TUsesGraph read FUsesGraph;
property Groups: TUGGroups read FGroups;
property CurrentUnit: TUGUnit read FCurrentUnit write SetCurrentUnit;
property AllUnitsMultiSelect: boolean read FAllUnitsMultiSelect write SetAllUnitsMultiSelect;
end;
var
UnitDependenciesWindow: TUnitDependenciesWindow;
procedure ShowUnitDependenciesClicked(Sender: TObject);
procedure ShowUnitDependencies(Show, BringToFront: boolean);
function CompareUDBaseNodes(UDNode1, UDNode2: Pointer): integer;
implementation
procedure ShowUnitDependenciesClicked(Sender: TObject);
begin
ShowUnitDependencies(true,true);
end;
procedure ShowUnitDependencies(Show, BringToFront: boolean);
begin
if UnitDependenciesWindow = Nil then
Application.CreateForm(TUnitDependenciesWindow, UnitDependenciesWindow);
if Show then
begin
IDEWindowCreators.ShowForm(UnitDependenciesWindow,BringToFront);
end;
end;
function CompareUDBaseNodes(UDNode1, UDNode2: Pointer): integer;
var
Node1: TUDBaseNode absolute UDNode1;
Node2: TUDBaseNode absolute UDNode2;
begin
Result:=ord(Node1.Typ)-ord(Node2.Typ);
if Result<>0 then exit;
case Node1.Typ of
udnDirectory: Result:=CompareFilenames(Node1.NodeText,Node2.NodeText);
else Result:=SysUtils.CompareText(Node1.NodeText,Node2.NodeText);
end;
end;
{ TUDNode }
constructor TUDNode.Create;
begin
ChildNodes:=TAVLTree.Create(@CompareUDBaseNodes);
end;
destructor TUDNode.Destroy;
begin
Clear;
FreeAndNil(ChildNodes);
inherited Destroy;
end;
procedure TUDNode.Clear;
begin
ChildNodes.FreeAndClear;
end;
function TUDNode.GetNode(aTyp: TUDNodeType; const ANodeText: string;
CreateIfNotExists: boolean): TUDNode;
var
Node: TUDBaseNode;
AVLNode: TAVLTreeNode;
begin
Node:=TUDBaseNode.Create;
Node.Typ:=aTyp;
Node.NodeText:=ANodeText;
AVLNode:=ChildNodes.Find(Node);
Node.Free;
if AVLNode<>nil then begin
Result:=TUDNode(AVLNode.Data);
end else if CreateIfNotExists then begin
Result:=TUDNode.Create;
Result.Typ:=aTyp;
Result.NodeText:=ANodeText;
ChildNodes.Add(Result);
Result.Parent:=Self;
end else
Result:=nil;
end;
function TUDNode.Count: integer;
begin
Result:=ChildNodes.Count;
end;
{ TUnitDependenciesWindow }
procedure TUnitDependenciesWindow.FormCreate(Sender: TObject);
begin
FUsesGraph:=CodeToolBoss.CreateUsesGraph;
FGroups:=TUGGroups.Create(FUsesGraph);
ProgressBar1.Style:=pbstMarquee;
AddStartAndTargetUnits;
Caption:='Unit Dependencies';
MainPageControl.ActivePage:=UnitsTabSheet;
SetupUnitsTabSheet;
SetupGroupsTabSheet;
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.AllUnitsMultiselectSpeedButtonClick(
Sender: TObject);
begin
AllUnitsMultiSelect:=AllUnitsMultiselectSpeedButton.Down;
end;
procedure TUnitDependenciesWindow.AllUnitsShowDirsSpeedButtonClick(
Sender: TObject);
begin
Include(FFlags,udwNeedUpdateAllUnitsTreeView);
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.AllUnitsShowGroupNodesSpeedButtonClick(
Sender: TObject);
begin
Include(FFlags,udwNeedUpdateAllUnitsTreeView);
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.AllUnitsFilterEditChange(Sender: TObject);
begin
Include(FFlags,udwNeedUpdateAllUnitsTreeView);
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.FormDestroy(Sender: TObject);
begin
IdleConnected:=false;
GroupsLvlGraph.Clear;
UnitsLvlGraph.Clear;
FreeAndNil(FGroups);
FreeAndNil(FAllUnitsRootUDNode);
FreeAndNil(FUsesGraph);
end;
procedure TUnitDependenciesWindow.GroupsLvlGraphSelectionChanged(Sender: TObject
);
begin
UpdateUnitsLvlGraph;
end;
procedure TUnitDependenciesWindow.OnIdle(Sender: TObject; var Done: Boolean);
var
Completed: boolean;
begin
if udwParsing in FFlags then begin
UsesGraph.Parse(true,Completed,200);
if Completed then begin
Exclude(FFlags,udwParsing);
CreateGroups;
ProgressBar1.Visible:=false;
ProgressBar1.Style:=pbstNormal;
Timer1.Enabled:=false;
UpdateAll;
end;
end else if udwNeedUpdateGroupsLvlGraph in FFlags then
UpdateGroupsLvlGraph
else if udwNeedUpdateUnitsLvlGraph in FFlags then
UpdateUnitsLvlGraph
else if udwNeedUpdateAllUnitsTreeView in FFlags then
UpdateAllUnitsTreeView
else
IdleConnected:=false;
end;
procedure TUnitDependenciesWindow.SearchPkgsCheckBoxChange(Sender: TObject);
begin
// ToDo: reparse
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.SearchSrcEditCheckBoxChange(Sender: TObject);
begin
// ToDo: reparse
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.Timer1Timer(Sender: TObject);
begin
end;
procedure TUnitDependenciesWindow.SearchCustomFilesBrowseButtonClick(Sender: TObject);
var
Dlg: TSelectDirectoryDialog;
s: TCaption;
aFilename: String;
p: Integer;
begin
Dlg:=TSelectDirectoryDialog.Create(nil);
try
InitIDEFileDialog(Dlg);
Dlg.Options:=Dlg.Options+[ofPathMustExist];
if not Dlg.Execute then exit;
aFilename:=TrimFilename(Dlg.FileName);
s:=SearchCustomFilesComboBox.Text;
p:=1;
if FindNextDelimitedItem(s,';',p,aFilename)<>'' then exit;
if s<>'' then s+=';';
s+=aFilename;
SearchCustomFilesComboBox.Text:=s;
// ToDo: Reparse
IdleConnected:=true;
finally
Dlg.Free;
end;
end;
procedure TUnitDependenciesWindow.SearchCustomFilesCheckBoxChange(
Sender: TObject);
begin
UpdateUnitsButtons;
// ToDo: reparse
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.SearchCustomFilesComboBoxChange(
Sender: TObject);
begin
// ToDo: reparse
IdleConnected:=true;
end;
procedure TUnitDependenciesWindow.SetIdleConnected(AValue: boolean);
begin
if FIdleConnected=AValue then Exit;
FIdleConnected:=AValue;
if IdleConnected then
Application.AddOnIdleHandler(@OnIdle)
else
Application.RemoveOnIdleHandler(@OnIdle);
end;
procedure TUnitDependenciesWindow.CreateGroups;
var
i: Integer;
begin
CreateProjectGroup(LazarusIDE.ActiveProject);
for i:=0 to PackageEditingInterface.GetPackageCount-1 do
CreatePackageGroup(PackageEditingInterface.GetPackages(i));
CreateFPCSrcGroups;
GuessGroupOfUnits;
end;
function TUnitDependenciesWindow.CreateProjectGroup(AProject: TLazProject
): TUGGroup;
var
i: Integer;
Filename: String;
CurUnit: TUGUnit;
ProjFile: TLazProjectFile;
begin
if AProject=nil then exit;
Result:=Groups.GetGroup(GroupPrefixProject,true);
//debugln(['TUnitDependenciesDialog.CreateProjectGroup ',Result.Name,' FileCount=',AProject.FileCount]);
for i:=0 to AProject.FileCount-1 do begin
ProjFile:=AProject.Files[i];
if not ProjFile.IsPartOfProject then continue;
Filename:=AProject.Files[i].Filename;
CurUnit:=UsesGraph.GetUnit(Filename,false);
if CurUnit=nil then continue;
if not (CurUnit is TUGGroupUnit) then begin
debugln(['TUnitDependenciesDialog.CreateProjectGroup WARNING: ',CurUnit.Filename,' ',CurUnit.Classname,' should be TUGGroupUnit']);
continue;
end;
if TUGGroupUnit(CurUnit).Group<>nil then continue;
Result.AddUnit(TUGGroupUnit(CurUnit));
end;
end;
function TUnitDependenciesWindow.CreatePackageGroup(APackage: TIDEPackage
): TUGGroup;
var
i: Integer;
Filename: String;
CurUnit: TUGUnit;
begin
if APackage=nil then exit;
Result:=Groups.GetGroup(APackage.Name,true);
//debugln(['TUnitDependenciesDialog.CreatePackageGroup ',Result.Name]);
for i:=0 to APackage.FileCount-1 do begin
Filename:=APackage.Files[i].GetFullFilename;
CurUnit:=UsesGraph.GetUnit(Filename,false);
if CurUnit is TUGGroupUnit then begin
if TUGGroupUnit(CurUnit).Group<>nil then continue;
Result.AddUnit(TUGGroupUnit(CurUnit));
end;
end;
end;
procedure TUnitDependenciesWindow.CreateFPCSrcGroups;
function ExtractFilePathStart(Filename: string; DirCount: integer): string;
var
p: Integer;
begin
p:=1;
while p<=length(Filename) do begin
if Filename[p]=PathDelim then begin
DirCount-=1;
if DirCount=0 then begin
Result:=LeftStr(Filename,p-1);
exit;
end;
end;
inc(p);
end;
Result:=Filename;
end;
var
FPCSrcDir: String;
Node: TAVLTreeNode;
CurUnit: TUGGroupUnit;
Directory: String;
Grp: TUGGroup;
begin
FPCSrcDir:=AppendPathDelim(GetFPCSrcDir);
// for each unit in the fpc source directory:
// if in rtl/ put into group GroupPrefixFPCSrc+RTL
// if in packages/<name>, put in group GroupPrefixFPCSrc+<name>
Node:=UsesGraph.FilesTree.FindLowest;
while Node<>nil do begin
CurUnit:=TUGGroupUnit(Node.Data);
Node:=UsesGraph.FilesTree.FindSuccessor(Node);
if TUGGroupUnit(CurUnit).Group<>nil then continue;
if CompareFilenames(FPCSrcDir,LeftStr(CurUnit.Filename,length(FPCSrcDir)))<>0
then
continue;
// a unit in the FPC sources
Directory:=ExtractFilePath(CurUnit.Filename);
Directory:=copy(Directory,length(FPCSrcDir)+1,length(Directory));
Directory:=ExtractFilePathStart(Directory,2);
if LeftStr(Directory,length('rtl'))='rtl' then
Directory:='RTL'
else if LeftStr(Directory,length('packages'))='packages' then
System.Delete(Directory,1,length('packages'+PathDelim));
Grp:=Groups.GetGroup(GroupPrefixFPCSrc+Directory,true);
//debugln(['TUnitDependenciesDialog.CreateFPCSrcGroups ',Grp.Name]);
Grp.AddUnit(TUGGroupUnit(CurUnit));
end;
end;
procedure TUnitDependenciesWindow.GuessGroupOfUnits;
var
Node: TAVLTreeNode;
CurUnit: TUGGroupUnit;
Filename: String;
Owners: TFPList;
i: Integer;
Group: TUGGroup;
CurDirectory: String;
LastDirectory: Char;
begin
Owners:=nil;
LastDirectory:='.';
Node:=UsesGraph.FilesTree.FindLowest;
while Node<>nil do begin
CurUnit:=TUGGroupUnit(Node.Data);
if CurUnit.Group=nil then begin
Filename:=CurUnit.Filename;
//debugln(['TUnitDependenciesDialog.GuessGroupOfUnits no group for ',Filename]);
CurDirectory:=ExtractFilePath(Filename);
if CompareFilenames(CurDirectory,LastDirectory)<>0 then begin
FreeAndNil(Owners);
Owners:=PackageEditingInterface.GetPossibleOwnersOfUnit(Filename,[piosfIncludeSourceDirectories]);
end;
Group:=nil;
if (Owners<>nil) then begin
for i:=0 to Owners.Count-1 do begin
if TObject(Owners[i]) is TLazProject then begin
Group:=Groups.GetGroup(GroupPrefixProject,true);
//debugln(['TUnitDependenciesDialog.GuessGroupOfUnits ',Group.Name]);
break;
end else if TObject(Owners[i]) is TIDEPackage then begin
Group:=Groups.GetGroup(TIDEPackage(Owners[i]).Name,true);
//debugln(['TUnitDependenciesDialog.GuessGroupOfUnits ',Group.Name]);
break;
end;
end;
end;
if Group=nil then begin
Group:=Groups.GetGroup(GroupNone,true);
//debugln(['TUnitDependenciesDialog.GuessGroupOfUnits ',Group.Name]);
end;
Group.AddUnit(TUGGroupUnit(CurUnit));
end;
Node:=UsesGraph.FilesTree.FindSuccessor(Node);
end;
FreeAndNil(Owners);
end;
procedure TUnitDependenciesWindow.SetCurrentUnit(AValue: TUGUnit);
begin
if FCurrentUnit=AValue then Exit;
FCurrentUnit:=AValue;
end;
procedure TUnitDependenciesWindow.SetAllUnitsMultiSelect(AValue: boolean);
begin
if FAllUnitsMultiSelect=AValue then Exit;
FAllUnitsMultiSelect:=AValue;
AllUnitsMultiselectSpeedButton.Down:=AllUnitsMultiSelect;
AllUnitsTreeView.MultiSelect:=AllUnitsMultiSelect;
end;
function TUnitDependenciesWindow.CreateAllUnitsTree: TUDNode;
var
Node: TUDNode;
ParentNode: TUDNode;
GroupName: String;
ShowDirectories: Boolean;
ShowGroups: Boolean;
NodeText: String;
RootNode: TUDNode;
Filter: String;
UGUnit: TUGGroupUnit;
AVLNode: TAVLTreeNode;
Group: TUGGroup;
GroupNode: TUDNode;
begin
Filter:=UTF8LowerCase(GetAllUnitsFilter);
ShowGroups:=AllUnitsShowGroupNodesSpeedButton.Down;
ShowDirectories:=AllUnitsShowDirsSpeedButton.Down;
RootNode:=TUDNode.Create;
for AVLNode in UsesGraph.FilesTree do begin
UGUnit:=TUGGroupUnit(AVLNode.Data);
NodeText:=ExtractFileName(UGUnit.Filename);
if (Filter<>'') and (Pos(Filter, UTF8LowerCase(NodeText))<1) then
continue;
Group:=UGUnit.Group;
if Group=nil then
GroupName:=GroupNone
else
GroupName:=Group.Name;
ParentNode:=RootNode;
if ShowGroups then begin
// create group nodes
GroupNode:=ParentNode.GetNode(udnGroup,GroupName,true);
GroupNode.Identifier:=GroupName;
GroupNode.Group:=GroupName;
ParentNode:=GroupNode;
end;
if ShowDirectories then begin
// create directory nodes
end;
Node:=ParentNode.GetNode(udnUnit, NodeText, true);
Node.Identifier:=UGUnit.Filename;
Node.Group:=GroupName;
end;
Result:=RootNode;
end;
procedure TUnitDependenciesWindow.AddStartAndTargetUnits;
var
aProject: TLazProject;
i: Integer;
SrcEdit: TSourceEditorInterface;
AFilename: String;
Pkg: TIDEPackage;
j: Integer;
PkgFile: TLazPackageFile;
begin
Include(FFlags,udwParsing);
UsesGraph.TargetAll:=true;
// project lpr
aProject:=LazarusIDE.ActiveProject;
if (aProject<>nil) and (aProject.MainFile<>nil) then
UsesGraph.AddStartUnit(aProject.MainFile.Filename);
// add all open packages
if SearchPkgsCheckBox.Checked then begin
for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin
Pkg:=PackageEditingInterface.GetPackages(i);
if not FilenameIsAbsolute(Pkg.Filename) then continue;
for j:=0 to Pkg.FileCount-1 do begin
PkgFile:=Pkg.Files[j];
if PkgFile.Removed then continue;
aFilename:=PkgFile.GetFullFilename;
if FilenameIsPascalUnit(AFilename) then
UsesGraph.AddStartUnit(AFilename);
end;
end;
end;
// add all source editor files
if SearchSrcEditCheckBox.Checked then begin
for i:=0 to SourceEditorManagerIntf.SourceEditorCount-1 do begin
SrcEdit:=SourceEditorManagerIntf.SourceEditors[i];
AFilename:=SrcEdit.FileName;
if FilenameIsPascalUnit(AFilename) then
UsesGraph.AddStartUnit(AFilename);
end;
end;
// additional units and directories
if SearchCustomFilesCheckBox.Checked then
AddAdditionalFilesAsStartUnits;
end;
procedure TUnitDependenciesWindow.AddAdditionalFilesAsStartUnits;
var
List: TCaption;
aFilename: String;
Files: TStrings;
i: Integer;
p: Integer;
begin
List:=SearchCustomFilesComboBox.Text;
p:=1;
while p<=length(List) do begin
aFilename:=TrimAndExpandFilename(GetNextDelimitedItem(List,';',p));
if (AFilename='') then continue;
if not FileExistsCached(aFilename) then continue;
if DirPathExistsCached(aFilename) then begin
aFilename:=AppendPathDelim(aFilename);
// add all units in directory
Files:=nil;
try
CodeToolBoss.DirectoryCachePool.GetListing(aFilename,Files,false);
if Files<>nil then begin
for i:=0 to Files.Count-1 do begin
if FilenameIsPascalUnit(Files[i]) then
UsesGraph.AddStartUnit(aFilename+Files[i]);
end;
end;
finally
Files.Free;
end;
end else begin
// add a single file
UsesGraph.AddStartUnit(aFilename);
end;
end;
end;
procedure TUnitDependenciesWindow.SetupGroupsTabSheet;
begin
GroupsTabSheet.Caption:='Projects and packages';
GroupsLvlGraph:=TLvlGraphControl.Create(Self);
with GroupsLvlGraph do
begin
Name:='GroupsLvlGraph';
Caption:='';
Align:=alTop;
Height:=200;
NodeStyle.GapBottom:=5;
Parent:=GroupsTabSheet;
OnSelectionChanged:=@GroupsLvlGraphSelectionChanged;
end;
GroupsSplitter.Top:=GroupsLvlGraph.Height;
UnitsLvlGraph:=TLvlGraphControl.Create(Self);
with UnitsLvlGraph do
begin
Name:='UnitsLvlGraph';
Caption:='';
Align:=alClient;
NodeStyle.GapBottom:=5;
Parent:=GroupsTabSheet;
end;
end;
procedure TUnitDependenciesWindow.SetupUnitsTabSheet;
begin
UnitsTabSheet.Caption:='Units';
// start searching
SearchCustomFilesCheckBox.Caption:='Additional directories:';
SearchCustomFilesCheckBox.Hint:='By default only the project units and the source editor units are searched. Add here a list of directories separated by semicolon to search as well.';
SearchCustomFilesComboBox.Text:='';
SearchCustomFilesBrowseButton.Caption:='Browse';
SearchPkgsCheckBox.Caption:='All package units';
SearchSrcEditCheckBox.Caption:='All source editor units';
// view all units
AllUnitsGroupBox.Caption:='All units';
AllUnitsFilterEdit.Text:='(Filter)';
AllUnitsMultiselectSpeedButton.Hint:='Allow to select multiple units';
AllUnitsShowDirsSpeedButton.Hint:='Show nodes for directories';
AllUnitsShowDirsSpeedButton.LoadGlyphFromLazarusResource('pkg_hierarchical');
AllUnitsShowGroupNodesSpeedButton.Hint:='Show nodes for project and packages';
AllUnitsShowGroupNodesSpeedButton.LoadGlyphFromLazarusResource('pkg_hierarchical');
AllUnitsSearchEdit.Text:='(Search)';
AllUnitsSearchNextSpeedButton.Hint:='Search next occurence of this phrase';
AllUnitsSearchNextSpeedButton.LoadGlyphFromLazarusResource('arrow_down');
AllUnitsSearchPrevSpeedButton.Hint:='Search previous occurence of this phrase';
AllUnitsSearchPrevSpeedButton.LoadGlyphFromLazarusResource('arrow_up');
// selected units
SelectedUnitsGroupBox.Caption:='Selected units';
SelUnitsSearchEdit.Text:='(Search)';
SelUnitsSearchNextSpeedButton.Hint:='Search next unit of this phrase';
SelUnitsSearchNextSpeedButton.LoadGlyphFromLazarusResource('arrow_down');
SelUnitsSearchPrevSpeedButton.Hint:='Search previous unit of this phrase';
SelUnitsSearchPrevSpeedButton.LoadGlyphFromLazarusResource('arrow_up');
UpdateUnitsButtons;
end;
procedure TUnitDependenciesWindow.UpdateUnitsButtons;
begin
SearchCustomFilesComboBox.Enabled:=SearchCustomFilesCheckBox.Checked;
SearchCustomFilesBrowseButton.Enabled:=SearchCustomFilesCheckBox.Checked;
end;
procedure TUnitDependenciesWindow.UpdateAll;
begin
UpdateGroupsLvlGraph;
UpdateUnitsLvlGraph;
UpdateAllUnitsTreeView;
end;
procedure TUnitDependenciesWindow.UpdateGroupsLvlGraph;
var
AVLNode: TAVLTreeNode;
Group: TUGGroup;
Graph: TLvlGraph;
PkgList: TFPList;
i: Integer;
RequiredPkg: TIDEPackage;
GroupObj: TObject;
GraphGroup: TLvlGraphNode;
UnitNode: TAVLTreeNode;
GrpUnit: TUGGroupUnit;
UsedUnit: TUGGroupUnit;
begin
Exclude(FFlags,udwNeedUpdateGroupsLvlGraph);
GroupsLvlGraph.BeginUpdate;
Graph:=GroupsLvlGraph.Graph;
Graph.Clear;
AVLNode:=Groups.Groups.FindLowest;
while AVLNode<>nil do begin
Group:=TUGGroup(AVLNode.Data);
AVLNode:=Groups.Groups.FindSuccessor(AVLNode);
GraphGroup:=Graph.GetNode(Group.Name,true);
GraphGroup.Data:=Group;
GroupObj:=nil;
if IsProjectGroup(Group) then begin
// project
GroupObj:=LazarusIDE.ActiveProject;
GraphGroup.Selected:=true;
end else begin
// package
GroupObj:=PackageEditingInterface.FindPackageWithName(Group.Name);
end;
if GroupObj<>nil then begin
// add lpk dependencies
PkgList:=nil;
try
PackageEditingInterface.GetRequiredPackages(GroupObj,PkgList,[pirNotRecursive]);
if (PkgList<>nil) then begin
// add for each dependency an edge in the Graph
for i:=0 to PkgList.Count-1 do begin
RequiredPkg:=TIDEPackage(PkgList[i]);
Graph.GetEdge(GraphGroup,Graph.GetNode(RequiredPkg.Name,true),true);
end;
end;
finally
PkgList.Free;
end;
end else if IsFPCSrcGroup(Group) then begin
// add FPC source dependencies
UnitNode:=Group.Units.FindLowest;
while UnitNode<>nil do begin
GrpUnit:=TUGGroupUnit(UnitNode.Data);
UnitNode:=Group.Units.FindSuccessor(UnitNode);
if GrpUnit.UsesUnits=nil then continue;
for i:=0 to GrpUnit.UsesUnits.Count-1 do begin
UsedUnit:=TUGGroupUnit(TUGUses(GrpUnit.UsesUnits[i]).UsesUnit);
if (UsedUnit.Group=nil) or (UsedUnit.Group=Group) then continue;
Graph.GetEdge(GraphGroup,Graph.GetNode(UsedUnit.Group.Name,true),true);
end;
end;
end;
end;
GroupsLvlGraph.EndUpdate;
end;
procedure TUnitDependenciesWindow.UpdateUnitsLvlGraph;
function UnitToCaption(AnUnit: TUGUnit): string;
begin
Result:=ExtractFileNameOnly(AnUnit.Filename);
end;
var
GraphGroup: TLvlGraphNode;
NewUnits: TFilenameToPointerTree;
UnitGroup: TUGGroup;
AVLNode: TAVLTreeNode;
GroupUnit: TUGGroupUnit;
i: Integer;
HasChanged: Boolean;
Graph: TLvlGraph;
CurUses: TUGUses;
SourceGraphNode: TLvlGraphNode;
TargetGraphNode: TLvlGraphNode;
NewGroups: TStringToPointerTree;
UsedUnit: TUGGroupUnit;
begin
Exclude(FFlags,udwNeedUpdateUnitsLvlGraph);
NewGroups:=TStringToPointerTree.Create(false);
NewUnits:=TFilenameToPointerTree.Create(false);
try
// fetch new list of units
GraphGroup:=GroupsLvlGraph.Graph.FirstSelected;
while GraphGroup<>nil do begin
UnitGroup:=TUGGroup(GraphGroup.Data);
if UnitGroup<>nil then begin
NewGroups[UnitGroup.Name]:=UnitGroup;
AVLNode:=UnitGroup.Units.FindLowest;
while AVLNode<>nil do begin
GroupUnit:=TUGGroupUnit(AVLNode.Data);
NewUnits[GroupUnit.Filename]:=GroupUnit;
AVLNode:=UnitGroup.Units.FindSuccessor(AVLNode);
end;
end;
GraphGroup:=GraphGroup.NextSelected;
end;
// check if something changed
Graph:=UnitsLvlGraph.Graph;
HasChanged:=false;
i:=0;
AVLNode:=NewUnits.Tree.FindLowest;
while AVLNode<>nil do begin
GroupUnit:=TUGGroupUnit(NewUnits.GetNodeData(AVLNode)^.Value);
if (Graph.NodeCount<=i) or (Graph.Nodes[i].Data<>Pointer(GroupUnit)) then
begin
HasChanged:=true;
break;
end;
i+=1;
AVLNode:=NewUnits.Tree.FindSuccessor(AVLNode);
end;
if i<Graph.NodeCount then HasChanged:=true;
if not HasChanged then exit;
// units changed -> update level graph of units
UnitsLvlGraph.BeginUpdate;
Graph.Clear;
AVLNode:=NewUnits.Tree.FindLowest;
while AVLNode<>nil do begin
GroupUnit:=TUGGroupUnit(NewUnits.GetNodeData(AVLNode)^.Value);
SourceGraphNode:=Graph.GetNode(UnitToCaption(GroupUnit),true);
if GroupUnit.UsesUnits<>nil then begin
for i:=0 to GroupUnit.UsesUnits.Count-1 do begin
CurUses:=TUGUses(GroupUnit.UsesUnits[i]);
UsedUnit:=TUGGroupUnit(CurUses.UsesUnit);
if UsedUnit.Group=nil then continue;
if not NewGroups.Contains(UsedUnit.Group.Name) then continue;
TargetGraphNode:=Graph.GetNode(UnitToCaption(UsedUnit),true);
Graph.GetEdge(SourceGraphNode,TargetGraphNode,true);
end;
end;
AVLNode:=NewUnits.Tree.FindSuccessor(AVLNode);
end;
UnitsLvlGraph.EndUpdate;
finally
NewGroups.Free;
NewUnits.Free;
end;
end;
procedure TUnitDependenciesWindow.UpdateAllUnitsTreeView;
procedure CreateTVNodes(TV: TTreeView; ParentTVNode: TTreeNode;
ParentUDNode: TUDNode);
var
AVLNode: TAVLTreeNode;
UDNode: TUDNode;
TVNode: TTreeNode;
begin
if ParentUDNode=nil then exit;
AVLNode:=ParentUDNode.ChildNodes.FindLowest;
while AVLNode<>nil do begin
UDNode:=TUDNode(AVLNode.Data);
TVNode:=TV.Items.AddChild(ParentTVNode,UDNode.NodeText);
TVNode.Data:=UDNode;
CreateTVNodes(TV,TVNode,UDNode);
TVNode.Expanded:=true;
AVLNode:=ParentUDNode.ChildNodes.FindSuccessor(AVLNode);
end;
end;
var
TV: TTreeView;
OldExpanded: TTreeNodeExpandedState;
begin
Exclude(FFlags,udwNeedUpdateAllUnitsTreeView);
TV:=AllUnitsTreeView;
TV.BeginUpdate;
// save old expanded state
if TV.Items.Count>1 then
OldExpanded:=TTreeNodeExpandedState.Create(TV)
else
OldExpanded:=nil;
// clear
FreeAndNil(FAllUnitsRootUDNode);
TV.Items.Clear;
// create nodes
FAllUnitsRootUDNode:=CreateAllUnitsTree;
CreateTVNodes(TV,nil,FAllUnitsRootUDNode);
// restore old expanded state
if OldExpanded<>nil then begin
OldExpanded.Apply(TV);
OldExpanded.Free;
end;
TV.EndUpdate;
end;
function TUnitDependenciesWindow.NodeTextToUnit(NodeText: string): TUGUnit;
var
AVLNode: TAVLTreeNode;
begin
AVLNode:=UsesGraph.FilesTree.FindLowest;
while AVLNode<>nil do begin
Result:=TUGUnit(AVLNode.Data);
if NodeText=UGUnitToNodeText(Result) then exit;
AVLNode:=UsesGraph.FilesTree.FindSuccessor(AVLNode);
end;
Result:=nil;
end;
function TUnitDependenciesWindow.UGUnitToNodeText(UGUnit: TUGUnit): string;
begin
Result:=ExtractFileName(UGUnit.Filename);
end;
function TUnitDependenciesWindow.GetFPCSrcDir: string;
var
UnitSet: TFPCUnitSetCache;
begin
UnitSet:=CodeToolBoss.GetUnitSetForDirectory('');
Result:=UnitSet.FPCSourceDirectory;
end;
function TUnitDependenciesWindow.IsFPCSrcGroup(Group: TUGGroup): boolean;
begin
Result:=(Group<>nil) and (LeftStr(Group.Name,length(GroupPrefixFPCSrc))=GroupPrefixFPCSrc);
end;
function TUnitDependenciesWindow.IsProjectGroup(Group: TUGGroup): boolean;
begin
Result:=(Group<>nil) and (Group.Name=GroupPrefixProject);
end;
function TUnitDependenciesWindow.GetAllUnitsFilter: string;
begin
Result:=AllUnitsFilterEdit.Text;
if Result='(Filter)' then
Result:='';
end;
{$R *.lfm}
end.