lazarus/components/codetools/ide/codyunitdepwnd.pas
mattias b8da2cafab cody: circlediagram: create categories
git-svn-id: trunk@40011 -
2013-01-28 18:20:29 +00:00

745 lines
20 KiB
ObjectPascal

unit CodyUnitDepWnd;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, types, AVL_Tree, contnrs, FileUtil, lazutf8classes,
LazLogger, TreeFilterEdit, CTUnitGraph, CodeToolManager, DefineTemplates,
Forms, Controls, Graphics, Dialogs, ExtCtrls, Buttons, ComCtrls,
LCLType, LazIDEIntf, ProjectIntf, IDEWindowIntf;
resourcestring
a='';
rsSelectAUnit = 'Select a unit';
rsClose = 'Close';
const
DefaultCategoryGap = 0.04;
DefaultFirstCategory = 0;
DefaultCategoryMinSize = 0.05;
type
TCustomCircleDiagramControl = class;
TCircleDiagramCategory = class;
{ TCircleDiagramItem }
TCircleDiagramItem = class(TPersistent)
private
FCaption: TCaption;
FCategory: TCircleDiagramCategory;
FSize: single;
procedure SetCaption(AValue: TCaption);
procedure SetSize(AValue: single);
procedure UpdateLayout;
public
constructor Create(TheCategory: TCircleDiagramCategory);
destructor Destroy; override;
property Category: TCircleDiagramCategory read FCategory;
property Caption: TCaption read FCaption write SetCaption;
property Size: single read FSize write SetSize;
end;
{ TCircleDiagramCategory }
TCircleDiagramCategory = class(TPersistent)
private
FCaption: TCaption;
FColor: TColor;
FDiagram: TCustomCircleDiagramControl;
FMinSize: single;
fItems: TObjectList; // list of TCircleDiagramItem
function GetItems(Index: integer): TCircleDiagramItem;
procedure SetCaption(AValue: TCaption);
procedure SetColor(AValue: TColor);
procedure SetMinSize(AValue: single);
procedure UpdateLayout;
procedure Invalidate;
procedure InternalRemoveItem(Item: TCircleDiagramItem);
public
constructor Create(TheDiagram: TCustomCircleDiagramControl);
destructor Destroy; override;
function InsertItem(Index: integer; aCaption: string): TCircleDiagramItem;
function AddItem(aCaption: string): TCircleDiagramItem;
property Diagram: TCustomCircleDiagramControl read FDiagram;
property Caption: TCaption read FCaption write SetCaption;
property MinSize: single read FMinSize write SetMinSize default DefaultCategoryMinSize;
function Count: integer;
property Items[Index: integer]: TCircleDiagramItem read GetItems;
property Color: TColor read FColor write SetColor;
end;
TCircleDiagramCtrlFlag = (
cdcNeedUpdateLayout
);
TCircleDiagramCtrlFlags = set of TCircleDiagramCtrlFlag;
{ TCustomCircleDiagramControl }
TCustomCircleDiagramControl = class(TCustomControl)
private
FCategoryGap: single;
FCenterCaption: TCaption;
FCenterCaptionRect: TRect;
FFirstCategory: single;
fCategories: TObjectList; // list of TCircleDiagramCategory
fUpdateLock: integer;
fFlags: TCircleDiagramCtrlFlags;
function GetCategories(Index: integer): TCircleDiagramCategory;
procedure SetCategoryGap(AValue: single);
procedure SetCenterCaption(AValue: TCaption);
procedure SetFirstCategory(AValue: single);
procedure InternalRemoveCategory(Category: TCircleDiagramCategory);
protected
//procedure WMVScroll(var Msg: TLMScroll); message LM_VSCROLL;
//procedure WMMouseWheel(var Message: TLMMouseEvent); message LM_MOUSEWHEEL;
procedure CreateWnd; override;
procedure UpdateScrollBar;
procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
//procedure MouseDown(Button:TMouseButton; Shift:TShiftState; X,Y:integer); override;
//procedure MouseMove(Shift:TShiftState; X,Y:integer); override;
//procedure MouseUp(Button:TMouseButton; Shift:TShiftState; X,Y:integer); override;
//procedure KeyDown(var Key: Word; Shift: TShiftState); override;
//procedure HandleStandardKeys(var Key: Word; Shift: TShiftState); virtual;
//procedure HandleKeyUp(var Key: Word; Shift: TShiftState); virtual;
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property CenterCaption: TCaption read FCenterCaption write SetCenterCaption;
procedure Clear;
procedure BeginUpdate; virtual;
procedure EndUpdate; virtual;
procedure UpdateLayout;
procedure EraseBackground({%H-}DC: HDC); override;
function InsertCategory(Index: integer; aCaption: TCaption): TCircleDiagramCategory;
function AddCategory(aCaption: TCaption): TCircleDiagramCategory;
function IndexOfCategory(aCaption: TCaption): integer;
function FindCategory(aCaption: TCaption): TCircleDiagramCategory;
property CategoryGap: single read FCategoryGap write SetCategoryGap default DefaultCategoryGap; // in part of a full circle
property FirstCategory: single read FFirstCategory write SetFirstCategory default DefaultFirstCategory; // in part of a full circle starting at top
function CategoryCount: integer;
property Categories[Index: integer]: TCircleDiagramCategory read GetCategories;
property Color default clWhite;
property CenterCaptionRect: TRect read FCenterCaptionRect;
end;
{ TCircleDiagramControl }
TCircleDiagramControl = class(TCustomCircleDiagramControl)
published
property Align;
property Anchors;
property BorderSpacing;
property BorderStyle;
property BorderWidth;
property Color;
property Constraints;
property DragKind;
property DragCursor;
property DragMode;
property Enabled;
property Font;
property ParentColor default False;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property TabStop default True;
property Tag;
property Visible;
property OnClick;
property OnContextPopup;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMouseDown;
property OnMouseEnter;
property OnMouseLeave;
property OnMouseMove;
property OnMouseUp;
property OnShowHint;
property OnStartDrag;
property OnUTF8KeyPress;
end;
TUDDUsesType = (
uddutInterfaceUses,
uddutImplementationUses,
uddutUsedByInterface,
uddutUsedByImplementation
);
TUDDUsesTypes = set of TUDDUsesType;
{ TUnitDependenciesDialog }
TUnitDependenciesDialog = class(TForm)
BtnPanel: TPanel;
CloseBitBtn: TBitBtn;
CurUnitPanel: TPanel;
CurUnitSplitter: TSplitter;
CurUnitTreeView: TTreeView;
ProgressBar1: TProgressBar;
Timer1: TTimer;
CurUnitTreeFilterEdit: TTreeFilterEdit;
procedure CloseBitBtnClick(Sender: TObject);
procedure CurUnitTreeViewSelectionChanged(Sender: TObject);
procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
procedure Timer1Timer(Sender: TObject);
private
FCurrentUnit: TUGUnit;
FIdleConnected: boolean;
FUsesGraph: TUsesGraph;
fCircleCategories: array[TUDDUsesType] of TCircleDiagramCategory;
procedure SetCurrentUnit(AValue: TUGUnit);
procedure SetIdleConnected(AValue: boolean);
procedure AddStartAndTargetUnits;
procedure UpdateAll;
procedure UpdateCurUnitDiagram;
procedure UpdateCurUnitTreeView;
function NodeTextToUnit(NodeText: string): TUGUnit;
function UGUnitToNodeText(UGUnit: TUGUnit): string;
public
CurUnitDiagram: TCircleDiagramControl;
property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
property UsesGraph: TUsesGraph read FUsesGraph;
property CurrentUnit: TUGUnit read FCurrentUnit write SetCurrentUnit;
end;
var
UnitDependenciesDialog: TUnitDependenciesDialog;
procedure ShowUnitDependenciesDialog(Sender: TObject);
implementation
procedure ShowUnitDependenciesDialog(Sender: TObject);
var
Dlg: TUnitDependenciesDialog;
begin
Dlg:=TUnitDependenciesDialog.Create(nil);
try
Dlg.ShowModal;
finally
Dlg.Free;
end;
end;
{ TCustomCircleDiagramControl }
procedure TCustomCircleDiagramControl.SetCategoryGap(AValue: single);
begin
if AValue<0 then AValue:=0;
if AValue>0.3 then AValue:=0.3;
if FCategoryGap=AValue then Exit;
FCategoryGap:=AValue;
UpdateLayout;
end;
function TCustomCircleDiagramControl.GetCategories(Index: integer
): TCircleDiagramCategory;
begin
Result:=TCircleDiagramCategory(fCategories[Index]);
end;
procedure TCustomCircleDiagramControl.SetCenterCaption(AValue: TCaption);
begin
if FCenterCaption=AValue then Exit;
FCenterCaption:=AValue;
UpdateLayout;
end;
procedure TCustomCircleDiagramControl.SetFirstCategory(AValue: single);
begin
if FFirstCategory=AValue then Exit;
FFirstCategory:=AValue;
UpdateLayout;
end;
procedure TCustomCircleDiagramControl.InternalRemoveCategory(
Category: TCircleDiagramCategory);
begin
fCategories.Remove(Category);
UpdateLayout;
end;
procedure TCustomCircleDiagramControl.CreateWnd;
begin
inherited CreateWnd;
UpdateScrollBar;
end;
procedure TCustomCircleDiagramControl.UpdateScrollBar;
begin
end;
procedure TCustomCircleDiagramControl.DoSetBounds(ALeft, ATop, AWidth,
AHeight: integer);
begin
inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
UpdateScrollBar;
end;
procedure TCustomCircleDiagramControl.Paint;
begin
inherited Paint;
if cdcNeedUpdateLayout in fFlags then
UpdateLayout;
// background
Canvas.Brush.Style:=bsSolid;
Canvas.Brush.Color:=Color;
Canvas.FillRect(ClientRect);
// center caption
Canvas.TextOut(FCenterCaptionRect.Left,FCenterCaptionRect.Top,CenterCaption);
end;
constructor TCustomCircleDiagramControl.Create(AOwner: TComponent);
begin
BeginUpdate;
try
inherited Create(AOwner);
fCategories:=TObjectList.create(true);
Color:=clWhite;
finally
EndUpdate;
end;
end;
destructor TCustomCircleDiagramControl.Destroy;
begin
BeginUpdate; // disable updates
FreeAndNil(fCategories);
inherited Destroy;
end;
procedure TCustomCircleDiagramControl.Clear;
begin
BeginUpdate;
try
while CategoryCount>0 do
Categories[0].Free;
finally
EndUpdate;
end;
end;
procedure TCustomCircleDiagramControl.BeginUpdate;
begin
inc(fUpdateLock);
end;
procedure TCustomCircleDiagramControl.EndUpdate;
begin
if fUpdateLock=0 then
raise Exception.Create('TCustomCircleDiagramControl.EndUpdate');
dec(fUpdateLock);
if fUpdateLock=0 then begin
if cdcNeedUpdateLayout in fFlags then
UpdateLayout;
end;
end;
procedure TCustomCircleDiagramControl.UpdateLayout;
var
aSize: TSize;
begin
if (fUpdateLock>0) or (not IsVisible) or (not HandleAllocated) then begin
Include(fFlags,cdcNeedUpdateLayout);
exit;
end;
Exclude(fFlags,cdcNeedUpdateLayout);
// center caption
aSize:=Canvas.TextExtent(CenterCaption);
FCenterCaptionRect:=Bounds((ClientWidth-aSize.cx) div 2,
(ClientHeight-aSize.cy) div 2,aSize.cx,aSize.cy);
end;
procedure TCustomCircleDiagramControl.EraseBackground(DC: HDC);
begin
// do not erase background, Paint will paint the whole area
end;
function TCustomCircleDiagramControl.InsertCategory(Index: integer;
aCaption: TCaption): TCircleDiagramCategory;
begin
Result:=TCircleDiagramCategory.Create(Self);
Result.Caption:=aCaption;
fCategories.Insert(Index,Result);
end;
function TCustomCircleDiagramControl.AddCategory(aCaption: TCaption
): TCircleDiagramCategory;
begin
Result:=InsertCategory(CategoryCount,aCaption);
end;
function TCustomCircleDiagramControl.IndexOfCategory(aCaption: TCaption
): integer;
begin
Result:=CategoryCount-1;
while Result>=0 do begin
if Categories[Result].Caption=aCaption then exit;
dec(Result);
end;
end;
function TCustomCircleDiagramControl.FindCategory(aCaption: TCaption
): TCircleDiagramCategory;
var
i: Integer;
begin
i:=IndexOfCategory(aCaption);
if i>=0 then
Result:=Categories[i]
else
Result:=nil;
end;
function TCustomCircleDiagramControl.CategoryCount: integer;
begin
Result:=fCategories.Count;
end;
{ TCircleDiagramCategory }
procedure TCircleDiagramCategory.SetCaption(AValue: TCaption);
begin
if FCaption=AValue then Exit;
FCaption:=AValue;
end;
procedure TCircleDiagramCategory.SetColor(AValue: TColor);
begin
if FColor=AValue then Exit;
FColor:=AValue;
Invalidate;
end;
function TCircleDiagramCategory.GetItems(Index: integer): TCircleDiagramItem;
begin
Result:=TCircleDiagramItem(fItems[Index]);
end;
procedure TCircleDiagramCategory.SetMinSize(AValue: single);
begin
if FMinSize=AValue then Exit;
FMinSize:=AValue;
end;
procedure TCircleDiagramCategory.UpdateLayout;
begin
if Diagram<>nil then
Diagram.UpdateLayout;
end;
procedure TCircleDiagramCategory.Invalidate;
begin
if Diagram<>nil then
Diagram.Invalidate;
end;
procedure TCircleDiagramCategory.InternalRemoveItem(Item: TCircleDiagramItem);
begin
fItems.Remove(Item);
UpdateLayout;
end;
constructor TCircleDiagramCategory.Create(
TheDiagram: TCustomCircleDiagramControl);
begin
FDiagram:=TheDiagram;
fItems:=TObjectList.Create(true);
end;
destructor TCircleDiagramCategory.Destroy;
begin
if Diagram<>nil then
Diagram.InternalRemoveCategory(Self);
FreeAndNil(fItems);
inherited Destroy;
end;
function TCircleDiagramCategory.InsertItem(Index: integer; aCaption: string
): TCircleDiagramItem;
begin
Result:=TCircleDiagramItem.Create(Self);
Result.Caption:=aCaption;
fItems.Insert(Index,Result);
end;
function TCircleDiagramCategory.AddItem(aCaption: string): TCircleDiagramItem;
begin
Result:=InsertItem(Count,aCaption);
end;
function TCircleDiagramCategory.Count: integer;
begin
Result:=fItems.Count;
end;
{ TCircleDiagramItem }
procedure TCircleDiagramItem.SetCaption(AValue: TCaption);
begin
if FCaption=AValue then Exit;
FCaption:=AValue;
UpdateLayout;
end;
procedure TCircleDiagramItem.SetSize(AValue: single);
begin
if FSize=AValue then Exit;
FSize:=AValue;
UpdateLayout;
end;
procedure TCircleDiagramItem.UpdateLayout;
begin
if Category<>nil then
Category.UpdateLayout;
end;
constructor TCircleDiagramItem.Create(TheCategory: TCircleDiagramCategory);
begin
FCategory:=TheCategory;
end;
destructor TCircleDiagramItem.Destroy;
begin
if Category<>nil then
Category.InternalRemoveItem(Self);
inherited Destroy;
end;
{ TUnitDependenciesDialog }
procedure TUnitDependenciesDialog.CloseBitBtnClick(Sender: TObject);
begin
ModalResult:=mrCancel;
end;
procedure TUnitDependenciesDialog.CurUnitTreeViewSelectionChanged(
Sender: TObject);
var
CurUnit: TUGUnit;
begin
if CurUnitTreeView.Selected=nil then exit;
CurUnit:=NodeTextToUnit(CurUnitTreeView.Selected.Text);
if CurUnit=nil then exit;
CurrentUnit:=CurUnit;
end;
procedure TUnitDependenciesDialog.FormClose(Sender: TObject;
var CloseAction: TCloseAction);
begin
IDEDialogLayoutList.SaveLayout(Self);
end;
procedure TUnitDependenciesDialog.FormCreate(Sender: TObject);
begin
FUsesGraph:=CodeToolBoss.CreateUsesGraph;
ProgressBar1.Style:=pbstMarquee;
AddStartAndTargetUnits;
Caption:='Unit Dependencies';
CloseBitBtn.Caption:=rsClose;
IDEDialogLayoutList.ApplyLayout(Self,600,400);
CurUnitDiagram:=TCircleDiagramControl.Create(Self);
with CurUnitDiagram do begin
Name:='CurUnitDiagram';
Align:=alClient;
fCircleCategories[uddutInterfaceUses]:=AddCategory('Interface uses');
fCircleCategories[uddutImplementationUses]:=AddCategory('Implementation uses');
fCircleCategories[uddutUsedByInterface]:=AddCategory('Used by interfaces');
fCircleCategories[uddutUsedByImplementation]:=AddCategory('Used by implementations');
CenterCaption:=rsSelectAUnit;
Parent:=Self;
end;
IdleConnected:=true;
end;
procedure TUnitDependenciesDialog.FormDestroy(Sender: TObject);
begin
IdleConnected:=false;
FreeAndNil(FUsesGraph);
end;
procedure TUnitDependenciesDialog.OnIdle(Sender: TObject; var Done: Boolean);
var
Completed: boolean;
begin
UsesGraph.Parse(true,Completed,200);
if Completed then begin
IdleConnected:=false;
ProgressBar1.Visible:=false;
ProgressBar1.Style:=pbstNormal;
Timer1.Enabled:=false;
UpdateAll;
end;
end;
procedure TUnitDependenciesDialog.Timer1Timer(Sender: TObject);
begin
UpdateAll;
end;
procedure TUnitDependenciesDialog.SetIdleConnected(AValue: boolean);
begin
if FIdleConnected=AValue then Exit;
FIdleConnected:=AValue;
if IdleConnected then
Application.AddOnIdleHandler(@OnIdle)
else
Application.RemoveOnIdleHandler(@OnIdle);
end;
procedure TUnitDependenciesDialog.SetCurrentUnit(AValue: TUGUnit);
begin
if FCurrentUnit=AValue then Exit;
FCurrentUnit:=AValue;
UpdateCurUnitDiagram;
end;
procedure TUnitDependenciesDialog.AddStartAndTargetUnits;
var
aProject: TLazProject;
begin
UsesGraph.TargetAll:=true;
// project lpr
aProject:=LazarusIDE.ActiveProject;
if (aProject<>nil) and (aProject.MainFile<>nil) then
UsesGraph.AddStartUnit(aProject.MainFile.Filename);
// ToDo: add all open packages
end;
procedure TUnitDependenciesDialog.UpdateAll;
begin
UpdateCurUnitTreeView;
end;
procedure TUnitDependenciesDialog.UpdateCurUnitDiagram;
procedure UpdateCircleCategory(List: TFPList; t: TUDDUsesType);
// List is CurrentUnit.UsesUnits or CurrentUnit.UsedByUnits
var
i: Integer;
CurUses: TUGUses;
Item: TCircleDiagramItem;
CurUnit: TUGUnit;
Cnt: Integer;
s: String;
begin
Cnt:=0;
for i:=0 to List.Count-1 do begin
CurUses:=TUGUses(List[i]);
if CurUses.InImplementation<>(t in [uddutImplementationUses,uddutUsedByImplementation])
then continue;
if t in [uddutInterfaceUses,uddutImplementationUses] then
CurUnit:=CurUses.Owner
else
CurUnit:=CurUses.UsesUnit;
s:=ExtractFileName(CurUnit.Filename);
if fCircleCategories[t].Count>Cnt then begin
Item:=fCircleCategories[t].Items[Cnt];
Item.Caption:=s
end else
Item:=fCircleCategories[t].AddItem(s);
inc(Cnt);
end;
while fCircleCategories[t].Count>Cnt do
fCircleCategories[t].Items[Cnt].Free;
end;
begin
CurUnitDiagram.BeginUpdate;
try
if CurrentUnit<>nil then begin
CurUnitDiagram.CenterCaption:=ExtractFilename(CurrentUnit.Filename);
UpdateCircleCategory(CurrentUnit.UsesUnits,uddutInterfaceUses);
UpdateCircleCategory(CurrentUnit.UsesUnits,uddutImplementationUses);
UpdateCircleCategory(CurrentUnit.UsedByUnits,uddutUsedByInterface);
UpdateCircleCategory(CurrentUnit.UsedByUnits,uddutUsedByImplementation);
end else begin
CurUnitDiagram.CenterCaption:=rsSelectAUnit;
end;
finally
CurUnitDiagram.EndUpdate;
end;
end;
procedure TUnitDependenciesDialog.UpdateCurUnitTreeView;
var
AVLNode: TAVLTreeNode;
CurUnit: TUGUnit;
sl: TStringListUTF8;
i: Integer;
begin
CurUnitTreeView.BeginUpdate;
sl:=TStringListUTF8.Create;
try
CurUnitTreeView.Items.Clear;
AVLNode:=UsesGraph.FilesTree.FindLowest;
while AVLNode<>nil do begin
CurUnit:=TUGUnit(AVLNode.Data);
sl.Add(UGUnitToNodeText(CurUnit));
AVLNode:=UsesGraph.FilesTree.FindSuccessor(AVLNode);
end;
sl.CustomSort(@CompareStringListItemsUTF8LowerCase);
for i:=0 to sl.Count-1 do begin
CurUnitTreeView.Items.Add(nil,sl[i]);
end;
finally
sl.Free;
CurUnitTreeView.EndUpdate;
end;
end;
function TUnitDependenciesDialog.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 TUnitDependenciesDialog.UGUnitToNodeText(UGUnit: TUGUnit): string;
begin
Result:=ExtractFileName(UGUnit.Filename);
end;
{$R *.lfm}
end.