2
/***************************************************************************
6
***************************************************************************/
8
*****************************************************************************
10
* This file is part of the Lazarus Component Library (LCL) *
12
* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
13
* for details about the copyright. *
15
* This program is distributed in the hope that it will be useful, *
16
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
17
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
19
*****************************************************************************
21
Author: Mattias Gaertner
24
This unit contains visual components for docking and streaming.
27
- move the docking code to TCustomAnchoredDockManager
28
and keep only the resizing code here.
29
- restoring layout: pages
30
- restoring layout: move form after inserting a control
31
- restoring layout: spiral splitter
32
- save TLazDockConfigNode to stream (atm only xml implemented)
33
- load TLazDockConfigNode from stream (atm only xml implemented)
42
Classes, Math, SysUtils, TypInfo, LCLProc, Controls, Forms, Menus,
43
LCLStrConsts, AvgLvlTree, StringHashList, ExtCtrls, LazConfigStorage,
44
LDockCtrlEdit, LDockTree;
47
TNonDockConfigNames = (
48
ndcnControlName, // '-Control ' + AControl.Name
49
ndcnChildIndex, // '-ID ' + IntToStr(AControl index in Parent) +' '+ AControl.ClassName
50
ndcnParent // '-Parent' : AControl.Parent
54
NonDockConfigNamePrefixes: array[TNonDockConfigNames] of string = (
63
ldcntSplitterLeftRight,// vertical splitter, can be moved left/right
64
ldcntSplitterUpDown, // horizontal splitter, can be moved up/down
70
LDConfigNodeTypeNames: array[TLDConfigNodeType] of string = (
81
{ TLazDockConfigNode }
83
TLazDockConfigNode = class(TPersistent)
88
FParent: TLazDockConfigNode;
89
FSides: array[TAnchorKind] of string;
90
FTheType: TLDConfigNodeType;
92
FWindowState: TWindowState;
93
function GetChildCount: Integer;
94
function GetChilds(Index: integer): TLazDockConfigNode;
95
function GetSides(Side: TAnchorKind): string;
96
procedure SetBounds(const AValue: TRect);
97
procedure SetClientBounds(const AValue: TRect);
98
procedure SetName(const AValue: string);
99
procedure SetParent(const AValue: TLazDockConfigNode);
100
procedure SetSides(Side: TAnchorKind; const AValue: string);
101
procedure SetTheType(const AValue: TLDConfigNodeType);
102
procedure DoAdd(ChildNode: TLazDockConfigNode);
103
procedure DoRemove(ChildNode: TLazDockConfigNode);
105
constructor Create(ParentNode: TLazDockConfigNode);
106
constructor Create(ParentNode: TLazDockConfigNode; const AName: string);
107
destructor Destroy; override;
109
procedure Assign(Source: TPersistent); override;
110
function FindByName(const AName: string; Recursive: boolean = false;
111
WithRoot: boolean = true): TLazDockConfigNode;
112
function IndexOf(const AName: string): Integer;
113
function GetScreenBounds: TRect;
114
function FindNeighbour(SiblingSide: TAnchorKind;
115
NilIfAmbiguous: boolean;
116
IgnoreSplitters: boolean = true): TLazDockConfigNode;
117
function IsTheOnlyNeighbour(Node: TLazDockConfigNode;
118
SiblingSide: TAnchorKind): boolean;
119
procedure SaveToConfig(Config: TConfigStorage; const Path: string = '');
120
procedure LoadFromConfig(Config: TConfigStorage; const Path: string = '');
121
function GetPath: string;
122
procedure WriteDebugReport;
123
function DebugLayoutAsString: string;
125
property Bounds: TRect read FBounds write SetBounds;
126
property ClientBounds: TRect read FClientBounds write SetClientBounds;
127
property Parent: TLazDockConfigNode read FParent write SetParent;
128
property Sides[Side: TAnchorKind]: string read GetSides write SetSides;
129
property ChildCount: Integer read GetChildCount;
130
property Children[Index: integer]: TLazDockConfigNode read GetChilds; default;
132
property TheType: TLDConfigNodeType read FTheType write SetTheType
133
default ldcntControl;
134
property Name: string read FName write SetName;
135
property WindowState: TWindowState read FWindowState write FWindowState;
140
TLazDockerConfig = class
143
FRoot: TLazDockConfigNode;
145
constructor Create(const ADockerName: string; ANode: TLazDockConfigNode);
146
destructor Destroy; override;
147
procedure WriteDebugReport;
148
property DockerName: string read FDockerName;
149
property Root: TLazDockConfigNode read FRoot;
152
TCustomLazControlDocker = class;
153
TCustomLazDockingManager = class;
155
{ TAnchoredDockManager }
157
TAnchoredDockManager = class(TCustomAnchoredDockManager)
159
FConfigs: TCustomLazDockingManager;
161
procedure DisableLayout(Control: TControl); override;
162
procedure EnableLayout(Control: TControl); override;
163
property Configs: TCustomLazDockingManager read FConfigs;
166
{ TCustomLazDockingManager }
168
TCustomLazDockingManager = class(TComponent)
171
FManager: TAnchoredDockManager;
172
FConfigs: TFPList;// list of TLazDockerConfig
173
function GetConfigCount: Integer;
174
function GetConfigs(Index: Integer): TLazDockerConfig;
175
function GetDockerCount: Integer;
176
function GetDockers(Index: Integer): TCustomLazControlDocker;
178
procedure Remove(Docker: TCustomLazControlDocker);
179
function Add(Docker: TCustomLazControlDocker): Integer;
181
constructor Create(TheOwner: TComponent); override;
182
destructor Destroy; override;
183
function FindDockerByName(const ADockerName: string;
184
Ignore: TCustomLazControlDocker = nil): TCustomLazControlDocker;
185
function FindControlByDockerName(const ADockerName: string;
186
Ignore: TCustomLazControlDocker = nil): TControl;
187
function FindDockerByControl(AControl: TControl;
188
Ignore: TCustomLazControlDocker = nil): TCustomLazControlDocker;
189
function CreateUniqueName(const AName: string;
190
Ignore: TCustomLazControlDocker): string;
191
function GetControlConfigName(AControl: TControl): string;
192
procedure DisableLayout(Control: TControl);
193
procedure EnableLayout(Control: TControl);
194
procedure SaveToConfig(Config: TConfigStorage; const Path: string = '');
195
procedure LoadFromConfig(Config: TConfigStorage; const Path: string = '');
196
procedure AddOrReplaceConfig(const DockerName: string;
197
Config: TLazDockConfigNode);
198
procedure ClearConfigs;
199
function GetConfigWithDockerName(const DockerName: string
201
function CreateLayout(const DockerName: string; VisibleControl: TControl;
202
ExceptionOnError: boolean = false): TLazDockConfigNode;
203
function ConfigIsCompatible(RootNode: TLazDockConfigNode;
204
ExceptionOnError: boolean = false): boolean;
206
procedure WriteDebugReport;
208
property Manager: TAnchoredDockManager read FManager;
209
property DockerCount: Integer read GetDockerCount;
210
property Dockers[Index: Integer]: TCustomLazControlDocker read GetDockers; default;
211
property ConfigCount: Integer read GetConfigCount;
212
property Configs[Index: Integer]: TLazDockerConfig read GetConfigs;
215
{ TLazDockingManager }
217
TLazDockingManager = class(TCustomLazDockingManager)
229
{ TCustomLazControlDocker
230
A component to connect a form to the TLazDockingManager.
231
When the control gets visible TCustomLazControlDocker restores the layout.
232
Before the control gets invisible, TCustomLazControlDocker saves the layout.
234
TCustomLazControlDocker = class(TComponent)
239
FExtendPopupMenu: boolean;
240
FLayoutLock: integer;
241
FLocalizedName: string;
242
FManager: TCustomLazDockingManager;
243
FMenus: TFPList;// list of TLCDMenuItem
244
FPopupMenuItem: TMenuItem;
245
procedure SetControl(const AValue: TControl);
246
procedure SetDockerName(const AValue: string);
247
procedure SetExtendPopupMenu(const AValue: boolean);
248
procedure SetLocalizedName(const AValue: string);
249
procedure SetManager(const AValue: TCustomLazDockingManager);
250
procedure PopupMenuItemClick(Sender: TObject);
252
procedure UpdatePopupMenu; virtual;
253
procedure Loaded; override;
254
function GetLocalizedName: string;
255
procedure ControlVisibleChanging(Sender: TObject);
256
procedure ControlVisibleChanged(Sender: TObject);
257
function CreateFormAndDockWithSplitter(Layout: TLazDockConfigNode;
258
Side: TAnchorKind): boolean;
259
function DockAsPage(Layout: TLazDockConfigNode): boolean;
260
procedure FixControlBounds(Layout: TLazDockConfigNode;
261
ResizedControl: TControl);
262
procedure ShrinkNeighbourhood(Layout: TLazDockConfigNode;
263
AControl: TControl; Sides: TAnchors);
264
function FindPageNeighbours(Layout: TLazDockConfigNode;
265
StartControl: TControl;
266
out AnchorControls: TAnchorControlsRect
267
): TFPList; // list of TControls
268
procedure Notification(AComponent: TComponent;
269
Operation: TOperation); override;
270
function FindLCDMenuItem(AMenu: TMenu): TLCDMenuItem;
271
function FindLCDMenuItem(AMenuItem: TMenuItem): TLCDMenuItem;
273
constructor Create(TheOwner: TComponent); override;
274
destructor Destroy; override;
275
procedure ShowDockingEditor; virtual;
276
function GetLayoutFromControl: TLazDockConfigNode;
277
procedure SaveLayout;
278
procedure RestoreLayout;
279
procedure DisableLayout;
280
procedure EnableLayout;
281
function ControlIsDocked: boolean;
282
function GetControlName(AControl: TControl): string;
283
procedure AddPopupMenu(Menu: TPopupMenu);
284
procedure RemovePopupMenu(Menu: TPopupMenu);
285
property Control: TControl read FControl write SetControl;
286
property Manager: TCustomLazDockingManager read FManager write SetManager;
287
property ExtendPopupMenu: boolean read FExtendPopupMenu write SetExtendPopupMenu default true;
288
property PopupMenuItem: TMenuItem read FPopupMenuItem;
289
property LocalizedName: string read FLocalizedName write SetLocalizedName;
290
property DockerName: string read FDockerName write SetDockerName;
291
property Enabled: boolean read FEnabled write FEnabled;// true to auto restore layout on show
292
property LayoutLock: integer read FLayoutLock;
295
{ TLazControlDocker }
297
TLazControlDocker = class(TCustomLazControlDocker)
301
property ExtendPopupMenu;
307
function LDConfigNodeTypeNameToType(const s: string): TLDConfigNodeType;
309
function FindExclusiveSplitter(ControlList: TFPList; Side: TAnchorKind
311
function FindNextControlAnchoredToBoundary(AControl: TControl;
312
Boundary, SearchDirection: TAnchorKind): TControl;
313
function FindSplitterRectangularNeighbourhood(Splitter: TLazDockSplitter;
314
SplitterSide: TAnchorKind; out Bounds: TAnchorControlsRect): TFPList;
316
function dbgs(Node: TLazDockConfigNode): string; overload;
326
RegisterComponents('Misc',[TLazDockingManager,TLazControlDocker]);
329
function LDConfigNodeTypeNameToType(const s: string): TLDConfigNodeType;
331
for Result:=Low(TLDConfigNodeType) to High(TLDConfigNodeType) do
332
if CompareText(LDConfigNodeTypeNames[Result],s)=0 then exit;
333
Result:=ldcntControl;
336
function FindExclusiveSplitter(ControlList: TFPList;
337
Side: TAnchorKind): TLazDockSplitter;
338
{ find a splitter, that is not part of ControlList and anchored on one side
339
only to the controls in ControlList
341
For example: A,B,C,S1,S2 (S1,S2 are the splitters between)
350
will return the splitter to the left and Side=akLeft.
355
AParent: TWinControl;
357
AnchoredToControlList: Boolean;
358
AnchoredToOther: Boolean;
361
if (ControlList=nil) or (ControlList.Count=0) then exit;
362
AControl:=TControl(ControlList[0]);
363
if AControl.Parent=nil then exit;
364
AParent:=AControl.Parent;
365
for i:=0 to AParent.ControlCount-1 do begin
366
Result:=TLazDockSplitter(AParent.Controls[i]);
367
if (Result is TLazDockSplitter)
368
and (ControlList.IndexOf(Result)<0)
370
// ASplitter is a splitter which is not in the ControlList
371
// => check if the splitter is exclusively anchored
372
AnchoredToControlList:=false;
373
AnchoredToOther:=false;
374
for j:=0 to AParent.ControlCount-1 do begin
375
AControl:=TControl(ControlList[j]);
376
if (AControl.AnchorSide[Side].Control=Result) then
378
if ControlList.IndexOf(AControl)>=0 then
379
AnchoredToControlList:=true
381
AnchoredToOther:=true;
385
if AnchoredToControlList and not AnchoredToOther then
393
function FindNextControlAnchoredToBoundary(
394
AControl: TControl; Boundary, SearchDirection: TAnchorKind): TControl;
395
{ Finds the next control anchored to the same as AControl
398
------------------------------------
402
With Boundary=akTop and SearchDirection=akRight the next of A is the splitter
403
to the right, then the splitter right of B, then C, ...
406
AParent: TWinControl;
408
BoundaryControl: TControl;
410
Result:=AControl.AnchorSide[SearchDirection].Control;
411
if (Result<>nil) then begin
412
if Result.Parent=AControl.Parent then
417
AParent:=AControl.Parent;
418
if AParent=nil then exit;
419
BoundaryControl:=AControl.AnchorSide[Boundary].Control;
420
if BoundaryControl=nil then exit;
421
for i:=0 to AParent.ControlCount-1 do begin
422
Result:=AParent.Controls[i];
423
if (Result.AnchorSide[Boundary].Control=BoundaryControl)
424
and (Result.AnchorSide[OppositeAnchor[SearchDirection]].Control=AControl)
432
function FindSplitterRectangularNeighbourhood(
433
Splitter: TLazDockSplitter; SplitterSide: TAnchorKind;
434
out Bounds: TAnchorControlsRect): TFPList;
435
{ Find a list of controls, building a rectangular area (without holes) touching
436
the complete SplitterSide of Splitter.
437
RectBounds will be the four bounding controls (Parent or Siblings).
439
For example: akRight of
449
will find A,B,C and the two splitter controls between A,B,C.
452
function IsBoundary(AControl: TControl): boolean;
456
for a:=Low(TAnchorKind) to High(TAnchorKind) do if Bounds[a]=AControl then
462
BoundSide1: TAnchorKind;
463
BoundSide2: TAnchorKind;
469
OppSide: TAnchorKind;
472
BoundSide1:=ClockwiseAnchor[SplitterSide];
473
BoundSide2:=OppositeAnchor[BoundSide1];
474
OppSide:=OppositeAnchor[SplitterSide];
475
Bounds[OppSide]:=Splitter;
476
Bounds[BoundSide1]:=Splitter.AnchorSide[BoundSide1].Control;
477
Bounds[BoundSide2]:=Splitter.AnchorSide[BoundSide2].Control;
478
Bounds[SplitterSide]:=nil;
479
if (Bounds[BoundSide1]=nil) or (Bounds[BoundSide2]=nil) then exit;
481
{ search for a splitter, bounded the same as Splitter
489
AControl:=FindNextControlAnchoredToBoundary(AControl,BoundSide1,SplitterSide);
490
if AControl=nil then break;
491
if (AControl is TLazDockSplitter)
492
and (AControl.AnchorSide[BoundSide1].Control=Bounds[BoundSide1])
493
and (AControl.AnchorSide[BoundSide2].Control=Bounds[BoundSide2]) then begin
495
Bounds[SplitterSide]:=AControl;
500
if (Bounds[SplitterSide]=nil)
501
and (Bounds[BoundSide1]<>Splitter.Parent) then begin
504
| | "Splitter" is the left one
508
AControl:=Bounds[BoundSide1].AnchorSide[SplitterSide].Control;
509
if (AControl is TLazDockSplitter)
510
and (AControl.AnchorSide[BoundSide2].Control=Bounds[BoundSide2]) then
511
Bounds[SplitterSide]:=AControl;
514
if (Bounds[SplitterSide]=nil)
515
and (Bounds[BoundSide2]<>Splitter.Parent) then begin
518
| | "Splitter" is the left one
522
AControl:=Bounds[BoundSide2].AnchorSide[SplitterSide].Control;
523
if (AControl is TLazDockSplitter)
524
and (AControl.AnchorSide[BoundSide1].Control=Bounds[BoundSide1]) then
525
Bounds[SplitterSide]:=AControl;
528
if (Bounds[SplitterSide]=nil)
529
and (Bounds[BoundSide1]<>Splitter.Parent) then begin
532
| | "Splitter" is the left one
536
AControl:=Bounds[BoundSide1].AnchorSide[SplitterSide].Control;
538
and (Bounds[BoundSide2]<>nil)
539
and (AControl=Bounds[BoundSide2].AnchorSide[SplitterSide].Control) then
540
Bounds[SplitterSide]:=AControl;
543
if Bounds[SplitterSide]=nil then exit;
545
// find all controls between the Bounds
547
// find a first control in the area
548
AControl:=FindNextControlAnchoredToBoundary(Splitter,BoundSide1,SplitterSide);
549
if (AControl=nil) or (AControl=Bounds[SplitterSide]) then exit;
550
Result:=TFPlist.Create;
551
Result.Add(AControl);
553
// add the others with flood fill
555
while i<Result.Count-1 do begin
556
AControl:=TControl(Result[i]);
557
// test all anchored to
558
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
559
Candidate:=AControl.AnchorSide[a].Control;
560
if (not IsBoundary(Candidate)) and (Result.IndexOf(Candidate)<0) then
561
Result.Add(Candidate);
563
// test all anchored by
564
for j:=0 to Splitter.Parent.ControlCount-1 do begin
565
Candidate:=Splitter.Parent.Controls[j];
566
if IsBoundary(Candidate) then continue;
567
if Result.IndexOf(Candidate)>=0 then continue;
568
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
569
if Candidate.AnchorSide[a].Control=AControl then begin
570
Result.Add(Candidate);
579
function dbgs(Node: TLazDockConfigNode): string;
581
if Node=nil then begin
584
Result:=Node.Name+'{Type='+LDConfigNodeTypeNames[Node.TheType]
585
+',ChildCnt='+IntToStr(Node.ChildCount)+'}';
589
{ TCustomLazControlDocker }
591
procedure TCustomLazControlDocker.SetManager(
592
const AValue: TCustomLazDockingManager);
594
if FManager=AValue then exit;
595
//DebugLn('TCustomLazControlDocker.SetManager Old=',DbgSName(Manager),' New=',DbgSName(AValue));
596
if FManager<>nil then FManager.Remove(Self);
598
if FManager<>nil then FManager.Add(Self);
602
procedure TCustomLazControlDocker.UpdatePopupMenu;
603
// creates or deletes the PopupMenuItem to the PopupMenu of Control
605
if [csDesigning, csLoading, csDestroying] * ComponentState <> [] then Exit;
607
//DebugLn('TCustomLazControlDocker.UpdatePopupMenu ',DbgSName(Control),' Manager=',DbgSName(Manager),' PopupMenu=',dbgs((Control<>nil) and (Control.PopupMenu<>nil)),' ExtendPopupMenu=',dbgs(ExtendPopupMenu));
609
if ExtendPopupMenu and (Control<>nil) and (Control.PopupMenu<>nil)
610
and (Manager<>nil) then begin
611
//DebugLn('TCustomLazControlDocker.UpdatePopupMenu ADDING');
612
AddPopupMenu(Control.PopupMenu);
614
// delete PopupMenuItem
615
if (Control<>nil) and (Control.PopupMenu<>nil) then
616
RemovePopupMenu(Control.PopupMenu);
620
procedure TCustomLazControlDocker.Loaded;
626
procedure TCustomLazControlDocker.ShowDockingEditor;
628
Dlg: TLazDockControlEditorDlg;
630
TargetDocker: TCustomLazControlDocker;
632
CurDocker: TCustomLazControlDocker;
635
if (Manager=nil) or (Control=nil) then
636
raise Exception.Create('TCustomLazControlDocker.ShowDockingEditor no docking available');
637
Dlg:=TLazDockControlEditorDlg.Create(nil);
639
// fill the list of controls this control can dock to
640
Dlg.DockControlComboBox.Text:='';
641
Dlg.DockControlComboBox.Items.BeginUpdate;
642
//DebugLn('TCustomLazControlDocker.ShowDockingEditor Self=',DockerName,' Manager.DockerCount=',dbgs(Manager.DockerCount));
644
Dlg.DockControlComboBox.Items.Clear;
645
for i:=0 to Manager.DockerCount-1 do begin
646
CurDocker:=Manager.Dockers[i];
647
//DebugLn('TCustomLazControlDocker.ShowDockingEditor Self=',DockerName,' CurDocker=',CurDocker.DockerName);
648
if CurDocker=Self then continue;
649
if CurDocker.Control=nil then continue;
650
Dlg.DockControlComboBox.Items.Add(CurDocker.GetLocalizedName);
652
Dlg.DockControlComboBox.Enabled:=Dlg.DockControlComboBox.Items.Count>0;
654
Dlg.DockControlComboBox.Items.EndUpdate;
657
// enable Undock button, if Control is docked
658
Dlg.UndockGroupBox.Enabled:=ControlIsDocked;
660
// enable enlarge buttons
661
Dlg.EnlargeLeftSpeedButton.Visible:=
662
Manager.Manager.EnlargeControl(Control,akLeft,true);
663
Dlg.EnlargeTopSpeedButton.Visible:=
664
Manager.Manager.EnlargeControl(Control,akTop,true);
665
Dlg.EnlargeRightSpeedButton.Visible:=
666
Manager.Manager.EnlargeControl(Control,akRight,true);
667
Dlg.EnlargeBottomSpeedButton.Visible:=
668
Manager.Manager.EnlargeControl(Control,akBottom,true);
670
Dlg.EnlargeGroupBox.Visible := Dlg.EnlargeLeftSpeedButton.Visible or
671
Dlg.EnlargeTopSpeedButton.Visible or
672
Dlg.EnlargeRightSpeedButton.Visible or
673
Dlg.EnlargeBottomSpeedButton.Visible;
675
if Dlg.ShowModal=mrOk then begin
677
case Dlg.DlgResult of
680
Manager.Manager.UndockControl(Control,true);
681
ldcedrDockLeft,ldcedrDockRight,ldcedrDockTop,
682
ldcedrDockBottom,ldcedrDockPage:
686
for i:=0 to Manager.DockerCount-1 do begin
687
CurDocker:=Manager.Dockers[i];
688
if CurDocker=Self then continue;
689
if Dlg.DockControlComboBox.Text=CurDocker.GetLocalizedName then
690
TargetDocker:=CurDocker;
692
if TargetDocker=nil then begin
693
RaiseGDBException('TCustomLazControlDocker.ShowDockingEditor TargetDocker=nil');
695
case Dlg.DlgResult of
696
ldcedrDockLeft: Side:=alLeft;
697
ldcedrDockRight: Side:=alRight;
698
ldcedrDockTop: Side:=alTop;
699
ldcedrDockBottom: Side:=alBottom;
700
ldcedrDockPage: Side:=alClient;
701
else RaiseGDBException('TCustomLazControlDocker.ShowDockingEditor ?');
703
Manager.Manager.DockControl(Control,Side,TargetDocker.Control);
705
ldcedrEnlargeLeft,ldcedrEnlargeTop,ldcedrEnlargeRight,ldcedrEnlargeBottom:
708
case Dlg.DlgResult of
709
ldcedrEnlargeLeft: Anchor:=akLeft;
710
ldcedrEnlargeRight: Anchor:=akRight;
711
ldcedrEnlargeTop: Anchor:=akTop;
712
ldcedrEnlargeBottom: Anchor:=akBottom;
713
else RaiseGDBException('TCustomLazControlDocker.ShowDockingEditor ?');
715
Manager.Manager.EnlargeControl(Control,Anchor);
724
function TCustomLazControlDocker.GetLocalizedName: string;
726
Result:=LocalizedName;
727
if LocalizedName='' then begin
729
if (Result='') and (Control<>nil) then
730
Result:=Control.Name;
736
procedure TCustomLazControlDocker.ControlVisibleChanging(Sender: TObject);
738
if Manager=nil then exit;
739
if Control<>Sender then begin
740
DebugLn('TCustomLazControlDocker.ControlVisibleChanging WARNING: ',
741
DbgSName(Control),'<>',DbgSName(Sender));
744
{$IFDEF VerboseAnchorDocking}
745
DebugLn(['TCustomLazControlDocker.ControlVisibleChanging Sender=',DbgSName(Sender),' Control.Visible=',Control.Visible]);
748
if FLayoutLock>0 then begin
749
DebugLn(['TCustomLazControlDocker.ControlVisibleChanging ',DbgSName(Control),' ignore because FLayoutLock=',FLayoutLock]);
753
if Control.Visible then begin
754
// control will be hidden -> the layout will change
755
// save the layout for later restore
757
{$IFDEF VerboseAnchorDocking}
758
DebugLn(['TCustomLazControlDocker.ControlVisibleChanging Parent=',DbgSName(Control.Parent)]);
760
end else if ([csDestroying,csDesigning,csLoading]*ComponentState=[]) then begin
761
// the control will become visible -> dock it to restore the last layout
766
procedure TCustomLazControlDocker.ControlVisibleChanged(Sender: TObject);
768
if Manager=nil then exit;
769
{$IFDEF VerboseAnchorDocking}
770
DebugLn(['TCustomLazControlDocker.ControlVisibleChanged Sender=',DbgSName(Sender),' Control.Visible=',Control.Visible]);
773
if FLayoutLock>0 then begin
774
//DebugLn(['TCustomLazControlDocker.ControlVisibleChanged ',DbgSName(Control),' ignore because FLayoutLock=',FLayoutLock]);
778
if Control.Visible then begin
779
// the control has become visible
780
end else if ([csDesigning,csLoading]*ComponentState=[]) then begin
781
// control was hidden (or destroyed)
784
and (Manager.Manager<>nil) then begin
786
DebugLn(['TCustomLazControlDocker.ControlVisibleChanged auto undock ',DbgSName(Control)]);
787
Manager.Manager.UndockControl(Control,false);
792
function TCustomLazControlDocker.CreateFormAndDockWithSplitter(
793
Layout: TLazDockConfigNode; Side: TAnchorKind): boolean;
794
{ Add a splitter to Side and dock to it. For example:
797
--------+ -------------+
801
====| -> ====#| Self ||
805
--------+ -------------+
806
If A has no parent, a TLazDockForm is created.
808
To get space for Self, either A,B are shrinked
809
and/or the parent of A,B is enlarged (including the grand parents of A,B).
812
function FindNextNeighbour(SplitterNode: TLazDockConfigNode;
813
Neighbours: TFPList; Append: boolean): boolean;
819
Splitter, CurSplitter: TLazDockSplitter;
820
OldAnchor, CurAnchor: TControl;
821
NewNeighbour: TControl;
823
Node: TLazDockConfigNode;
826
if Neighbours=nil then exit;
828
Neighbour:=TControl(Neighbours[Neighbours.Count-1])
830
Neighbour:=TControl(Neighbours[0]);
831
if Neighbour.Parent=nil then exit;
832
if not GetLazDockSplitterOrParent(Neighbour,OppositeAnchor[Side],OldAnchor)
835
if (Side in [akLeft,akRight]) then begin
836
if Append then Search:=akBottom else Search:=akTop;
838
if Append then Search:=akRight else Search:=akLeft;
841
if not GetLazDockSplitter(Neighbour,Search,Splitter) then exit;
842
if (not GetLazDockSplitterOrParent(Splitter,OppositeAnchor[Side],CurAnchor))
843
or (CurAnchor<>OldAnchor) then exit;
844
// find neighbour (anchored to Splitter and OldAnchor)
846
for i:=0 to Neighbour.Parent.ControlCount-1 do begin
847
Sibling:=Neighbour.Parent.Controls[i];
848
if Sibling=Neighbour then continue;
849
if (not GetLazDockSplitter(Sibling,OppositeAnchor[Search],CurSplitter))
850
or (CurSplitter<>Splitter) then continue;
851
if (not GetLazDockSplitterOrParent(Splitter,OppositeAnchor[Side],CurAnchor))
852
or (CurAnchor<>OldAnchor) then continue;
853
// Neighbour control found
854
NewNeighbour:=Sibling;
857
if NewNeighbour=nil then exit;
858
// check if this control is mentioned in Layout as Neighbour
859
NodeName:=Manager.GetControlConfigName(NewNeighbour);
860
if NodeName='' then exit;
861
Node:=Layout.FindByName(NodeName,true);
862
if Node=nil then exit;
863
if CompareText(Node.Sides[OppositeAnchor[Side]],SplitterNode.Name)<>0 then
865
// success: NewNeighbour is a neighbour on the current form and in the Layout
867
Neighbours.Add(Splitter);
868
Neighbours.Add(NewNeighbour);
870
Neighbours.Insert(0,Neighbour);
871
Neighbours.Insert(0,Splitter);
877
SelfNode: TLazDockConfigNode;
878
SplitterNode: TLazDockConfigNode;
879
NeighbourNode: TLazDockConfigNode;
880
NeighbourControl: TControl;
881
NewParent: TWinControl;
882
Splitter: TLazDockSplitter;
884
NewParentCreated: Boolean;
885
SplitterSize: LongInt;
890
LeftTopNeighbour: TControl;
891
RightBottomNeighbour: TControl;
894
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter DockerName="',DockerName,'"']);
895
SelfNode:=Layout.FindByName(DockerName,true);
896
if SelfNode=nil then begin
897
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter SelfNode not found DockerName="',DockerName,'"']);
900
SplitterNode:=Layout.FindByName(SelfNode.Sides[Side]);
901
if SplitterNode=nil then begin
902
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter SplitterNode not found "',SelfNode.Sides[Side],'"']);
906
// search one Neighbour
907
NeighbourNode:=SplitterNode.FindNeighbour(OppositeAnchor[Side],false);
908
if NeighbourNode=nil then begin
909
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter NeighbourNode not found']);
912
NeighbourControl:=Manager.FindControlByDockerName(NeighbourNode.Name);
913
if NeighbourControl=nil then begin
914
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter NeighbourControl not found "',NeighbourNode.Name,'"']);
921
if NeighbourControl.Parent=nil then begin
922
// NeighbourControl is a standalone control (e.g. an undocked form)
923
// => create a new TLazDockForm and put both controls into it
924
NewParent:=Manager.Manager.CreateForm;
925
NewParentCreated:=true;
927
// NeighbourControl is docked
928
NewParent:=NeighbourControl.Parent;
929
NewParentCreated:=false;
932
NewParent.DisableAlign;
935
Splitter:=TLazDockSplitter.Create(nil);
936
Splitter.Align:=alNone;
937
Splitter.Beveled:=true;
938
Splitter.ResizeAnchor:=Side;
939
Splitter.Parent:=NewParent;
940
if Side in [akLeft,akRight] then
941
SplitterSize:=Manager.Manager.GetSplitterWidth(Splitter)
943
SplitterSize:=Manager.Manager.GetSplitterHeight(Splitter);
944
if Side in [akLeft,akRight] then
945
Splitter.Width:=SplitterSize
947
Splitter.Height:=SplitterSize;
948
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter Splitter=',DbgSName(Splitter),' ',dbgs(Splitter.BoundsRect)]);
950
if NewParentCreated then begin
951
// resize NewParent to bounds of NeighbourControl
952
if (NewParent is TCustomForm)
953
and (NeighbourControl is TCustomForm) then;
954
TCustomForm(NewParent).WindowState:=
955
TCustomForm(NeighbourControl).WindowState;
956
NewParent.BoundsRect:=NeighbourControl.BoundsRect;
957
NeighbourControl.Parent:=NewParent;
958
NeighbourControl.Align:=alNone;
960
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter NewParent=',DbgSName(NewParent),' ',dbgs(NewParent.BoundsRect)]);
961
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter NeighbourControl=',DbgSName(NeighbourControl),' ',dbgs(NeighbourControl.BoundsRect)]);
963
// move Control to the new parent
964
Control.Parent:=NewParent;
965
Control.Align:=alNone;
966
Control.BoundsRect:=SelfNode.Bounds;
967
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter Control=',DbgSName(Control),' ',dbgs(Control.BoundsRect)]);
969
if NewParentCreated then begin
970
// one Neighbour, one splitter and the Control
971
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
974
Control.AnchorToNeighbour(a,0,Splitter)
976
Control.AnchorParallel(a,0,NewParent);
978
if (Side in [akLeft,akRight]) <> (a in [akLeft,akRight]) then
979
Splitter.AnchorParallel(a,0,NewParent);
981
if a=OppositeAnchor[Side] then
982
NeighbourControl.AnchorToNeighbour(a,0,Splitter)
984
NeighbourControl.AnchorParallel(a,0,NewParent);
987
// several Neighbours
989
// find all Neighbours
990
Neighbours:=TFPList.Create;
991
Neighbours.Add(NeighbourControl);
992
while FindNextNeighbour(SplitterNode,Neighbours,false) do ;
993
while FindNextNeighbour(SplitterNode,Neighbours,true) do ;
994
// Neighbours now contains all controls, that need to be reanchored
995
// to the new Splitter
997
if Side in [akLeft,akRight] then
1001
Side3:=OppositeAnchor[Side2];
1002
LeftTopNeighbour:=TControl(Neighbours[0]);
1003
RightBottomNeighbour:=TControl(Neighbours[Neighbours.Count-1]);
1006
Control.AnchorToNeighbour(Side,0,Splitter);
1007
Control.AnchorSame(OppositeAnchor[Side],NeighbourControl);
1008
Control.AnchorSame(Side2,LeftTopNeighbour);
1009
Control.AnchorSame(Side3,RightBottomNeighbour);
1012
Splitter.AnchorSame(Side2,LeftTopNeighbour);
1013
Splitter.AnchorSame(Side3,RightBottomNeighbour);
1015
// anchor Neighbours
1016
for i:=0 to Neighbours.Count-1 do begin
1017
NeighbourControl:=TControl(Neighbours[i]);
1018
DebugLn(['TCustomLazControlDocker.CreateFormAndDockWithSplitter NeighbourControl=',DbgSName(NeighbourControl),' i=',i]);
1019
NeighbourControl.AnchorToNeighbour(OppositeAnchor[Side],0,Splitter);
1023
if Side in [akLeft,akRight] then
1024
ShrinkNeighbourhood(Layout,Control,[akLeft,akRight])
1026
ShrinkNeighbourhood(Layout,Control,[akTop,akBottom]);
1027
FixControlBounds(Layout,Control);
1028
Manager.Manager.UpdateTitlePosition(Control);
1032
if NewParent<>nil then begin
1033
NewParent.EnableAlign;
1034
NewParent.Visible:=true;
1041
function TCustomLazControlDocker.DockAsPage(Layout: TLazDockConfigNode
1043
// dock as page like in Layout
1044
// Requirements: Parent in Layout is a ldcntPage and a parent control exists.
1046
SelfNode: TLazDockConfigNode;
1047
PageNode: TLazDockConfigNode;
1048
PageNodeIndex: LongInt;
1049
PagesNode: TLazDockConfigNode;
1050
NeighbourNode: TLazDockConfigNode;
1051
NeighbourControl: TControl;
1052
TopForm: TLazDockForm;
1053
Pages: TLazDockPages;
1054
NeighbourPage: TLazDockPage;
1055
NeighbourControlPageIndex: LongInt;
1058
NeighbourList: TFPList;
1059
AnchorControls: TAnchorControlsRect;
1060
TopFormBounds: TRect;
1065
DebugLn(['TCustomLazControlDocker.DockAsPage DockerName="',DockerName,'"']);
1066
SelfNode:=Layout.FindByName(DockerName,true);
1067
if SelfNode=nil then begin
1068
DebugLn(['TCustomLazControlDocker.DockAsPage SelfNode not found DockerName="',DockerName,'"']);
1071
PageNode:=SelfNode.Parent;
1072
if PageNode=nil then begin
1073
DebugLn(['TCustomLazControlDocker.DockAsPage SelfNode.Parent=nil DockerName="',DockerName,'"']);
1076
if PageNode.TheType<>ldcntPage then begin
1077
DebugLn(['TCustomLazControlDocker.DockAsPage PageNode.TheType<>ldcntPage DockerName="',DockerName,'"']);
1080
if PageNode.ChildCount<>1 then begin
1081
DebugLn(['TCustomLazControlDocker.DockAsPage PageNode.ChildCount<>1 DockerName="',DockerName,'"']);
1085
PagesNode:=PageNode.Parent;
1086
PageNodeIndex:=PagesNode.IndexOf(PageNode.Name);
1087
if PageNodeIndex>0 then
1088
NeighbourNode:=PagesNode.Children[PageNodeIndex-1].Children[0]
1090
NeighbourNode:=PagesNode.Children[PageNodeIndex+1].Children[0];
1091
NeighbourControl:=Manager.FindControlByDockerName(NeighbourNode.Name);
1092
if NeighbourControl=nil then begin
1093
DebugLn(['TCustomLazControlDocker.DockAsPage NeighbourControl not found "',NeighbourNode.Name,'"']);
1097
if NeighbourControl.Parent=nil then begin
1098
// NeighbourControl is a top level control (no parents, no neighbours)
1099
// => create a TLazDockForm with a TLazDockPages and two TLazDockPage
1100
TopForm:=Manager.Manager.CreateForm;
1101
TopFormBounds:=PagesNode.Bounds;
1102
// TODO: shrink TopFormBounds
1103
TopForm.BoundsRect:=TopFormBounds;
1105
Pages:=TLazDockPages.Create(nil);
1108
Pages.Parent:=TopForm;
1109
Pages.AnchorClient(0);
1110
if PageNodeIndex>0 then begin
1111
Pages.Pages.Add(NeighbourControl.Caption);
1112
Pages.Pages.Add(Control.Caption);
1113
NeighbourPage:=Pages.Page[0];
1114
Page:=Pages.Page[1];
1116
Pages.Pages.Add(Control.Caption);
1117
Pages.Pages.Add(NeighbourControl.Caption);
1118
Page:=Pages.Page[0];
1119
NeighbourPage:=Pages.Page[1];
1121
NeighbourControl.Parent:=NeighbourPage;
1122
NeighbourControl.AnchorClient(0);
1123
Control.Parent:=Page;
1124
Control.AnchorClient(0);
1128
end else if NeighbourControl.Parent is TLazDockPage then begin
1129
// NeighbourControl is on a page
1130
// => insert a new page
1131
NeighbourPage:=TLazDockPage(NeighbourControl.Parent);
1132
NeighbourControlPageIndex:=NeighbourPage.PageIndex;
1133
if PageNodeIndex>0 then begin
1135
PageIndex:=NeighbourControlPageIndex;
1138
PageIndex:=NeighbourControlPageIndex+1;
1140
Pages.Pages.Insert(PageIndex,Control.Caption);
1141
Page:=Pages.Page[PageIndex];
1142
Control.Parent:=Page;
1143
Control.AnchorClient(0);
1144
// TODO enlarge parents
1146
// NeighbourControl is a child control, but the parent is not yet a page
1147
// => collect a rectangular area of neighbour controls to build a page
1148
NeighbourList:=FindPageNeighbours(Layout,NeighbourControl,AnchorControls);
1150
NeighbourControl.Parent.DisableAlign;
1151
// TODO: create a PageControl and two pages. And move the neighbours onto
1152
// one page and Control to the other page.
1154
// create a TLazDockPages
1155
Pages:=TLazDockPages.Create(nil);
1156
// add it to the place where the neighbours are
1157
Pages.Parent:=NeighbourControl.Parent;
1158
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
1159
Pages.AnchorSide[a].Control:=AnchorControls[a];
1160
if (AnchorControls[a]=Pages.Parent)=(a in [akLeft,akTop]) then
1161
Pages.AnchorSide[a].Side:=asrLeft
1163
Pages.AnchorSide[a].Side:=asrRight;
1165
Pages.Anchors:=[akLeft,akTop,akRight,akBottom];
1167
// create the two pages
1168
Pages.Pages.Insert(0,NeighbourControl.Caption);
1169
NeighbourPage:=Pages.Page[0];
1171
// move the neighbours
1172
for i:=0 to NeighbourList.Count-1 do begin
1173
NeighbourControl:=TControl(NeighbourList[i]);
1174
NeighbourControl.Parent:=NeighbourPage;
1176
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
1177
if NeighbourControl.AnchorSide[a].Control=AnchorControls[a] then begin
1178
NeighbourControl.AnchorSide[a].Control:=NeighbourPage;
1179
if a in [akLeft,akTop] then
1180
NeighbourControl.AnchorSide[a].Side:=asrLeft;
1185
// add a second page
1187
Pages.Pages.Insert(PageIndex,Control.Caption);
1188
Page:=Pages.Page[PageIndex];
1190
// add the control into the second page
1191
Control.Parent:=Page;
1192
Control.AnchorClient(0);
1196
NeighbourControl.Parent.EnableAlign;
1203
procedure TCustomLazControlDocker.FixControlBounds(Layout: TLazDockConfigNode;
1204
ResizedControl: TControl);
1205
{ Fix bounds after inserting AddedControl }
1207
TControlInfo = record
1209
Docker: TLazDockerConfig;
1210
Node: TLazDockConfigNode;
1212
MinLeftValid: boolean;
1213
MinLeftCalculating: boolean;
1215
MinTopValid: boolean;
1216
MinTopCalculating: boolean;
1217
MinClientSize: TPoint;
1218
MinClientSizeValid: boolean;
1220
PControlInfo = ^TControlInfo;
1222
ControlToInfo: TPointerToPointerTree;
1223
NodeToInfo: TPointerToPointerTree;
1225
procedure InitInfos;
1227
ControlToInfo:=TPointerToPointerTree.Create;
1228
NodeToInfo:=TPointerToPointerTree.Create;
1231
procedure FreeInfos;
1233
AControlPtr: Pointer;
1237
if ControlToInfo.GetFirst(AControlPtr,AnInfo) then begin
1239
Info:=PControlInfo(AnInfo);
1241
until not ControlToInfo.GetNext(AControlPtr,AControlPtr,AnInfo);
1247
function GetInfo(AControl: TControl): PControlInfo;
1249
Result:=ControlToInfo[AControl];
1250
if Result=nil then begin
1252
FillChar(Result^,SizeOf(TControlInfo),0);
1253
Result^.Control:=AControl;
1255
Layout.FindByName(Manager.GetControlConfigName(AControl),true);
1256
ControlToInfo[AControl]:=Result;
1257
if ControlToInfo[AControl]<>Result then
1258
RaiseGDBException('');
1262
function CalculateMinimumLeft(AControl: TControl): integer;
1266
procedure Improve(Neighbour: TControl);
1268
if Neighbour=nil then exit;
1269
if Neighbour.Parent<>AControl.Parent then exit;
1270
//DebugLn(['Left Improve AControl=',DbgSName(AControl),' Neighbour=',DbgSName(Neighbour)]);
1271
Info^.MinLeft:=Max(Info^.MinLeft,
1272
CalculateMinimumLeft(Neighbour)+Neighbour.Width);
1279
Info:=GetInfo(AControl);
1280
if not Info^.MinLeftValid then begin
1281
//DebugLn(['CalculateMinimumLeft ',DbgSName(AControl)]);
1282
if Info^.MinLeftCalculating then
1283
raise Exception.Create('anchor circle (left)');
1284
Info^.MinLeftCalculating:=true;
1287
if (akLeft in AControl.Anchors) then
1288
Improve(AControl.AnchorSide[akLeft].Control);
1289
if AControl.Parent<>nil then begin
1290
for i:=0 to AControl.Parent.ControlCount-1 do begin
1291
Sibling:=AControl.Parent.Controls[i];
1292
if Sibling=AControl then continue;
1293
if (akRight in Sibling.Anchors)
1294
and (Sibling.AnchorSide[akRight].Control=AControl) then
1299
Info^.MinLeftCalculating:=false;
1300
Info^.MinLeftValid:=true;
1301
//DebugLn(['CalculateMinimumLeft END ',DbgSName(AControl),' ',GetInfo(AControl)^.MinLeftValid]);
1303
Result:=Info^.MinLeft;
1306
function CalculateMinimumTop(AControl: TControl): integer;
1310
procedure Improve(Neighbour: TControl);
1312
if Neighbour=nil then exit;
1313
if Neighbour.Parent<>AControl.Parent then exit;
1314
Info^.MinTop:=Max(Info^.MinTop,
1315
CalculateMinimumTop(Neighbour)+Neighbour.Height);
1322
Info:=GetInfo(AControl);
1323
if not Info^.MinTopValid then begin
1324
if Info^.MinTopCalculating then
1325
raise Exception.Create('anchor circle (top)');
1326
Info^.MinTopCalculating:=true;
1329
if (akTop in AControl.Anchors) then
1330
Improve(AControl.AnchorSide[akTop].Control);
1331
if AControl.Parent<>nil then begin
1332
for i:=0 to AControl.Parent.ControlCount-1 do begin
1333
Sibling:=AControl.Parent.Controls[i];
1334
if Sibling=AControl then continue;
1335
if (akBottom in Sibling.Anchors)
1336
and (Sibling.AnchorSide[akBottom].Control=AControl) then
1341
Info^.MinTopCalculating:=false;
1342
Info^.MinTopValid:=true;
1344
Result:=Info^.MinTop;
1347
function CalculateClientSize(AControl: TControl): TPoint;
1349
AWinControl: TWinControl;
1351
ChildControl: TControl;
1354
if AControl is TWinControl then begin
1355
AWinControl:=TWinControl(AControl);
1356
for i:=0 to AWinControl.ControlCount-1 do begin
1357
ChildControl:=AWinControl.Controls[i];
1358
Result.X:=Max(Result.X,CalculateMinimumLeft(ChildControl)
1359
+ChildControl.Width);
1360
Result.Y:=Max(Result.Y,CalculateMinimumTop(ChildControl)
1361
+ChildControl.Height);
1366
procedure ApplyBounds(ParentClientWidth, ParentClientHeight: Integer);
1373
SideControl: TControl;
1375
for i:=0 to ResizedControl.Parent.ControlCount-1 do begin
1376
Sibling:=ResizedControl.Parent.Controls[i];
1377
Info:=GetInfo(Sibling);
1378
NewRect.Left:=Info^.MinLeft;
1379
NewRect.Right:=NewRect.Left+Sibling.Width;
1380
SideControl:=Sibling.AnchorSide[akRight].Control;
1381
if (akRight in Sibling.Anchors) and (SideControl<>nil) then begin
1382
if SideControl=ResizedControl.Parent then
1383
NewRect.Right:=ParentClientWidth
1384
else if SideControl.Parent=ResizedControl.Parent then
1385
NewRect.Right:=CalculateMinimumLeft(SideControl);
1387
NewRect.Top:=Info^.MinTop;
1388
NewRect.Bottom:=NewRect.Top+Sibling.Height;
1389
SideControl:=Sibling.AnchorSide[akBottom].Control;
1390
if (akBottom in Sibling.Anchors) and (SideControl<>nil) then begin
1391
if SideControl=ResizedControl.Parent then
1392
NewRect.Bottom:=ParentClientHeight
1393
else if SideControl.Parent=ResizedControl.Parent then
1394
NewRect.Bottom:=CalculateMinimumTop(SideControl);
1396
OldRect:=Sibling.BoundsRect;
1397
if not CompareRect(@OldRect,@NewRect) then begin
1398
DebugLn(['ApplyBounds Sibling=',DbgSName(Sibling),' NewRect=',dbgs(NewRect)]);
1399
Sibling.BoundsRect:=NewRect;
1406
CurParent: TWinControl;
1408
DiffHeight: Integer;
1409
AlignDisabledControl: TWinControl;
1411
DebugLn(['TCustomLazControlDocker.FixControlBounds ',DbgSName(ResizedControl)]);
1412
CurParent:=ResizedControl.Parent;
1413
if CurParent=nil then begin
1414
DebugLn(['TCustomLazControlDocker.FixControlBounds WARNING: no parent']);
1417
CurParent.DisableAlign;
1420
// calculate minimum left, top, right, bottom of all siblings
1421
ParentSize:=CalculateClientSize(CurParent);
1422
DiffWidth:=ParentSize.X-CurParent.ClientWidth;
1423
DiffHeight:=ParentSize.Y-CurParent.ClientHeight;
1424
if (DiffWidth<>0) or (DiffHeight<>0) then begin
1425
// parent needs resizing
1426
DebugLn(['TCustomLazControlDocker.FixControlBounds Parent=',DbgSName(ResizedControl.Parent),' needs resizing to ',dbgs(ParentSize)]);
1427
AlignDisabledControl:=CurParent.Parent;
1428
if AlignDisabledControl<>nil then
1429
AlignDisabledControl.DisableAlign;
1431
CurParent.ClientWidth:=ParentSize.X;
1432
CurParent.ClientHeight:=ParentSize.Y;
1433
if CurParent.Parent<>nil then begin
1434
// parent is a child
1435
// => resize parent and fix the position recursively
1436
FixControlBounds(Layout,CurParent);
1438
// parent is a free form
1439
// => decide where to move the form on the screen using the Layout
1442
DebugLn(['TCustomLazControlDocker.FixControlBounds TODO move parent ',DbgSName(CurParent)]);
1445
if AlignDisabledControl<>nil then
1446
AlignDisabledControl.EnableAlign;
1449
ApplyBounds(ParentSize.X,ParentSize.Y);
1452
CurParent.EnableAlign;
1456
procedure TCustomLazControlDocker.ShrinkNeighbourhood(
1457
Layout: TLazDockConfigNode; AControl: TControl; Sides: TAnchors);
1458
{ shrink neighbour controls according to Layout
1459
A neighbour is the first control left or top of AControl, that can be shrinked
1460
and is only anchored to AControl.
1462
procedure ShrinkControl(CurControl: TControl; Side: TAnchorKind); forward;
1464
procedure ShrinkNeighboursOnSide(CurControl: TControl; Side: TAnchorKind);
1465
// shrink all controls, that are anchored on Side of CurControl
1467
Neighbour: TControl;
1470
DebugLn(['ShrinkNeighboursOnSide START ',DbgSName(CurControl),' ',AnchorNames[Side]]);
1471
if Side in CurControl.Anchors then begin
1472
Neighbour:=CurControl.AnchorSide[Side].Control;
1473
DebugLn(['ShrinkNeighboursOnSide Neighbour=',DbgSName(Neighbour)]);
1474
ShrinkControl(Neighbour,Side);
1476
for i:=0 to CurControl.Parent.ControlCount-1 do begin
1477
Neighbour:=CurControl.Parent.Controls[i];
1478
if (OppositeAnchor[Side] in Neighbour.Anchors)
1479
and (Neighbour.AnchorSide[OppositeAnchor[Side]].Control=CurControl)
1481
ShrinkControl(Neighbour,Side);
1485
procedure ShrinkControl(CurControl: TControl; Side: TAnchorKind);
1488
Node: TLazDockConfigNode;
1491
DebugLn(['ShrinkControl START ',DbgSName(CurControl),' Side=',AnchorNames[Side]]);
1492
if (CurControl=nil) or (CurControl=AControl)
1493
or (CurControl.Parent<>AControl.Parent) then
1495
if CurControl is TCustomSplitter then begin
1496
// a splitter can not be shrinked
1497
// => try to shrink the controls on the other side of the splitter
1498
ShrinkNeighboursOnSide(CurControl,Side);
1501
// shrink according to Layout
1502
NodeName:=Manager.GetControlConfigName(CurControl);
1503
if NodeName='' then exit;
1504
Node:=Layout.FindByName(NodeName,true);
1505
if Node=nil then exit;
1506
CurBounds:=Node.Bounds;
1507
DebugLn(['ShrinkControl ',DbgSName(CurControl),' Side=',AnchorNames[Side],' LayoutBounds=',dbgs(CurBounds)]);
1508
if Side in [akLeft,akRight] then
1509
CurControl.Width:=Min(CurControl.Width,CurBounds.Right-CurBounds.Left)
1511
CurControl.Height:=Min(CurControl.Height,CurBounds.Bottom-CurBounds.Top);
1517
DebugLn(['TCustomLazControlDocker.ShrinkNeighbourhood AControl=',DbgSName(AControl)]);
1518
AControl.Parent.DisableAlign;
1520
for a:=Low(TAnchorKind) to High(TAnchorKind) do
1522
ShrinkNeighboursOnSide(AControl,a);
1524
AControl.Parent.EnableAlign;
1528
function TCustomLazControlDocker.FindPageNeighbours(Layout: TLazDockConfigNode;
1529
StartControl: TControl; out AnchorControls: TAnchorControlsRect): TFPList;
1530
{ Creates a list of TControl, containing StartControl and neighbours,
1531
which are on the same page according to Layout and are a rectangular area.
1532
AnchorControls are the four boundaries of the rectangular area and the list
1533
contains all controls within these boundaries (and with the same Parent as
1537
TPageCompatibility = (pcUnknown, pcNotOnSamePage, pcSamePage);
1539
ControlList: TFPList;
1540
PageNode: TLazDockConfigNode;
1541
Parent: TWinControl;
1542
Compatibility: array of TPageCompatibility;
1544
procedure InitCompatibility;
1549
Node: TLazDockConfigNode;
1551
// check all siblings if the Layout knows them
1552
SetLength(Compatibility,Parent.ControlCount);
1553
for i:=0 to Parent.ControlCount-1 do begin
1554
Compatibility[i]:=pcUnknown;
1555
AControl:=Parent.Controls[i];
1556
if AControl is TLazDockSplitter then continue;
1557
NodeName:=Manager.GetControlConfigName(AControl);
1558
if NodeName='' then continue;
1559
Node:=Layout.FindByName(NodeName,true);
1560
if Node<>nil then begin
1561
if Node.Parent=PageNode then
1562
Compatibility[i]:=pcSamePage
1564
Compatibility[i]:=pcNotOnSamePage;
1569
function CheckSolution(Candidates: TFPList): boolean;
1571
ARect: TAnchorControlsRect;
1577
// find the minimum rectangle around the current selection
1578
if not GetEnclosingControlRect(Candidates,ARect) then exit;
1579
// get the controls in the rectangle
1580
AllList:=GetEnclosedControls(ARect);
1582
for i:=0 to AllList.Count-1 do begin
1583
Index:=Parent.GetControlIndex(TControl(AllList[i]));
1584
if Index<0 then exit(false);
1585
if Compatibility[Index]=pcNotOnSamePage then exit(false);
1587
// AllList fits => use it as solution
1588
ControlList.Assign(AllList);
1589
AnchorControls:=ARect;
1596
function TryLayoutSolution: boolean;
1597
// check if a 1:1 of the layout is possible
1602
for i:=0 to Parent.ControlCount-1 do begin
1603
if Compatibility[i]=pcSamePage then
1604
ControlList.Add(Parent.Controls[i]);
1606
Result:=CheckSolution(ControlList);
1609
procedure TrySubsets;
1610
// add controls to the selection
1615
List:=TFPList.Create;
1616
List.Add(StartControl);
1617
CheckSolution(List);
1620
// add on more control to the selection
1621
if Compatibility[i]=pcSamePage then begin
1622
List.Add(Parent.Controls[i]);
1623
if not CheckSolution(List) then
1624
List.Remove(Parent.Controls[i]);
1632
StartNodeName: String;
1633
StartNode: TLazDockConfigNode;
1637
ControlList:=TFPList.Create;
1638
ControlList.Add(StartControl);
1639
for a:=Low(TAnchorKind) to High(TAnchorKind) do
1640
AnchorControls[a]:=StartControl.AnchorSide[a].Control;
1643
StartNodeName:=Manager.GetControlConfigName(StartControl);
1644
if StartNodeName='' then exit;
1645
StartNode:=Layout.FindByName(StartNodeName,true);
1646
if StartNode=nil then exit;
1647
PageNode:=StartNode.Parent;
1648
if PageNode=nil then exit;
1651
Parent:=StartControl.Parent;
1654
// try some possibilities
1655
if (not TryLayoutSolution) then
1658
Result:=ControlList;
1661
procedure TCustomLazControlDocker.Notification(AComponent: TComponent;
1662
Operation: TOperation);
1666
inherited Notification(AComponent, Operation);
1667
if Operation=opRemove then
1670
if AComponent=FControl then
1672
if FControl.PopupMenu <> nil then
1673
Item := FindLCDMenuItem(FControl.PopupMenu);
1674
FControl.RemoveAllHandlersOfObject(Self);
1678
if (AComponent is TMenu) then
1679
Item := FindLCDMenuItem(TMenu(AComponent));
1681
if (AComponent is TMenuItem) then
1682
Item := FindLCDMenuItem(TMenu(AComponent));
1686
FMenus.Remove(Item);
1688
if Item.Item <> AComponent then
1689
FreeAndNil(Item.Item);
1695
function TCustomLazControlDocker.FindLCDMenuItem(AMenu: TMenu): TLCDMenuItem;
1699
if (FMenus<>nil) and (AMenu<>nil) then
1700
for i:=0 to FMenus.Count-1 do begin
1701
Result:=TLCDMenuItem(FMenus[i]);
1702
if Result.Menu=AMenu then exit;
1707
function TCustomLazControlDocker.FindLCDMenuItem(AMenuItem: TMenuItem
1712
if (FMenus<>nil) and (AMenuItem<>nil) then
1713
for i:=0 to FMenus.Count-1 do begin
1714
Result:=TLCDMenuItem(FMenus[i]);
1715
if Result.Item=AMenuItem then exit;
1720
function TCustomLazControlDocker.GetControlName(AControl: TControl): string;
1724
Result:=Manager.GetControlConfigName(AControl);
1725
if Result='' then begin
1726
if AControl=Control.Parent then
1727
Result:=NonDockConfigNamePrefixes[ndcnParent]
1728
else if AControl.Name<>'' then
1729
Result:=NonDockConfigNamePrefixes[ndcnControlName]+AControl.Name
1730
else if AControl.Parent<>nil then begin
1731
i:=AControl.Parent.ControlCount-1;
1732
while (i>=0) and (AControl.Parent.Controls[i]<>AControl) do dec(i);
1733
Result:=NonDockConfigNamePrefixes[ndcnChildIndex]+IntToStr(i)+' '
1734
+AControl.ClassName;
1739
procedure TCustomLazControlDocker.AddPopupMenu(Menu: TPopupMenu);
1741
LCDItem: TLCDMenuItem;
1743
if FindLCDMenuItem(Menu)<>nil then exit;
1744
if FMenus=nil then FMenus:=TFPList.Create;
1745
LCDItem:=TLCDMenuItem.Create;
1747
FMenus.Add(LCDItem);
1748
Menu.FreeNotification(Self);
1749
LCDItem.Item:=TMenuItem.Create(Self);
1750
LCDItem.Item.Caption:=rsDocking;
1751
LCDItem.Item.OnClick:=@PopupMenuItemClick;
1752
Menu.Items.Add(LCDItem.Item);
1755
procedure TCustomLazControlDocker.RemovePopupMenu(Menu: TPopupMenu);
1759
Item:=FindLCDMenuItem(Menu);
1760
if Item=nil then exit;
1761
FMenus.Remove(Item);
1762
FreeAndNil(Item.Item);
1767
function TCustomLazControlDocker.GetLayoutFromControl: TLazDockConfigNode;
1769
procedure CopyChildsLayout(ParentNode: TLazDockConfigNode;
1770
ParentNodeControl: TWinControl);
1771
// saves for each child node the names of the anchor side controls
1774
ChildNode: TLazDockConfigNode;
1775
ChildControl: TControl;
1777
ChildNames: TStringHashList;// name to control mapping
1779
CurAnchorControl: TControl;
1780
CurAnchorCtrlName: String;
1781
CurAnchorNode: TLazDockConfigNode;
1783
ChildNames:=TStringHashList.Create(false);
1785
// build mapping of name to control
1786
ChildNames.Data[ParentNode.Name]:=ParentNodeControl;
1787
for i:=0 to ParentNodeControl.ControlCount-1 do begin
1788
ChildControl:=ParentNodeControl.Controls[i];
1789
ChildName:=GetControlName(ChildControl);
1790
if ChildName<>'' then
1791
ChildNames.Data[ChildName]:=ChildControl;
1793
// build mapping control to node
1796
for i:=0 to ParentNode.ChildCount-1 do begin
1797
ChildNode:=ParentNode[i];
1798
ChildControl:=TControl(ChildNames.Data[ChildNode.Name]);
1799
if ChildControl=nil then continue;
1800
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
1801
CurAnchorControl:=ChildControl.AnchorSide[a].Control;
1802
if CurAnchorControl=nil then continue;
1803
if CurAnchorControl=ParentNodeControl then
1804
CurAnchorNode:=ParentNode
1806
CurAnchorCtrlName:=GetControlName(CurAnchorControl);
1807
CurAnchorNode:=ParentNode.FindByName(CurAnchorCtrlName);
1808
if CurAnchorNode=nil then
1809
RaiseGDBException('inconsistency');
1811
//DebugLn('CopyChildsLayout ',DbgSName(CurAnchorControl),' CurAnchorCtrlName="',CurAnchorCtrlName,'"');
1812
ChildNode.Sides[a]:=CurAnchorNode.Name;
1820
function AddNode(ParentNode: TLazDockConfigNode;
1821
AControl: TControl): TLazDockConfigNode;
1824
CurChildControl: TControl;
1825
NeedChildNodes: boolean;
1827
Result:=TLazDockConfigNode.Create(ParentNode,GetControlName(AControl));
1830
if AControl is TLazDockSplitter then begin
1831
if TLazDockSplitter(AControl).ResizeAnchor in [akLeft,akRight] then
1832
Result.FTheType:=ldcntSplitterLeftRight
1834
Result.FTheType:=ldcntSplitterUpDown;
1835
end else if AControl is TLazDockForm then
1836
Result.FTheType:=ldcntForm
1837
else if AControl is TLazDockPages then
1838
Result.FTheType:=ldcntPages
1839
else if AControl is TLazDockPage then
1840
Result.FTheType:=ldcntPage
1842
Result.FTheType:=ldcntControl;
1845
Result.FBounds:=AControl.BoundsRect;
1846
if AControl is TWinControl then
1847
Result.FClientBounds:=TWinControl(AControl).GetChildsRect(false)
1849
Result.FClientBounds:=Rect(0,0,Result.FBounds.Right-Result.FBounds.Left,
1850
Result.FBounds.Bottom-Result.FBounds.Top);
1853
if AControl is TCustomForm then
1854
Result.WindowState:=TCustomForm(AControl).WindowState;
1857
if (AControl is TWinControl) then begin
1858
// check if children need nodes
1859
NeedChildNodes:=(AControl is TLazDockPages)
1860
or (AControl is TLazDockPage);
1861
if not NeedChildNodes then begin
1862
for i:=0 to TWinControl(AControl).ControlCount-1 do begin
1863
CurChildControl:=TWinControl(AControl).Controls[i];
1864
if Manager.FindDockerByControl(CurChildControl,nil)<>nil then begin
1865
NeedChildNodes:=true;
1871
if NeedChildNodes then begin
1872
for i:=0 to TWinControl(AControl).ControlCount-1 do begin
1873
CurChildControl:=TWinControl(AControl).Controls[i];
1874
AddNode(Result,CurChildControl);
1876
for i:=0 to Result.ChildCount-1 do begin
1879
CopyChildsLayout(Result,TWinControl(AControl));
1884
RootControl: TControl;
1886
if (Control=nil) or (Manager=nil) then exit(nil);
1888
RootControl:=Control;
1889
while RootControl.Parent<>nil do
1890
RootControl:=RootControl.Parent;
1891
Result:=AddNode(nil,RootControl);
1894
procedure TCustomLazControlDocker.SaveLayout;
1896
Layout: TLazDockConfigNode;
1898
if Manager=nil then exit;
1899
Layout:=GetLayoutFromControl;
1900
if (Layout=nil) then exit;
1901
Manager.AddOrReplaceConfig(DockerName,Layout);
1904
procedure TCustomLazControlDocker.RestoreLayout;
1905
{ Goals of this algorithm:
1906
- If a form is hidden and immediately shown again, the layout should be
1908
That's why a TCustomLazControlDocker stores the complete layout on every
1909
hide. And restores it on every show.
1910
- If an application is closed and all dock forms are closed (in any order)
1911
the layout should be restored on startup, when the forms
1912
are created (in any order).
1913
This is done by saving the layout before all forms are closed.
1916
Example 1: Docking to a side.
1930
Then put A into a new TLazDockForm, add a splitter and Self.
1933
Example 2: Docking in between
1943
+------------------+
1944
|+---+|+----+|+---+|
1945
|| A |||Self||| C ||
1946
|+---+|+----+|+---+|
1947
+------------------+
1949
Then enlarge the parent of A and C, add a splitter and Self.
1954
+-------------------------+
1955
|+-----------------------+|
1957
|+-----------------------+|
1958
|=========================|
1959
|+---+#+-----------+#+---+|
1965
|| |#+-----------+#+---+|
1966
|| F |#===================|
1967
|| |#+-----------------+|
1969
|+---+#+-----------------+|
1970
+-------------------------+
1974
There is no other form yet, so just show it at the old position.
1975
+-----------------------+
1977
+-----------------------+
1981
B is the bottom sibling of A. Put A into a new TLazDockForm, add a splitter,
1982
enlarge B horizontally.
1984
+-------------------------+
1985
|+-----------------------+|
1987
|+-----------------------+|
1988
|=========================|
1989
|+-----------------------+|
1995
|+-----------------------+|
1996
+-------------------------+
2000
C is the bottom sibling of B. Enlarge the parent vertically, add a splitter
2001
and enlarge C horizontally.
2003
+-------------------------+
2004
|+-----------------------+|
2006
|+-----------------------+|
2007
|=========================|
2008
|+-----------------------+|
2014
|+-----------------------+|
2015
|=========================|
2016
|+-----------------------+|
2018
|+-----------------------+|
2019
+-------------------------+
2023
D is below of A, and left of B and C. Shrink B and C, add a splitter.
2025
+-------------------------+
2026
|+-----------------------+|
2028
|+-----------------------+|
2029
|=========================|
2030
|+---+#+-----------------+|
2036
|| |#+-----------------+|
2037
|| |#===================|
2038
|| |#+-----------------+|
2040
|+---+#+-----------------+|
2041
+-------------------------+
2045
Shrink B, add a splitter.
2047
+-------------------------+
2048
|+-----------------------+|
2050
|+-----------------------+|
2051
|=========================|
2052
|+---+#+-----------+#+---+|
2058
|| |#+-----------+#+---+|
2059
|| |#===================|
2060
|| |#+-----------------+|
2062
|+---+#+-----------------+|
2063
+-------------------------+
2067
Shrink D and add a splitter.
2069
+-------------------------+
2070
|+-----------------------+|
2072
|+-----------------------+|
2073
|=========================|
2074
|+---+#+-----------+#+---+|
2080
|| |#+-----------+#+---+|
2081
|| F |#===================|
2082
|| |#+-----------------+|
2084
|+---+#+-----------------+|
2085
+-------------------------+
2088
Layout: TLazDockConfigNode;
2089
SelfNode: TLazDockConfigNode;
2091
function FindNode(const ANodeName: string): TLazDockConfigNode;
2093
if ANodeName='' then
2096
Result:=Layout.FindByName(ANodeName,true,true);
2099
function FindControl(const ADockerName: string): TControl;
2101
Result:=Manager.FindControlByDockerName(ADockerName);
2104
function DockWithSpiralSplitter: boolean;
2110
function SplitterDocking: boolean;
2113
SplitterCount: Integer;
2114
SideNode: TLazDockConfigNode;
2118
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
2119
SideNode:=FindNode(SelfNode.Sides[a]);
2121
and (SideNode.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown])
2123
if SideNode.IsTheOnlyNeighbour(SelfNode,a)
2124
and CreateFormAndDockWithSplitter(Layout,a) then
2127
if (SplitterCount=4) and DockWithSpiralSplitter then
2133
function PageDocking: boolean;
2136
if (SelfNode.TheType<>ldcntPage) then exit;
2137
if (SelfNode.Parent.ChildCount<>1) then exit;
2138
Result:=DockAsPage(Layout);
2144
{$IFDEF VerboseAnchorDocking}
2145
DebugLn(['TCustomLazControlDocker.RestoreLayout A ',DockerName,' Control=',DbgSName(Control)]);
2147
if (Manager=nil) or (Control=nil) then exit;
2150
Layout:=Manager.CreateLayout(DockerName,Control,false);
2151
if (Layout=nil) then exit;
2152
SelfNode:=Layout.FindByName(DockerName,true);
2153
DebugLn(['TCustomLazControlDocker.RestoreLayout ',SelfNode<>nil,' DockerName=',DockerName]);
2154
if (SelfNode=nil) or (SelfNode.TheType<>ldcntControl) then exit;
2156
if SelfNode.Parent<>nil then begin
2157
// this control was docked
2158
if SplitterDocking then exit;
2159
if PageDocking then exit;
2162
// default: do not dock, just move
2163
DebugLn(['TCustomLazControlDocker.RestoreLayout ',DockerName,' not docking, just moving ...']);
2164
NewBounds:=SelfNode.GetScreenBounds;
2165
Control.SetBoundsKeepBase(NewBounds.Left,NewBounds.Top,
2166
NewBounds.Right-NewBounds.Left,
2167
NewBounds.Bottom-NewBounds.Top);
2168
DebugLn(['TCustomLazControlDocker.RestoreLayout ',WindowStateToStr(Layout.WindowState),' Layout.Name=',Layout.Name]);
2169
if (Control is TCustomForm) and (Control.Parent=nil) then
2170
TCustomForm(Control).WindowState:=Layout.WindowState;
2172
DebugLn(['TCustomLazControlDocker.RestoreLayout END Control=',DbgSName(Control),' Control.BoundsRect=',dbgs(Control.BoundsRect)]);
2177
procedure TCustomLazControlDocker.DisableLayout;
2182
procedure TCustomLazControlDocker.EnableLayout;
2187
function TCustomLazControlDocker.ControlIsDocked: boolean;
2189
Result:=(Control<>nil)
2190
and (Control.Parent<>nil)
2191
and ((Control.Parent is TLazDockForm) or (Control.Parent is TLazDockPage));
2194
constructor TCustomLazControlDocker.Create(TheOwner: TComponent);
2196
inherited Create(TheOwner);
2197
if (not (csLoading in ComponentState))
2198
and (TheOwner is TControl) then
2200
Control:=TControl(TheOwner);
2201
ExtendPopupMenu:=true;
2204
destructor TCustomLazControlDocker.Destroy;
2213
if FMenus <> nil then begin
2216
for i := OldMenus.Count - 1 downto 0 do
2218
Item:=TLCDMenuItem(OldMenus[i]);
2219
FreeAndNil(Item.Item);
2226
procedure TCustomLazControlDocker.PopupMenuItemClick(Sender: TObject);
2231
procedure TCustomLazControlDocker.SetControl(const AValue: TControl);
2233
WinControl: TWinControl;
2235
if FControl=AValue then exit;
2236
if FControl<>nil then begin
2237
FControl.RemoveAllHandlersOfObject(Self);
2238
FControl.RemoveFreeNotification(Self);
2239
if (Manager<>nil) and (FControl is TWinControl) then
2241
WinControl:=TWinControl(FControl);
2242
WinControl.UseDockManager:=false;
2243
WinControl.DockManager:=nil;
2247
if Control<>nil then begin
2248
Control.AddHandlerOnVisibleChanging(@ControlVisibleChanging);
2249
Control.AddHandlerOnVisibleChanged(@ControlVisibleChanged);
2250
Control.FreeNotification(Self);
2251
if (Manager<>nil) and (FControl is TWinControl) then
2253
WinControl:=TWinControl(FControl);
2254
WinControl.DockManager:=Manager.Manager;
2255
WinControl.UseDockManager:=true;
2258
if (DockerName='') and (FControl<>nil) then
2259
DockerName:=FControl.Name;
2263
procedure TCustomLazControlDocker.SetDockerName(const AValue: string);
2265
NewDockerName: String;
2267
if FDockerName=AValue then exit;
2268
NewDockerName:=AValue;
2269
if Manager<>nil then
2270
NewDockerName:=Manager.CreateUniqueName(NewDockerName,Self);
2271
FDockerName:=NewDockerName;
2274
procedure TCustomLazControlDocker.SetExtendPopupMenu(const AValue: boolean);
2276
if FExtendPopupMenu=AValue then exit;
2277
FExtendPopupMenu:=AValue;
2281
procedure TCustomLazControlDocker.SetLocalizedName(const AValue: string);
2283
if FLocalizedName=AValue then exit;
2284
FLocalizedName:=AValue;
2287
{ TCustomLazDockingManager }
2289
procedure TCustomLazDockingManager.Remove(Docker: TCustomLazControlDocker);
2291
WinControl: TWinControl;
2293
if Docker.Control is TWinControl then
2295
WinControl:=TWinControl(Docker.Control);
2296
WinControl.UseDockManager:=false;
2297
WinControl.DockManager:=nil;
2299
FDockers.Remove(Docker);
2302
function TCustomLazDockingManager.Add(Docker: TCustomLazControlDocker): Integer;
2304
WinControl: TWinControl;
2306
Docker.DockerName:=CreateUniqueName(Docker.DockerName,nil);
2307
Result:=FDockers.Add(Docker);
2308
if Docker.Control is TWinControl then
2310
WinControl:=TWinControl(Docker.Control);
2311
WinControl.DockManager:=Manager;
2312
WinControl.UseDockManager:=true;
2316
function TCustomLazDockingManager.GetDockers(Index: Integer
2317
): TCustomLazControlDocker;
2319
Result:=TCustomLazControlDocker(FDockers[Index]);
2322
function TCustomLazDockingManager.GetDockerCount: Integer;
2324
Result:=FDockers.Count;
2327
function TCustomLazDockingManager.GetConfigCount: Integer;
2329
if FConfigs<>nil then
2330
Result:=FConfigs.Count
2335
function TCustomLazDockingManager.GetConfigs(Index: Integer
2336
): TLazDockerConfig;
2338
Result:=TLazDockerConfig(FConfigs[Index]);
2341
constructor TCustomLazDockingManager.Create(TheOwner: TComponent);
2343
inherited Create(TheOwner);
2344
FDockers:=TFPList.Create;
2345
FManager:=TAnchoredDockManager.Create(nil);
2346
FManager.FConfigs:=Self;
2349
destructor TCustomLazDockingManager.Destroy;
2353
for i:=FDockers.Count-1 downto 0 do
2354
Dockers[i].Manager:=nil;
2355
FreeAndNil(FDockers);
2356
FreeAndNil(FManager);
2358
FreeAndNil(FConfigs);
2362
function TCustomLazDockingManager.FindDockerByName(const ADockerName: string;
2363
Ignore: TCustomLazControlDocker): TCustomLazControlDocker;
2368
while (i>=0) do begin
2370
if (CompareText(Result.DockerName,ADockerName)=0) and (Ignore<>Result) then
2377
function TCustomLazDockingManager.FindControlByDockerName(
2378
const ADockerName: string; Ignore: TCustomLazControlDocker): TControl;
2380
Docker: TCustomLazControlDocker;
2382
Docker:=FindDockerByName(ADockerName);
2386
Result:=Docker.Control;
2389
function TCustomLazDockingManager.FindDockerByControl(AControl: TControl;
2390
Ignore: TCustomLazControlDocker): TCustomLazControlDocker;
2395
while (i>=0) do begin
2397
if (Result.Control=AControl) and (Ignore<>Result) then
2404
function TCustomLazDockingManager.CreateUniqueName(const AName: string;
2405
Ignore: TCustomLazControlDocker): string;
2408
if FindDockerByName(Result,Ignore)=nil then exit;
2409
Result:=CreateFirstIdentifier(Result);
2410
while FindDockerByName(Result,Ignore)<>nil do
2411
Result:=CreateNextIdentifier(Result);
2414
function TCustomLazDockingManager.GetControlConfigName(AControl: TControl
2417
Docker: TCustomLazControlDocker;
2419
Docker:=FindDockerByControl(AControl,nil);
2421
Result:=Docker.DockerName
2426
procedure TCustomLazDockingManager.DisableLayout(Control: TControl);
2428
Docker: TCustomLazControlDocker;
2430
Docker:=FindDockerByControl(Control);
2432
Docker.DisableLayout;
2435
procedure TCustomLazDockingManager.EnableLayout(Control: TControl);
2437
Docker: TCustomLazControlDocker;
2439
Docker:=FindDockerByControl(Control);
2441
Docker.EnableLayout;
2444
procedure TCustomLazDockingManager.SaveToConfig(Config: TConfigStorage;
2445
const Path: string);
2448
ADocker: TCustomLazControlDocker;
2449
CurDockConfig: TLazDockerConfig;
2453
for i:=0 to DockerCount-1 do begin
2454
ADocker:=Dockers[i];
2455
if ((ADocker.Control<>nil) and ADocker.Control.Visible) then begin
2461
Config.SetDeleteValue(Path+'Configs/Count',ConfigCount,0);
2462
for i:=0 to ConfigCount-1 do begin
2463
SubPath:=Path+'Config'+IntToStr(i)+'/';
2464
CurDockConfig:=Configs[i];
2465
Config.SetDeleteValue(SubPath+'DockerName/Value',CurDockConfig.DockerName,'');
2466
CurDockConfig.Root.SaveToConfig(Config,SubPath);
2470
procedure TCustomLazDockingManager.LoadFromConfig(Config: TConfigStorage;
2471
const Path: string);
2474
NewConfigCount: LongInt;
2476
NewRoot: TLazDockConfigNode;
2477
NewDockerName: String;
2478
NewRootName: String;
2480
// merge the configs
2481
NewConfigCount:=Config.GetValue(Path+'Configs/Count',0);
2482
//DebugLn(['TCustomLazDockingManager.LoadFromConfig NewConfigCount=',NewConfigCount]);
2483
for i:=0 to NewConfigCount-1 do begin
2484
SubPath:=Path+'Config'+IntToStr(i)+'/';
2485
NewDockerName:=Config.GetValue(SubPath+'DockerName/Value','');
2486
if NewDockerName='' then continue;
2487
NewRootName:=Config.GetValue(SubPath+'Name/Value','');
2488
if NewRootName='' then continue;
2489
//DebugLn(['TCustomLazDockingManager.LoadFromConfig NewDockerName=',NewDockerName,' NewRootName=',NewRootName]);
2490
NewRoot:=TLazDockConfigNode.Create(nil,NewRootName);
2491
NewRoot.LoadFromConfig(Config,SubPath);
2492
AddOrReplaceConfig(NewDockerName,NewRoot);
2493
//NewRoot.WriteDebugReport;
2497
procedure TCustomLazDockingManager.AddOrReplaceConfig(
2498
const DockerName: string; Config: TLazDockConfigNode);
2501
CurConfig: TLazDockerConfig;
2503
if FConfigs=nil then
2504
FConfigs:=TFPList.Create;
2505
for i:=FConfigs.Count-1 downto 0 do begin
2506
CurConfig:=Configs[i];
2507
if CompareText(CurConfig.DockerName,DockerName)=0 then begin
2508
CurConfig.FRoot.Free;
2509
CurConfig.FRoot:=Config;
2513
FConfigs.Add(TLazDockerConfig.Create(DockerName,Config));
2516
procedure TCustomLazDockingManager.WriteDebugReport;
2519
ADocker: TCustomLazControlDocker;
2521
DebugLn('TCustomLazDockingManager.WriteDebugReport DockerCount=',dbgs(DockerCount));
2522
for i:=0 to DockerCount-1 do begin
2523
ADocker:=Dockers[i];
2524
DebugLn(' ',dbgs(i),' Name="',ADocker.Name,'" DockerName="',ADocker.DockerName,'"');
2528
procedure TCustomLazDockingManager.ClearConfigs;
2532
if FConfigs=nil then exit;
2533
for i:=0 to FConfigs.Count-1 do TObject(FConfigs[i]).Free;
2537
function TCustomLazDockingManager.GetConfigWithDockerName(
2538
const DockerName: string): TLazDockerConfig;
2543
while (i>=0) do begin
2545
if CompareText(Result.DockerName,DockerName)=0 then exit;
2551
function TCustomLazDockingManager.CreateLayout(const DockerName: string;
2552
VisibleControl: TControl; ExceptionOnError: boolean): TLazDockConfigNode;
2553
// create a usable config
2554
// This means: search a config, create a copy
2555
// and remove all nodes without visible controls.
2556
{$DEFINE VerboseAnchorDockCreateLayout}
2558
Root: TLazDockConfigNode;
2559
CurDockControl: TControl;
2561
function ControlIsVisible(AControl: TControl): boolean;
2564
if (AControl=nil) then exit;
2565
if (not AControl.IsVisible) and (AControl<>VisibleControl) then exit;
2566
if (CurDockControl<>nil) and (CurDockControl<>AControl.GetTopParent) then
2571
function FindNode(const AName: string): TLazDockConfigNode;
2576
Result:=Root.FindByName(AName,true,true);
2579
procedure DeleteNode(var DeletingNode: TLazDockConfigNode);
2581
function DeleteOwnSideSplitter(Side: TAnchorKind;
2582
var SplitterNode: TLazDockConfigNode): boolean;
2583
{ check if DeletingNode has a splitter to Side, and this node is the only
2584
node anchored to the splitter at this side.
2585
If yes, it removes the splitter and the DeletingNode and reconnects the
2586
nodes using the splitter with the opposite side
2588
---------+ --------+
2596
---------+ --------+
2600
Sibling: TLazDockConfigNode;
2601
OppositeSide: TAnchorKind;
2604
// check if this is the only node using this Side of the splitter
2605
if not SplitterNode.IsTheOnlyNeighbour(DeletingNode,Side) then
2608
// All nodes, that uses the splitter from the other side will now be
2609
// anchored to the other side of DeletingNode
2610
OppositeSide:=OppositeAnchor[Side];
2611
for i:=0 to DeletingNode.Parent.ChildCount-1 do begin
2612
Sibling:=DeletingNode.Parent.Children[i];
2613
if CompareText(Sibling.Sides[OppositeSide],SplitterNode.Name)=0 then
2614
Sibling.Sides[OppositeSide]:=DeletingNode.Sides[OppositeSide];
2618
FreeAndNil(SplitterNode);
2623
function UnbindSpiralNode: boolean;
2624
{ DeletingNode has 4 splitters like a spiral.
2625
In this case merge the two vertical splitters.
2636
LeftSplitter: TLazDockConfigNode;
2637
RightSplitter: TLazDockConfigNode;
2639
Sibling: TLazDockConfigNode;
2641
LeftSplitter:=FindNode(DeletingNode.Sides[akLeft]);
2642
RightSplitter:=FindNode(DeletingNode.Sides[akRight]);
2643
// remove LeftSplitter
2645
// 1. enlarge RightSplitter
2646
if CompareText(RightSplitter.Sides[akTop],DeletingNode.Sides[akTop])=0 then
2647
RightSplitter.Sides[akTop]:=LeftSplitter.Sides[akTop];
2648
if CompareText(RightSplitter.Sides[akBottom],DeletingNode.Sides[akBottom])=0 then
2649
RightSplitter.Sides[akBottom]:=LeftSplitter.Sides[akBottom];
2651
// 2. anchor all siblings using LeftSplitter to now use RightSplitter
2652
for i:=0 to DeletingNode.Parent.ChildCount-1 do begin
2653
Sibling:=DeletingNode.Parent.Children[i];
2654
if Sibling=DeletingNode then continue;
2655
if CompareText(Sibling.Sides[akLeft],LeftSplitter.Name)=0 then
2656
Sibling.Sides[akLeft]:=RightSplitter.Name;
2657
if CompareText(Sibling.Sides[akRight],LeftSplitter.Name)=0 then
2658
Sibling.Sides[akRight]:=RightSplitter.Name;
2661
// 3. delete LeftSplitter
2662
FreeAndNil(LeftSplitter);
2669
SiblingNode: TLazDockConfigNode;
2670
SplitterCount: Integer;// number of shared splitters
2672
DebugLn(['DeleteNode ',DeletingNode.Name]);
2674
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
2675
SiblingNode:=FindNode(DeletingNode.Sides[a]);
2676
if (SiblingNode<>nil)
2677
and (SiblingNode.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown])
2679
// there is a splitter
2680
if DeleteOwnSideSplitter(a,SiblingNode) then begin
2684
inc(SplitterCount);// not own => shared
2685
if SplitterCount=4 then begin
2686
// this is a spiral splitter node -> merge two splitters
2693
FreeAndNil(DeletingNode);
2696
procedure SimplifyOnePageNode(var PagesNode: TLazDockConfigNode);
2697
{ PagesNode has only one page left.
2698
Remove Page and Pages node and move the content to the parent
2701
ParentNode: TLazDockConfigNode;
2702
PageNode: TLazDockConfigNode;
2704
Child: TLazDockConfigNode;
2711
DebugLn(['SimplifyOnePageNode ',dbgs(PagesNode)]);
2712
ParentNode:=PagesNode.Parent;
2713
if ParentNode=nil then RaiseGDBException('');
2714
if (PagesNode.TheType<>ldcntPages) then RaiseGDBException('');
2715
if PagesNode.ChildCount<>1 then RaiseGDBException('');
2716
PageNode:=PagesNode.Children[0];
2717
PagesBounds:=PagesNode.Bounds;
2718
OffsetX:=PagesBounds.Left;
2719
OffsetY:=PagesBounds.Top;
2720
for i:=0 to PageNode.ChildCount-1 do begin
2721
Child:=PageNode.Children[i];
2722
// changes parent of child
2723
Child.Parent:=ParentNode;
2724
// move children to place where PagesNode was
2725
ChildBounds:=Child.Bounds;
2726
OffsetRect(ChildBounds,OffsetX,OffsetY);
2727
Child.Bounds:=ChildBounds;
2728
// change anchors of child
2729
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
2730
if CompareText(Child.Sides[a],PageNode.Name)=0 then
2731
Child.Sides[a]:=PagesNode.Sides[a];
2734
FreeAndNil(PagesNode);
2735
//debugln(Root.DebugLayoutAsString);
2738
procedure SimplifyOneChildForm(var FormNode: TLazDockConfigNode);
2739
{ FormNode has only one child left.
2740
Remove Form node and replace root with child
2746
Child: TLazDockConfigNode;
2749
OldFormNode: TLazDockConfigNode;
2751
//DebugLn(['SimplifyOneChildForm ',dbgs(FormNode)]);
2752
if FormNode<>Root then RaiseGDBException('');
2753
if FormNode.ChildCount<>1 then RaiseGDBException('');
2754
FormBounds:=FormNode.Bounds;
2755
OffsetX:=FormBounds.Left;
2756
OffsetY:=FormBounds.Top;
2757
Child:=FormNode.Children[0];
2758
// changes parent of child
2759
Child.Parent:=FormNode.Parent;
2760
Child.WindowState:=FormNode.WindowState;
2761
// move child to place where FormNode was
2762
ChildBounds:=Child.Bounds;
2763
OffsetRect(ChildBounds,OffsetX,OffsetY);
2764
Child.Bounds:=ChildBounds;
2765
// change anchors of child
2766
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
2767
if CompareText(Child.Sides[a],FormNode.Name)=0 then
2768
Child.Sides[a]:=FormNode.Sides[a];
2770
OldFormNode:=FormNode;
2773
//Root.WriteDebugReport;
2776
procedure RemoveEmptyNodes(var Node: TLazDockConfigNode);
2777
// remove unneeded child nodes
2778
// if no children left and Node itself is unneeded, it s freed and set to nil
2781
Docker: TCustomLazControlDocker;
2782
Child: TLazDockConfigNode;
2784
if Node=nil then exit;
2785
{$IFDEF VerboseAnchorDockCreateLayout}
2786
DebugLn(['RemoveEmptyNodes ',Node.Name,' Node.ChildCount=',Node.ChildCount]);
2789
// remove unneeded children
2790
i:=Node.ChildCount-1;
2792
Child:=Node.Children[i];
2793
RemoveEmptyNodes(Child);// beware: this can delete more than one child
2795
if i>=Node.ChildCount then i:=Node.ChildCount-1;
2798
case Node.TheType of
2801
Docker:=FindDockerByName(Node.Name);
2802
// if the associated control does not exist or is not visible,
2803
// then delete the node
2804
if (Docker=nil) then begin
2805
{$IFDEF VerboseAnchorDockCreateLayout}
2806
DebugLn(['RemoveEmptyNodes delete unknown node: ',dbgs(Node)]);
2810
else if not ControlIsVisible(Docker.Control) then begin
2811
{$IFDEF VerboseAnchorDockCreateLayout}
2812
DebugLn(['RemoveEmptyNodes delete invisible node: ',dbgs(Node)]);
2818
// these are auto created parent node. If they have no children: delete
2819
if Node.ChildCount=0 then begin
2820
{$IFDEF VerboseAnchorDockCreateLayout}
2821
DebugLn(['RemoveEmptyNodes delete node without children: ',dbgs(Node)]);
2826
// these are auto created parent node. If they have no children: delete
2827
// if they have only one child: delete node and move children up
2828
if Node.ChildCount=0 then begin
2829
{$IFDEF VerboseAnchorDockCreateLayout}
2830
DebugLn(['RemoveEmptyNodes delete node without children: ',dbgs(Node)]);
2833
end else if Node.ChildCount=1 then begin
2834
// Only one child left
2835
SimplifyOneChildForm(Node);
2838
// these are auto created parent node. If they have no children: delete
2839
// if they have only one child: delete node and move child up
2840
if Node.ChildCount=0 then begin
2841
{$IFDEF VerboseAnchorDockCreateLayout}
2842
DebugLn(['RemoveEmptyNodes delete node without children: ',dbgs(Node)]);
2845
end else if Node.ChildCount=1 then begin
2846
// Only one child left
2847
SimplifyOnePageNode(Node);
2852
function AllControlsAreOnSameForm: boolean;
2856
function Check(Node: TLazDockConfigNode): boolean;
2861
if Node.TheType=ldcntControl then begin
2862
CurForm:=FindControlByDockerName(Node.Name);
2863
if (CurForm<>nil) then begin
2864
while CurForm.Parent<>nil do
2865
CurForm:=CurForm.Parent;
2866
if CurForm<>VisibleControl then begin
2867
if RootForm=nil then
2869
else if RootForm<>CurForm then
2875
for i:=0 to Node.ChildCount-1 do
2876
if not Check(Node.Children[i]) then exit(false);
2882
Result:=Check(Root);
2885
// FPC bug: when this function is internal of FindNearestControlNode then get win32 linker error
2886
function FindOwnSplitterSiblingWithControl(Node: TLazDockConfigNode
2887
): TLazDockConfigNode;
2888
{ find a sibling, that is a direct neighbour behind a splitter, and the
2889
splitter is only used by the node and the sibling
2899
SplitterNode: TLazDockConfigNode;
2901
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
2902
if Node.Sides[a]='' then continue;
2903
SplitterNode:=FindNode(Node.Sides[a]);
2904
if (SplitterNode.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown])
2905
and SplitterNode.IsTheOnlyNeighbour(Node,a) then begin
2906
Result:=SplitterNode.FindNeighbour(OppositeAnchor[a],true);
2907
if Result<>nil then exit;
2913
function FindNearestControlNode: TLazDockConfigNode;
2915
function FindSiblingWithControl(Node: TLazDockConfigNode
2916
): TLazDockConfigNode;
2918
ParentNode: TLazDockConfigNode;
2921
ParentNode:=Node.Parent;
2922
for i:=0 to ParentNode.ChildCount-1 do begin
2923
Result:=ParentNode.Children[i];
2924
if CompareText(Result.Name,DockerName)=0 then continue;
2925
if Result.TheType=ldcntControl then
2931
function FindPageSiblingWithControl(Node: TLazDockConfigNode
2932
): TLazDockConfigNode;
2933
{ find direct page sibling
2935
Node is the only child of a page
2936
A sibling page has a single child with a control
2939
PagesNode: TLazDockConfigNode;
2940
PageNode: TLazDockConfigNode;
2943
// check if node is on a page without siblings
2944
PageNode:=Node.Parent;
2945
if (PageNode=nil) or (PageNode.TheType<>ldcntPage)
2946
or (PageNode.ChildCount>1) then exit;
2947
// check if left page has only one control
2948
PagesNode:=PageNode.Parent;
2949
PageIndex:=PagesNode.IndexOf(PageNode.Name);
2951
and (PagesNode[PageIndex-1].ChildCount=1) then begin
2952
Result:=PagesNode[PageIndex-1].Children[0];
2953
if Result.TheType=ldcntControl then exit;
2955
// check if right page has only one control
2956
if (PageIndex<PagesNode.ChildCount-1)
2957
and (PagesNode[PageIndex+1].ChildCount=1) then begin
2958
Result:=PagesNode[PageIndex+1].Children[0];
2959
if Result.TheType=ldcntControl then exit;
2964
function FindOtherNodeWithControl(Node: TLazDockConfigNode
2965
): TLazDockConfigNode;
2970
if (Node.TheType=ldcntControl)
2971
and (CompareText(Node.Name,DockerName)<>0) then
2973
for i:=0 to Node.ChildCount-1 do begin
2974
Result:=FindOtherNodeWithControl(Node.Children[i]);
2975
if Result<>nil then exit;
2980
Node: TLazDockConfigNode;
2982
Node:=FindNode(DockerName);
2983
Result:=FindOwnSplitterSiblingWithControl(Node);
2984
if Result<>nil then exit;
2985
Result:=FindSiblingWithControl(Node);
2986
Node:=Root.FindByName(DockerName);
2987
Result:=FindPageSiblingWithControl(Node);
2988
if Result<>nil then exit;
2989
Result:=FindOtherNodeWithControl(Root);
2993
Config: TLazDockerConfig;
2994
CurControl: TControl;
2995
NearestControlNode: TLazDockConfigNode;
2998
CurDockControl:=nil;
3001
Config:=GetConfigWithDockerName(DockerName);
3003
DebugLn(['TCustomLazDockingManager.CreateLayout DockerName="',DockerName,'"']);
3005
Config.WriteDebugReport;
3007
if (Config=nil) or (Config.Root=nil) then begin
3008
DebugLn(['TCustomLazDockingManager.CreateLayout DockerName="',DockerName,'" No control']);
3011
CurControl:=FindControlByDockerName(DockerName);
3012
if not ControlIsVisible(CurControl) then begin
3013
DebugLn(['TCustomLazDockingManager.CreateLayout DockerName="',DockerName,'" CurControl=',DbgSName(CurControl),' control not visible']);
3016
if (not ConfigIsCompatible(Config.Root,ExceptionOnError)) then begin
3017
DebugLn(['TCustomLazDockingManager.CreateLayout DockerName="',DockerName,'" CurControl=',DbgSName(CurControl),' config is not compatible']);
3021
// create a copy of the config
3022
Root:=TLazDockConfigNode.Create(nil);
3024
Root.Assign(Config.Root);
3026
// clean up by removing all invisible, unknown and empty nodes
3027
RemoveEmptyNodes(Root);
3029
// check if all used controls are on the same dock form
3030
if not AllControlsAreOnSameForm then begin
3031
DebugLn(['TCustomLazDockingManager.CreateLayout Not all Controls are on the same Form. Using only one form...']);
3032
// the used controls are distributed on different dock forms
3033
// => choose one dock form and remove the nodes of the others
3034
NearestControlNode:=FindNearestControlNode;
3035
if NearestControlNode=nil then RaiseGDBException('');
3036
CurDockControl:=FindControlByDockerName(NearestControlNode.Name);
3037
if CurDockControl=nil then RaiseGDBException('');
3038
CurDockControl:=CurDockControl.GetTopParent;
3039
// remove nodes of other dock forms
3040
RemoveEmptyNodes(Root);
3041
//DebugLn(['TCustomLazDockingManager.CreateLayout After removing nodes of other dock forms:']);
3044
DebugLn(['TCustomLazDockingManager.CreateLayout After removing unneeded nodes:']);
3045
Root.WriteDebugReport;
3054
function TCustomLazDockingManager.ConfigIsCompatible(
3055
RootNode: TLazDockConfigNode; ExceptionOnError: boolean): boolean;
3057
function CheckNode(Node: TLazDockConfigNode): boolean;
3059
procedure Error(const Msg: string);
3063
s:='Error: Node="'+Node.GetPath+'"';
3064
s:=s+' NodeType='+LDConfigNodeTypeNames[Node.TheType];
3067
if ExceptionOnError then raise Exception.Create(s);
3070
function CheckSideAnchored(a: TAnchorKind): boolean;
3072
SiblingName: string;
3073
Sibling: TLazDockConfigNode;
3075
procedure ErrorWrongSplitter;
3077
Error('invalid Node.Sides[a] Node="'+Node.Name+'"'
3078
+' Node.Sides['+AnchorNames[a]+']="'+Node.Sides[a]+'"');
3082
SiblingName:=Node.Sides[a];
3083
if SiblingName='' then begin
3084
Error('Node.Sides[a]=''''');
3087
Sibling:=RootNode.FindByName(SiblingName,true);
3088
if Sibling=nil then begin
3089
Error('Node.Sides[a] not found');
3092
if Sibling=Node.Parent then
3093
exit(true); // anchored to parent: ok
3094
if (a in [akLeft,akRight]) and (Sibling.TheType=ldcntSplitterLeftRight)
3096
exit(true); // left/right side anchored to a left/right splitter: ok
3097
if (a in [akTop,akBottom]) and (Sibling.TheType=ldcntSplitterUpDown)
3099
exit(true); // top/bottom side anchored to a up/down splitter: ok
3100
// otherwise: not ok
3105
function CheckAllSidesAnchored: boolean;
3109
for a:=Low(TAnchorKind) to High(TAnchorKind) do
3110
if not CheckSideAnchored(a) then exit(false);
3114
function CheckSideNotAnchored(a: TAnchorKind): boolean;
3116
if Node.Sides[a]<>'' then begin
3117
Error('Sides[a]<>''''');
3123
function CheckNoSideAnchored: boolean;
3127
for a:=Low(TAnchorKind) to High(TAnchorKind) do
3128
if not CheckSideNotAnchored(a) then exit(false);
3132
function CheckHasChilds: boolean;
3134
if Node.ChildCount=0 then begin
3135
Error('ChildCount=0');
3141
function CheckHasNoChilds: boolean;
3143
if Node.ChildCount>0 then begin
3144
Error('ChildCount>0');
3150
function CheckHasParent: boolean;
3152
if Node.Parent=nil then begin
3153
Error('Parent=nil');
3159
function CheckUniqueCorner(Side1, Side2: TAnchorKind): boolean;
3162
Child: TLazDockConfigNode;
3165
if Node.Parent=nil then exit;
3166
if Node.Sides[Side1]='' then exit;
3167
if Node.Sides[Side2]='' then exit;
3168
for i:=0 to Node.Parent.ChildCount-1 do begin
3169
Child:=Node.Parent.Children[i];
3170
if Child=Node then continue;
3171
if (CompareText(Node.Sides[Side1],Child.Sides[Side1])=0)
3172
and (CompareText(Node.Sides[Side2],Child.Sides[Side2])=0) then begin
3173
Error('overlapping nodes');
3185
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
3186
if Node.Sides[a]<>'' then begin
3187
if CompareText(Node.Sides[a],Node.Name)=0 then begin
3188
Error('Node.Sides[a]=Node');
3191
if RootNode.FindByName(Node.Sides[a],true)=nil then begin
3192
Error('unknown Node.Sides[a]');
3198
case Node.TheType of
3201
// a control node contains a TControl
3202
if not CheckAllSidesAnchored then exit;
3206
// a dock form is a dummy control, used as top level container
3207
if Node.Parent<>nil then begin
3208
Error('Parent<>nil');
3211
if not CheckHasChilds then exit;
3212
if not CheckNoSideAnchored then exit;
3216
// a pages node has only page nodes as children
3217
if not CheckHasParent then exit;
3218
if not CheckHasChilds then exit;
3219
for i:=0 to Node.ChildCount-1 do
3220
if Node.Children[i].TheType<>ldcntPage then begin
3221
Error('Children[i].TheType<>ldcntPage');
3224
if not CheckAllSidesAnchored then exit;
3228
// a page is the child of a pages node, and a container
3229
if not CheckHasParent then exit;
3230
if not CheckHasChilds then exit;
3231
if Node.Parent.TheType<>ldcntPages then begin
3232
Error('Parent.TheType<>ldcntPages');
3235
if not CheckNoSideAnchored then exit;
3237
ldcntSplitterLeftRight:
3239
// a vertical splitter can be moved left/right
3240
if not CheckHasParent then exit;
3241
if not CheckHasNoChilds then exit;
3242
if not CheckSideNotAnchored(akLeft) then exit;
3243
if not CheckSideNotAnchored(akRight) then exit;
3244
if not CheckSideAnchored(akTop) then exit;
3245
if not CheckSideAnchored(akBottom) then exit;
3247
ldcntSplitterUpDown:
3249
// a horizontal splitter can be moved up/down
3250
// it is anchored left and right, and not top/bottom
3251
// it is not a root node
3252
// it has no children
3253
if not CheckHasParent then exit;
3254
if not CheckHasNoChilds then exit;
3255
if not CheckSideNotAnchored(akTop) then exit;
3256
if not CheckSideNotAnchored(akBottom) then exit;
3257
if not CheckSideAnchored(akLeft) then exit;
3258
if not CheckSideAnchored(akRight) then exit;
3261
Error('unknown type');
3265
if not CheckUniqueCorner(akLeft,akTop) then exit;
3266
if not CheckUniqueCorner(akLeft,akBottom) then exit;
3267
if not CheckUniqueCorner(akRight,akTop) then exit;
3268
if not CheckUniqueCorner(akRight,akBottom) then exit;
3271
for i:=0 to Node.ChildCount-1 do
3272
if not CheckNode(Node.Children[i]) then exit;
3278
if RootNode=nil then exit(false);
3279
Result:=CheckNode(RootNode);
3282
{ TLazDockConfigNode }
3284
function TLazDockConfigNode.GetSides(Side: TAnchorKind): string;
3286
Result:=FSides[Side];
3289
function TLazDockConfigNode.GetChildCount: Integer;
3291
if FChilds<>nil then
3292
Result:=FChilds.Count
3297
function TLazDockConfigNode.GetChilds(Index: integer): TLazDockConfigNode;
3299
Result:=TLazDockConfigNode(FChilds[Index]);
3302
procedure TLazDockConfigNode.SetBounds(const AValue: TRect);
3304
if CompareRect(@FBounds,@AValue) then exit;
3308
procedure TLazDockConfigNode.SetClientBounds(const AValue: TRect);
3310
if CompareRect(@FClientBounds,@AValue) then exit;
3311
FClientBounds:=AValue;
3314
procedure TLazDockConfigNode.SetName(const AValue: string);
3316
if FName=AValue then exit;
3320
procedure TLazDockConfigNode.SetParent(const AValue: TLazDockConfigNode);
3322
if FParent=AValue then exit;
3323
if FParent<>nil then
3324
FParent.DoRemove(Self);
3326
if FParent<>nil then
3327
FParent.DoAdd(Self);
3330
procedure TLazDockConfigNode.SetSides(Side: TAnchorKind;
3331
const AValue: string);
3333
FSides[Side]:=AValue;
3336
procedure TLazDockConfigNode.SetTheType(const AValue: TLDConfigNodeType);
3338
if FTheType=AValue then exit;
3342
procedure TLazDockConfigNode.DoAdd(ChildNode: TLazDockConfigNode);
3344
if FChilds=nil then FChilds:=TFPList.Create;
3345
FChilds.Add(ChildNode);
3348
procedure TLazDockConfigNode.DoRemove(ChildNode: TLazDockConfigNode);
3350
if TObject(FChilds[FChilds.Count-1])=ChildNode then
3351
FChilds.Delete(FChilds.Count-1)
3353
FChilds.Remove(ChildNode);
3356
constructor TLazDockConfigNode.Create(ParentNode: TLazDockConfigNode);
3358
FTheType:=ldcntControl;
3362
constructor TLazDockConfigNode.Create(ParentNode: TLazDockConfigNode;
3363
const AName: string);
3369
destructor TLazDockConfigNode.Destroy;
3378
procedure TLazDockConfigNode.Clear;
3382
if FChilds=nil then exit;
3383
for i:=ChildCount-1 downto 0 do Children[i].Free;
3387
procedure TLazDockConfigNode.Assign(Source: TPersistent);
3389
Src: TLazDockConfigNode;
3391
SrcChild: TLazDockConfigNode;
3392
NewChild: TLazDockConfigNode;
3395
if Source is TLazDockConfigNode then begin
3397
Src:=TLazDockConfigNode(Source);
3398
FBounds:=Src.FBounds;
3399
FClientBounds:=Src.FClientBounds;
3401
FWindowState:=Src.FWindowState;
3402
for a:=Low(TAnchorKind) to High(TAnchorKind) do
3403
FSides[a]:=Src.FSides[a];
3404
FTheType:=Src.FTheType;
3405
for i:=0 to Src.ChildCount-1 do begin
3406
SrcChild:=Src.Children[i];
3407
NewChild:=TLazDockConfigNode.Create(Self);
3408
NewChild.Assign(SrcChild);
3411
inherited Assign(Source);
3414
function TLazDockConfigNode.FindByName(const AName: string;
3415
Recursive: boolean; WithRoot: boolean): TLazDockConfigNode;
3419
if WithRoot and (CompareText(Name,AName)=0) then exit(Self);
3420
if FChilds<>nil then
3421
for i:=0 to FChilds.Count-1 do begin
3422
Result:=Children[i];
3423
if CompareText(Result.Name,AName)=0 then exit;
3424
if Recursive then begin
3425
Result:=Result.FindByName(AName,true,false);
3426
if Result<>nil then exit;
3432
function TLazDockConfigNode.IndexOf(const AName: string): Integer;
3434
if FChilds<>nil then begin
3435
Result:=FChilds.Count-1;
3436
while (Result>=0) and (CompareText(Children[Result].Name,AName)<>0) do
3443
function TLazDockConfigNode.GetScreenBounds: TRect;
3449
Node: TLazDockConfigNode;
3451
NewWidth:=FBounds.Right-FBounds.Left;
3452
NewHeight:=FBounds.Bottom-FBounds.Top;
3453
NewLeft:=FBounds.Left;
3454
NewTop:=FBounds.Top;
3456
while Node<>nil do begin
3457
inc(NewLeft,Node.FBounds.Left+Node.FClientBounds.Left);
3458
inc(NewTop,Node.FBounds.Top+Node.FClientBounds.Top);
3461
Result:=Classes.Bounds(NewLeft,NewTop,NewWidth,NewHeight);
3464
function TLazDockConfigNode.FindNeighbour(SiblingSide: TAnchorKind;
3465
NilIfAmbiguous: boolean; IgnoreSplitters: boolean): TLazDockConfigNode;
3468
ParentNode: TLazDockConfigNode;
3469
Child: TLazDockConfigNode;
3473
for i:=0 to ParentNode.ChildCount-1 do begin
3474
Child:=ParentNode.Children[i];
3475
if Child=Self then continue;
3477
and (Child.TheType in [ldcntSplitterLeftRight,ldcntSplitterUpDown]) then
3479
if CompareText(Child.Sides[SiblingSide],Name)=0 then begin
3482
else if NilIfAmbiguous then
3488
function TLazDockConfigNode.IsTheOnlyNeighbour(Node: TLazDockConfigNode;
3489
SiblingSide: TAnchorKind): boolean;
3490
{ check if one side is only used by Node.
3491
For example: If only Node.Sides[SiblingSide]=Name
3498
Result:=FindNeighbour(SiblingSide,true)<>nil;
3501
procedure TLazDockConfigNode.SaveToConfig(Config: TConfigStorage;
3502
const Path: string);
3506
Child: TLazDockConfigNode;
3509
Config.SetDeleteValue(Path+'Name/Value',Name,'');
3510
Config.SetDeleteValue(Path+'Type/Value',LDConfigNodeTypeNames[TheType],
3511
LDConfigNodeTypeNames[ldcntControl]);
3512
Config.SetDeleteValue(Path+'Bounds/',FBounds,Rect(0,0,0,0));
3513
Config.SetDeleteValue(Path+'ClientBounds/',FClientBounds,
3514
Rect(0,0,FBounds.Right-FBounds.Left,FBounds.Bottom-FBounds.Top));
3515
Config.SetDeleteValue(Path+'WindowState/Value',WindowStateToStr(WindowState),
3516
WindowStateToStr(wsNormal));
3519
for a:=Low(TAnchorKind) to High(TAnchorKind) do
3520
Config.SetDeleteValue(Path+'Sides/'+AnchorNames[a]+'/Name',Sides[a],'');
3523
Config.SetDeleteValue(Path+'Children/Count',ChildCount,0);
3524
for i:=0 to ChildCount-1 do begin
3526
SubPath:=Path+'Child'+IntToStr(i+1)+'/';
3527
Child.SaveToConfig(Config,SubPath);
3531
procedure TLazDockConfigNode.LoadFromConfig(Config: TConfigStorage;
3532
const Path: string);
3536
NewChildCount: LongInt;
3537
NewChildName: String;
3538
NewChild: TLazDockConfigNode;
3542
// Note: 'Name' is stored only for information, but not restored on load
3543
TheType:=LDConfigNodeTypeNameToType(Config.GetValue(Path+'Type/Value',
3544
LDConfigNodeTypeNames[ldcntControl]));
3545
Config.GetValue(Path+'Bounds/',FBounds,Rect(0,0,0,0));
3546
Config.GetValue(Path+'ClientBounds/',FClientBounds,
3547
Rect(0,0,FBounds.Right-FBounds.Left,FBounds.Bottom-FBounds.Top));
3548
WindowState:=StrToWindowState(config.GetValue(Path+'WindowState/Value',''));
3551
for a:=Low(TAnchorKind) to High(TAnchorKind) do
3552
Sides[a]:=Config.GetValue(Path+'Sides/'+AnchorNames[a]+'/Name','');
3555
NewChildCount:=Config.GetValue(Path+'Children/Count',0);
3556
for i:=0 to NewChildCount-1 do begin
3557
SubPath:=Path+'Child'+IntToStr(i+1)+'/';
3558
NewChildName:=Config.GetValue(SubPath+'Name/Value','');
3559
NewChild:=TLazDockConfigNode.Create(Self,NewChildName);
3560
NewChild.Parent:=Self;
3561
NewChild.LoadFromConfig(Config,SubPath);
3565
procedure TLazDockConfigNode.WriteDebugReport;
3567
procedure WriteNode(const Prefix: string; ANode: TLazDockConfigNode);
3573
if ANode=nil then exit;
3574
DbgOut(Prefix,'Name="'+ANode.Name+'"');
3575
DbgOut(' Type=',GetEnumName(TypeInfo(TLDConfigNodeType),ord(ANode.TheType)));
3576
DbgOut(' Bounds='+dbgs(ANode.Bounds));
3577
DbgOut(' ClientBounds='+dbgs(ANode.ClientBounds));
3578
DbgOut(' Children='+dbgs(ANode.ChildCount));
3579
DbgOut(' WindowState='+WindowStateToStr(ANode.WindowState));
3580
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
3584
DbgOut(' '+AnchorNames[a]+'="'+s+'"');
3587
for i:=0 to ANode.ChildCount-1 do begin
3588
WriteNode(Prefix+' ',ANode[i]);
3593
DebugLn('TLazDockConfigNode.WriteDebugReport Root=',dbgs(Self));
3594
WriteNode(' ',Self);
3595
DebugLn(DebugLayoutAsString);
3599
function TLazDockConfigNode.DebugLayoutAsString: string;
3601
TArrayOfRect = array of TRect;
3604
MinSizeValid, MinSizeCalculating: boolean;
3606
MinLeftValid, MinLeftCalculating: boolean;
3608
MinTopValid, MinTopCalculating: boolean;
3610
PNodeInfo = ^TNodeInfo;
3615
NodeInfos: TPointerToPointerTree;// TLazDockConfigNode to PNodeInfo
3617
procedure InitNodeInfos;
3619
NodeInfos:=TPointerToPointerTree.Create;
3622
procedure FreeNodeInfos;
3625
NodePtr, InfoPtr: Pointer;
3627
NodeInfos.GetFirst(NodePtr,InfoPtr);
3629
Item:=PNodeInfo(InfoPtr);
3630
if Item=nil then break;
3632
until not NodeInfos.GetNext(NodePtr,NodePtr,InfoPtr);
3636
function GetNodeInfo(Node: TLazDockConfigNode): PNodeInfo;
3638
Result:=PNodeInfo(NodeInfos[Node]);
3639
if Result=nil then begin
3641
FillChar(Result^,SizeOf(TNodeInfo),0);
3642
NodeInfos[Node]:=Result;
3646
procedure w(x,y: Integer; const s: string; MaxX: Integer = 0);
3650
for i:=1 to length(s) do begin
3651
if (MaxX>0) and (x+i>MaxX) then exit;
3652
Result[LogCols*(y-1) + x + i-1]:=s[i];
3656
procedure wfillrect(const ARect: TRect; c: char);
3661
for x:=ARect.Left to ARect.Right do
3662
for y:=ARect.Top to ARect.Bottom do
3666
procedure wrectangle(const ARect: TRect);
3668
w(ARect.Left,ARect.Top,'+');
3669
w(ARect.Right,ARect.Top,'+');
3670
w(ARect.Left,ARect.Bottom,'+');
3671
w(ARect.Right,ARect.Bottom,'+');
3672
if ARect.Left<ARect.Right then begin
3673
if ARect.Top<ARect.Bottom then begin
3674
wfillrect(Rect(ARect.Left+1,ARect.Top,ARect.Right-1,ARect.Top),'-');// top line
3675
wfillrect(Rect(ARect.Left+1,ARect.Bottom,ARect.Right-1,ARect.Bottom),'-');// bottom line
3676
wfillrect(Rect(ARect.Left,ARect.Top+1,ARect.Left,ARect.Bottom-1),'|');// left line
3677
wfillrect(Rect(ARect.Right,ARect.Top+1,ARect.Right,ARect.Bottom-1),'|');// right line
3679
wfillrect(Rect(ARect.Left+1,ARect.Top,ARect.Right-1,ARect.Top),'=');// horizontal line
3682
wfillrect(Rect(ARect.Left,ARect.Top+1,ARect.Left,ARect.Bottom-1),'#');// vertical line
3686
function MapRect(const OriginalRect, OldBounds, NewBounds: TRect): TRect;
3688
function MapX(i: Integer): Integer;
3690
Result:=NewBounds.Left+
3691
(((i-OldBounds.Left)*(NewBounds.Right-NewBounds.Left))
3692
div (OldBounds.Right-OldBounds.Left));
3695
function MapY(i: Integer): Integer;
3697
Result:=NewBounds.Top+
3698
(((i-OldBounds.Top)*(NewBounds.Bottom-NewBounds.Top))
3699
div (OldBounds.Bottom-OldBounds.Top));
3703
Result.Left:=MapX(OriginalRect.Left);
3704
Result.Top:=MapY(OriginalRect.Left);
3705
Result.Right:=MapX(OriginalRect.Left);
3706
Result.Bottom:=MapY(OriginalRect.Left);
3709
function GetMinSize(Node: TLazDockConfigNode): TPoint; forward;
3711
function GetMinPos(Node: TLazDockConfigNode; Side: TAnchorKind): Integer;
3712
// calculates left or top position of Node
3714
function Compute(var MinPosValid, MinPosCalculating: boolean;
3715
var MinPos: Integer): Integer;
3717
procedure Improve(Neighbour: TLazDockConfigNode);
3719
NeighbourPos: LongInt;
3720
NeighbourSize: TPoint;
3721
NeighbourLength: LongInt;
3723
if Neighbour=nil then exit;
3724
if Neighbour.Parent<>Node.Parent then exit;
3725
NeighbourPos:=GetMinPos(Neighbour,Side);
3726
NeighbourSize:=GetMinSize(Neighbour);
3728
NeighbourLength:=NeighbourSize.X
3730
NeighbourLength:=NeighbourSize.Y;
3731
MinPos:=Max(MinPos,NeighbourPos+NeighbourLength);
3735
Sibling: TLazDockConfigNode;
3738
if MinPosCalculating then begin
3739
DebugLn(['DebugLayoutAsString.GetMinPos.Compute WARNING: anchor circle detected']);
3743
if (not MinPosValid) then begin
3745
MinPosCalculating:=true;
3746
if Node.Sides[Side]<>'' then begin
3747
Sibling:=FindByName(Node.Sides[Side],true,true);
3750
if Node.Parent<>nil then begin
3751
for i:=0 to Node.Parent.ChildCount-1 do begin
3752
Sibling:=Node.Parent.Children[i];
3753
if CompareText(Sibling.Sides[OppositeAnchor[Side]],Node.Name)=0 then
3757
MinPosCalculating:=false;
3765
Info:=GetNodeInfo(Node);
3766
//DebugLn(['GetMinPos ',Node.Name,' ',AnchorNames[Side],' ',Info^.MinLeftCalculating]);
3768
Result:=Compute(Info^.MinLeftValid,Info^.MinLeftCalculating,Info^.MinLeft)
3770
Result:=Compute(Info^.MinTopValid,Info^.MinTopCalculating,Info^.MinTop);
3773
function GetChildsMinSize(Node: TLazDockConfigNode): TPoint;
3774
// calculate the minimum size needed to draw the content of the node
3777
ChildMinSize: TPoint;
3778
Child: TLazDockConfigNode;
3781
//DebugLn(['GetChildsMinSize ',Node.name]);
3783
if Node.TheType=ldcntPages then begin
3784
// maximum size of all pages
3785
for i:=0 to Node.ChildCount-1 do begin
3786
ChildMinSize:=GetMinSize(Node.Children[i]);
3787
Result.X:=Max(Result.X,ChildMinSize.X);
3788
Result.Y:=Max(Result.Y,ChildMinSize.Y);
3791
for i:=0 to Node.ChildCount-1 do begin
3792
Child:=Node.Children[i];
3793
ChildSize:=GetMinSize(Child);
3794
Result.X:=Max(Result.X,GetMinPos(Child,akLeft)+ChildSize.X);
3795
Result.Y:=Max(Result.Y,GetMinPos(Child,akTop)+ChildSize.Y);
3800
function GetMinSize(Node: TLazDockConfigNode): TPoint;
3801
// calculate the minimum size needed to draw the node
3803
ChildMinSize: TPoint;
3806
//DebugLn(['GetMinSize ',Node.name]);
3807
Info:=GetNodeInfo(Node);
3808
if Info^.MinSizeValid then begin
3809
Result:=Info^.MinSize;
3812
if Info^.MinSizeCalculating then begin
3813
DebugLn(['DebugLayoutAsString.GetMinSize WARNING: anchor circle detected']);
3818
Info^.MinSizeCalculating:=true;
3819
Result.X:=2+length(Node.Name);// border plus caption
3820
Result.Y:=2; // border
3821
if (Node.ChildCount=0) then begin
3822
case Node.TheType of
3823
ldcntSplitterLeftRight,ldcntSplitterUpDown:
3824
Result:=Point(1,1); // splitters don't need captions
3827
ChildMinSize:=GetChildsMinSize(Node);
3828
Result.X:=Max(Result.X,ChildMinSize.X+2);
3829
Result.Y:=Max(Result.Y,ChildMinSize.Y+2);
3831
Info^.MinSize:=Result;
3832
Info^.MinSizeValid:=true;
3833
Info^.MinSizeCalculating:=false;
3836
procedure DrawNode(Node: TLazDockConfigNode; ARect: TRect);
3839
Child: TLazDockConfigNode;
3842
AnchorNode: TLazDockConfigNode;
3844
//DebugLn(['DrawNode Node=',Node.Name,' ARect=',dbgs(ARect)]);
3846
w(ARect.Left+1,ARect.Top,Node.Name,ARect.Right);
3848
for i := 0 to Node.ChildCount-1 do begin
3849
Child:=Node.Children[i];
3850
ChildRect.Left:=ARect.Left+1+GetMinPos(Child,akLeft);
3851
ChildRect.Top:=ARect.Top+1+GetMinPos(Child,akTop);
3852
ChildSize:=GetMinSize(Child);
3853
ChildRect.Right:=ChildRect.Left+ChildSize.X-1;
3854
ChildRect.Bottom:=ChildRect.Top+ChildSize.Y-1;
3855
if Child.Sides[akRight]<>'' then begin
3856
AnchorNode:=FindByName(Child.Sides[akRight]);
3857
if AnchorNode=Node then
3858
ChildRect.Right:=ARect.Right-1
3859
else if AnchorNode.Parent=Node then
3860
ChildRect.Right:=ARect.Left+1+GetMinPos(AnchorNode,akLeft)-1;
3862
if Child.Sides[akBottom]<>'' then begin
3863
AnchorNode:=FindByName(Child.Sides[akBottom]);
3864
if AnchorNode=Node then
3865
ChildRect.Bottom:=ARect.Bottom-1
3866
else if AnchorNode.Parent=Node then
3867
ChildRect.Bottom:=ARect.Top+1+GetMinPos(AnchorNode,akTop)-1;
3869
DrawNode(Child,ChildRect);
3870
if Node.TheType=ldcntPages then begin
3871
// paint only one page
3881
Cols:=StrToIntDef(Application.GetOptionValue('ldcn-colunms'),79);
3882
Rows:=StrToIntDef(Application.GetOptionValue('ldcn-rows'),20);
3887
LogCols:=Cols+length(e);
3888
SetLength(Result,LogCols*Rows);
3890
FillChar(Result[1],length(Result),' ');
3895
DrawNode(Self,Rect(1,1,Cols,Rows));
3901
function TLazDockConfigNode.GetPath: string;
3903
Node: TLazDockConfigNode;
3907
while Node<>nil do begin
3909
Result:=Node.Name+'/'+Result
3916
{ TLazDockerConfig }
3918
constructor TLazDockerConfig.Create(const ADockerName: string;
3919
ANode: TLazDockConfigNode);
3921
FDockerName:=ADockerName;
3925
destructor TLazDockerConfig.Destroy;
3927
FRoot.Free; // who will clear it else?
3931
procedure TLazDockerConfig.WriteDebugReport;
3933
DebugLn(['TLazDockerConfig.WriteDebugReport DockerName="',DockerName,'"']);
3934
if Root<>nil then begin
3935
Root.WriteDebugReport;
3937
DebugLn([' Root=nil']);
3941
{ TAnchoredDockManager }
3943
procedure TAnchoredDockManager.DisableLayout(Control: TControl);
3945
FConfigs.DisableLayout(Control);
3946
inherited DisableLayout(Control);
3949
procedure TAnchoredDockManager.EnableLayout(Control: TControl);
3951
inherited EnableLayout(Control);
3952
FConfigs.EnableLayout(Control);