lazarus/lcl/ldockctrl.pas
mattias 43b02b83ea fixed compilation under fpc 2.1.1
git-svn-id: trunk@9694 -
2006-07-31 07:38:52 +00:00

2524 lines
76 KiB
ObjectPascal

{ $Id: ldocktree.pas 8153 2005-11-14 21:53:06Z mattias $ }
{
/***************************************************************************
LDockCtrl.pas
-----------------
***************************************************************************/
*****************************************************************************
* *
* This file is part of the Lazarus Component Library (LCL) *
* *
* See the file COPYING.modifiedLGPL, included in this distribution, *
* for details about the copyright. *
* *
* This program 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. *
* *
*****************************************************************************
Author: Mattias Gaertner
Abstract:
This unit contains visual components for docking and streaming.
ToDo:
- restoring layout, when a docked control becomes visible
- save TLazDockConfigNode to stream
- load TLazDockConfigNode from stream
}
unit LDockCtrl;
{$mode objfpc}{$H+}
interface
uses
Classes, Math, SysUtils, TypInfo, LCLProc, Controls, Forms, Menus,
LCLStrConsts, AvgLvlTree, StringHashList, LazConfigStorage, LDockCtrlEdit,
LDockTree;
type
TNonDockConfigNames = (
ndcnControlName, // '-Name ' + AControl.Name
ndcnChildIndex, // '-ID ' + IntToStr(AControl index in Parent) +' '+ AControl.ClassName
ndcnParent // '-Parent' : AControl.Parent
);
const
NonDockConfigNamePrefixes: array[TNonDockConfigNames] of string = (
'-Name ',
'-ID ',
'-Parent');
type
TLDConfigNodeType = (
ldcntControl,
ldcntForm,
ldcntSplitterLeftRight,// vertical splitter, can be moved left/right
ldcntSplitterUpDown, // horizontal splitter, can be moved up/down
ldcntPages,
ldcntPage
);
const
LDConfigNodeTypeNames: array[TLDConfigNodeType] of string = (
'Control',
'Form',
'SplitterLeftRight',
'SplitterUpDown',
'Pages',
'Page'
);
type
{ TLazDockConfigNode }
TLazDockConfigNode = class(TPersistent)
private
FBounds: TRect;
FClientBounds: TRect;
FName: string;
FParent: TLazDockConfigNode;
FSides: array[TAnchorKind] of string;
FTheType: TLDConfigNodeType;
FChilds: TFPList;
function GetChildCount: Integer;
function GetChilds(Index: integer): TLazDockConfigNode;
function GetSides(Side: TAnchorKind): string;
procedure SetBounds(const AValue: TRect);
procedure SetClientBounds(const AValue: TRect);
procedure SetName(const AValue: string);
procedure SetParent(const AValue: TLazDockConfigNode);
procedure SetSides(Side: TAnchorKind; const AValue: string);
procedure SetTheType(const AValue: TLDConfigNodeType);
procedure DoAdd(ChildNode: TLazDockConfigNode);
procedure DoRemove(ChildNode: TLazDockConfigNode);
public
constructor Create(ParentNode: TLazDockConfigNode);
constructor Create(ParentNode: TLazDockConfigNode; const AName: string);
destructor Destroy; override;
procedure Clear;
procedure Assign(Source: TPersistent); override;
function FindByName(const AName: string; Recursive: boolean = false;
WithRoot: boolean = true): TLazDockConfigNode;
function IndexOf(const AName: string): Integer;
function GetScreenBounds: TRect;
procedure SaveToConfig(Config: TConfigStorage; const Path: string = '');
procedure LoadFromConfig(Config: TConfigStorage; const Path: string = '');
function GetPath: string;
procedure WriteDebugReport;
function DebugLayoutAsString: string;
public
property Bounds: TRect read FBounds write SetBounds;
property ClientBounds: TRect read FClientBounds write SetClientBounds;
property Parent: TLazDockConfigNode read FParent write SetParent;
property Sides[Side: TAnchorKind]: string read GetSides write SetSides;
property ChildCount: Integer read GetChildCount;
property Childs[Index: integer]: TLazDockConfigNode read GetChilds; default;
published
property TheType: TLDConfigNodeType read FTheType write SetTheType default ldcntControl;
property Name: string read FName write SetName;
end;
{ TLazDockerConfig }
TLazDockerConfig = class
private
FDockerName: string;
FRoot: TLazDockConfigNode;
public
constructor Create(const ADockerName: string; ANode: TLazDockConfigNode);
procedure WriteDebugReport;
property DockerName: string read FDockerName;
property Root: TLazDockConfigNode read FRoot;
end;
TCustomLazControlDocker = class;
{ TCustomLazDockingManager }
TCustomLazDockingManager = class(TComponent)
private
FDockers: TFPList;
FManager: TAnchoredDockManager;
FConfigs: TFPList;// list of TLazDockerConfig
function GetConfigCount: Integer;
function GetConfigs(Index: Integer): TLazDockerConfig;
function GetDockerCount: Integer;
function GetDockers(Index: Integer): TCustomLazControlDocker;
protected
procedure Remove(Docker: TCustomLazControlDocker);
function Add(Docker: TCustomLazControlDocker): Integer;
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
function FindDockerByName(const ADockerName: string;
Ignore: TCustomLazControlDocker = nil): TCustomLazControlDocker;
function FindControlByDockerName(const ADockerName: string;
Ignore: TCustomLazControlDocker = nil): TControl;
function FindDockerByControl(AControl: TControl;
Ignore: TCustomLazControlDocker = nil): TCustomLazControlDocker;
function CreateUniqueName(const AName: string;
Ignore: TCustomLazControlDocker): string;
function GetControlConfigName(AControl: TControl): string;
procedure SaveToConfig(Config: TConfigStorage; const Path: string = '');
procedure LoadFromConfig(Config: TConfigStorage; const Path: string = '');
procedure AddOrReplaceConfig(const DockerName: string;
Config: TLazDockConfigNode);
procedure ClearConfigs;
function GetConfigWithDockerName(const DockerName: string
): TLazDockerConfig;
function CreateLayout(const DockerName: string; VisibleControl: TControl;
ExceptionOnError: boolean = false): TLazDockConfigNode;
function ConfigIsCompatible(RootNode: TLazDockConfigNode;
ExceptionOnError: boolean = false): boolean;
procedure WriteDebugReport;
public
property Manager: TAnchoredDockManager read FManager;
property DockerCount: Integer read GetDockerCount;
property Dockers[Index: Integer]: TCustomLazControlDocker read GetDockers; default;
property ConfigCount: Integer read GetConfigCount;
property Configs[Index: Integer]: TLazDockerConfig read GetConfigs;
end;
{ TLazDockingManager }
TLazDockingManager = class(TCustomLazDockingManager)
published
end;
{ TCustomLazControlDocker
A component to connect a form to the TLazDockingManager.
When the control gets visible TCustomLazControlDocker restores the layout.
Before the control gets invisible, TCustomLazControlDocker saves the layout.
}
TCustomLazControlDocker = class(TComponent)
private
FControl: TControl;
FDockerName: string;
FEnabled: boolean;
FExtendPopupMenu: boolean;
FLocalizedName: string;
FManager: TCustomLazDockingManager;
FPopupMenuItem: TMenuItem;
procedure SetControl(const AValue: TControl);
procedure SetDockerName(const AValue: string);
procedure SetExtendPopupMenu(const AValue: boolean);
procedure SetLocalizedName(const AValue: string);
procedure SetManager(const AValue: TCustomLazDockingManager);
procedure PopupMenuItemClick(Sender: TObject);
protected
procedure UpdatePopupMenu; virtual;
procedure Loaded; override;
function GetLocalizedName: string;
procedure ControlVisibleChanging(Sender: TObject);
procedure ControlVisibleChanged(Sender: TObject);
public
constructor Create(TheOwner: TComponent); override;
procedure ShowDockingEditor; virtual;
function GetLayoutFromControl: TLazDockConfigNode;
procedure SaveLayout;
procedure RestoreLayout;
function GetControlName(AControl: TControl): string;
property Control: TControl read FControl write SetControl;
property Manager: TCustomLazDockingManager read FManager write SetManager;
property ExtendPopupMenu: boolean read FExtendPopupMenu write SetExtendPopupMenu;
property PopupMenuItem: TMenuItem read FPopupMenuItem;
property LocalizedName: string read FLocalizedName write SetLocalizedName;
property DockerName: string read FDockerName write SetDockerName;
property Enabled: boolean read FEnabled write FEnabled;// true to auto restore layout on show
end;
{ TLazControlDocker }
TLazControlDocker = class(TCustomLazControlDocker)
published
property Control;
property Manager;
property ExtendPopupMenu;
property DockerName;
property Enabled;
end;
function LDConfigNodeTypeNameToType(const s: string): TLDConfigNodeType;
function dbgs(Node: TLazDockConfigNode): string; overload;
procedure Register;
implementation
function LDConfigNodeTypeNameToType(const s: string): TLDConfigNodeType;
begin
for Result:=Low(TLDConfigNodeType) to High(TLDConfigNodeType) do
if CompareText(LDConfigNodeTypeNames[Result],s)=0 then exit;
Result:=ldcntControl;
end;
function dbgs(Node: TLazDockConfigNode): string;
begin
if Node=nil then begin
Result:='nil';
end else begin
Result:=Node.Name+'{Type='+LDConfigNodeTypeNames[Node.TheType]
+',ChildCnt='+IntToStr(Node.ChildCount)+'}';
end;
end;
procedure Register;
begin
RegisterComponents('Misc',[TLazDockingManager,TLazControlDocker]);
end;
{ TCustomLazControlDocker }
procedure TCustomLazControlDocker.SetManager(
const AValue: TCustomLazDockingManager);
begin
if FManager=AValue then exit;
//DebugLn('TCustomLazControlDocker.SetManager Old=',DbgSName(Manager),' New=',DbgSName(AValue));
if FManager<>nil then FManager.Remove(Self);
FManager:=AValue;
if FManager<>nil then FManager.Add(Self);
UpdatePopupMenu;
end;
procedure TCustomLazControlDocker.UpdatePopupMenu;
// creates or deletes the PopupMenuItem to the PopupMenu of Control
begin
if [csDestroying,csDesigning]*ComponentState<>[] then exit;
if csLoading in ComponentState then exit;
//DebugLn('TCustomLazControlDocker.UpdatePopupMenu ',DbgSName(Control),' Manager=',DbgSName(Manager),' PopupMenu=',dbgs((Control<>nil) and (Control.PopupMenu<>nil)),' ExtendPopupMenu=',dbgs(ExtendPopupMenu));
if ExtendPopupMenu and (Control<>nil) and (Control.PopupMenu<>nil)
and (Manager<>nil) then begin
//DebugLn('TCustomLazControlDocker.UpdatePopupMenu ADDING');
if (PopupMenuItem<>nil) and (PopupMenuItem.Parent<>Control.PopupMenu.Items)
then begin
// PopupMenuItem is in the old PopupMenu -> delete it
FreeAndNil(FPopupMenuItem);
end;
if (PopupMenuItem=nil) then begin
// create a new PopupMenuItem
FPopupMenuItem:=TMenuItem.Create(Self);
PopupMenuItem.Caption:=rsDocking;
PopupMenuItem.OnClick:=@PopupMenuItemClick;
end;
if PopupMenuItem.Parent=nil then begin
// add PopupMenuItem to Control.PopupMenu
Control.PopupMenu.Items.Add(PopupMenuItem);
end;
end else begin
// delete PopupMenuItem
FreeAndNil(FPopupMenuItem);
end;
end;
procedure TCustomLazControlDocker.Loaded;
begin
inherited Loaded;
UpdatePopupMenu;
end;
procedure TCustomLazControlDocker.ShowDockingEditor;
var
Dlg: TLazDockControlEditorDlg;
i: Integer;
TargetDocker: TCustomLazControlDocker;
Side: TAlign;
CurDocker: TCustomLazControlDocker;
begin
Dlg:=TLazDockControlEditorDlg.Create(nil);
try
// fill the list of controls this control can dock to
Dlg.DockControlComboBox.Text:='';
Dlg.DockControlComboBox.Items.BeginUpdate;
//DebugLn('TCustomLazControlDocker.ShowDockingEditor Self=',DockerName,' Manager.DockerCount=',dbgs(Manager.DockerCount));
try
Dlg.DockControlComboBox.Items.Clear;
for i:=0 to Manager.DockerCount-1 do begin
CurDocker:=Manager.Dockers[i];
//DebugLn('TCustomLazControlDocker.ShowDockingEditor Self=',DockerName,' CurDocker=',CurDocker.DockerName);
if CurDocker=Self then continue;
if CurDocker.Control=nil then continue;
Dlg.DockControlComboBox.Items.Add(CurDocker.GetLocalizedName);
end;
Dlg.DockControlComboBox.Enabled:=Dlg.DockControlComboBox.Items.Count>0;
finally
Dlg.DockControlComboBox.Items.EndUpdate;
end;
// enable Undock button, if Control is docked
Dlg.UndockGroupBox.Enabled:=(Control.Parent<>nil)
and (Control.Parent.ControlCount>1);
if Dlg.ShowModal=mrOk then begin
// dock or undock
case Dlg.DlgResult of
ldcedrUndock:
// undock
Manager.Manager.UndockControl(Control,true);
ldcedrDockLeft,ldcedrDockRight,ldcedrDockTop,
ldcedrDockBottom,ldcedrDockPage:
// dock
begin
TargetDocker:=nil;
for i:=0 to Manager.DockerCount-1 do begin
CurDocker:=Manager.Dockers[i];
if CurDocker=Self then continue;
if Dlg.DockControlComboBox.Text=CurDocker.GetLocalizedName then
TargetDocker:=CurDocker;
end;
if TargetDocker=nil then begin
RaiseGDBException('TCustomLazControlDocker.ShowDockingEditor TargetDocker=nil');
end;
case Dlg.DlgResult of
ldcedrDockLeft: Side:=alLeft;
ldcedrDockRight: Side:=alRight;
ldcedrDockTop: Side:=alTop;
ldcedrDockBottom: Side:=alBottom;
ldcedrDockPage: Side:=alClient;
else RaiseGDBException('TCustomLazControlDocker.ShowDockingEditor ?');
end;
Manager.Manager.DockControl(Control,Side,TargetDocker.Control);
end;
end;
end;
finally
Dlg.Free;
end;
end;
function TCustomLazControlDocker.GetLocalizedName: string;
begin
Result:=LocalizedName;
if LocalizedName='' then begin
Result:=DockerName;
if (Result='') and (Control<>nil) then
Result:=Control.Name;
if Result='' then
Result:=Name;
end;
end;
procedure TCustomLazControlDocker.ControlVisibleChanging(Sender: TObject);
begin
if Control<>Sender then begin
DebugLn('TCustomLazControlDocker.ControlVisibleChanging WARNING: ',
DbgSName(Control),'<>',DbgSName(Sender));
exit;
end;
DebugLn(['TCustomLazControlDocker.ControlVisibleChanging Sender=',DbgSName(Sender)]);
DumpStack;
if Control.Visible then begin
// control will be hidden -> the layout will change
// save the layout for later restore
SaveLayout;
end else if ([csDestroying,csDesigning,csLoading]*ComponentState=[]) then begin
// the control will become visible -> dock it to restore the last layout
RestoreLayout;
end;
end;
procedure TCustomLazControlDocker.ControlVisibleChanged(Sender: TObject);
begin
DebugLn(['TCustomLazControlDocker.ControlVisibleChanged Sender=',DbgSName(Sender)]);
DumpStack;
end;
function TCustomLazControlDocker.GetControlName(AControl: TControl): string;
var
i: Integer;
begin
Result:=Manager.GetControlConfigName(AControl);
if Result='' then begin
if AControl=Control.Parent then
Result:=NonDockConfigNamePrefixes[ndcnParent]
else if AControl.Name<>'' then
Result:=NonDockConfigNamePrefixes[ndcnControlName]+AControl.Name
else if AControl.Parent<>nil then begin
i:=AControl.Parent.ControlCount-1;
while (i>=0) and (AControl.Parent.Controls[i]<>AControl) do dec(i);
Result:=NonDockConfigNamePrefixes[ndcnChildIndex]+IntToStr(i)+' '
+AControl.ClassName;
end;
end;
end;
function TCustomLazControlDocker.GetLayoutFromControl: TLazDockConfigNode;
procedure CopyChildsLayout(ParentNode: TLazDockConfigNode;
ParentNodeControl: TWinControl);
// saves for each child node the names of the anchor side controls
var
i: Integer;
ChildNode: TLazDockConfigNode;
ChildControl: TControl;
a: TAnchorKind;
ChildNames: TStringHashList;// name to control mapping
ChildName: String;
CurAnchorControl: TControl;
CurAnchorCtrlName: String;
CurAnchorNode: TLazDockConfigNode;
begin
ChildNames:=TStringHashList.Create(false);
try
// build mapping of name to control
ChildNames.Data[ParentNode.Name]:=ParentNodeControl;
for i:=0 to ParentNodeControl.ControlCount-1 do begin
ChildControl:=ParentNodeControl.Controls[i];
ChildName:=GetControlName(ChildControl);
if ChildName<>'' then
ChildNames.Data[ChildName]:=ChildControl;
end;
// build mapping control to node
// set 'Sides'
for i:=0 to ParentNode.ChildCount-1 do begin
ChildNode:=ParentNode[i];
ChildControl:=TControl(ChildNames.Data[ChildNode.Name]);
if ChildControl=nil then continue;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
CurAnchorControl:=ChildControl.AnchorSide[a].Control;
if CurAnchorControl=nil then continue;
if CurAnchorControl=ParentNodeControl then
CurAnchorNode:=ParentNode
else begin
CurAnchorCtrlName:=GetControlName(CurAnchorControl);
CurAnchorNode:=ParentNode.FindByName(CurAnchorCtrlName);
if CurAnchorNode=nil then
RaiseGDBException('inconsistency');
end;
//DebugLn('CopyChildsLayout ',DbgSName(CurAnchorControl),' CurAnchorCtrlName="',CurAnchorCtrlName,'"');
ChildNode.Sides[a]:=CurAnchorNode.Name;
end;
end;
finally
ChildNames.Free;
end;
end;
function AddNode(ParentNode: TLazDockConfigNode;
AControl: TControl): TLazDockConfigNode;
var
i: Integer;
CurChildControl: TControl;
NeedChildNodes: boolean;
begin
Result:=TLazDockConfigNode.Create(ParentNode,GetControlName(AControl));
// The Type
if AControl is TLazDockSplitter then begin
if TLazDockSplitter(AControl).ResizeAnchor in [akLeft,akRight] then
Result.FTheType:=ldcntSplitterLeftRight
else
Result.FTheType:=ldcntSplitterUpDown;
end else if AControl is TLazDockForm then
Result.FTheType:=ldcntForm
else if AControl is TLazDockPages then
Result.FTheType:=ldcntPages
else if AControl is TLazDockPage then
Result.FTheType:=ldcntPage
else
Result.FTheType:=ldcntControl;
// Bounds
Result.FBounds:=AControl.BoundsRect;
if AControl is TWinControl then
Result.FClientBounds:=TWinControl(AControl).GetChildsRect(false)
else
Result.FClientBounds:=Rect(0,0,Result.FBounds.Right-Result.FBounds.Left,
Result.FBounds.Bottom-Result.FBounds.Top);
// Childs
if (AControl is TWinControl) then begin
// check if childs need nodes
NeedChildNodes:=(AControl is TLazDockPages)
or (AControl is TLazDockPage);
if not NeedChildNodes then begin
for i:=0 to TWinControl(AControl).ControlCount-1 do begin
CurChildControl:=TWinControl(AControl).Controls[i];
if Manager.FindDockerByControl(CurChildControl,nil)<>nil then begin
NeedChildNodes:=true;
break;
end;
end;
end;
// add child nodes
if NeedChildNodes then begin
for i:=0 to TWinControl(AControl).ControlCount-1 do begin
CurChildControl:=TWinControl(AControl).Controls[i];
AddNode(Result,CurChildControl);
end;
for i:=0 to Result.ChildCount-1 do begin
end;
end;
CopyChildsLayout(Result,TWinControl(AControl));
end;
end;
var
RootControl: TControl;
begin
if (Control=nil) or (Manager=nil) then exit(nil);
RootControl:=Control;
while RootControl.Parent<>nil do
RootControl:=RootControl.Parent;
Result:=AddNode(nil,RootControl);
end;
procedure TCustomLazControlDocker.SaveLayout;
var
Layout: TLazDockConfigNode;
begin
if Manager=nil then exit;
Layout:=GetLayoutFromControl;
if (Layout=nil) then exit;
Manager.AddOrReplaceConfig(DockerName,Layout);
end;
procedure TCustomLazControlDocker.RestoreLayout;
{ TODO
Goals of this algorithm:
- If a form is hidden and immediately shown again, the layout should be
restored 1:1.
That's why a TCustomLazControlDocker stores the complete layout on every
hide. And restores it on every show.
- If an application is closed and all dock forms are closed (in any order)
the layout should be restored on startup, when the forms
are created (in any order).
This is done by saving the layout before all forms are closed.
Example 1: Docking to a side.
Current:
+---+
| A |
+---+
Formerly:
+------------+
|+---+|+----+|
|| A |||Self||
|+---+|+----+|
+------------+
Then put A into a new TLazDockForm, add a splitter and Self.
Example 2: Docking in between
Current:
+-----------+
|+---+|+---+|
|| A ||| C ||
|+---+|+---+|
+-----------+
Formerly:
+------------------+
|+---+|+----+|+---+|
|| A |||Self||| C ||
|+---+|+----+|+---+|
+------------------+
Then enlarge the parent of A and C, add a splitter and Self.
Example:
Formerly:
+-------------------------+
|+-----------------------+|
|| A ||
|+-----------------------+|
|=========================|
|+---+#+-----------+#+---+|
|| D |#| |#| ||
|+---+#| |#| ||
|=====#| B |#| E ||
|+---+#| |#| ||
|| |#| |#| ||
|| |#+-----------+#+---+|
|| F |#===================|
|| |#+-----------------+|
|| |#| C ||
|+---+#+-----------------+|
+-------------------------+
1. Showing A:
There is no other form yet, so just show it at the old position.
+-----------------------+
| A |
+-----------------------+
2. Showing B:
B is the bottom sibling of A. Put A into a new TLazDockForm, add a splitter,
enlarge B horizontally.
+-------------------------+
|+-----------------------+|
|| A ||
|+-----------------------+|
|=========================|
|+-----------------------+|
|| ||
|| ||
|| B ||
|| ||
|| ||
|+-----------------------+|
+-------------------------+
3. Showing C:
C is the bottom sibling of B. Enlarge the parent vertically, add a splitter
and enlarge C horizontally.
+-------------------------+
|+-----------------------+|
|| A ||
|+-----------------------+|
|=========================|
|+-----------------------+|
|| ||
|| ||
|| B ||
|| ||
|| ||
|+-----------------------+|
|=========================|
|+-----------------------+|
|| C ||
|+-----------------------+|
+-------------------------+
4. Showing D:
D is below of A, and left of B and C. Shrink B and C, add a splitter.
+-------------------------+
|+-----------------------+|
|| A ||
|+-----------------------+|
|=========================|
|+---+#+-----------------+|
|| |#| ||
|| |#| ||
|| |#| B ||
|| |#| ||
|| D |#| ||
|| |#+-----------------+|
|| |#===================|
|| |#+-----------------+|
|| |#| C ||
|+---+#+-----------------+|
+-------------------------+
5. Showing E:
Shrink B, add a splitter.
+-------------------------+
|+-----------------------+|
|| A ||
|+-----------------------+|
|=========================|
|+---+#+-----------+#+---+|
|| |#| |#| ||
|| |#| |#| ||
|| |#| B |#| E ||
|| |#| |#| ||
|| D |#| |#| ||
|| |#+-----------+#+---+|
|| |#===================|
|| |#+-----------------+|
|| |#| C ||
|+---+#+-----------------+|
+-------------------------+
6. Showing F:
Shrink D and add a splitter.
+-------------------------+
|+-----------------------+|
|| A ||
|+-----------------------+|
|=========================|
|+---+#+-----------+#+---+|
|| D |#| |#| ||
|+---+#| |#| ||
|=====#| B |#| E ||
|+---+#| |#| ||
|| |#| |#| ||
|| |#+-----------+#+---+|
|| F |#===================|
|| |#+-----------------+|
|| |#| C ||
|+---+#+-----------------+|
+-------------------------+
}
var
Layout: TLazDockConfigNode;
SelfNode: TLazDockConfigNode;
function FindNode(const ANodeName: string): TLazDockConfigNode;
begin
if ANodeName='' then
Result:=nil
else
Result:=Layout.FindByName(ANodeName,true,true);
end;
function FindNodeUsingSplitter(Splitter: TLazDockConfigNode;
SiblingSide: TAnchorKind; NilIfAmbiguous: boolean): TLazDockConfigNode;
var
i: Integer;
ParentNode: TLazDockConfigNode;
Child: TLazDockConfigNode;
begin
Result:=nil;
ParentNode:=Splitter.Parent;
for i:=0 to ParentNode.ChildCount-1 do begin
Child:=ParentNode.Childs[i];
if CompareText(Child.Sides[SiblingSide],Splitter.Name)=0 then begin
if Result=nil then
Result:=Child
else if NilIfAmbiguous then
exit(nil);
end;
end;
end;
function SplitterIsOnlyUsedByNodeAtSide(Splitter, Node: TLazDockConfigNode;
SiblingSide: TAnchorKind): boolean;
{ check if one side of the Splitter is only used by Node.
For example: If only Node.Sides[SiblingSide]=Splitter.Name
---------+
--+#+---+|
B |#| A ||
--+#+---+|
---------+}
begin
Result:=FindNodeUsingSplitter(Splitter,SiblingSide,true)<>nil;
end;
function DockWithOwnSplitter(Side: TAnchorKind): boolean;
{ Add a splitter to Side and dock to it. For example:
--------+ -----------+
---+| ----+#+---+|
B | -> B |#| A ||
---+| ----+#+---+|
--------+ -----------+
If B has no parent, a TLazDockForm is created.
To get space for A, either B is shrinked and/or the parent of B is enlarged
(including the grand parents of B).
}
begin
// TODO
Result:=false;
end;
function DockWithSpiralSpltter: boolean;
begin
// TODO
Result:=false;
end;
function SplitterDocking: boolean;
var
a: TAnchorKind;
SplitterCount: Integer;
SideNode: TLazDockConfigNode;
begin
SplitterCount:=0;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
SideNode:=FindNode(SelfNode.Sides[a]);
if (SideNode<>nil)
and (SideNode.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown])
then begin
if SplitterIsOnlyUsedByNodeAtSide(SideNode,SelfNode,a)
and DockWithOwnSplitter(a) then
exit(true);
inc(SplitterCount);
if (SplitterCount=4) and DockWithSpiralSpltter then
exit(true);
end;
end;
Result:=false;
end;
var
NewBounds: TRect;
begin
DebugLn(['TCustomLazControlDocker.RestoreLayout A ',DockerName,' Control=',DbgSName(Control)]);
if (Manager=nil) or (Control=nil) then exit;
Layout:=Manager.CreateLayout(DockerName,Control,false);
if (Layout=nil) then exit;
try
SelfNode:=Layout.FindByName(DockerName,true);
DebugLn(['TCustomLazControlDocker.RestoreLayout ',SelfNode<>nil,' DockerName=',DockerName]);
if (SelfNode=nil) or (SelfNode.TheType<>ldcntControl) then exit;
if SelfNode.Parent<>nil then begin
// this control was docked
case SelfNode.Parent.TheType of
ldcntPage:
begin
// this control was docked as child of a page
DebugLn(['TCustomLazControlDocker.RestoreLayout TODO restore page']);
end;
ldcntControl,ldcntForm:
begin
// this control was docked on a form as child
DebugLn(['TCustomLazControlDocker.RestoreLayout restore splitter']);
if SplitterDocking then exit;
end;
else
exit;
end;
end;
// default: do not dock, just move
DebugLn(['TCustomLazControlDocker.RestoreLayout ',DockerName,' not docking, just moving ...']);
NewBounds:=SelfNode.GetScreenBounds;
Control.SetBoundsKeepBase(NewBounds.Left,NewBounds.Top,
NewBounds.Right-NewBounds.Left,
NewBounds.Bottom-NewBounds.Top);
finally
Layout.Free;
end;
end;
constructor TCustomLazControlDocker.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
if (not (csLoading in ComponentState))
and (TheOwner is TControl) then
// use as default
Control:=TControl(TheOwner);
ExtendPopupMenu:=true;
end;
procedure TCustomLazControlDocker.PopupMenuItemClick(Sender: TObject);
begin
ShowDockingEditor;
end;
procedure TCustomLazControlDocker.SetControl(const AValue: TControl);
begin
if FControl=AValue then exit;
if FControl<>nil then
FControl.RemoveAllHandlersOfObject(Self);
FControl:=AValue;
if Control<>nil then begin
Control.AddHandlerOnVisibleChanging(@ControlVisibleChanging);
Control.AddHandlerOnVisibleChanged(@ControlVisibleChanged);
end;
if DockerName='' then
DockerName:=AValue.Name;
UpdatePopupMenu;
end;
procedure TCustomLazControlDocker.SetDockerName(const AValue: string);
var
NewDockerName: String;
begin
if FDockerName=AValue then exit;
NewDockerName:=AValue;
if Manager<>nil then
NewDockerName:=Manager.CreateUniqueName(NewDockerName,Self);
FDockerName:=NewDockerName;
end;
procedure TCustomLazControlDocker.SetExtendPopupMenu(const AValue: boolean);
begin
if FExtendPopupMenu=AValue then exit;
FExtendPopupMenu:=AValue;
UpdatePopupMenu;
end;
procedure TCustomLazControlDocker.SetLocalizedName(const AValue: string);
begin
if FLocalizedName=AValue then exit;
FLocalizedName:=AValue;
end;
{ TCustomLazDockingManager }
procedure TCustomLazDockingManager.Remove(Docker: TCustomLazControlDocker);
begin
FDockers.Remove(Docker);
end;
function TCustomLazDockingManager.Add(Docker: TCustomLazControlDocker): Integer;
begin
Docker.DockerName:=CreateUniqueName(Docker.DockerName,nil);
Result:=FDockers.Add(Docker);
end;
function TCustomLazDockingManager.GetDockers(Index: Integer
): TCustomLazControlDocker;
begin
Result:=TCustomLazControlDocker(FDockers[Index]);
end;
function TCustomLazDockingManager.GetDockerCount: Integer;
begin
Result:=FDockers.Count;
end;
function TCustomLazDockingManager.GetConfigCount: Integer;
begin
if FConfigs<>nil then
Result:=FConfigs.Count
else
Result:=0;
end;
function TCustomLazDockingManager.GetConfigs(Index: Integer
): TLazDockerConfig;
begin
Result:=TLazDockerConfig(FConfigs[Index]);
end;
constructor TCustomLazDockingManager.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
FDockers:=TFPList.Create;
FManager:=TAnchoredDockManager.Create;
end;
destructor TCustomLazDockingManager.Destroy;
var
i: Integer;
begin
for i:=FDockers.Count-1 downto 0 do
Dockers[i].Manager:=nil;
FreeAndNil(FDockers);
FreeAndNil(FManager);
ClearConfigs;
FreeAndNil(FConfigs);
inherited Destroy;
end;
function TCustomLazDockingManager.FindDockerByName(const ADockerName: string;
Ignore: TCustomLazControlDocker): TCustomLazControlDocker;
var
i: Integer;
begin
i:=DockerCount-1;
while (i>=0) do begin
Result:=Dockers[i];
if (CompareText(Result.DockerName,ADockerName)=0) and (Ignore<>Result) then
exit;
dec(i);
end;
Result:=nil;
end;
function TCustomLazDockingManager.FindControlByDockerName(
const ADockerName: string; Ignore: TCustomLazControlDocker): TControl;
var
Docker: TCustomLazControlDocker;
begin
Docker:=FindDockerByName(ADockerName);
if Docker=nil then
Result:=nil
else
Result:=Docker.Control;
end;
function TCustomLazDockingManager.FindDockerByControl(AControl: TControl;
Ignore: TCustomLazControlDocker): TCustomLazControlDocker;
var
i: Integer;
begin
i:=DockerCount-1;
while (i>=0) do begin
Result:=Dockers[i];
if (Result.Control=AControl) and (Ignore<>Result) then
exit;
dec(i);
end;
Result:=nil;
end;
function TCustomLazDockingManager.CreateUniqueName(const AName: string;
Ignore: TCustomLazControlDocker): string;
begin
Result:=AName;
if FindDockerByName(Result,Ignore)=nil then exit;
Result:=CreateFirstIdentifier(Result);
while FindDockerByName(Result,Ignore)<>nil do
Result:=CreateNextIdentifier(Result);
end;
function TCustomLazDockingManager.GetControlConfigName(AControl: TControl
): string;
var
Docker: TCustomLazControlDocker;
begin
Docker:=FindDockerByControl(AControl,nil);
if Docker<>nil then
Result:=Docker.DockerName
else
Result:='';
end;
procedure TCustomLazDockingManager.SaveToConfig(Config: TConfigStorage;
const Path: string);
var
i: Integer;
ADocker: TCustomLazControlDocker;
CurDockConfig: TLazDockerConfig;
SubPath: String;
begin
// collect configs
for i:=0 to DockerCount-1 do begin
ADocker:=Dockers[i];
if ((ADocker.Control<>nil) and ADocker.Control.Visible) then begin
ADocker.SaveLayout;
end;
end;
// save configs
Config.SetDeleteValue(Path+'Configs/Count',ConfigCount,0);
for i:=0 to ConfigCount-1 do begin
SubPath:=Path+'Config'+IntToStr(i)+'/';
CurDockConfig:=Configs[i];
Config.SetDeleteValue(SubPath+'DockerName/Value',CurDockConfig.DockerName,'');
CurDockConfig.Root.SaveToConfig(Config,SubPath);
end;
end;
procedure TCustomLazDockingManager.LoadFromConfig(Config: TConfigStorage;
const Path: string);
var
i: Integer;
NewConfigCount: LongInt;
SubPath: String;
NewRoot: TLazDockConfigNode;
NewDockerName: String;
NewRootName: String;
begin
// merge the configs
NewConfigCount:=Config.GetValue(Path+'Configs/Count',0);
//DebugLn(['TCustomLazDockingManager.LoadFromConfig NewConfigCount=',NewConfigCount]);
for i:=0 to NewConfigCount-1 do begin
SubPath:=Path+'Config'+IntToStr(i)+'/';
NewDockerName:=Config.GetValue(SubPath+'DockerName/Value','');
if NewDockerName='' then continue;
NewRootName:=Config.GetValue(SubPath+'Name/Value','');
if NewRootName='' then continue;
//DebugLn(['TCustomLazDockingManager.LoadFromConfig NewDockerName=',NewDockerName,' NewRootName=',NewRootName]);
NewRoot:=TLazDockConfigNode.Create(nil,NewRootName);
NewRoot.LoadFromConfig(Config,SubPath);
AddOrReplaceConfig(NewDockerName,NewRoot);
//NewRoot.WriteDebugReport;
end;
end;
procedure TCustomLazDockingManager.AddOrReplaceConfig(
const DockerName: string; Config: TLazDockConfigNode);
var
i: Integer;
CurConfig: TLazDockerConfig;
begin
if FConfigs=nil then
FConfigs:=TFPList.Create;
for i:=FConfigs.Count-1 downto 0 do begin
CurConfig:=Configs[i];
if CompareText(CurConfig.DockerName,DockerName)=0 then begin
CurConfig.FRoot.Free;
CurConfig.FRoot:=Config;
exit;
end;
end;
FConfigs.Add(TLazDockerConfig.Create(DockerName,Config));
end;
procedure TCustomLazDockingManager.WriteDebugReport;
var
i: Integer;
ADocker: TCustomLazControlDocker;
begin
DebugLn('TCustomLazDockingManager.WriteDebugReport DockerCount=',dbgs(DockerCount));
for i:=0 to DockerCount-1 do begin
ADocker:=Dockers[i];
DebugLn(' ',dbgs(i),' Name="',ADocker.Name,'" DockerName="',ADocker.DockerName,'"');
end;
end;
procedure TCustomLazDockingManager.ClearConfigs;
var
i: Integer;
begin
if FConfigs=nil then exit;
for i:=0 to FConfigs.Count-1 do TObject(FConfigs[i]).Free;
FConfigs.Clear;
end;
function TCustomLazDockingManager.GetConfigWithDockerName(
const DockerName: string): TLazDockerConfig;
var
i: Integer;
begin
i:=ConfigCount-1;
while (i>=0) do begin
Result:=Configs[i];
if CompareText(Result.DockerName,DockerName)=0 then exit;
dec(i);
end;
Result:=nil;
end;
function TCustomLazDockingManager.CreateLayout(const DockerName: string;
VisibleControl: TControl; ExceptionOnError: boolean): TLazDockConfigNode;
// create a usable config
// This means: search a config, create a copy
// and remove all nodes without visible controls.
var
Root: TLazDockConfigNode;
CurDockControl: TControl;
function ControlIsVisible(AControl: TControl): boolean;
begin
Result:=false;
if (AControl=nil) then exit;
if (not AControl.IsVisible) and (AControl<>VisibleControl) then exit;
if (CurDockControl<>nil) and (CurDockControl<>AControl.GetTopParent) then
exit;
Result:=true;
end;
function FindNode(const AName: string): TLazDockConfigNode;
begin
if AName='' then
Result:=nil
else
Result:=Root.FindByName(AName,true,true);
end;
function FindNodeUsingSplitter(Splitter: TLazDockConfigNode;
SiblingSide: TAnchorKind; NilIfAmbiguous: boolean): TLazDockConfigNode;
var
i: Integer;
ParentNode: TLazDockConfigNode;
Child: TLazDockConfigNode;
begin
Result:=nil;
ParentNode:=Splitter.Parent;
for i:=0 to ParentNode.ChildCount-1 do begin
Child:=ParentNode.Childs[i];
if CompareText(Child.Sides[SiblingSide],Splitter.Name)=0 then begin
if Result=nil then
Result:=Child
else if NilIfAmbiguous then
exit(nil);
end;
end;
end;
function SplitterIsOnlyUsedByNodeAtSide(Splitter, Node: TLazDockConfigNode;
SiblingSide: TAnchorKind): boolean;
{ check if one side of the Splitter is only used by Node.
For example: If only Node.Sides[SiblingSide]=Splitter.Name
---------+
--+#+---+|
B |#| A ||
--+#+---+|
---------+}
begin
Result:=FindNodeUsingSplitter(Splitter,SiblingSide,true)<>nil;
end;
procedure DeleteNode(var DeletingNode: TLazDockConfigNode);
function DeleteOwnSideSplitter(Side: TAnchorKind;
var SplitterNode: TLazDockConfigNode): boolean;
{ check if DeletingNode has a splitter to Side, and this node is the only
node anchored to the splitter at this side.
If yes, it removes the splitter and the DeletingNode and reconnects the
nodes using the splitter with the opposite side
For example:
---------+ --------+
--+#+---+| ---+|
B |#| A || -> B ||
--+#+---+| ---+|
---------+ --------+
}
var
i: Integer;
Sibling: TLazDockConfigNode;
OppositeSide: TAnchorKind;
begin
Result:=false;
// check if this is the only node using this Side of the splitter
if not SplitterIsOnlyUsedByNodeAtSide(SplitterNode,DeletingNode,Side) then
exit;
// All nodes, that uses the splitter from the other side will now be
// anchored to the other side of DeletingNode
OppositeSide:=OppositeAnchor[Side];
for i:=0 to DeletingNode.Parent.ChildCount-1 do begin
Sibling:=DeletingNode.Parent.Childs[i];
if CompareText(Sibling.Sides[OppositeSide],SplitterNode.Name)=0 then
Sibling.Sides[OppositeSide]:=DeletingNode.Sides[OppositeSide];
end;
// delete splitter
FreeAndNil(SplitterNode);
Result:=true;
end;
function UnbindSpiralNode: boolean;
{ DeletingNode has 4 splitters like a spiral.
In this case merge the two vertical splitters.
For example:
| |
-------| -----|
|+---+| |
|| A || -> |
|+---+| |
|-------- |------
| |
}
var
LeftSplitter: TLazDockConfigNode;
RightSplitter: TLazDockConfigNode;
i: Integer;
Sibling: TLazDockConfigNode;
begin
LeftSplitter:=FindNode(DeletingNode.Sides[akLeft]);
RightSplitter:=FindNode(DeletingNode.Sides[akRight]);
// remove LeftSplitter
// 1. enlarge RightSplitter
if CompareText(RightSplitter.Sides[akTop],DeletingNode.Sides[akTop])=0 then
RightSplitter.Sides[akTop]:=LeftSplitter.Sides[akTop];
if CompareText(RightSplitter.Sides[akBottom],DeletingNode.Sides[akBottom])=0 then
RightSplitter.Sides[akBottom]:=LeftSplitter.Sides[akBottom];
// 2. anchor all siblings using LeftSplitter to now use RightSplitter
for i:=0 to DeletingNode.Parent.ChildCount-1 do begin
Sibling:=DeletingNode.Parent.Childs[i];
if Sibling=DeletingNode then continue;
if CompareText(Sibling.Sides[akLeft],LeftSplitter.Name)=0 then
Sibling.Sides[akLeft]:=RightSplitter.Name;
if CompareText(Sibling.Sides[akRight],LeftSplitter.Name)=0 then
Sibling.Sides[akRight]:=RightSplitter.Name;
end;
// 3. delete LeftSplitter
FreeAndNil(LeftSplitter);
Result:=true;
end;
var
a: TAnchorKind;
SiblingNode: TLazDockConfigNode;
SplitterCount: Integer;// number of shared splitters
begin
DebugLn(['DeleteNode ',DeletingNode.Name]);
SplitterCount:=0;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
SiblingNode:=FindNode(DeletingNode.Sides[a]);
if (SiblingNode<>nil)
and (SiblingNode.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown])
then begin
// there is a splitter
if DeleteOwnSideSplitter(a,SiblingNode) then begin
// splitter deleted
break;
end else begin
inc(SplitterCount);// not own => shared
if SplitterCount=4 then begin
// this is a spiral splitter node -> merge two splitters
UnbindSpiralNode;
break;
end;
end;
end;
end;
FreeAndNil(DeletingNode);
end;
procedure SimplifyOnePageNode(var PagesNode: TLazDockConfigNode);
{ PagesNode has only one page left.
Remove Page and Pages node and move the content to the parent
}
var
ParentNode: TLazDockConfigNode;
PageNode: TLazDockConfigNode;
i: Integer;
Child: TLazDockConfigNode;
ChildBounds: TRect;
PagesBounds: TRect;
OffsetX: Integer;
OffsetY: Integer;
a: TAnchorKind;
begin
DebugLn(['SimplifyOnePageNode ',dbgs(PagesNode)]);
ParentNode:=PagesNode.Parent;
if ParentNode=nil then RaiseGDBException('');
if (PagesNode.TheType<>ldcntPages) then RaiseGDBException('');
if PagesNode.ChildCount<>1 then RaiseGDBException('');
PageNode:=PagesNode.Childs[0];
PagesBounds:=PagesNode.Bounds;
OffsetX:=PagesBounds.Left;
OffsetY:=PagesBounds.Top;
for i:=0 to PageNode.ChildCount-1 do begin
Child:=PageNode.Childs[i];
// changes parent of child
Child.Parent:=ParentNode;
// move childs to place where PagesNode was
ChildBounds:=Child.Bounds;
OffsetRect(ChildBounds,OffsetX,OffsetY);
Child.Bounds:=ChildBounds;
// change anchors of child
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if CompareText(Child.Sides[a],PageNode.Name)=0 then
Child.Sides[a]:=PagesNode.Sides[a];
end;
end;
FreeAndNil(PagesNode);
//debugln(Root.DebugLayoutAsString);
end;
procedure SimplifyOneChildForm(var FormNode: TLazDockConfigNode);
{ FormNode has only one child left.
Remove Form node and replace root with child
}
var
FormBounds: TRect;
OffsetX: LongInt;
OffsetY: LongInt;
Child: TLazDockConfigNode;
ChildBounds: TRect;
a: TAnchorKind;
OldFormNode: TLazDockConfigNode;
begin
DebugLn(['SimplifyOneChildForm ',dbgs(FormNode)]);
if FormNode<>Root then RaiseGDBException('');
if FormNode.ChildCount<>1 then RaiseGDBException('');
FormBounds:=FormNode.Bounds;
OffsetX:=FormBounds.Left;
OffsetY:=FormBounds.Top;
Child:=FormNode.Childs[0];
// changes parent of child
Child.Parent:=FormNode.Parent;
// move child to place where FormNode was
ChildBounds:=Child.Bounds;
OffsetRect(ChildBounds,OffsetX,OffsetY);
Child.Bounds:=ChildBounds;
// change anchors of child
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if CompareText(Child.Sides[a],FormNode.Name)=0 then
Child.Sides[a]:=FormNode.Sides[a];
end;
OldFormNode:=FormNode;
FormNode:=Child;
OldFormNode.Free;
//Root.WriteDebugReport;
end;
procedure RemoveEmptyNodes(var Node: TLazDockConfigNode);
// remove unneeded child nodes
// if no childs left and Node itself is unneeded, it s freed and set to nil
var
i: Integer;
Docker: TCustomLazControlDocker;
Child: TLazDockConfigNode;
begin
if Node=nil then exit;
DebugLn(['RemoveEmptyNodes ',Node.Name,' Node.ChildCount=',Node.ChildCount]);
// remove unneeded childs
i:=Node.ChildCount-1;
while i>=0 do begin
Child:=Node.Childs[i];
RemoveEmptyNodes(Child);// beware: this can delete more than one child
dec(i);
if i>=Node.ChildCount then i:=Node.ChildCount-1;
end;
case Node.TheType of
ldcntControl:
begin
Docker:=FindDockerByName(Node.Name);
// if the associated control does not exist or is not visible,
// then delete the node
if (Docker=nil) then begin
DebugLn(['RemoveEmptyNodes delete unknown node: ',dbgs(Node)]);
DeleteNode(Node);
end
else if not ControlIsVisible(Docker.Control) then begin
DebugLn(['RemoveEmptyNodes delete invisible node: ',dbgs(Node)]);
DeleteNode(Node);
end;
end;
ldcntPage:
// these are auto created parent node. If they have no childs: delete
if Node.ChildCount=0 then begin
DebugLn(['RemoveEmptyNodes delete node without childs: ',dbgs(Node)]);
DeleteNode(Node);
end;
ldcntForm:
// these are auto created parent node. If they have no childs: delete
// if they have only one child: delete node and move childs up
if Node.ChildCount=0 then begin
DebugLn(['RemoveEmptyNodes delete node without childs: ',dbgs(Node)]);
DeleteNode(Node);
end else if Node.ChildCount=1 then begin
// Only one child left
SimplifyOneChildForm(Node);
end;
ldcntPages:
// these are auto created parent node. If they have no childs: delete
// if they have only one child: delete node and move child up
if Node.ChildCount=0 then begin
DebugLn(['RemoveEmptyNodes delete node without childs: ',dbgs(Node)]);
DeleteNode(Node);
end else if Node.ChildCount=1 then begin
// Only one child left
SimplifyOnePageNode(Node);
end;
end;
end;
function AllControlsAreOnSameForm: boolean;
var
RootForm: TControl;
function Check(Node: TLazDockConfigNode): boolean;
var
i: Integer;
CurForm: TControl;
begin
if Node.TheType=ldcntControl then begin
CurForm:=FindControlByDockerName(Node.Name);
if (CurForm<>nil) then begin
while CurForm.Parent<>nil do
CurForm:=CurForm.Parent;
if CurForm<>VisibleControl then begin
if RootForm=nil then
RootForm:=CurForm
else if RootForm<>CurForm then
exit(false);
end;
end;
end;
// check childs
for i:=0 to Node.ChildCount-1 do
if not Check(Node.Childs[i]) then exit(false);
Result:=true;
end;
begin
RootForm:=nil;
Result:=Check(Root);
end;
function FindNearestControlNode: TLazDockConfigNode;
function FindOwnSplitterSiblingWithControl(Node: TLazDockConfigNode
): TLazDockConfigNode;
{ find a sibling, that is a direct neighbour behind a splitter, and the
splitter is only used by the node and the sibling
For example:
---------+
--+#+---+|
B |#| A ||
--+#+---+|
---------+
}
var
a: TAnchorKind;
SplitterNode: TLazDockConfigNode;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if Node.Sides[a]='' then continue;
SplitterNode:=FindNode(Node.Sides[a]);
if (SplitterNode.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown])
and SplitterIsOnlyUsedByNodeAtSide(SplitterNode,Node,a) then
begin
Result:=FindNodeUsingSplitter(SplitterNode,OppositeAnchor[a],true);
if Result<>nil then exit;
end;
end;
Result:=nil;
end;
function FindSiblingWithControl(Node: TLazDockConfigNode
): TLazDockConfigNode;
var
ParentNode: TLazDockConfigNode;
i: Integer;
begin
ParentNode:=Node.Parent;
for i:=0 to ParentNode.ChildCount-1 do begin
Result:=ParentNode.Childs[i];
if CompareText(Result.Name,DockerName)=0 then continue;
if Result.TheType=ldcntControl then
exit;
end;
Result:=nil;
end;
function FindPageSiblingWithControl(Node: TLazDockConfigNode
): TLazDockConfigNode;
{ find direct page sibling
This means:
Node is the only child of a page
A sibling page has a single child with a control
}
var
PagesNode: TLazDockConfigNode;
PageNode: TLazDockConfigNode;
PageIndex: LongInt;
begin
// check if node is on a page without siblings
PageNode:=Node.Parent;
if (PageNode=nil) or (PageNode.TheType<>ldcntPage)
or (PageNode.ChildCount>1) then exit;
// check if left page has only one control
PagesNode:=PageNode.Parent;
PageIndex:=PagesNode.IndexOf(PageNode.Name);
if (PageIndex>0)
and (PagesNode[PageIndex-1].ChildCount=1) then begin
Result:=PagesNode[PageIndex-1].Childs[0];
if Result.TheType=ldcntControl then exit;
end;
// check if right page has only one control
if (PageIndex<PagesNode.ChildCount-1)
and (PagesNode[PageIndex+1].ChildCount=1) then begin
Result:=PagesNode[PageIndex+1].Childs[0];
if Result.TheType=ldcntControl then exit;
end;
Result:=nil;
end;
function FindOtherNodeWithControl(Node: TLazDockConfigNode
): TLazDockConfigNode;
var
i: Integer;
begin
Result:=nil;
if (Node.TheType=ldcntControl)
and (CompareText(Node.Name,DockerName)<>0) then
exit(Node);
for i:=0 to Node.ChildCount-1 do begin
Result:=FindOtherNodeWithControl(Node.Childs[i]);
if Result<>nil then exit;
end;
end;
var
Node: TLazDockConfigNode;
begin
Node:=FindNode(DockerName);
Result:=FindOwnSplitterSiblingWithControl(Node);
if Result<>nil then exit;
Result:=FindSiblingWithControl(Node);
Node:=Root.FindByName(DockerName);
Result:=FindPageSiblingWithControl(Node);
if Result<>nil then exit;
Result:=FindOtherNodeWithControl(Root);
end;
var
Config: TLazDockerConfig;
CurControl: TControl;
NearestControlNode: TLazDockConfigNode;
begin
Result:=nil;
CurDockControl:=nil;
Root:=nil;
Config:=GetConfigWithDockerName(DockerName);
DebugLn(['TCustomLazDockingManager.CreateLayout DockerName="',DockerName,'"']);
config.WriteDebugReport;
if (Config=nil) or (Config.Root=nil) then exit;
CurControl:=FindControlByDockerName(DockerName);
DebugLn(['TCustomLazDockingManager.CreateLayout CurControl=',DbgSName(CurControl)]);
if not ControlIsVisible(CurControl) then exit;
DebugLn(['TCustomLazDockingManager.CreateLayout CurControl is treated as visible']);
if (not ConfigIsCompatible(Config.Root,ExceptionOnError)) then exit;
DebugLn(['TCustomLazDockingManager.CreateLayout Config is compatible']);
// create a copy of the config
Root:=TLazDockConfigNode.Create(nil);
try
Root.Assign(Config.Root);
// clean up by removing all invisible, unknown and empty nodes
RemoveEmptyNodes(Root);
DebugLn(['TCustomLazDockingManager.CreateLayout After removing unneeded nodes:']);
Root.WriteDebugReport;
// check if all used controls are on the same dock form
if not AllControlsAreOnSameForm then begin
DebugLn(['TCustomLazDockingManager.CreateLayout not all Controls are on the same Form']);
// the used controls are distributed on different dock forms
// => choose one dock form and remove the nodes of the others
NearestControlNode:=FindNearestControlNode;
if NearestControlNode=nil then RaiseGDBException('');
CurDockControl:=FindControlByDockerName(NearestControlNode.Name);
if CurDockControl=nil then RaiseGDBException('');
CurDockControl:=CurDockControl.GetTopParent;
// remove nodes of other dock forms
RemoveEmptyNodes(Root);
DebugLn(['TCustomLazDockingManager.CreateLayout After removing nodes of other dock forms:']);
Root.WriteDebugReport;
end;
Result:=Root;
Root:=nil;
finally
Root.Free;
end;
end;
function TCustomLazDockingManager.ConfigIsCompatible(
RootNode: TLazDockConfigNode; ExceptionOnError: boolean): boolean;
function CheckNode(Node: TLazDockConfigNode): boolean;
procedure Error(const Msg: string);
var
s: String;
begin
s:='Error: Node="'+Node.GetPath+'"';
s:=s+' NodeType='+LDConfigNodeTypeNames[Node.TheType];
s:=s+Msg;
DebugLn(s);
if ExceptionOnError then raise Exception.Create(s);
end;
function CheckSideAnchored(a: TAnchorKind): boolean;
var
SiblingName: string;
Sibling: TLazDockConfigNode;
procedure ErrorWrongSplitter;
begin
Error('invalid Node.Sides[a] Node="'+Node.Name+'"'
+' Node.Sides['+AnchorNames[a]+']="'+Node.Sides[a]+'"');
end;
begin
SiblingName:=Node.Sides[a];
if SiblingName='' then begin
Error('Node.Sides[a]=''''');
exit(false);
end;
Sibling:=RootNode.FindByName(SiblingName,true);
if Sibling=nil then begin
Error('Node.Sides[a] not found');
exit(false);
end;
if Sibling=Node.Parent then
exit(true); // anchored to parent: ok
if (a in [akLeft,akRight]) and (Sibling.TheType=ldcntSplitterLeftRight)
then
exit(true); // left/right side anchored to a left/right splitter: ok
if (a in [akTop,akBottom]) and (Sibling.TheType=ldcntSplitterUpDown)
then
exit(true); // top/bottom side anchored to a up/down splitter: ok
// otherwise: not ok
ErrorWrongSplitter;
Result:=false;
end;
function CheckAllSidesAnchored: boolean;
var
a: TAnchorKind;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do
if not CheckSideAnchored(a) then exit(false);
Result:=true;
end;
function CheckSideNotAnchored(a: TAnchorKind): boolean;
begin
if Node.Sides[a]<>'' then begin
Error('Sides[a]<>''''');
Result:=false;
end else
Result:=true;
end;
function CheckNoSideAnchored: boolean;
var
a: TAnchorKind;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do
if not CheckSideNotAnchored(a) then exit(false);
Result:=true;
end;
function CheckHasChilds: boolean;
begin
if Node.ChildCount=0 then begin
Error('ChildCount=0');
Result:=false;
end else
Result:=true;
end;
function CheckHasNoChilds: boolean;
begin
if Node.ChildCount>0 then begin
Error('ChildCount>0');
Result:=false;
end else
Result:=true;
end;
function CheckHasParent: boolean;
begin
if Node.Parent=nil then begin
Error('Parent=nil');
Result:=false;
end else
Result:=true;
end;
function CheckUniqueCorner(Side1, Side2: TAnchorKind): boolean;
var
i: Integer;
Child: TLazDockConfigNode;
begin
Result:=true;
if Node.Parent=nil then exit;
if Node.Sides[Side1]='' then exit;
if Node.Sides[Side2]='' then exit;
for i:=0 to Node.Parent.ChildCount-1 do begin
Child:=Node.Parent.Childs[i];
if Child=Node then continue;
if (CompareText(Node.Sides[Side1],Child.Sides[Side1])=0)
and (CompareText(Node.Sides[Side2],Child.Sides[Side2])=0) then begin
Error('overlapping nodes');
exit(false);
end;
end;
end;
var
a: TAnchorKind;
i: Integer;
begin
Result:=false;
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
if Node.Sides[a]<>'' then begin
if CompareText(Node.Sides[a],Node.Name)=0 then begin
Error('Node.Sides[a]=Node');
exit;
end;
if RootNode.FindByName(Node.Sides[a],true)=nil then begin
Error('unknown Node.Sides[a]');
exit;
end;
end;
end;
case Node.TheType of
ldcntControl:
begin
// a control node contains a TControl
if not CheckAllSidesAnchored then exit;
end;
ldcntForm:
begin
// a dock form is a dummy control, used as top level container
if Node.Parent<>nil then begin
Error('Parent<>nil');
exit;
end;
if not CheckHasChilds then exit;
if not CheckNoSideAnchored then exit;
end;
ldcntPages:
begin
// a pages node has only page nodes as childs
if not CheckHasChilds then exit;
for i:=0 to Node.ChildCount-1 do
if Node.Childs[i].TheType<>ldcntPage then begin
Error('Childs[i].TheType<>ldcntPage');
exit;
end;
if not CheckAllSidesAnchored then exit;
end;
ldcntPage:
begin
// a page is the child of a pages node, and a container
if not CheckHasParent then exit;
if not CheckHasChilds then exit;
if Node.Parent.TheType<>ldcntPages then begin
Error('Parent.TheType<>ldcntPages');
exit;
end;
if not CheckNoSideAnchored then exit;
end;
ldcntSplitterLeftRight:
begin
// a vertical splitter can be moved left/right
if not CheckHasParent then exit;
if not CheckHasNoChilds then exit;
if not CheckSideNotAnchored(akLeft) then exit;
if not CheckSideNotAnchored(akRight) then exit;
CheckSideAnchored(akTop);
CheckSideAnchored(akBottom);
end;
ldcntSplitterUpDown:
begin
// a horizontal splitter can be moved up/down
// it is anchored left and right, and not top/bottom
// it is not a root node
// it has no childs
if not CheckHasParent then exit;
if not CheckHasNoChilds then exit;
if not CheckSideNotAnchored(akTop) then exit;
if not CheckSideNotAnchored(akBottom) then exit;
CheckSideAnchored(akLeft);
CheckSideAnchored(akRight);
end;
else
Error('unknown type');
exit;
end;
if not CheckUniqueCorner(akLeft,akTop) then exit;
if not CheckUniqueCorner(akLeft,akBottom) then exit;
if not CheckUniqueCorner(akRight,akTop) then exit;
if not CheckUniqueCorner(akRight,akBottom) then exit;
// check childs
for i:=0 to Node.ChildCount-1 do
if not CheckNode(Node.Childs[i]) then exit;
Result:=true;
end;
begin
if RootNode=nil then exit(false);
Result:=CheckNode(RootNode);
end;
{ TLazDockConfigNode }
function TLazDockConfigNode.GetSides(Side: TAnchorKind): string;
begin
Result:=FSides[Side];
end;
function TLazDockConfigNode.GetChildCount: Integer;
begin
if FChilds<>nil then
Result:=FChilds.Count
else
Result:=0;
end;
function TLazDockConfigNode.GetChilds(Index: integer): TLazDockConfigNode;
begin
Result:=TLazDockConfigNode(FChilds[Index]);
end;
procedure TLazDockConfigNode.SetBounds(const AValue: TRect);
begin
if CompareRect(@FBounds,@AValue) then exit;
FBounds:=AValue;
end;
procedure TLazDockConfigNode.SetClientBounds(const AValue: TRect);
begin
if CompareRect(@FClientBounds,@AValue) then exit;
FClientBounds:=AValue;
end;
procedure TLazDockConfigNode.SetName(const AValue: string);
begin
if FName=AValue then exit;
FName:=AValue;
end;
procedure TLazDockConfigNode.SetParent(const AValue: TLazDockConfigNode);
begin
if FParent=AValue then exit;
if FParent<>nil then
FParent.DoRemove(Self);
FParent:=AValue;
if FParent<>nil then
FParent.DoAdd(Self);
end;
procedure TLazDockConfigNode.SetSides(Side: TAnchorKind;
const AValue: string);
begin
FSides[Side]:=AValue;
end;
procedure TLazDockConfigNode.SetTheType(const AValue: TLDConfigNodeType);
begin
if FTheType=AValue then exit;
FTheType:=AValue;
end;
procedure TLazDockConfigNode.DoAdd(ChildNode: TLazDockConfigNode);
begin
if FChilds=nil then FChilds:=TFPList.Create;
FChilds.Add(ChildNode);
end;
procedure TLazDockConfigNode.DoRemove(ChildNode: TLazDockConfigNode);
begin
if TObject(FChilds[FChilds.Count-1])=ChildNode then
FChilds.Delete(FChilds.Count-1)
else
FChilds.Remove(ChildNode);
end;
constructor TLazDockConfigNode.Create(ParentNode: TLazDockConfigNode);
begin
FTheType:=ldcntControl;
Parent:=ParentNode;
end;
constructor TLazDockConfigNode.Create(ParentNode: TLazDockConfigNode;
const AName: string);
begin
FName:=AName;
Create(ParentNode);
end;
destructor TLazDockConfigNode.Destroy;
begin
Clear;
Parent:=nil;
FChilds.Free;
FChilds:=nil;
inherited Destroy;
end;
procedure TLazDockConfigNode.Clear;
var
i: Integer;
begin
if FChilds=nil then exit;
for i:=ChildCount-1 downto 0 do Childs[i].Free;
FChilds.Clear;
end;
procedure TLazDockConfigNode.Assign(Source: TPersistent);
var
Src: TLazDockConfigNode;
i: Integer;
SrcChild: TLazDockConfigNode;
NewChild: TLazDockConfigNode;
a: TAnchorKind;
begin
if Source is TLazDockConfigNode then begin
Clear;
Src:=TLazDockConfigNode(Source);
FBounds:=Src.FBounds;
FClientBounds:=Src.FClientBounds;
FName:=Src.FName;
for a:=Low(TAnchorKind) to High(TAnchorKind) do
FSides[a]:=Src.FSides[a];
FTheType:=Src.FTheType;
for i:=0 to Src.ChildCount-1 do begin
SrcChild:=Src.Childs[i];
NewChild:=TLazDockConfigNode.Create(Self);
NewChild.Assign(SrcChild);
end;
end else
inherited Assign(Source);
end;
function TLazDockConfigNode.FindByName(const AName: string;
Recursive: boolean; WithRoot: boolean): TLazDockConfigNode;
var
i: Integer;
begin
if WithRoot and (CompareText(Name,AName)=0) then exit(Self);
if FChilds<>nil then
for i:=0 to FChilds.Count-1 do begin
Result:=Childs[i];
if CompareText(Result.Name,AName)=0 then exit;
if Recursive then begin
Result:=Result.FindByName(AName,true,false);
if Result<>nil then exit;
end;
end;
Result:=nil;
end;
function TLazDockConfigNode.IndexOf(const AName: string): Integer;
begin
if FChilds<>nil then begin
Result:=FChilds.Count-1;
while (Result>=0) and (CompareText(Childs[Result].Name,AName)<>0) do
dec(Result);
end else begin
Result:=-1;
end;
end;
function TLazDockConfigNode.GetScreenBounds: TRect;
var
NewWidth: Integer;
NewHeight: Integer;
NewLeft: LongInt;
NewTop: LongInt;
Node: TLazDockConfigNode;
begin
NewWidth:=FBounds.Right-FBounds.Left;
NewHeight:=FBounds.Bottom-FBounds.Top;
NewLeft:=FBounds.Left;
NewTop:=FBounds.Top;
Node:=Parent;
while Node<>nil do begin
inc(NewLeft,Node.FBounds.Left+Node.FClientBounds.Left);
inc(NewTop,Node.FBounds.Top+Node.FClientBounds.Top);
Node:=Node.Parent;
end;
Result:=Classes.Bounds(NewLeft,NewTop,NewWidth,NewHeight);
end;
procedure TLazDockConfigNode.SaveToConfig(Config: TConfigStorage;
const Path: string);
var
a: TAnchorKind;
i: Integer;
Child: TLazDockConfigNode;
SubPath: String;
begin
Config.SetDeleteValue(Path+'Name/Value',Name,'');
Config.SetDeleteValue(Path+'Type/Value',LDConfigNodeTypeNames[TheType],
LDConfigNodeTypeNames[ldcntControl]);
Config.SetDeleteValue(Path+'Bounds/',FBounds,Rect(0,0,0,0));
Config.SetDeleteValue(Path+'ClientBounds/',FClientBounds,
Rect(0,0,FBounds.Right-FBounds.Left,FBounds.Bottom-FBounds.Top));
// Sides
for a:=Low(TAnchorKind) to High(TAnchorKind) do
Config.SetDeleteValue(Path+'Sides/'+AnchorNames[a]+'/Name',Sides[a],'');
// childs
Config.SetDeleteValue(Path+'Childs/Count',ChildCount,0);
for i:=0 to ChildCount-1 do begin
Child:=Childs[i];
SubPath:=Path+'Child'+IntToStr(i+1)+'/';
Child.SaveToConfig(Config,SubPath);
end;
end;
procedure TLazDockConfigNode.LoadFromConfig(Config: TConfigStorage;
const Path: string);
var
a: TAnchorKind;
i: Integer;
NewChildCount: LongInt;
NewChildName: String;
NewChild: TLazDockConfigNode;
SubPath: String;
begin
Clear;
// Note: 'Name' is stored only for information, but not restored on load
TheType:=LDConfigNodeTypeNameToType(Config.GetValue(Path+'Type/Value',
LDConfigNodeTypeNames[ldcntControl]));
Config.GetValue(Path+'Bounds/',FBounds,Rect(0,0,0,0));
Config.GetValue(Path+'ClientBounds/',FClientBounds,
Rect(0,0,FBounds.Right-FBounds.Left,FBounds.Bottom-FBounds.Top));
// Sides
for a:=Low(TAnchorKind) to High(TAnchorKind) do
Sides[a]:=Config.GetValue(Path+'Sides/'+AnchorNames[a]+'/Name','');
// childs
NewChildCount:=Config.GetValue(Path+'Childs/Count',0);
for i:=0 to NewChildCount-1 do begin
SubPath:=Path+'Child'+IntToStr(i+1)+'/';
NewChildName:=Config.GetValue(SubPath+'Name/Value','');
NewChild:=TLazDockConfigNode.Create(Self,NewChildName);
NewChild.Parent:=Self;
NewChild.LoadFromConfig(Config,SubPath);
end;
end;
procedure TLazDockConfigNode.WriteDebugReport;
procedure WriteNode(const Prefix: string; ANode: TLazDockConfigNode);
var
a: TAnchorKind;
i: Integer;
s: string;
begin
if ANode=nil then exit;
DbgOut(Prefix,'Name="'+ANode.Name+'"');
DbgOut(' Type=',GetEnumName(TypeInfo(TLDConfigNodeType),ord(ANode.TheType)));
DbgOut(' Bounds='+dbgs(ANode.Bounds));
DbgOut(' ClientBounds='+dbgs(ANode.ClientBounds));
DbgOut(' Childs='+dbgs(ANode.ChildCount));
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
s:=ANode.Sides[a];
if s='' then
s:='?';
DbgOut(' '+AnchorNames[a]+'="'+s+'"');
end;
debugln;
for i:=0 to ANode.ChildCount-1 do begin
WriteNode(Prefix+' ',ANode[i]);
end;
end;
begin
DebugLn('TLazDockConfigNode.WriteDebugReport Root=',dbgs(Self));
WriteNode(' ',Self);
DebugLn(DebugLayoutAsString);
DumpStack;
end;
function TLazDockConfigNode.DebugLayoutAsString: string;
type
TArrayOfRect = array of TRect;
TNodeInfo = record
MinSize: TPoint;
MinSizeValid, MinSizeCalculating: boolean;
MinLeft: integer;
MinLeftValid, MinLeftCalculating: boolean;
MinTop: Integer;
MinTopValid, MinTopCalculating: boolean;
end;
PNodeInfo = ^TNodeInfo;
var
Cols: LongInt;
Rows: LongInt;
LogCols: Integer;
NodeInfos: TPointerToPointerTree;// TLazDockConfigNode to PNodeInfo
procedure InitNodeInfos;
begin
NodeInfos:=TPointerToPointerTree.Create;
end;
procedure FreeNodeInfos;
var
Item: PNodeInfo;
NodePtr, InfoPtr: Pointer;
begin
NodeInfos.GetFirst(NodePtr,InfoPtr);
repeat
Item:=PNodeInfo(InfoPtr);
if Item=nil then break;
Dispose(Item);
until not NodeInfos.GetNext(NodePtr,NodePtr,InfoPtr);
end;
function GetNodeInfo(Node: TLazDockConfigNode): PNodeInfo;
begin
Result:=PNodeInfo(NodeInfos[Node]);
if Result=nil then begin
New(Result);
FillChar(Result^,SizeOf(TNodeInfo),0);
NodeInfos[Node]:=Result;
end;
end;
procedure w(x,y: Integer; const s: string; MaxX: Integer = 0);
var
i: Integer;
begin
for i:=1 to length(s) do begin
if (MaxX>0) and (x+i>MaxX) then exit;
Result[LogCols*(y-1) + x + i-1]:=s[i];
end;
end;
procedure wfillrect(const ARect: TRect; c: char);
var
x: LongInt;
y: LongInt;
begin
for x:=ARect.Left to ARect.Right do
for y:=ARect.Top to ARect.Bottom do
w(x,y,c);
end;
procedure wrectangle(const ARect: TRect);
begin
w(ARect.Left,ARect.Top,'+');
w(ARect.Right,ARect.Top,'+');
w(ARect.Left,ARect.Bottom,'+');
w(ARect.Right,ARect.Bottom,'+');
if ARect.Left<ARect.Right then begin
if ARect.Top<ARect.Bottom then begin
wfillrect(Rect(ARect.Left+1,ARect.Top,ARect.Right-1,ARect.Top),'-');// top line
wfillrect(Rect(ARect.Left+1,ARect.Bottom,ARect.Right-1,ARect.Bottom),'-');// bottom line
wfillrect(Rect(ARect.Left,ARect.Top+1,ARect.Left,ARect.Bottom-1),'|');// left line
wfillrect(Rect(ARect.Right,ARect.Top+1,ARect.Right,ARect.Bottom-1),'|');// right line
end else begin
wfillrect(Rect(ARect.Left+1,ARect.Top,ARect.Right-1,ARect.Top),'=');// horizontal line
end;
end else begin
wfillrect(Rect(ARect.Left,ARect.Top+1,ARect.Left,ARect.Bottom-1),'#');// vertical line
end;
end;
function MapRect(const OriginalRect, OldBounds, NewBounds: TRect): TRect;
function MapX(i: Integer): Integer;
begin
Result:=NewBounds.Left+
(((i-OldBounds.Left)*(NewBounds.Right-NewBounds.Left))
div (OldBounds.Right-OldBounds.Left));
end;
function MapY(i: Integer): Integer;
begin
Result:=NewBounds.Top+
(((i-OldBounds.Top)*(NewBounds.Bottom-NewBounds.Top))
div (OldBounds.Bottom-OldBounds.Top));
end;
begin
Result.Left:=MapX(OriginalRect.Left);
Result.Top:=MapY(OriginalRect.Left);
Result.Right:=MapX(OriginalRect.Left);
Result.Bottom:=MapY(OriginalRect.Left);
end;
function GetMinSize(Node: TLazDockConfigNode): TPoint; forward;
function GetMinPos(Node: TLazDockConfigNode; Side: TAnchorKind): Integer;
// calculates left or top position of Node
function Compute(var MinPosValid, MinPosCalculating: boolean; var MinPos: Integer): Integer;
procedure Improve(Neighbour: TLazDockConfigNode);
var
NeighbourPos: LongInt;
NeighbourSize: TPoint;
NeighbourLength: LongInt;
begin
if Neighbour=nil then exit;
if Neighbour.Parent<>Node.Parent then exit;
NeighbourPos:=GetMinPos(Neighbour,Side);
NeighbourSize:=GetMinSize(Neighbour);
if Side=akLeft then
NeighbourLength:=NeighbourSize.X
else
NeighbourLength:=NeighbourSize.Y;
MinPos:=Max(MinPos,NeighbourPos+NeighbourLength);
end;
var
Sibling: TLazDockConfigNode;
i: Integer;
begin
if MinPosCalculating then begin
DebugLn(['DebugLayoutAsString.GetMinPos.Compute WARNING: anchor circle detected']);
DumpStack;
exit(1);
end;
if (not MinPosValid) then begin
MinPosValid:=true;
MinPosCalculating:=true;
if Node.Sides[Side]<>'' then begin
Sibling:=FindByName(Node.Sides[Side],true,true);
Improve(Sibling);
end;
if Node.Parent<>nil then begin
for i:=0 to Node.Parent.ChildCount-1 do begin
Sibling:=Node.Parent.Childs[i];
if CompareText(Sibling.Sides[OppositeAnchor[Side]],Node.Name)=0 then
Improve(Sibling);
end;
end;
MinPosCalculating:=false;
end;
Result:=MinPos;
end;
var
Info: PNodeInfo;
begin
Info:=GetNodeInfo(Node);
//DebugLn(['GetMinPos ',Node.Name,' ',AnchorNames[Side],' ',Info^.MinLeftCalculating]);
if Side=akLeft then
Result:=Compute(Info^.MinLeftValid,Info^.MinLeftCalculating,Info^.MinLeft)
else
Result:=Compute(Info^.MinTopValid,Info^.MinTopCalculating,Info^.MinTop);
end;
function GetChildsMinSize(Node: TLazDockConfigNode): TPoint;
// calculate the minimum size needed to draw the content of the node
var
i: Integer;
ChildMinSize: TPoint;
Child: TLazDockConfigNode;
ChildSize: TPoint;
begin
//DebugLn(['GetChildsMinSize ',Node.name]);
Result:=Point(0,0);
if Node.TheType=ldcntPages then begin
// maximum size of all pages
for i:=0 to Node.ChildCount-1 do begin
ChildMinSize:=GetMinSize(Node.Childs[i]);
Result.X:=Max(Result.X,ChildMinSize.X);
Result.Y:=Max(Result.Y,ChildMinSize.Y);
end;
end else begin
for i:=0 to Node.ChildCount-1 do begin
Child:=Node.Childs[i];
ChildSize:=GetMinSize(Child);
Result.X:=Max(Result.X,GetMinPos(Child,akLeft)+ChildSize.X);
Result.Y:=Max(Result.Y,GetMinPos(Child,akTop)+ChildSize.Y);
end;
end;
end;
function GetMinSize(Node: TLazDockConfigNode): TPoint;
// calculate the minimum size needed to draw the node
var
ChildMinSize: TPoint;
Info: PNodeInfo;
begin
//DebugLn(['GetMinSize ',Node.name]);
Info:=GetNodeInfo(Node);
if Info^.MinSizeValid then begin
Result:=Info^.MinSize;
exit;
end;
if Info^.MinSizeCalculating then begin
DebugLn(['DebugLayoutAsString.GetMinSize WARNING: anchor circle detected']);
DumpStack;
Result:=Point(1,1);
exit;
end;
Info^.MinSizeCalculating:=true;
Result.X:=2+length(Node.Name);// border plus caption
Result.Y:=2; // border
if (Node.ChildCount=0) then begin
case Node.TheType of
ldcntSplitterLeftRight,ldcntSplitterUpDown:
Result:=Point(1,1); // splitters don't need captions
end;
end else begin
ChildMinSize:=GetChildsMinSize(Node);
Result.X:=Max(Result.X,ChildMinSize.X+2);
Result.Y:=Max(Result.Y,ChildMinSize.Y+2);
end;
Info^.MinSize:=Result;
Info^.MinSizeValid:=true;
Info^.MinSizeCalculating:=false;
end;
procedure DrawNode(Node: TLazDockConfigNode; ARect: TRect);
var
i: Integer;
Child: TLazDockConfigNode;
ChildSize: TPoint;
ChildRect: TRect;
begin
//DebugLn(['DrawNode Node=',Node.Name,' ARect=',dbgs(ARect)]);
wrectangle(ARect);
w(ARect.Left+1,ARect.Top,Node.Name,ARect.Right);
for i := 0 to Node.ChildCount-1 do begin
Child:=Node.Childs[i];
ChildRect.Left:=ARect.Left+1+GetMinPos(Child,akLeft);
ChildRect.Top:=ARect.Top+1+GetMinPos(Child,akTop);
ChildSize:=GetMinSize(Child);
ChildRect.Right:=ChildRect.Left+ChildSize.X-1;
ChildRect.Bottom:=ChildRect.Top+ChildSize.Y-1;
if CompareText(Child.Sides[akRight],Node.Name)=0 then
ChildRect.Right:=ARect.Right-1;
if CompareText(Child.Sides[akBottom],Node.Name)=0 then
ChildRect.Bottom:=ARect.Bottom-1;
DrawNode(Child,ChildRect);
if Node.TheType=ldcntPages then begin
// paint only one page
break;
end;
end;
end;
var
e: string;
y: Integer;
begin
Cols:=StrToIntDef(Application.GetOptionValue('ldcn-colunms'),79);
Rows:=StrToIntDef(Application.GetOptionValue('ldcn-rows'),20);
InitNodeInfos;
try
e:=LineEnding;
LogCols:=Cols+length(e);
SetLength(Result,LogCols*Rows);
// fill space
FillChar(Result[1],length(Result),' ');
// add line endings
for y:=1 to Rows do
w(Cols+1,y,e);
// draw node
DrawNode(Self,Rect(1,1,Cols,Rows));
finally
FreeNodeInfos;
end;
end;
function TLazDockConfigNode.GetPath: string;
var
Node: TLazDockConfigNode;
begin
Result:='';
Node:=Self;
while Node<>nil do begin
if Result<>'' then
Result:=Node.Name+'/'+Result
else
Result:=Node.Name;
Node:=Node.Parent;
end;
end;
{ TLazDockerConfig }
constructor TLazDockerConfig.Create(const ADockerName: string;
ANode: TLazDockConfigNode);
begin
FDockerName:=ADockerName;
FRoot:=ANode;
end;
procedure TLazDockerConfig.WriteDebugReport;
begin
DebugLn(['TLazDockerConfig.WriteDebugReport DockerName="',DockerName,'"']);
if Root<>nil then begin
Root.WriteDebugReport;
end else begin
DebugLn([' Root=nil']);
end;
end;
end.