1
{ Unit implementing anchor docking storage tree.
3
Copyright (C) 2010 Mattias Gaertner mattias@freepascal.org
5
This library is free software; you can redistribute it and/or modify it
6
under the terms of the GNU Library General Public License as published by
7
the Free Software Foundation; either version 2 of the License, or (at your
8
option) any later version with the following modification:
10
As a special exception, the copyright holders of this library give you
11
permission to link this library with independent modules to produce an
12
executable, regardless of the license terms of these independent modules,and
13
to copy and distribute the resulting executable under terms of your choice,
14
provided that you also meet, for each linked independent module, the terms
15
and conditions of the license of that module. An independent module is a
16
module which is not derived from or based on this library. If you modify
17
this library, you may extend this exception to your version of the library,
18
but you are not obligated to do so. If you do not wish to do so, delete this
19
exception statement from your version.
21
This program is distributed in the hope that it will be useful, but WITHOUT
22
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
23
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
26
You should have received a copy of the GNU Library General Public License
27
along with this library; if not, write to the Free Software Foundation,
28
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30
Unit AnchorDockStorage;
37
Math, Classes, SysUtils, LCLProc, AvgLvlTree, ExtCtrls, ComCtrls, Forms,
38
Controls, LazConfigStorage, AnchorDockStr;
41
AnchorDockSplitterName = 'AnchorDockSplitter';
42
AnchorDockSiteName = 'AnchorDockSite';
48
adltnSplitterHorizontal,
49
adltnSplitterVertical,
53
TADLTreeNodeTypes = set of TADLTreeNodeType;
55
TADLHeaderPosition = (
62
TADLHeaderPositions = set of TADLHeaderPosition;
64
EAnchorDockLayoutError = class(Exception);
66
{ TAnchorDockLayoutTreeNode }
68
TAnchorDockLayoutTreeNode = class
71
fAnchors: array[TAnchorKind] of string;
72
FBoundSplitterPos: integer;
74
FHeaderPosition: TADLHeaderPosition;
77
FNodes: TFPList; // list of TAnchorDockLayoutTreeNode
78
FNodeType: TADLTreeNodeType;
79
FParent: TAnchorDockLayoutTreeNode;
81
FTabPosition: TTabPosition;
82
FWindowState: TWindowState;
83
function GetAnchors(Site: TAnchorKind): string;
84
function GetBottom: integer;
85
function GetHeight: integer;
86
function GetLeft: integer;
87
function GetNodes(Index: integer): TAnchorDockLayoutTreeNode;
88
function GetRight: integer;
89
function GetTop: integer;
90
function GetWidth: integer;
91
procedure SetAlign(const AValue: TAlign);
92
procedure SetAnchors(Site: TAnchorKind; const AValue: string);
93
procedure SetBottom(const AValue: integer);
94
procedure SetBoundSplitterPos(const AValue: integer);
95
procedure SetBoundsRect(const AValue: TRect);
96
procedure SetHeaderPosition(const AValue: TADLHeaderPosition);
97
procedure SetHeight(const AValue: integer);
98
procedure SetLeft(const AValue: integer);
99
procedure SetMonitor(const AValue: integer);
100
procedure SetName(const AValue: string);
101
procedure SetNodeType(const AValue: TADLTreeNodeType);
102
procedure SetParent(const AValue: TAnchorDockLayoutTreeNode);
103
procedure SetRight(const AValue: integer);
104
procedure SetWorkAreaRect(const AValue: TRect);
105
procedure SetTabPosition(const AValue: TTabPosition);
106
procedure SetTop(const AValue: integer);
107
procedure SetWidth(const AValue: integer);
108
procedure SetWindowState(const AValue: TWindowState);
111
destructor Destroy; override;
113
function IsEqual(Node: TAnchorDockLayoutTreeNode): boolean;
114
procedure Assign(Node: TAnchorDockLayoutTreeNode);
115
procedure Assign(AControl: TControl);
116
procedure LoadFromConfig(Config: TConfigStorage);
117
procedure SaveToConfig(Config: TConfigStorage);
118
function FindChildNode(aName: string; Recursive: boolean): TAnchorDockLayoutTreeNode;
119
function FindControlNode: TAnchorDockLayoutTreeNode;
120
procedure CheckConsistency; virtual;
123
procedure Simplify(ExistingNames: TStrings);
124
procedure DeleteNode(ChildNode: TAnchorDockLayoutTreeNode);
125
function FindNodeBoundSplitter(ChildNode: TAnchorDockLayoutTreeNode;
126
Side: TAnchorKind): TAnchorDockLayoutTreeNode;
127
procedure DeleteNodeBoundSplitter(Splitter, ChildNode: TAnchorDockLayoutTreeNode;
129
procedure DeleteSpiralSplitter(ChildNode: TAnchorDockLayoutTreeNode);
130
procedure ReplaceWithChildren(ChildNode: TAnchorDockLayoutTreeNode);
133
procedure IncreaseChangeStamp; virtual;
134
property Name: string read FName write SetName;
135
property NodeType: TADLTreeNodeType read FNodeType write SetNodeType;
136
property Parent: TAnchorDockLayoutTreeNode read FParent write SetParent;
137
property Left: integer read GetLeft write SetLeft;
138
property Top: integer read GetTop write SetTop;
139
property Width: integer read GetWidth write SetWidth;
140
property Height: integer read GetHeight write SetHeight;
141
property Right: integer read GetRight write SetRight;
142
property Bottom: integer read GetBottom write SetBottom;
143
property BoundsRect: TRect read FBoundsRect write SetBoundsRect;
144
property BoundSplitterPos: integer read FBoundSplitterPos write SetBoundSplitterPos;
145
property WorkAreaRect: TRect read FWorkAreaRect write SetWorkAreaRect;
146
property Anchors[Site: TAnchorKind]: string read GetAnchors write SetAnchors; // empty means default (parent)
147
property Align: TAlign read FAlign write SetAlign;
148
property WindowState: TWindowState read FWindowState write SetWindowState;
149
property Monitor: integer read FMonitor write SetMonitor;
150
property HeaderPosition: TADLHeaderPosition read FHeaderPosition write SetHeaderPosition;
151
property TabPosition: TTabPosition read FTabPosition write SetTabPosition;
152
function Count: integer;
153
function IsSplitter: boolean;
154
function IsRootWindow: boolean;
155
property Nodes[Index: integer]: TAnchorDockLayoutTreeNode read GetNodes; default;
158
TAnchorDockLayoutTree = class;
160
{ TAnchorDockLayoutTreeRootNode }
162
TAnchorDockLayoutTreeRootNode = class(TAnchorDockLayoutTreeNode)
164
FTree: TAnchorDockLayoutTree;
166
procedure IncreaseChangeStamp; override;
167
property Tree: TAnchorDockLayoutTree read FTree write FTree;
168
procedure CheckConsistency; override;
171
{ TAnchorDockLayoutTree }
173
TAnchorDockLayoutTree = class
177
FRoot: TAnchorDockLayoutTreeRootNode;
178
procedure SetModified(const AValue: boolean);
181
destructor Destroy; override;
183
procedure LoadFromConfig(Config: TConfigStorage);
184
procedure SaveToConfig(Config: TConfigStorage);
185
procedure IncreaseChangeStamp;
186
property ChangeStamp: int64 read FChangeStamp;
187
property Modified: boolean read FModified write SetModified;
188
property Root: TAnchorDockLayoutTreeRootNode read FRoot;
189
function NewNode(aParent: TAnchorDockLayoutTreeNode): TAnchorDockLayoutTreeNode;
192
{ TAnchorDockRestoreLayout }
194
TAnchorDockRestoreLayout = class
196
FControlNames: TStrings;
197
FLayout: TAnchorDockLayoutTree;
198
procedure SetControlNames(const AValue: TStrings);
200
constructor Create; overload;
201
constructor Create(aLayout: TAnchorDockLayoutTree); overload;
202
destructor Destroy; override;
203
function IndexOfControlName(AName: string): integer;
204
function HasControlName(AName: string): boolean;
205
procedure RemoveControlName(AName: string);
206
procedure UpdateControlNames;
207
procedure LoadFromConfig(Config: TConfigStorage);
208
procedure SaveToConfig(Config: TConfigStorage);
209
property ControlNames: TStrings read FControlNames write SetControlNames;
210
property Layout: TAnchorDockLayoutTree read FLayout;
213
{ TAnchorDockRestoreLayouts }
215
TAnchorDockRestoreLayouts = class
218
function GetItems(Index: integer): TAnchorDockRestoreLayout;
221
destructor Destroy; override;
223
procedure Delete(Index: integer);
224
function IndexOfName(AControlName: string): integer;
225
function FindByName(AControlName: string): TAnchorDockRestoreLayout;
226
procedure Add(Layout: TAnchorDockRestoreLayout; RemoveOther: boolean);
227
procedure RemoveByName(AControlName: string);
228
procedure LoadFromConfig(Config: TConfigStorage);
229
procedure SaveToConfig(Config: TConfigStorage);
230
function ConfigIsEmpty(Config: TConfigStorage): boolean;
231
function Count: integer;
232
property Items[Index: integer]: TAnchorDockRestoreLayout read GetItems;
237
TADNameToControl = class
240
function IndexOfName(const aName: string): integer;
241
function GetControl(const aName: string): TControl;
242
procedure SetControl(const aName: string; const AValue: TControl);
245
destructor Destroy; override;
246
function ControlToName(AControl: TControl): string;
247
property Control[const aName: string]: TControl read GetControl write SetControl; default;
248
procedure RemoveControl(AControl: TControl);
249
procedure WriteDebugReport(Msg: string);
253
ADLTreeNodeTypeNames: array[TADLTreeNodeType] of string = (
257
'SplitterHorizontal',
262
ADLWindowStateNames: array[TWindowState] of string = (
268
ADLHeaderPositionNames: array[TADLHeaderPosition] of string = (
275
ADLTabPostionNames: array[TTabPosition] of string = (
281
ADLAlignNames: array[TAlign] of string = (
291
function NameToADLTreeNodeType(s: string): TADLTreeNodeType;
292
function NameToADLWindowState(s: string): TWindowState;
293
function NameToADLHeaderPosition(s: string): TADLHeaderPosition;
294
function NameToADLTabPosition(s: string): TTabPosition;
295
function NameToADLAlign(s: string): TAlign;
296
function dbgs(const NodeType: TADLTreeNodeType): string; overload;
298
procedure WriteDebugLayout(Title: string; RootNode: TObject);
299
function DebugLayoutAsString(RootNode: TObject): string;
300
procedure DebugWriteChildAnchors(RootNode: TAnchorDockLayoutTreeNode); overload;
301
procedure DebugWriteChildAnchors(RootControl: TWinControl;
302
OnlyWinControls, OnlyForms: boolean); overload;
306
function NameToADLTreeNodeType(s: string): TADLTreeNodeType;
308
for Result:=low(TADLTreeNodeType) to high(TADLTreeNodeType) do
309
if s=ADLTreeNodeTypeNames[Result] then exit;
313
function NameToADLWindowState(s: string): TWindowState;
315
for Result:=low(TWindowState) to high(TWindowState) do
316
if s=ADLWindowStateNames[Result] then exit;
320
function NameToADLHeaderPosition(s: string): TADLHeaderPosition;
322
for Result:=low(TADLHeaderPosition) to high(TADLHeaderPosition) do
323
if s=ADLHeaderPositionNames[Result] then exit;
327
function NameToADLTabPosition(s: string): TTabPosition;
329
for Result:=low(TTabPosition) to high(TTabPosition) do
330
if s=ADLTabPostionNames[Result] then exit;
334
function NameToADLAlign(s: string): TAlign;
336
for Result:=low(TAlign) to high(TAlign) do
337
if s=ADLAlignNames[Result] then exit;
341
function dbgs(const NodeType: TADLTreeNodeType): string; overload;
343
Result:=ADLTreeNodeTypeNames[NodeType];
346
procedure WriteDebugLayout(Title: string; RootNode: TObject);
348
debugln(['WriteDebugLayout ',Title,':']);
349
debugln(DebugLayoutAsString(RootNode));
352
function DebugLayoutAsString(RootNode: TObject): string;
356
MinSizeValid, MinSizeCalculating: boolean;
358
MinLeftValid, MinLeftCalculating: boolean;
360
MinTopValid, MinTopCalculating: boolean;
362
PNodeInfo = ^TNodeInfo;
367
NodeInfos: TPointerToPointerTree;// TObject to PNodeInfo
369
procedure InitNodeInfos;
371
NodeInfos:=TPointerToPointerTree.Create;
374
procedure FreeNodeInfos;
377
NodePtr, InfoPtr: Pointer;
379
NodeInfos.GetFirst(NodePtr,InfoPtr);
381
Item:=PNodeInfo(InfoPtr);
382
if Item=nil then break;
384
until not NodeInfos.GetNext(NodePtr,NodePtr,InfoPtr);
388
function GetNodeInfo(Node: TObject): PNodeInfo;
390
Result:=PNodeInfo(NodeInfos[Node]);
391
if Result=nil then begin
393
FillChar(Result^,SizeOf(TNodeInfo),0);
394
NodeInfos[Node]:=Result;
398
procedure w(x,y: Integer; const s: string; MaxX: Integer = 0);
402
for i:=1 to length(s) do begin
403
if (MaxX>0) and (x+i>MaxX) then exit;
404
Result[LogCols*(y-1) + x + i-1]:=s[i];
408
procedure wfillrect(const ARect: TRect; c: char);
413
for x:=ARect.Left to ARect.Right do
414
for y:=ARect.Top to ARect.Bottom do
418
procedure wrectangle(const ARect: TRect);
420
w(ARect.Left,ARect.Top,'+');
421
w(ARect.Right,ARect.Top,'+');
422
w(ARect.Left,ARect.Bottom,'+');
423
w(ARect.Right,ARect.Bottom,'+');
424
if ARect.Left<ARect.Right then begin
425
if ARect.Top<ARect.Bottom then begin
426
wfillrect(Rect(ARect.Left+1,ARect.Top,ARect.Right-1,ARect.Top),'-');// top line
427
wfillrect(Rect(ARect.Left+1,ARect.Bottom,ARect.Right-1,ARect.Bottom),'-');// bottom line
428
wfillrect(Rect(ARect.Left,ARect.Top+1,ARect.Left,ARect.Bottom-1),'|');// left line
429
wfillrect(Rect(ARect.Right,ARect.Top+1,ARect.Right,ARect.Bottom-1),'|');// right line
431
wfillrect(Rect(ARect.Left+1,ARect.Top,ARect.Right-1,ARect.Top),'=');// horizontal line
434
wfillrect(Rect(ARect.Left,ARect.Top+1,ARect.Left,ARect.Bottom-1),'#');// vertical line
438
function MapRect(const OriginalRect, OldBounds, NewBounds: TRect): TRect;
440
function MapX(i: Integer): Integer;
442
Result:=NewBounds.Left+
443
(((i-OldBounds.Left)*(NewBounds.Right-NewBounds.Left))
444
div (OldBounds.Right-OldBounds.Left));
447
function MapY(i: Integer): Integer;
449
Result:=NewBounds.Top+
450
(((i-OldBounds.Top)*(NewBounds.Bottom-NewBounds.Top))
451
div (OldBounds.Bottom-OldBounds.Top));
455
Result.Left:=MapX(OriginalRect.Left);
456
Result.Top:=MapY(OriginalRect.Left);
457
Result.Right:=MapX(OriginalRect.Left);
458
Result.Bottom:=MapY(OriginalRect.Left);
461
function GetParentNode(Node: TObject): TObject;
463
if Node is TControl then
464
Result:=TControl(Node).Parent
465
else if Node is TAnchorDockLayoutTreeNode then
466
Result:=TAnchorDockLayoutTreeNode(Node).Parent
471
function GetSiblingNode(Node: TObject; Side: TAnchorKind): TObject;
474
if Node=nil then exit;
475
if Node is TControl then begin
476
if not (Side in TControl(Node).Anchors) then exit;
477
Result:=TControl(Node).AnchorSide[Side].Control;
478
if Result=TControl(Node).Parent then
480
end else if Node is TAnchorDockLayoutTreeNode then begin
481
if TAnchorDockLayoutTreeNode(Node).Parent<>nil then
482
Result:=TAnchorDockLayoutTreeNode(Node).Parent.FindChildNode(
483
TAnchorDockLayoutTreeNode(Node).Anchors[Side],false);
487
function GetAnchorNode(Node: TObject; Side: TAnchorKind): TObject;
489
ADLNode: TAnchorDockLayoutTreeNode;
492
if Node=nil then exit;
493
if Node is TControl then begin
494
if not (Side in TControl(Node).Anchors) then exit;
495
Result:=TControl(Node).AnchorSide[Side].Control;
496
end else if Node is TAnchorDockLayoutTreeNode then begin
497
ADLNode:=TAnchorDockLayoutTreeNode(Node);
498
if ((ADLNode.NodeType=adltnSplitterVertical)
499
and (Side in [akLeft,akRight]))
500
or ((ADLNode.NodeType=adltnSplitterHorizontal)
501
and (Side in [akTop,akBottom]))
504
else if (ADLNode.Anchors[Side]<>'') then begin
505
if ADLNode.Parent<>nil then
506
Result:=ADLNode.Parent.FindChildNode(
507
ADLNode.Anchors[Side],false);
509
Result:=GetParentNode(Node);
513
function IsSplitter(Node: TObject): boolean;
515
Result:=(Node is TCustomSplitter)
516
or ((Node is TAnchorDockLayoutTreeNode)
517
and (TAnchorDockLayoutTreeNode(Node).IsSplitter));
520
function IsPages(Node: TObject): boolean;
522
Result:=(Node is TCustomTabControl)
523
or ((Node is TAnchorDockLayoutTreeNode)
524
and (TAnchorDockLayoutTreeNode(Node).NodeType in [adltnPages,adltnNone]));
527
function GetName(Node: TObject): string;
529
if Node is TControl then
530
Result:=TControl(Node).Name
531
else if Node is TAnchorDockLayoutTreeNode then
532
Result:=TAnchorDockLayoutTreeNode(Node).Name
534
Result:=DbgSName(Node);
537
function GetChildCount(Node: TObject): integer;
539
if Node is TWinControl then
540
Result:=TWinControl(Node).ControlCount
541
else if Node is TAnchorDockLayoutTreeNode then
542
Result:=TAnchorDockLayoutTreeNode(Node).Count
547
function GetChild(Node: TObject; Index: integer): TObject;
549
if Node is TWinControl then
550
Result:=TWinControl(Node).Controls[Index]
551
else if Node is TAnchorDockLayoutTreeNode then
552
Result:=TAnchorDockLayoutTreeNode(Node).Nodes[Index]
557
function GetMinSize(Node: TObject): TPoint; forward;
559
function GetMinPos(Node: TObject; Side: TAnchorKind): Integer;
560
// calculates left or top position of Node
562
function Compute(var MinPosValid, MinPosCalculating: boolean;
563
var MinPos: Integer): Integer;
565
procedure Improve(Neighbour: TObject);
567
NeighbourPos: LongInt;
568
NeighbourSize: TPoint;
569
NeighbourLength: LongInt;
571
if Neighbour=nil then exit;
572
if GetParentNode(Neighbour)<>GetParentNode(Node) then exit;
573
NeighbourPos:=GetMinPos(Neighbour,Side);
574
NeighbourSize:=GetMinSize(Neighbour);
576
NeighbourLength:=NeighbourSize.X
578
NeighbourLength:=NeighbourSize.Y;
579
MinPos:=Max(MinPos,NeighbourPos+NeighbourLength);
587
if MinPosCalculating then begin
588
DebugLn(['DebugLayoutAsString.GetMinPos.Compute WARNING: anchor circle detected RootNode=',DbgSName(RootNode)]);
589
if RootNode is TWinControl then
590
DebugWriteChildAnchors(TWinControl(RootNode),true,true)
591
else if RootNode is TAnchorDockLayoutTreeNode then
592
DebugWriteChildAnchors(TAnchorDockLayoutTreeNode(RootNode));
593
RaiseGDBException('circle detected');
595
if (not MinPosValid) then begin
597
MinPosCalculating:=true;
598
Sibling:=GetSiblingNode(Node,Side);
601
ParentNode:=GetParentNode(Node);
602
if ParentNode<>nil then begin
603
for i:=0 to GetChildCount(ParentNode)-1 do begin
604
Sibling:=GetChild(ParentNode,i);
605
if Node=GetSiblingNode(Sibling,OppositeAnchor[Side]) then
609
MinPosCalculating:=false;
617
Info:=GetNodeInfo(Node);
618
//DebugLn(['GetMinPos ',Node.Name,' ',DbgS(Side),' ',Info^.MinLeftCalculating]);
620
Result:=Compute(Info^.MinLeftValid,Info^.MinLeftCalculating,Info^.MinLeft)
622
Result:=Compute(Info^.MinTopValid,Info^.MinTopCalculating,Info^.MinTop);
625
function GetChildsMinSize(Node: TObject): TPoint;
626
// calculate the minimum size needed to draw the content of the node
630
ChildMinSize: TPoint;
632
//DebugLn(['GetChildsMinSize ',Node.name]);
634
if IsPages(Node) then begin
635
// maximum size of all pages
636
for i:=0 to GetChildCount(Node)-1 do begin
637
ChildMinSize:=GetMinSize(GetChild(Node,i));
638
Result.X:=Max(Result.X,ChildMinSize.X);
639
Result.Y:=Max(Result.Y,ChildMinSize.Y);
642
for i:=0 to GetChildCount(Node)-1 do begin
643
Child:=GetChild(Node,i);
644
ChildMinSize:=GetMinSize(Child);
645
Result.X:=Max(Result.X,GetMinPos(Child,akLeft)+ChildMinSize.X);
646
Result.Y:=Max(Result.Y,GetMinPos(Child,akTop)+ChildMinSize.Y);
651
function GetMinSize(Node: TObject): TPoint;
652
// calculate the minimum size needed to draw the node
654
ChildMinSize: TPoint;
657
//DebugLn(['GetMinSize ',Node.name]);
658
Info:=GetNodeInfo(Node);
659
if Info^.MinSizeValid then begin
660
Result:=Info^.MinSize;
663
if Info^.MinSizeCalculating then begin
664
DebugLn(['DebugLayoutAsString.GetMinSize WARNING: anchor circle detected']);
669
Info^.MinSizeCalculating:=true;
670
Result.X:=2+length(GetName(Node));// border plus name
671
Result.Y:=2; // border
672
if GetChildCount(Node)=0 then begin
673
if IsSplitter(Node) then
674
Result:=Point(1,1); // splitters don't need captions
676
ChildMinSize:=GetChildsMinSize(Node);
677
Result.X:=Max(Result.X,ChildMinSize.X+2);
678
Result.Y:=Max(Result.Y,ChildMinSize.Y+2);
680
//debugln(['GetMinSize ',GetName(Node),' Splitter=',IsSplitter(Node),' MinSize=',dbgs(Result)]);
681
Info^.MinSize:=Result;
682
Info^.MinSizeValid:=true;
683
Info^.MinSizeCalculating:=false;
686
procedure DrawNode(Node: TObject; ARect: TRect);
694
DebugLn(['DrawNode Node=',GetName(Node),' ARect=',dbgs(ARect)]);
696
w(ARect.Left+1,ARect.Top,GetName(Node),ARect.Right);
698
for i := 0 to GetChildCount(Node)-1 do begin
699
Child:=GetChild(Node,i);
700
ChildRect.Left:=ARect.Left+1+GetMinPos(Child,akLeft);
701
ChildRect.Top:=ARect.Top+1+GetMinPos(Child,akTop);
702
ChildSize:=GetMinSize(Child);
703
ChildRect.Right:=ChildRect.Left+ChildSize.X-1;
704
ChildRect.Bottom:=ChildRect.Top+ChildSize.Y-1;
705
AnchorNode:=GetAnchorNode(Child,akRight);
706
if AnchorNode<>nil then begin
707
if AnchorNode=Node then
708
ChildRect.Right:=ARect.Right-1
709
else if GetParentNode(AnchorNode)=Node then
710
ChildRect.Right:=ARect.Left+1+GetMinPos(AnchorNode,akLeft)-1;
712
AnchorNode:=GetAnchorNode(Child,akBottom);
713
if AnchorNode<>nil then begin
714
if AnchorNode=Node then
715
ChildRect.Bottom:=ARect.Bottom-1
716
else if GetParentNode(AnchorNode)=Node then
717
ChildRect.Bottom:=ARect.Top+1+GetMinPos(AnchorNode,akTop)-1;
719
DrawNode(Child,ChildRect);
720
if IsPages(Node) then begin
721
// paint only one page
731
Cols:=StrToIntDef(Application.GetOptionValue('ldcn-colunms'),79);
732
Rows:=StrToIntDef(Application.GetOptionValue('ldcn-rows'),20);
737
LogCols:=Cols+length(e);
738
SetLength(Result,LogCols*Rows);
740
FillChar(Result[1],length(Result),' ');
745
DrawNode(RootNode,Rect(1,1,Cols,Rows));
751
procedure DebugWriteChildAnchors(RootNode: TAnchorDockLayoutTreeNode);
753
procedure WriteControl(Node: TAnchorDockLayoutTreeNode; Prefix: string);
757
AnchorControl: TAnchorDockLayoutTreeNode;
761
DbgOut('"'+Node.Name+'"');
762
DbgOut(' Type='+dbgs(Node.NodeType));
763
DbgOut(' Bounds=',dbgs(Node.BoundsRect)
764
,',w=',dbgs(Node.BoundsRect.Right-Node.BoundsRect.Left)
765
,',h=',dbgs(Node.BoundsRect.Bottom-Node.BoundsRect.Top));
766
if Node.WindowState<>wsNormal then
767
DbgOut(' WindowState=',dbgs(Node.WindowState));
768
if Node.Monitor<>0 then
769
DbgOut(' Monitor=',dbgs(Node.Monitor));
770
if Node.BoundSplitterPos<>0 then
771
DbgOut(' SplitterPos=',dbgs(Node.BoundSplitterPos));
772
if (Node.WorkAreaRect.Right>0) and (Node.WorkAreaRect.Bottom>0) then
773
DbgOut(' WorkArea=',dbgs(Node.WorkAreaRect));
775
for a:=low(TAnchorKind) to high(TAnchorKind) do begin
776
if Node.Anchors[a]<>'' then
777
AnchorControl:=Node.Parent.FindChildNode(Node.Anchors[a],False)
780
if AnchorControl=nil then
783
AnchorName:=AnchorControl.Name;
784
debugln([Prefix,' ',dbgs(a),'=',AnchorName]);
786
for i:=0 to Node.Count-1 do
787
WriteControl(Node[i],Prefix+' ');
793
debugln(['DebugWriteChildAnchors RootNode="',RootNode.Name,'" Type=',dbgs(RootNode.NodeType)]);
794
for i:=0 to RootNode.Count-1 do
795
WriteControl(RootNode[i],' ');
798
procedure DebugWriteChildAnchors(RootControl: TWinControl;
799
OnlyWinControls, OnlyForms: boolean); overload;
801
procedure WriteControl(AControl: TControl; Prefix: string);
805
AnchorControl: TControl;
808
if OnlyWinControls and (not (AControl is TWinControl)) then exit;
809
if OnlyForms and (not (AControl is TCustomForm)) then exit;
810
if not AControl.IsControlVisible then exit;
812
debugln([Prefix,DbgSName(AControl),' Caption="',dbgstr(AControl.Caption),'" Align=',dbgs(AControl.Align),' Bounds=',dbgs(AControl.BoundsRect)]);
813
for a:=low(TAnchorKind) to high(TAnchorKind) do begin
814
AnchorControl:=AControl.AnchorSide[a].Control;
815
if AnchorControl=AControl.Parent then
817
else if AnchorControl is TCustomForm then
818
AnchorName:='"'+AnchorControl.Name+'"'
820
AnchorName:=DbgSName(AnchorControl);
821
debugln([Prefix,' ',dbgs(a),'=',a in AControl.Anchors,' ',AnchorName,' ',dbgs(a,AControl.AnchorSide[a].Side)]);
823
if AControl is TWinControl then begin
824
for i:=0 to TWinControl(AControl).ControlCount-1 do
825
WriteControl(TWinControl(AControl).Controls[i],Prefix+' ');
832
debugln(['WriteChildAnchors ',DbgSName(RootControl),' Caption="',RootControl.Caption,'" Align=',dbgs(RootControl.Align)]);
833
for i:=0 to RootControl.ControlCount-1 do
834
WriteControl(RootControl.Controls[i],' ');
837
{ TAnchorDockLayoutTreeNode }
839
function TAnchorDockLayoutTreeNode.GetNodes(Index: integer
840
): TAnchorDockLayoutTreeNode;
842
Result:=TAnchorDockLayoutTreeNode(FNodes[Index]);
845
function TAnchorDockLayoutTreeNode.GetRight: integer;
847
Result:=FBoundsRect.Right;
850
function TAnchorDockLayoutTreeNode.GetHeight: integer;
852
Result:=FBoundsRect.Bottom-FBoundsRect.Top;
855
function TAnchorDockLayoutTreeNode.GetBottom: integer;
857
Result:=FBoundsRect.Bottom;
860
function TAnchorDockLayoutTreeNode.GetAnchors(Site: TAnchorKind): string;
862
Result:=fAnchors[Site];
865
function TAnchorDockLayoutTreeNode.GetLeft: integer;
867
Result:=FBoundsRect.Left;
870
function TAnchorDockLayoutTreeNode.GetTop: integer;
872
Result:=FBoundsRect.Top;
875
function TAnchorDockLayoutTreeNode.GetWidth: integer;
877
Result:=FBoundsRect.Right-FBoundsRect.Left;
880
procedure TAnchorDockLayoutTreeNode.SetAlign(const AValue: TAlign);
882
if FAlign=AValue then exit;
887
procedure TAnchorDockLayoutTreeNode.SetAnchors(Site: TAnchorKind;
888
const AValue: string);
890
if Anchors[Site]=AValue then exit;
891
fAnchors[Site]:=AValue;
895
procedure TAnchorDockLayoutTreeNode.SetBottom(const AValue: integer);
897
if GetBottom=AValue then exit;
898
FBoundsRect.Bottom:=AValue;
902
procedure TAnchorDockLayoutTreeNode.SetBoundSplitterPos(const AValue: integer);
904
if FBoundSplitterPos=AValue then exit;
905
FBoundSplitterPos:=AValue;
909
procedure TAnchorDockLayoutTreeNode.SetBoundsRect(const AValue: TRect);
911
if CompareRect(@FBoundsRect,@AValue) then exit;
916
procedure TAnchorDockLayoutTreeNode.SetHeaderPosition(
917
const AValue: TADLHeaderPosition);
919
if FHeaderPosition=AValue then exit;
920
FHeaderPosition:=AValue;
924
procedure TAnchorDockLayoutTreeNode.SetHeight(const AValue: integer);
926
if Height=AValue then exit;
927
FBoundsRect.Bottom:=FBoundsRect.Top+AValue;
931
procedure TAnchorDockLayoutTreeNode.SetLeft(const AValue: integer);
933
if Left=AValue then exit;
934
FBoundsRect.Left:=AValue;
938
procedure TAnchorDockLayoutTreeNode.SetMonitor(const AValue: integer);
940
if FMonitor=AValue then exit;
945
procedure TAnchorDockLayoutTreeNode.SetName(const AValue: string);
947
if FName=AValue then exit;
952
procedure TAnchorDockLayoutTreeNode.SetNodeType(const AValue: TADLTreeNodeType);
954
if FNodeType=AValue then exit;
959
procedure TAnchorDockLayoutTreeNode.SetParent(
960
const AValue: TAnchorDockLayoutTreeNode);
962
if FParent=AValue then exit;
963
if FParent<>nil then begin
964
FParent.FNodes.Remove(Self);
965
FParent.IncreaseChangeStamp;
969
FParent.FNodes.Add(Self);
973
procedure TAnchorDockLayoutTreeNode.SetRight(const AValue: integer);
975
if Right=AValue then exit;
976
FBoundsRect.Right:=AValue;
980
procedure TAnchorDockLayoutTreeNode.SetWorkAreaRect(const AValue: TRect);
982
if CompareRect(@FWorkAreaRect,@AValue) then exit;
983
FWorkAreaRect:=AValue;
987
procedure TAnchorDockLayoutTreeNode.SetTabPosition(const AValue: TTabPosition);
989
if FTabPosition=AValue then exit;
990
FTabPosition:=AValue;
994
procedure TAnchorDockLayoutTreeNode.SetTop(const AValue: integer);
996
if Top=AValue then exit;
997
FBoundsRect.Top:=AValue;
1001
procedure TAnchorDockLayoutTreeNode.SetWidth(const AValue: integer);
1003
if Width=AValue then exit;
1004
FBoundsRect.Right:=FBoundsRect.Left+AValue;
1005
IncreaseChangeStamp;
1008
procedure TAnchorDockLayoutTreeNode.SetWindowState(const AValue: TWindowState);
1010
if FWindowState=AValue then exit;
1011
FWindowState:=AValue;
1012
IncreaseChangeStamp;
1015
constructor TAnchorDockLayoutTreeNode.Create;
1017
FNodes:=TFPList.Create;
1020
destructor TAnchorDockLayoutTreeNode.Destroy;
1028
procedure TAnchorDockLayoutTreeNode.Clear;
1033
FillByte(FBoundsRect,sizeOf(FBoundsRect),0);
1034
while Count>0 do Nodes[Count-1].Free;
1035
NodeType:=adltnNone;
1036
WindowState:=wsNormal;
1039
HeaderPosition:=adlhpAuto;
1041
BoundSplitterPos:=0;
1042
WorkAreaRect:=Rect(0,0,0,0);
1043
for a:=low(TAnchorKind) to high(TAnchorKind) do
1047
function TAnchorDockLayoutTreeNode.IsEqual(Node: TAnchorDockLayoutTreeNode
1054
if (not CompareRect(@FBoundsRect,@Node.FBoundsRect))
1055
or (Count<>Node.Count)
1056
or (NodeType<>Node.NodeType)
1057
or (Name<>Node.Name)
1058
or (Align<>Node.Align)
1059
or (WindowState<>Node.WindowState)
1060
or (HeaderPosition<>Node.HeaderPosition)
1061
or (TabPosition<>Node.TabPosition)
1062
or (BoundSplitterPos<>Node.BoundSplitterPos)
1063
or (not CompareRect(@FWorkAreaRect,@Node.FWorkAreaRect))
1066
for a:=low(TAnchorKind) to high(TAnchorKind) do
1067
if Anchors[a]<>Node.Anchors[a] then exit;
1068
for i:=0 to Count-1 do
1069
if not Nodes[i].IsEqual(Node.Nodes[i]) then exit;
1073
procedure TAnchorDockLayoutTreeNode.Assign(Node: TAnchorDockLayoutTreeNode);
1076
Child: TAnchorDockLayoutTreeNode;
1080
NodeType:=Node.NodeType;
1081
BoundsRect:=Node.BoundsRect;
1083
WindowState:=Node.WindowState;
1084
HeaderPosition:=Node.HeaderPosition;
1085
TabPosition:=Node.TabPosition;
1086
BoundSplitterPos:=Node.BoundSplitterPos;
1087
WorkAreaRect:=Node.WorkAreaRect;
1088
for a:=low(TAnchorKind) to high(TAnchorKind) do
1089
Anchors[a]:=Node.Anchors[a];
1090
while Count>Node.Count do Nodes[Count-1].Free;
1091
for i:=0 to Node.Count-1 do begin
1092
if i=Count then begin
1093
Child:=TAnchorDockLayoutTreeNode.Create;
1098
Child.Assign(Node.Nodes[i]);
1102
procedure TAnchorDockLayoutTreeNode.Assign(AControl: TControl);
1104
AnchorControl: TControl;
1107
Name:=AControl.Name;
1108
BoundsRect:=AControl.BoundsRect;
1109
Align:=AControl.Align;
1110
if (AControl.Parent=nil) and (AControl is TCustomForm) then begin
1111
WindowState:=TCustomForm(AControl).WindowState;
1112
Monitor:=TCustomForm(AControl).Monitor.MonitorNum;
1113
WorkAreaRect:=TCustomForm(AControl).Monitor.WorkareaRect;
1115
WindowState:=wsNormal;
1116
if AControl is TCustomTabControl then
1117
TabPosition:=TCustomTabControl(AControl).TabPosition
1120
for a:=low(TAnchorKind) to high(TAnchorKind) do begin
1121
AnchorControl:=AControl.AnchorSide[a].Control;
1122
if (AnchorControl=nil) or (AnchorControl=AControl.Parent) then
1124
else if AnchorControl.Parent=AControl.Parent then
1125
Anchors[a]:=AnchorControl.Name;
1129
procedure TAnchorDockLayoutTreeNode.LoadFromConfig(Config: TConfigStorage);
1132
Child: TAnchorDockLayoutTreeNode;
1136
Name:=Config.GetValue('Name','');
1137
NodeType:=NameToADLTreeNodeType(Config.GetValue('Type',ADLTreeNodeTypeNames[adltnNone]));
1138
Left:=Config.GetValue('Bounds/Left',0);
1139
Top:=Config.GetValue('Bounds/Top',0);
1140
Width:=Config.GetValue('Bounds/Width',0);
1141
Height:=Config.GetValue('Bounds/Height',0);
1142
BoundSplitterPos:=Config.GetValue('Bounds/SplitterPos',0);
1143
Config.GetValue('Bounds/WorkArea/Rect/',FWorkAreaRect,Rect(0,0,0,0));
1144
Anchors[akLeft]:=Config.GetValue('Anchors/Left','');
1145
Anchors[akTop]:=Config.GetValue('Anchors/Top','');
1146
Anchors[akRight]:=Config.GetValue('Anchors/Right','');
1147
Anchors[akBottom]:=Config.GetValue('Anchors/Bottom','');
1148
Align:=NameToADLAlign(Config.GetValue('Anchors/Align',dbgs(alNone)));
1149
WindowState:=NameToADLWindowState(Config.GetValue('WindowState',ADLWindowStateNames[wsNormal]));
1150
HeaderPosition:=NameToADLHeaderPosition(Config.GetValue('Header/Position',ADLHeaderPositionNames[adlhpAuto]));
1151
TabPosition:=NameToADLTabPosition(Config.GetValue('Header/TabPosition',ADLTabPostionNames[tpTop]));
1152
Monitor:=Config.GetValue('Monitor',0);
1153
NewCount:=Config.GetValue('ChildCount',0);
1154
for i:=1 to NewCount do begin
1155
Config.AppendBasePath('Item'+IntToStr(i)+'/');
1156
Child:=TAnchorDockLayoutTreeNode.Create;
1158
Child.LoadFromConfig(Config);
1159
Config.UndoAppendBasePath;
1163
procedure TAnchorDockLayoutTreeNode.SaveToConfig(Config: TConfigStorage);
1167
Config.SetDeleteValue('Name',Name,'');
1168
Config.SetDeleteValue('Type',ADLTreeNodeTypeNames[NodeType],
1169
ADLTreeNodeTypeNames[adltnNone]);
1170
Config.SetDeleteValue('Bounds/Left',Left,0);
1171
Config.SetDeleteValue('Bounds/Top',Top,0);
1172
Config.SetDeleteValue('Bounds/Width',Width,0);
1173
Config.SetDeleteValue('Bounds/Height',Height,0);
1174
Config.SetDeleteValue('Bounds/SplitterPos',BoundSplitterPos,0);
1175
Config.SetDeleteValue('Bounds/WorkArea/Rect/',FWorkAreaRect,Rect(0,0,0,0));
1176
Config.SetDeleteValue('Anchors/Left',Anchors[akLeft],'');
1177
Config.SetDeleteValue('Anchors/Top',Anchors[akTop],'');
1178
Config.SetDeleteValue('Anchors/Right',Anchors[akRight],'');
1179
Config.SetDeleteValue('Anchors/Bottom',Anchors[akBottom],'');
1180
Config.SetDeleteValue('Anchors/Align',ADLAlignNames[Align],ADLAlignNames[alNone]);
1181
Config.SetDeleteValue('WindowState',ADLWindowStateNames[WindowState],
1182
ADLWindowStateNames[wsNormal]);
1183
Config.SetDeleteValue('Header/Position',ADLHeaderPositionNames[HeaderPosition],
1184
ADLHeaderPositionNames[adlhpAuto]);
1185
Config.SetDeleteValue('Header/TabPosition',ADLTabPostionNames[TabPosition],
1186
ADLTabPostionNames[tpTop]);
1187
Config.SetDeleteValue('Monitor',Monitor,0);
1188
Config.SetDeleteValue('ChildCount',Count,0);
1189
for i:=1 to Count do begin
1190
Config.AppendBasePath('Item'+IntToStr(i)+'/');
1191
Nodes[i-1].SaveToConfig(Config);
1192
Config.UndoAppendBasePath;
1196
function TAnchorDockLayoutTreeNode.FindChildNode(aName: string;
1197
Recursive: boolean): TAnchorDockLayoutTreeNode;
1201
for i:=0 to Count-1 do begin
1203
if CompareText(aName,Result.Name)=0 then exit;
1204
if Recursive then begin
1205
Result:=Result.FindChildNode(aName,true);
1206
if Result<>nil then exit;
1212
function TAnchorDockLayoutTreeNode.FindControlNode: TAnchorDockLayoutTreeNode;
1216
if NodeType=adltnControl then
1219
for i:=0 to Count-1 do begin
1220
Result:=Nodes[i].FindControlNode;
1221
if Result<>nil then exit;
1225
procedure TAnchorDockLayoutTreeNode.CheckConsistency;
1226
{ ToDo: check for topological sort }
1228
procedure CheckCornerIsUnique(Side1: TAnchorKind; Side1AnchorName: string;
1229
Side2: TAnchorKind; Side2AnchorName: string);
1232
Child, Found: TAnchorDockLayoutTreeNode;
1235
for i:=0 to Count-1 do begin
1237
if Child.IsSplitter then continue;
1238
if CompareText(Child.Anchors[Side1],Side1AnchorName)<>0 then continue;
1239
if CompareText(Child.Anchors[Side2],Side2AnchorName)<>0 then continue;
1241
raise EAnchorDockLayoutError.Create('overlapping controls found :'+Found.Name+','+Child.Name);
1245
raise EAnchorDockLayoutError.Create('empty space found :'+Name+' '+dbgs(Side1)+'='+Side1AnchorName+' '+dbgs(Side2)+'='+Side2AnchorName);
1250
Child: TAnchorDockLayoutTreeNode;
1252
Sibling: TAnchorDockLayoutTreeNode;
1255
if (NodeType=adltnNone) and (Parent<>nil) then
1256
raise EAnchorDockLayoutError.Create('invalid parent, root node');
1257
if (NodeType=adltnCustomSite) and (Parent.NodeType<>adltnNone) then
1258
raise EAnchorDockLayoutError.Create('invalid parent, custom sites parent must be nil');
1259
if (Parent<>nil) and IsSplitter and (Parent.NodeType<>adltnLayout) then
1260
raise EAnchorDockLayoutError.Create('invalid parent, splitter needs parent layout');
1263
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1264
if Anchors[Side]<>'' then begin
1265
// anchor must be a sibling
1268
Sibling:=Parent.FindChildNode(Anchors[Side],false);
1269
if (Sibling=nil) then
1270
raise EAnchorDockLayoutError.Create(
1271
Format(adrsAnchorNotFoundNodeAnchors, ['"', Name, '"', dbgs(Side),
1272
'"', Anchors[Side], '"']));
1273
// only anchor to splitter
1274
if not Sibling.IsSplitter then
1275
raise EAnchorDockLayoutError.Create(
1276
Format(adrsAnchorIsNotSplitterNodeAnchors, ['"', Name, '"', dbgs(Side
1277
), '"', Anchors[Side], '"']));
1278
// the free sides of a splitter must not be anchored
1279
if ((NodeType=adltnSplitterVertical) and (Side in [akLeft,akRight]))
1280
or ((NodeType=adltnSplitterHorizontal) and (Side in [akTop,akBottom]))
1282
raise EAnchorDockLayoutError.Create(
1283
Format(adrsAFreeSideOfASplitterMustNotBeAnchoredNodeTypeAncho, ['"',
1284
Name, '"', ADLTreeNodeTypeNames[NodeType], dbgs(Side), '"', Anchors[
1286
// a page must not be anchored
1287
if (Parent.NodeType=adltnPages) then
1288
raise EAnchorDockLayoutError.Create(
1289
Format(adrsAPageMustNotBeAnchoredNodeParentParentTypeAnchors, ['"',
1290
Name, '"', Parent.Name, ADLTreeNodeTypeNames[Parent.NodeType], dbgs(
1291
Side), '"', Anchors[Side], '"']));
1292
// check if anchored to the wrong side of a splitter
1293
if ((Sibling.NodeType=adltnSplitterHorizontal) and (Side in [akLeft,akRight]))
1294
or ((Sibling.NodeType=adltnSplitterVertical) and (Side in [akTop,akBottom]))
1296
raise EAnchorDockLayoutError.Create(
1297
Format(adrsAnchorToWrongSideOfSplitterNodeAnchors, ['"', Name, '"',
1298
dbgs(Side), '"', Anchors[Side], '"']));
1302
// only the root node, pages, layouts and customsite can have children
1303
if (Parent<>nil) and (Count>0)
1304
and (not (NodeType in [adltnLayout,adltnPages,adltnCustomSite]))
1306
raise EAnchorDockLayoutError.Create(
1307
Format(adrsNoChildrenAllowedForNodeType, ['"', Name, '"',
1308
ADLTreeNodeTypeNames[NodeType]]));
1309
if (NodeType=adltnCustomSite) then begin
1311
raise EAnchorDockLayoutError.Create(Format(
1312
adrsCustomDockSiteCanHaveOnlyOneSite, ['"', Name, '"']));
1315
// check if in each corner sits exactly one child
1316
if NodeType=adltnLayout then
1317
for Side:=low(TAnchorKind) to high(TAnchorKind) do
1318
CheckCornerIsUnique(Side,'',ClockwiseAnchor[Side],'');
1321
for i:=0 to Count-1 do begin
1323
Child.CheckConsistency;
1325
if (Child.NodeType=adltnSplitterHorizontal) then begin
1326
// check if splitter corners have exactly one sibling
1327
CheckCornerIsUnique(akLeft,Child.Anchors[akLeft],akTop,Child.Name);
1328
CheckCornerIsUnique(akLeft,Child.Anchors[akLeft],akBottom,Child.Name);
1329
CheckCornerIsUnique(akRight,Child.Anchors[akRight],akTop,Child.Name);
1330
CheckCornerIsUnique(akRight,Child.Anchors[akRight],akBottom,Child.Name);
1332
if (Child.NodeType=adltnSplitterVertical) then begin
1333
// check if splitter corners have exactly one sibling
1334
CheckCornerIsUnique(akTop,Child.Anchors[akTop],akLeft,Child.Name);
1335
CheckCornerIsUnique(akTop,Child.Anchors[akTop],akRight,Child.Name);
1336
CheckCornerIsUnique(akBottom,Child.Anchors[akBottom],akLeft,Child.Name);
1337
CheckCornerIsUnique(akBottom,Child.Anchors[akBottom],akRight,Child.Name);
1342
procedure TAnchorDockLayoutTreeNode.Simplify(ExistingNames: TStrings);
1343
{ Simplification rules:
1344
1. Control nodes without existing name are deleted.
1345
2. Empty layouts and pages are deleted
1346
3. pages and layouts with only one child are removed and its content moved up
1350
ChildNode: TAnchorDockLayoutTreeNode;
1352
// simplify children
1355
ChildNode:=Nodes[i];
1356
ChildNode.Simplify(ExistingNames);
1358
if (ChildNode.NodeType=adltnControl) then begin
1359
// leaf node => check if there is a control
1360
if (ChildNode.Name='') or (ExistingNames.IndexOf(ChildNode.Name)<0) then
1361
DeleteNode(ChildNode);
1362
end else if ChildNode.IsSplitter then begin
1364
// delete all children
1365
while ChildNode.Count>0 do
1367
end else if ChildNode.NodeType=adltnCustomSite then begin
1369
end else if ChildNode.Count=0 then begin
1370
// inner node without child => delete
1371
DeleteNode(ChildNode);
1372
end else if (ChildNode.Count=1)
1373
and (ChildNode.NodeType in [adltnLayout,adltnPages]) then begin
1374
// layouts and pages with only one child
1375
// => move grandchildren up and delete childnode
1376
ReplaceWithChildren(ChildNode);
1383
procedure TAnchorDockLayoutTreeNode.DeleteNode(
1384
ChildNode: TAnchorDockLayoutTreeNode);
1387
Sibling: TAnchorDockLayoutTreeNode;
1389
Splitter: TAnchorDockLayoutTreeNode;
1391
WriteDebugLayout('TAnchorDockLayoutTreeNode.DeleteNode BEFORE DELETE Self='+Name+' Child='+ChildNode.Name+' ',Self);
1392
ChildNode.Parent:=nil;
1394
if not ChildNode.IsSplitter then begin
1395
// delete node bound splitter (= a splitter only anchored to this node)
1396
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1397
Splitter:=FindNodeBoundSplitter(ChildNode,Side);
1398
if Splitter<>nil then begin
1399
DeleteNodeBoundSplitter(Splitter,ChildNode,OppositeAnchor[Side]);
1404
// delete spiral splitter
1405
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1406
Splitter:=FindChildNode(ChildNode.Anchors[Side],false);
1407
if (Splitter=nil) or (not Splitter.IsSplitter) then break;
1408
if Side=High(TAnchorKind) then begin
1409
DeleteSpiralSplitter(ChildNode);
1415
// remove references
1416
for i:=0 to Count-1 do begin
1418
for Side:=low(TAnchorKind) to high(TAnchorKind) do
1419
if Sibling.Anchors[Side]=ChildNode.Name then
1420
Sibling.Anchors[Side]:='';
1422
WriteDebugLayout('TAnchorDockLayoutTreeNode.DeleteNode AFTER DELETE Self='+Name+' Child='+ChildNode.Name+' ',Self);
1428
function TAnchorDockLayoutTreeNode.FindNodeBoundSplitter(
1429
ChildNode: TAnchorDockLayoutTreeNode; Side: TAnchorKind
1430
): TAnchorDockLayoutTreeNode;
1432
AnchorNode: TAnchorDockLayoutTreeNode;
1437
AnchorName:=ChildNode.Anchors[Side];
1438
if AnchorName='' then exit;
1439
AnchorNode:=FindChildNode(AnchorName,false);
1440
if (AnchorNode=nil) or (not AnchorNode.IsSplitter) then exit;
1441
for i:=0 to Count-1 do
1442
if (Nodes[i]<>ChildNode) and (Nodes[i].Anchors[Side]=AnchorName) then exit;
1446
procedure TAnchorDockLayoutTreeNode.DeleteNodeBoundSplitter(Splitter,
1447
ChildNode: TAnchorDockLayoutTreeNode; Side: TAnchorKind);
1448
{ delete node bound splitter (= a splitter only anchored to this node)
1450
Example: Side=akRight
1452
##################### #########
1453
---+S+--------+# ---+#
1454
---+S|AControl|# ---> ---+#
1455
---+S+--------+# ---+#
1456
##################### #########
1460
Sibling: TAnchorDockLayoutTreeNode;
1462
for i:=0 to Count-1 do begin
1464
if Sibling.Anchors[Side]=Splitter.Name then
1465
Sibling.Anchors[Side]:=ChildNode.Anchors[Side];
1467
DeleteNode(Splitter);
1470
procedure TAnchorDockLayoutTreeNode.DeleteSpiralSplitter(
1471
ChildNode: TAnchorDockLayoutTreeNode);
1472
{ Merge two splitters and delete one of them.
1473
Prefer the pair with shortest distance between.
1479
2|Node|3 ---> 111111111
1483
Everything anchored to 4 is now anchored to 1.
1484
And right side of 1 is now anchored to where the right side of 4 was anchored.
1487
Splitters: array[TAnchorKind] of TAnchorDockLayoutTreeNode;
1490
Sibling: TAnchorDockLayoutTreeNode;
1492
DeleteSplitter: TAnchorDockLayoutTreeNode;
1493
NextSide: TAnchorKind;
1495
// find the four splitters
1496
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1497
Splitters[Side]:=FindChildNode(ChildNode.Anchors[Side],false);
1498
if (Splitters[Side]=nil) or (not Splitters[Side].IsSplitter) then
1499
RaiseGDBException(''); // missing splitter
1501
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1502
// spiral splitters are connected to each other
1503
NextSide:=ClockwiseAnchor[Side];
1504
if Splitters[Side].Anchors[NextSide]<>Splitters[NextSide].Name then begin
1505
NextSide:=OppositeAnchor[NextSide];
1506
if Splitters[Side].Anchors[NextSide]<>Splitters[NextSide].Name then
1507
RaiseGDBException(''); // this is not a spiral splitter
1510
// Prefer the pair with shortest distance between
1511
if (Splitters[akRight].Left-Splitters[akLeft].Left)
1512
<(Splitters[akBottom].Top-Splitters[akTop].Top)
1517
DeleteSplitter:=Splitters[OppositeAnchor[Keep]];
1518
// transfer anchors from the deleting splitter to the kept splitter
1519
for i:=0 to Count-1 do begin
1521
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1522
if FindChildNode(Sibling.Anchors[Side],false)=DeleteSplitter then
1523
Sibling.Anchors[Side]:=Splitters[Keep].Name;
1526
// longen kept splitter
1527
NextSide:=ClockwiseAnchor[Keep];
1528
if Splitters[Keep].Anchors[NextSide]<>Splitters[NextSide].Name then
1529
NextSide:=OppositeAnchor[NextSide];
1530
Splitters[Keep].Anchors[NextSide]:=DeleteSplitter.Anchors[NextSide];
1531
// delete the splitter
1532
DeleteNode(DeleteSplitter);
1535
procedure TAnchorDockLayoutTreeNode.ReplaceWithChildren(
1536
ChildNode: TAnchorDockLayoutTreeNode);
1537
{ move all children of ChildNode up.
1538
All anchored to ChildNode (= their parent) use the anchors of ChildNode.
1542
GrandChild: TAnchorDockLayoutTreeNode;
1545
WriteDebugLayout('TAnchorDockLayoutTreeNode.ReplaceWithChildren BEFORE REPLACE Self='+Name+' Child='+ChildNode.Name+' ',Self);
1546
DebugWriteChildAnchors(Self);
1547
while ChildNode.Count>0 do begin
1548
GrandChild:=ChildNode[0];
1549
GrandChild.Parent:=Self;
1550
OffsetRect(GrandChild.FBoundsRect,ChildNode.Left,ChildNode.Top);
1551
for Side:=low(TAnchorKind) to high(TAnchorKind) do begin
1552
if GrandChild.Anchors[Side]='' then begin
1553
if ((GrandChild.NodeType=adltnSplitterHorizontal)
1554
and (Side in [akTop,akBottom]))
1555
or ((GrandChild.NodeType=adltnSplitterVertical)
1556
and (Side in [akLeft,akRight]))
1558
continue; // a free splitter sides => don't anchor it
1559
GrandChild.Anchors[Side]:=ChildNode.Anchors[Side];
1563
WriteDebugLayout('TAnchorDockLayoutTreeNode.ReplaceWithChildren AFTER REPLACE Self='+Name+' Child='+ChildNode.Name+' ',Self);
1565
DebugWriteChildAnchors(Self);
1568
procedure TAnchorDockLayoutTreeNode.IncreaseChangeStamp;
1570
if Parent<>nil then Parent.IncreaseChangeStamp;
1573
function TAnchorDockLayoutTreeNode.IsSplitter: boolean;
1575
Result:=NodeType in [adltnSplitterHorizontal,adltnSplitterVertical];
1578
function TAnchorDockLayoutTreeNode.IsRootWindow: boolean;
1580
Result:=(NodeType in [adltnLayout,adltnPages,adltnControl,adltnCustomSite])
1581
and ((Parent=nil) or (Parent.NodeType in [adltnNone]));
1584
function TAnchorDockLayoutTreeNode.Count: integer;
1586
Result:=FNodes.Count;
1589
{ TAnchorDockLayoutTreeRootNode }
1591
procedure TAnchorDockLayoutTreeRootNode.IncreaseChangeStamp;
1593
Tree.IncreaseChangeStamp;
1596
procedure TAnchorDockLayoutTreeRootNode.CheckConsistency;
1600
procedure RaiseNodePath(const Msg: string; Node: TAnchorDockLayoutTreeNode);
1605
while Node<>nil do begin
1614
procedure CheckNames(Node: TAnchorDockLayoutTreeNode);
1618
if (Node.Name='') and (Node<>Self) then
1619
RaiseNodePath(adrsEmptyName, Node);
1620
for i:=0 to Names.Count-1 do
1621
if CompareText(Names[i],Node.Name)=0 then
1622
RaiseNodePath(adrsDuplicateName, Node);
1623
Names.Add(Node.Name);
1624
for i:=0 to Node.Count-1 do
1625
CheckNames(Node[i]);
1629
// check that all names are unique
1630
Names:=TStringList.Create;
1636
inherited CheckConsistency;
1639
{ TAnchorDockLayoutTree }
1641
procedure TAnchorDockLayoutTree.SetModified(const AValue: boolean);
1643
if AValue then IncreaseChangeStamp;
1644
if FModified=AValue then exit;
1648
constructor TAnchorDockLayoutTree.Create;
1650
FRoot:=TAnchorDockLayoutTreeRootNode.Create;
1654
destructor TAnchorDockLayoutTree.Destroy;
1660
procedure TAnchorDockLayoutTree.Clear;
1666
procedure TAnchorDockLayoutTree.LoadFromConfig(Config: TConfigStorage);
1668
Config.AppendBasePath('Nodes/');
1669
FRoot.LoadFromConfig(Config);
1670
Config.UndoAppendBasePath;
1671
Root.CheckConsistency;
1674
procedure TAnchorDockLayoutTree.SaveToConfig(Config: TConfigStorage);
1676
Config.AppendBasePath('Nodes/');
1677
FRoot.SaveToConfig(Config);
1678
Config.UndoAppendBasePath;
1681
procedure TAnchorDockLayoutTree.IncreaseChangeStamp;
1683
if FChangeStamp<High(FChangeStamp) then
1686
FChangeStamp:=Low(FChangeStamp);
1689
function TAnchorDockLayoutTree.NewNode(aParent: TAnchorDockLayoutTreeNode
1690
): TAnchorDockLayoutTreeNode;
1692
Result:=TAnchorDockLayoutTreeNode.Create;
1693
Result.Parent:=aParent;
1696
{ TADNameToControl }
1698
function TADNameToControl.IndexOfName(const aName: string): integer;
1700
Result:=fItems.Count-1;
1701
while (Result>=0) and (CompareText(aName,fItems[Result])<>0) do
1705
function TADNameToControl.GetControl(const aName: string): TControl;
1709
i:=IndexOfName(aName);
1711
Result:=TControl(fItems.Objects[i])
1716
procedure TADNameToControl.SetControl(const aName: string;
1717
const AValue: TControl);
1721
i:=IndexOfName(aName);
1724
fItems.Objects[i]:=AValue;
1726
fItems.AddObject(aName,AValue);
1729
constructor TADNameToControl.Create;
1731
fItems:=TStringList.Create;
1734
destructor TADNameToControl.Destroy;
1740
function TADNameToControl.ControlToName(AControl: TControl): string;
1746
if fItems.Objects[i]=AControl then begin
1755
procedure TADNameToControl.RemoveControl(AControl: TControl);
1761
if fItems.Objects[i]=AControl then
1767
procedure TADNameToControl.WriteDebugReport(Msg: string);
1771
debugln(['TADNameToControl.WriteDebugReport ',fItems.Count,' ',Msg]);
1772
for i:=0 to fItems.Count-1 do begin
1773
debugln([' ',i,'/',fItems.Count,' "',dbgstr(fItems[i]),'" Control=',dbgsname(TControl(fItems.Objects[i]))]);
1777
{ TAnchorDockRestoreLayout }
1779
procedure TAnchorDockRestoreLayout.SetControlNames(const AValue: TStrings);
1781
if FControlNames=AValue then exit;
1782
FControlNames.Assign(AValue);
1785
constructor TAnchorDockRestoreLayout.Create;
1787
FControlNames:=TStringList.Create;
1788
FLayout:=TAnchorDockLayoutTree.Create;
1791
constructor TAnchorDockRestoreLayout.Create(aLayout: TAnchorDockLayoutTree);
1793
FControlNames:=TStringList.Create;
1798
destructor TAnchorDockRestoreLayout.Destroy;
1800
FreeAndNil(FLayout);
1801
FreeAndNil(FControlNames);
1805
function TAnchorDockRestoreLayout.IndexOfControlName(AName: string): integer;
1807
Result:=fControlNames.Count-1;
1808
while (Result>=0) and (CompareText(AName,FControlNames[Result])<>0) do
1812
function TAnchorDockRestoreLayout.HasControlName(AName: string): boolean;
1814
Result:=IndexOfControlName(AName)>=0;
1817
procedure TAnchorDockRestoreLayout.RemoveControlName(AName: string);
1821
for i:=FControlNames.Count-1 downto 0 do
1822
if CompareText(AName,FControlNames[i])=0 then
1823
FControlNames.Delete(i);
1826
procedure TAnchorDockRestoreLayout.UpdateControlNames;
1828
procedure Check(Node: TAnchorDockLayoutTreeNode);
1832
if (Node.Name<>'') and (Node.NodeType in [adltnControl,adltnCustomSite])
1833
and (not HasControlName(Node.Name)) then
1834
FControlNames.Add(Node.Name);
1835
for i:=0 to Node.Count-1 do
1840
FControlNames.Clear;
1844
procedure TAnchorDockRestoreLayout.LoadFromConfig(Config: TConfigStorage);
1848
Node: TAnchorDockLayoutTreeNode;
1850
FControlNames.Delimiter:=',';
1851
FControlNames.StrictDelimiter:=true;
1852
FControlNames.DelimitedText:=Config.GetValue('Names','');
1853
Layout.LoadFromConfig(Config);
1854
for i:=FControlNames.Count-1 downto 0 do begin
1855
AName:=FControlNames[i];
1856
if (AName<>'') and IsValidIdent(AName)
1857
and (Layout.Root<>nil) then begin
1858
Node:=Layout.Root.FindChildNode(AName,true);
1859
if (Node<>nil) and (Node.NodeType in [adltnControl,adltnCustomSite]) then
1862
FControlNames.Delete(i);
1866
procedure TAnchorDockRestoreLayout.SaveToConfig(Config: TConfigStorage);
1868
FControlNames.Delimiter:=',';
1869
FControlNames.StrictDelimiter:=true;
1870
Config.SetDeleteValue('Names',FControlNames.DelimitedText,'');
1871
Layout.SaveToConfig(Config);
1874
{ TAnchorDockRestoreLayouts }
1876
function TAnchorDockRestoreLayouts.GetItems(Index: integer
1877
): TAnchorDockRestoreLayout;
1879
Result:=TAnchorDockRestoreLayout(fItems[Index]);
1882
constructor TAnchorDockRestoreLayouts.Create;
1884
fItems:=TFPList.Create;
1887
destructor TAnchorDockRestoreLayouts.Destroy;
1894
procedure TAnchorDockRestoreLayouts.Clear;
1898
for i:=0 to fItems.Count-1 do
1899
TObject(fItems[i]).Free;
1903
procedure TAnchorDockRestoreLayouts.Delete(Index: integer);
1905
TObject(fItems[Index]).Free;
1906
fItems.Delete(Index);
1909
function TAnchorDockRestoreLayouts.IndexOfName(AControlName: string): integer;
1912
while (Result>=0) and not Items[Result].HasControlName(AControlName) do
1916
function TAnchorDockRestoreLayouts.FindByName(AControlName: string
1917
): TAnchorDockRestoreLayout;
1921
i:=IndexOfName(AControlName);
1928
procedure TAnchorDockRestoreLayouts.Add(Layout: TAnchorDockRestoreLayout;
1929
RemoveOther: boolean);
1933
if Layout=nil then exit;
1934
if RemoveOther then begin
1935
for i:=0 to Layout.ControlNames.Count-1 do
1936
RemoveByName(Layout.ControlNames[i]);
1941
procedure TAnchorDockRestoreLayouts.RemoveByName(AControlName: string);
1944
Layout: TAnchorDockRestoreLayout;
1946
for i:=Count-1 downto 0 do begin
1948
Layout.RemoveControlName(AControlName);
1949
if Layout.ControlNames.Count=0 then
1954
procedure TAnchorDockRestoreLayouts.LoadFromConfig(Config: TConfigStorage);
1957
NewItem: TAnchorDockRestoreLayout;
1961
NewCount:=Config.GetValue('Count',0);
1962
for i:=1 to NewCount do begin
1963
NewItem:=TAnchorDockRestoreLayout.Create;
1964
Config.AppendBasePath('Item'+IntToStr(i+1)+'/');
1966
NewItem.LoadFromConfig(Config);
1968
Config.UndoAppendBasePath;
1970
if NewItem.ControlNames.Count>0 then
1977
procedure TAnchorDockRestoreLayouts.SaveToConfig(Config: TConfigStorage);
1981
Config.SetDeleteValue('Count',Count,0);
1982
for i:=0 to Count-1 do begin
1983
Config.AppendBasePath('Item'+IntToStr(i+1)+'/');
1985
Items[i].SaveToConfig(Config);
1987
Config.UndoAppendBasePath;
1992
function TAnchorDockRestoreLayouts.ConfigIsEmpty(Config: TConfigStorage
1995
Result:=Config.GetValue('Count',0)<=0;
1998
function TAnchorDockRestoreLayouts.Count: integer;
2000
Result:=fItems.Count;