{ $Id$ } { /*************************************************************************** LDockTree.pas ----------------- ***************************************************************************/ ***************************************************************************** * * * This file is part of the Lazarus Component Library (LCL) * * * * See the file COPYING.modifiedLGPL, included in this distribution, * * for details about the copyright. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * ***************************************************************************** Author: Mattias Gaertner Abstract: This unit contains TLazDockTree, the default TDockTree for the LCL. } unit LDockTree; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LCLProc, LCLType, Forms, Controls, ExtCtrls, Menus, LCLStrConsts; type TLazDockPages = class; TLazDockPage = class; TLazDockSplitter = class; { TLazDockZone } TLazDockZone = class(TDockZone) private FPage: TLazDockPage; FPages: TLazDockPages; FSplitter: TLazDockSplitter; public destructor Destroy; override; function GetCaption: string; function GetParentControl: TWinControl; property Splitter: TLazDockSplitter read FSplitter write FSplitter; property Pages: TLazDockPages read FPages write FPages; property Page: TLazDockPage read FPage write FPage; end; { TLazDockTree } TLazDockTree = class(TDockTree) private FAutoFreeDockSite: boolean; protected procedure UndockControlForDocking(AControl: TControl); procedure BreakAnchors(Zone: TDockZone); procedure CreateDockLayoutHelperControls(Zone: TLazDockZone); procedure AnchorDockLayout(Zone: TLazDockZone); public constructor Create(TheDockSite: TWinControl); override; destructor Destroy; override; procedure InsertControl(AControl: TControl; InsertAt: TAlign; DropControl: TControl); override; procedure BuildDockLayout(Zone: TLazDockZone); procedure FindBorderControls(Zone: TLazDockZone; Side: TAnchorKind; var List: TFPList); function FindBorderControl(Zone: TLazDockZone; Side: TAnchorKind): TControl; function GetAnchorControl(Zone: TLazDockZone; Side: TAnchorKind; OutSide: boolean): TControl; public property AutoFreeDockSite: boolean read FAutoFreeDockSite write FAutoFreeDockSite; end; { TLazDockForm The default DockSite for a TLazDockTree Note: AnchorDocking does not use DockZone. if DockZone<>nil then If DockZone is a leaf (DockZone.ChildCount=0) then Only child control is DockZone.ChildControl else if DockZone.Orientation in [doHorizontal,doVertical] then Child controls are TLazDockForm and TSplitter else if DockZone.Orientation=doPages then Child control is a TLazDockPages } TLazDockForm = class(TCustomForm) private FDockZone: TDockZone; FMainControl: TControl; FPageControl: TLazDockPages; procedure SetMainControl(const AValue: TControl); protected procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure InsertControl(AControl: TControl; Index: integer); override; function CloseQuery: boolean; override; public procedure UpdateCaption; virtual; function FindMainControlCandidate: TControl; property DockZone: TDockZone read FDockZone; property PageControl: TLazDockPages read FPageControl; property MainControl: TControl read FMainControl write SetMainControl; end; { TLazDockPage Pretty the same as a TLazDockForm but as page of a TLazDockPages } TLazDockPage = class(TCustomPage) private FDockZone: TDockZone; function GetPageControl: TLazDockPages; public property DockZone: TDockZone read FDockZone; property PageControl: TLazDockPages read GetPageControl; end; { TLazDockPages } TLazDockPages = class(TCustomNotebook) private function GetActiveNotebookPageComponent: TLazDockPage; function GetNoteBookPage(Index: Integer): TLazDockPage; procedure SetActiveNotebookPageComponent(const AValue: TLazDockPage); protected function GetFloatingDockSiteClass: TWinControlClass; override; public constructor Create(TheOwner: TComponent); override; property Page[Index: Integer]: TLazDockPage read GetNoteBookPage; property ActivePageComponent: TLazDockPage read GetActiveNotebookPageComponent write SetActiveNotebookPageComponent; property Pages; end; TLazDockSplitter = class(TCustomSplitter) end; //---------------------------------------------------------------------------- { TAnchoredDockManager } TAnchoredDockManager = class(TDockManager) private FSplitterSize: integer; FUpdateCount: integer; protected procedure DeleteSideSplitter(Splitter: TLazDockSplitter; Side: TAnchorKind; NewAnchorControl: TControl); procedure CombineSpiralSplitterPair(Splitter1, Splitter2: TLazDockSplitter); procedure DeletePage(Page: TLazDockPage); procedure DeletePages(Pages: TLazDockPages); procedure DeleteDockForm(ADockForm: TLazDockForm); function GetAnchorDepth(AControl: TControl; Side: TAnchorKind): Integer; public constructor Create; procedure BeginUpdate; override; procedure EndUpdate; override; procedure GetControlBounds(Control: TControl; out AControlBounds: TRect); override; procedure DockControl(Control: TControl; InsertAt: TAlign; DropCtl: TControl); procedure UndockControl(Control: TControl; Float: boolean); procedure InsertControl(Control: TControl; InsertAt: TAlign; DropCtl: TControl); override; function EnlargeControl(Control: TControl; Side: TAnchorKind; Simulate: boolean = false): boolean; procedure LoadFromStream(Stream: TStream); override; procedure PaintSite(DC: HDC); override; procedure PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign; var DockRect: TRect); override; procedure RemoveControl(Control: TControl); override; procedure ResetBounds(Force: Boolean); override; procedure SaveToStream(Stream: TStream); override; procedure SetReplacingControl(Control: TControl); override; procedure ReplaceAnchoredControl(OldControl, NewControl: TControl); function GetSplitterWidth(Splitter: TControl): integer; function GetSplitterHeight(Splitter: TControl): integer; property SplitterSize: integer read FSplitterSize write FSplitterSize default 5; end; const DockAlignOrientations: array[TAlign] of TDockOrientation = ( doPages, //alNone, doVertical, //alTop, doVertical, //alBottom, doHorizontal,//alLeft, doHorizontal,//alRight, doPages, //alClient, doPages //alCustom ); function GetLazDockSplitter(Control: TControl; Side: TAnchorKind; out Splitter: TLazDockSplitter): boolean; function GetLazDockSplitterOrParent(Control: TControl; Side: TAnchorKind; out AnchorControl: TControl): boolean; function CountAnchoredControls(Control: TControl; Side: TAnchorKind ): Integer; function NeighbourCanBeShrinked(EnlargeControl, Neighbour: TControl; Side: TAnchorKind): boolean; implementation function GetLazDockSplitter(Control: TControl; Side: TAnchorKind; out Splitter: TLazDockSplitter): boolean; begin Result:=false; Splitter:=nil; if not (Side in Control.Anchors) then exit; Splitter:=TLazDockSplitter(Control.AnchorSide[Side].Control); if not (Splitter is TLazDockSplitter) then begin Splitter:=nil; exit; end; if Splitter.Parent<>Control.Parent then exit; Result:=true; end; function GetLazDockSplitterOrParent(Control: TControl; Side: TAnchorKind; out AnchorControl: TControl): boolean; begin Result:=false; AnchorControl:=nil; if not (Side in Control.Anchors) then exit; AnchorControl:=Control.AnchorSide[Side].Control; if (AnchorControl is TLazDockSplitter) and (AnchorControl.Parent=Control.Parent) then Result:=true else if AnchorControl=Control.Parent then Result:=true; end; function CountAnchoredControls(Control: TControl; Side: TAnchorKind): Integer; { return the number of siblings, that are anchored on Side of Control For example: if Side=akLeft it will return the number of controls, which right side is anchored to the left of Control } var i: Integer; Neighbour: TControl; begin Result:=0; for i:=0 to Control.Parent.ControlCount-1 do begin Neighbour:=Control.Parent.Controls[i]; if Neighbour=Control then continue; if (OppositeAnchor[Side] in Neighbour.Anchors) and (Neighbour.AnchorSide[OppositeAnchor[Side]].Control=Control) then inc(Result); end; end; function NeighbourCanBeShrinked(EnlargeControl, Neighbour: TControl; Side: TAnchorKind): boolean; const MinControlSize = 20; var Splitter: TLazDockSplitter; begin Result:=false; if not GetLazDockSplitter(EnlargeControl,OppositeAnchor[Side],Splitter) then exit; case Side of akLeft: // check if left side of Neighbour can be moved Result:=Neighbour.Left+Neighbour.Width >EnlargeControl.Left+EnlargeControl.Width+Splitter.Width+MinControlSize; akRight: // check if right side of Neighbour can be moved Result:=Neighbour.Left+MinControlSize+Splitter.WidthEnlargeControl.Top+EnlargeControl.Height+Splitter.Height+MinControlSize; akBottom: // check if bottom side of Neighbour can be moved Result:=Neighbour.Top+MinControlSize+Splitter.Heightnil then begin // TODO end; end; if AControl.Parent<>nil then begin AControl.Parent:=nil; end; end; procedure TLazDockTree.BreakAnchors(Zone: TDockZone); begin if Zone=nil then exit; if Zone.ChildControl<>nil then begin Zone.ChildControl.AnchorSide[akLeft].Control:=nil; Zone.ChildControl.AnchorSide[akTop].Control:=nil; Zone.ChildControl.Anchors:=[akLeft,akTop]; end; BreakAnchors(Zone.FirstChild); BreakAnchors(Zone.NextSibling); end; procedure TLazDockTree.CreateDockLayoutHelperControls(Zone: TLazDockZone); var ParentPages: TLazDockPages; ZoneIndex: LongInt; begin if Zone=nil then exit; // create needed TLazDockSplitter if (Zone.Parent<>nil) and (Zone.Parent.Orientation in [doVertical,doHorizontal]) and (Zone.PrevSibling<>nil) then begin // a zone with a side sibling -> needs a TLazDockSplitter if Zone.Splitter=nil then begin Zone.Splitter:=TLazDockSplitter.Create(nil); end; end else if Zone.Splitter<>nil then begin // zone no longer needs the splitter Zone.Splitter.Free; Zone.Splitter:=nil; end; // create needed TLazDockPages if (Zone.Orientation=doPages) then begin // a zone of pages -> needs a TLazDockPages if Zone.FirstChild=nil then RaiseGDBException('TLazDockTree.CreateDockLayoutHelperControls Inconsistency: doPages without childs'); if (Zone.Pages=nil) then begin Zone.Pages:=TLazDockPages.Create(nil); end; end else if Zone.Pages<>nil then begin // zone no longer needs the pages Zone.Pages.Free; Zone.Pages:=nil; end; // create needed TLazDockPage if (Zone.Parent<>nil) and (Zone.Parent.Orientation=doPages) then begin // a zone as page -> needs a TLazDockPage if (Zone.Page=nil) then begin ParentPages:=TLazDockZone(Zone.Parent).Pages; ZoneIndex:=Zone.GetIndex; ParentPages.Pages.Insert(ZoneIndex,Zone.GetCaption); Zone.Page:=ParentPages.Page[ZoneIndex]; end; end else if Zone.Page<>nil then begin // zone no longer needs the page Zone.Page.Free; Zone.Page:=nil; end; // create controls for childs and siblings CreateDockLayoutHelperControls(Zone.FirstChild as TLazDockZone); CreateDockLayoutHelperControls(Zone.NextSibling as TLazDockZone); end; procedure TLazDockTree.AnchorDockLayout(Zone: TLazDockZone); // setup all anchors between all docked controls and helper controls var AnchorControls: array[TAnchorKind] of TControl; a: TAnchorKind; SplitterSide: TAnchorKind; CurControl: TControl; NewAnchors: TAnchors; begin if Zone=nil then exit; // get outside anchor controls for a:=Low(TAnchorKind) to High(TAnchorKind) do AnchorControls[a]:=GetAnchorControl(Zone,a,true); // anchor splitter if (Zone.Splitter<>nil) then begin if Zone.Parent.Orientation=doHorizontal then SplitterSide:=akLeft else SplitterSide:=akTop; // IMPORTANT: first set the AnchorSide, then set the Anchors NewAnchors:=[akLeft,akRight,akTop,akBottom]-[SplitterSide]; for a:=Low(TAnchorKind) to High(TAnchorKind) do if a in NewAnchors then Zone.Splitter.AnchorSide[a].Control:=AnchorControls[a]; Zone.Splitter.Anchors:=NewAnchors; AnchorControls[SplitterSide]:=Zone.Splitter; end; // anchor pages if Zone.Pages<>nil then CurControl:=Zone.Pages else CurControl:=Zone.ChildControl; if CurControl<>nil then begin // IMPORTANT: first set the AnchorSide, then set the Anchors for a:=Low(TAnchorKind) to High(TAnchorKind) do CurControl.AnchorSide[a].Control:=AnchorControls[a]; CurControl.Anchors:=[akLeft,akRight,akTop,akBottom]; end; // anchor controls for childs and siblings AnchorDockLayout(Zone.FirstChild as TLazDockZone); AnchorDockLayout(Zone.NextSibling as TLazDockZone); end; constructor TLazDockTree.Create(TheDockSite: TWinControl); begin SetDockZoneClass(TLazDockZone); if TheDockSite=nil then begin TheDockSite:=TLazDockForm.Create(nil); TheDockSite.DockManager:=Self; FAutoFreeDockSite:=true; end; inherited Create(TheDockSite); end; destructor TLazDockTree.Destroy; begin if FAutoFreeDockSite then begin if DockSite.DockManager=Self then DockSite.DockManager:=nil; DockSite.Free; DockSite:=nil; end; inherited Destroy; end; procedure TLazDockTree.InsertControl(AControl: TControl; InsertAt: TAlign; DropControl: TControl); { undocks AControl and docks it into the tree It creates a new TDockZone for AControl and inserts it as a new leaf. It automatically changes the tree, so that the parent of the new TDockZone will have the Orientation for InsertAt. Example 1: A newly created TLazDockTree has only a DockSite (TLazDockForm) and a single TDockZone - the RootZone, which has as ChildControl the DockSite. Visual: +-DockSite--+ | | +-----------+ Tree of TDockZone: RootZone (DockSite,doNoOrient) Inserting the first control: InsertControl(Form1,alLeft,nil); Visual: +-DockSite---+ |+--Form1---+| || || |+----------+| +------------+ Tree of TDockZone: RootZone (DockSite,doHorizontal) +-Zone2 (Form1,doNoOrient) Dock Form2 right of Form1: InsertControl(Form2,alLeft,Form1); Visual: +-DockSite----------+ |+-Form1-+|+-Form2-+| || || || |+-------+|+-------+| +-------------------+ Tree of TDockZone: RootZone (DockSite,doHorizontal) +-Zone2 (Form1,doNoOrient) +-Zone3 (Form2,doNoOrient) } const SplitterWidth = 5; SplitterHeight = 5; var DropZone: TDockZone; NewZone: TLazDockZone; NewOrientation: TDockOrientation; NeedNewParentZone: Boolean; NewParentZone: TDockZone; OldParentZone: TDockZone; NewBounds: TRect; ASibling: TDockZone; begin if DropControl=nil then DropControl:=DockSite; DropZone:=RootZone.FindZone(DropControl); if DropZone=nil then raise Exception.Create('TLazDockTree.InsertControl DropControl is not part of this TDockTree'); NewOrientation:=DockAlignOrientations[InsertAt]; // undock UndockControlForDocking(AControl); // dock // create a new zone for AControl NewZone:=DockZoneClass.Create(Self,AControl) as TLazDockZone; // insert new zone into tree if (DropZone=RootZone) and (RootZone.FirstChild=nil) then begin // this is the first child debugln('TLazDockTree.InsertControl First Child'); RootZone.Orientation:=NewOrientation; RootZone.AddAsFirstChild(NewZone); if not AControl.Visible then DockSite.Visible:=false; DockSite.BoundsRect:=AControl.BoundsRect; AControl.Parent:=DockSite; if AControl.Visible then DockSite.Visible:=true; end else begin // there are already other childs // optimize DropZone if (DropZone.ChildCount>0) and (NewOrientation in [doHorizontal,doVertical]) and ((DropZone.Orientation=NewOrientation) or (DropZone.Orientation=doNoOrient)) then begin // docking on a side of an inner node is the same as docking to a side of // a child if InsertAt in [alLeft,alTop] then DropZone:=DropZone.FirstChild else DropZone:=DropZone.GetLastChild; end; // insert a new Parent Zone if needed NeedNewParentZone:=true; if (DropZone.Parent<>nil) then begin if (DropZone.Orientation=doNoOrient) then NeedNewParentZone:=false; if (DropZone.Orientation=NewOrientation) then NeedNewParentZone:=false; end; if NeedNewParentZone then begin // insert a new zone between current DropZone.Parent and DropZone // this new zone will become the new DropZone.Parent OldParentZone:=DropZone.Parent; NewParentZone:=DockZoneClass.Create(Self,nil); if OldParentZone<>nil then OldParentZone.ReplaceChild(DropZone,NewParentZone); NewParentZone.AddAsFirstChild(DropZone); end; // adjust Orientation in tree if DropZone.Parent.Orientation=doNoOrient then DropZone.Parent.Orientation:=NewOrientation; if DropZone.Parent.Orientation<>NewOrientation then RaiseGDBException('TLazDockTree.InsertControl Inconsistency DropZone.Orientation<>NewOrientation'); // insert new node if DropZone.Parent=nil then RaiseGDBException('TLazDockTree.InsertControl Inconsistency DropZone.Parent=nil'); if InsertAt in [alLeft,alTop] then DropZone.Parent.AddAsFirstChild(NewZone) else DropZone.Parent.AddAsLastChild(NewZone); // break anchors and resize DockSite BreakAnchors(RootZone); NewBounds:=DockSite.BoundsRect; case InsertAt of alLeft: dec(NewBounds.Left,SplitterWidth+AControl.Width); alRight: inc(NewBounds.Right,SplitterWidth+AControl.Width); alTop: dec(NewBounds.Top,SplitterHeight+AControl.Height); alBottom:inc(NewBounds.Bottom,SplitterHeight+AControl.Height); else // no change end; DockSite.BoundsRect:=NewBounds; // add AControl to DockSite AControl.Visible:=false; AControl.Parent:=nil; AControl.Align:=alNone; AControl.Anchors:=[akLeft,akTop]; AControl.AnchorSide[akLeft].Control:=nil; AControl.AnchorSide[akTop].Control:=nil; AControl.AutoSize:=false; // resize control RaiseGDBException('TLazDockTree.InsertControl TODO resize control'); if NewOrientation in [doHorizontal,doVertical] then begin ASibling:=NewZone.PrevSibling; if ASibling=nil then ASibling:=NewZone.NextSibling; if ASibling<>nil then begin if NewOrientation=doHorizontal then AControl.Height:=ASibling.Height else AControl.Width:=ASibling.Width; end; end; AControl.Parent:=NewZone.GetParentControl; // Build dock layout (anchors, splitters, pages) BuildDockLayout(RootZone as TLazDockZone); end; end; procedure TLazDockTree.BuildDockLayout(Zone: TLazDockZone); begin BreakAnchors(Zone); CreateDockLayoutHelperControls(Zone); AnchorDockLayout(Zone); end; procedure TLazDockTree.FindBorderControls(Zone: TLazDockZone; Side: TAnchorKind; var List: TFPList); begin if List=nil then List:=TFPList.Create; if Zone=nil then exit; if (Zone.Splitter<>nil) and (Zone.Parent<>nil) and (Zone.Orientation=doVertical) then begin // this splitter is leftmost, topmost, bottommost if Side in [akLeft,akTop,akBottom] then List.Add(Zone.Splitter); if Side=akLeft then begin // the splitter fills the whole left side => no more controls exit; end; end; if (Zone.Splitter<>nil) and (Zone.Parent<>nil) and (Zone.Orientation=doHorizontal) then begin // this splitter is topmost, leftmost, rightmost if Side in [akTop,akLeft,akRight] then List.Add(Zone.Splitter); if Side=akTop then begin // the splitter fills the whole top side => no more controls exit; end; end; if Zone.ChildControl<>nil then begin // the ChildControl fills the whole zone (except for the splitter) List.Add(Zone.ChildControl); exit; end; if Zone.Pages<>nil then begin // the pages fills the whole zone (except for the splitter) List.Add(Zone.Pages); exit; end; // go recursively through all child zones if (Zone.Parent<>nil) and (Zone.Orientation in [doVertical,doHorizontal]) and (Zone.FirstChild<>nil) then begin if Side in [akLeft,akTop] then FindBorderControls(Zone.FirstChild as TLazDockZone,Side,List) else FindBorderControls(Zone.GetLastChild as TLazDockZone,Side,List); end; end; function TLazDockTree.FindBorderControl(Zone: TLazDockZone; Side: TAnchorKind ): TControl; var List: TFPList; begin Result:=nil; if Zone=nil then exit; List:=nil; FindBorderControls(Zone,Side,List); if (List=nil) or (List.Count=0) then Result:=DockSite else Result:=TControl(List[0]); List.Free; end; function TLazDockTree.GetAnchorControl(Zone: TLazDockZone; Side: TAnchorKind; OutSide: boolean): TControl; // find a control to anchor the Zone's Side begin if Zone=nil then begin Result:=DockSite; exit; end; if not OutSide then begin // also check the Splitter and the Page if (Side=akLeft) and (Zone.Parent<>nil) and (Zone.Parent.Orientation=doHorizontal) and (Zone.Splitter<>nil) then begin Result:=Zone.Splitter; exit; end; if (Side=akTop) and (Zone.Parent<>nil) and (Zone.Parent.Orientation=doVertical) and (Zone.Splitter<>nil) then begin Result:=Zone.Splitter; exit; end; if (Zone.Page<>nil) then begin Result:=Zone.Page; exit; end; end; // search the neigbour zones: Result:=DockSite; if (Zone.Parent=nil) then exit; case Zone.Parent.Orientation of doHorizontal: if (Side=akLeft) and (Zone.PrevSibling<>nil) then Result:=FindBorderControl(Zone.PrevSibling as TLazDockZone,akRight) else if (Side=akRight) and (Zone.NextSibling<>nil) then Result:=FindBorderControl(Zone.NextSibling as TLazDockZone,akLeft) else Result:=GetAnchorControl(Zone.Parent as TLazDockZone,Side,false); doVertical: if (Side=akTop) and (Zone.PrevSibling<>nil) then Result:=FindBorderControl(Zone.PrevSibling as TLazDockZone,akBottom) else if (Side=akBottom) and (Zone.NextSibling<>nil) then Result:=FindBorderControl(Zone.NextSibling as TLazDockZone,akTop) else Result:=GetAnchorControl(Zone.Parent as TLazDockZone,Side,false); doPages: Result:=GetAnchorControl(Zone.Parent as TLazDockZone,Side,false); end; end; { TLazDockZone } destructor TLazDockZone.Destroy; begin inherited Destroy; FreeAndNil(FSplitter); FreeAndNil(FPage); FreeAndNil(FPages); end; function TLazDockZone.GetCaption: string; begin if ChildControl<>nil then Result:=ChildControl.Caption else Result:=IntToStr(GetIndex); end; function TLazDockZone.GetParentControl: TWinControl; var Zone: TDockZone; begin Result:=nil; Zone:=Parent; while Zone<>nil do begin if Zone.Orientation=doPages then begin Result:=(Zone as TLazDockZone).Pages; exit; end; if (Zone.Parent=nil) then begin if Zone.ChildControl is TWinControl then Result:=TWinControl(Zone.ChildControl); exit; end; Zone:=Zone.Parent; end; end; { TAnchoredDockManager } procedure TAnchoredDockManager.DeleteSideSplitter(Splitter: TLazDockSplitter; Side: TAnchorKind; NewAnchorControl: TControl); var SplitterParent: TWinControl; i: Integer; CurControl: TControl; NewSideRef: TAnchorSideReference; begin //DebugLn('TAnchoredDockManager.DeleteSideSplitter Splitter=',DbgSName(Splitter),' Side=',dbgs(Side),' NewAnchorControl=',DbgSName(NewAnchorControl)); SplitterParent:=Splitter.Parent; SplitterParent.DisableAlign; try for i:=0 to SplitterParent.ControlCount-1 do begin CurControl:=SplitterParent.Controls[i]; if CurControl.AnchorSide[Side].Control=Splitter then begin CurControl.AnchorSide[Side].Control:=NewAnchorControl; if NewAnchorControl=CurControl.Parent then NewSideRef:=DefaultSideForAnchorKind[OppositeAnchor[Side]] else NewSideRef:=DefaultSideForAnchorKind[Side]; CurControl.AnchorSide[Side].Side:=NewSideRef; //DebugLn('TAnchoredDockManager.DeleteSideSplitter Anchor ',DbgSName(CurControl),'(',dbgs(Side),') to ',DbgSName(NewAnchorControl)); end; end; Splitter.Free; finally SplitterParent.EnableAlign; end; end; procedure TAnchoredDockManager.CombineSpiralSplitterPair(Splitter1, Splitter2: TLazDockSplitter); { Anchor all controls anchored to Splitter2 to Splitter1, extend Splitter1, delete Splitter2. Example: Four spiral splitters: Before: | A | ---------| | +--+ | C B | | | | | +--+ | | ---------- | D The left and right splitter will be combined to one. After: | A | -------| | C B | | |------ | D } procedure MoveAnchorSide(AControl: TControl; Side: TAnchorKind); begin if AControl.AnchorSide[Side].Control=Splitter2 then AControl.AnchorSide[Side].Control:=Splitter1; end; procedure EnlargeSplitter(Side: TAnchorKind); begin if GetAnchorDepth(Splitter1,Side)Splitter2.Parent) then RaiseGDBException('TAnchoredDockManager.CombineSpiralSplitterPair Inconsistency: Splitters not siblings'); // check splitters have same orientation LeftRightSplitter:=(Splitter1.ResizeAnchor in [akLeft,akRight]); if LeftRightSplitter<>(Splitter2.ResizeAnchor in [akLeft,akRight]) then RaiseGDBException('TAnchoredDockManager.CombineSpiralSplitterPair Inconsistency: different orientation'); ParentControl.DisableAlign; try // move incident anchors from Splitter2 to Splitter1 for i:=0 to ParentControl.ControlCount-1 do begin CurControl:=ParentControl.Controls[i]; if CurControl=Splitter1 then continue; if CurControl=Splitter2 then continue; if LeftRightSplitter then begin MoveAnchorSide(CurControl,akLeft); MoveAnchorSide(CurControl,akRight); end else begin MoveAnchorSide(CurControl,akTop); MoveAnchorSide(CurControl,akBottom); end; end; // enlarge Splitter1 if LeftRightSplitter then begin // enlarge Splitter1 to top and bottom EnlargeSplitter(akTop); EnlargeSplitter(akBottom); end else begin // enlarge Splitter1 to left and right EnlargeSplitter(akLeft); EnlargeSplitter(akRight); end; // delete Splitter2 Splitter2.Free; finally ParentControl.EnableAlign; end; end; procedure TAnchoredDockManager.DeletePage(Page: TLazDockPage); var Pages: TLazDockPages; begin DebugLn('TAnchoredDockManager.DeletePage Page=',DbgSName(Page)); Pages:=Page.PageControl; Page.Free; if Pages.PageCount=0 then DeletePages(Pages); end; procedure TAnchoredDockManager.DeletePages(Pages: TLazDockPages); begin DebugLn('TAnchoredDockManager.DeletePages Pages=',DbgSName(Pages)); if Pages.Parent<>nil then UndockControl(Pages,false); Pages.Free; end; procedure TAnchoredDockManager.DeleteDockForm(ADockForm: TLazDockForm); begin DebugLn('TAnchoredDockManager.DeleteDockForm ADockForm=',DbgSName(ADockForm)); if ADockForm.Parent<>nil then UndockControl(ADockForm,false); ADockForm.Free; end; function TAnchoredDockManager.GetAnchorDepth(AControl: TControl; Side: TAnchorKind): Integer; var NewControl: TControl; begin Result:=0; while (AControl<>nil) do begin inc(Result); if not (Side in AControl.Anchors) then break; // loose end NewControl:=AControl.AnchorSide[Side].Control; if NewControl=nil then break; // loose end if NewControl.Parent<>AControl.Parent then break; // parent end if Result>AControl.Parent.ControlCount then break; // circle AControl:=NewControl; end; end; constructor TAnchoredDockManager.Create; begin FSplitterSize:=5; end; procedure TAnchoredDockManager.BeginUpdate; begin inc(FUpdateCount); end; procedure TAnchoredDockManager.EndUpdate; begin if FUpdateCount<=0 then RaiseGDBException('TAnchoredDockManager.EndUpdate'); dec(FUpdateCount); if FUpdateCount=0 then begin end; end; procedure TAnchoredDockManager.GetControlBounds(Control: TControl; out AControlBounds: TRect); begin AControlBounds:=Control.BoundsRect; end; {------------------------------------------------------------------------------- procedure TAnchoredDockManager.DockControl(Control: TControl; InsertAt: TAlign; DropCtl: TControl); Docks Control to or into DropCtl. Control.Parent must be nil. If InsertAt in [alLeft,alTop,alRight,alBottom] then Control will be docked to the side of DropCtl. Otherwise it is docked as Page to a TLazDockPages. Docking to a side: If DockCtl.Parent=nil then a parent will be created via DropCtl.ManualFloat. Then Control is added as child to DockCtl.Parent. Then a Splitter is added. Then all three are anchored. Docking as page: if DropCtl.Parent is not a TLazDockPage then a new TLazDockPages is created and replaces DropCtl and DropCtl is added as page. Then Control is added as page. -------------------------------------------------------------------------------} procedure TAnchoredDockManager.DockControl(Control: TControl; InsertAt: TAlign; DropCtl: TControl); var Splitter: TLazDockSplitter; NewDropCtlBounds: TRect; NewControlBounds: TRect; NewDropCtlWidth: Integer; SplitterBounds: TRect; a: TAnchorKind; ControlAnchor: TAnchorKind; DropCtlAnchor: TAnchorKind; NewDropCtlHeight: Integer; SplitterWidth: LongInt; SplitterHeight: LongInt; DockPages: TLazDockPages; DropCtlPage: TLazDockPage; NewPageIndex: Integer; NewPage: TLazDockPage; NewParent: TLazDockForm; ParentDisabledAlign: Boolean; begin if Control.Parent<>nil then RaiseGDBException('TAnchoredDockManager.InsertControl Control.Parent<>nil'); // dock Control to DropCtl case InsertAt of alLeft,alTop,alRight,alBottom: begin // dock Control to a side of DropCtl // e.g. alLeft: insert Control to the left of DropCtl DropCtlAnchor:=MainAlignAnchor[InsertAt]; ControlAnchor:=OppositeAnchor[DropCtlAnchor]; ParentDisabledAlign:=false; try // make sure, there is a parent HostSite if DropCtl.Parent=nil then begin // create a TLazDockForm as new parent NewParent:=TLazDockForm.Create(Application);// starts with Visible=false NewParent.DisableAlign; ParentDisabledAlign:=true; NewParent.BoundsRect:=DropCtl.BoundsRect; // move the WindowState to the new parent if DropCtl is TCustomForm then begin NewParent.WindowState:=TCustomForm(DropCtl).WindowState; TCustomForm(DropCtl).WindowState:=wsNormal; end; // first move DropCtl to the invsible parent, so changes do not cause flicker DropCtl.Parent:=NewParent; // init anchors of DropCtl DropCtl.Align:=alNone; DropCtl.AnchorClient(0); DropCtl.Anchors:=[akLeft,akTop,akRight,akBottom]; NewParent.Visible:=true; //DebugLn('TAnchoredDockManager.DockControl DropCtl=',DbgSName(DropCtl),' NewParent.BoundsRect=',dbgs(NewParent.BoundsRect)); end else begin if (DropCtl.Parent is TLazDockForm) then begin // ok end else if (DropCtl.Parent is TLazDockPage) then begin // ok end else begin RaiseGDBException('TAnchoredDockManager.InsertControl DropCtl has invalid parent'); end; end; if not ParentDisabledAlign then begin DropCtl.Parent.DisableAlign; ParentDisabledAlign:=true; end; // create a splitter Splitter:=TLazDockSplitter.Create(Control); Splitter.Align:=alNone; Splitter.Beveled:=true; Splitter.ResizeAnchor:=ControlAnchor; //debugln('TAnchoredDockManager.InsertControl A Control.Bounds=',DbgSName(Control),dbgs(Control.BoundsRect),' DropCtl.Bounds=',DbgSName(DropCtl),dbgs(DropCtl.BoundsRect),' Splitter.Bounds=',DbgSName(Splitter),dbgs(Splitter.BoundsRect)); // calculate new bounds NewDropCtlBounds:=DropCtl.BoundsRect; NewControlBounds:=NewDropCtlBounds; if InsertAt in [alLeft,alRight] then begin SplitterWidth:=Splitter.Constraints.MinMaxWidth(SplitterSize); NewDropCtlWidth:=NewDropCtlBounds.Right-NewDropCtlBounds.Left; dec(NewDropCtlWidth,Control.Width+SplitterWidth); NewDropCtlWidth:=DropCtl.Constraints.MinMaxWidth(NewDropCtlWidth); if InsertAt=alLeft then begin // alLeft: insert Control to the left of DropCtl NewDropCtlBounds.Left:=NewDropCtlBounds.Right-NewDropCtlWidth; NewControlBounds.Right:=NewDropCtlBounds.Left-SplitterWidth; SplitterBounds:=Rect(NewControlBounds.Right,NewDropCtlBounds.Top, NewDropCtlBounds.Left,NewDropCtlBounds.Bottom); end else begin // alRight: insert Control to the right of DropCtl NewDropCtlBounds.Right:=NewDropCtlBounds.Left+NewDropCtlWidth; NewControlBounds.Left:=NewDropCtlBounds.Right+SplitterWidth; SplitterBounds:=Rect(NewDropCtlBounds.Right,NewDropCtlBounds.Top, NewControlBounds.Left,NewDropCtlBounds.Bottom); //debugln('TAnchoredDockManager.InsertControl A NewDropCtlBounds=',dbgs(NewDropCtlBounds),' NewControlBounds=',dbgs(NewControlBounds),' SplitterBounds=',dbgs(SplitterBounds)); end; end else begin SplitterHeight:=Splitter.Constraints.MinMaxHeight(SplitterSize); NewDropCtlHeight:=NewDropCtlBounds.Bottom-NewDropCtlBounds.Top; dec(NewDropCtlHeight,Control.Height+SplitterHeight); NewDropCtlHeight:=DropCtl.Constraints.MinMaxHeight(NewDropCtlHeight); if InsertAt=alTop then begin // alTop: insert Control to the top of DropCtl NewDropCtlBounds.Top:=NewDropCtlBounds.Bottom-NewDropCtlHeight; NewControlBounds.Bottom:=NewDropCtlBounds.Top-SplitterHeight; SplitterBounds:=Rect(NewDropCtlBounds.Left,NewControlBounds.Bottom, NewDropCtlBounds.Right,NewDropCtlBounds.Top); end else begin // alBottom: insert Control to the bottom of DropCtl NewDropCtlBounds.Bottom:=NewDropCtlBounds.Top+NewDropCtlHeight; NewControlBounds.Top:=NewDropCtlBounds.Bottom+SplitterHeight; SplitterBounds:=Rect(NewDropCtlBounds.Left,NewDropCtlBounds.Bottom, NewDropCtlBounds.Right,NewControlBounds.Top); end; //debugln('TAnchoredDockManager.InsertControl A NewDropCtlBounds=',dbgs(NewDropCtlBounds),' NewControlBounds=',dbgs(NewControlBounds),' SplitterBounds=',dbgs(SplitterBounds)); end; // position splitter Splitter.BoundsRect:=SplitterBounds; if InsertAt in [alLeft,alRight] then begin Splitter.AnchorSide[akTop].Assign(DropCtl.AnchorSide[akTop]); Splitter.AnchorSide[akBottom].Assign(DropCtl.AnchorSide[akBottom]); Splitter.Anchors:=[akLeft,akTop,akBottom]; end else begin Splitter.AnchorSide[akLeft].Assign(DropCtl.AnchorSide[akLeft]); Splitter.AnchorSide[akRight].Assign(DropCtl.AnchorSide[akRight]); Splitter.Anchors:=[akLeft,akTop,akRight]; end; Splitter.Parent:=DropCtl.Parent; // position Control Control.Align:=alNone; for a:=Low(TAnchorKind) to High(TAnchorKind) do Control.AnchorSide[a].Control:=nil; Control.AnchorSide[DropCtlAnchor].Assign(DropCtl.AnchorSide[DropCtlAnchor]); Control.AnchorToNeighbour(ControlAnchor,0,Splitter); if InsertAt in [alLeft,alRight] then begin Control.AnchorSide[akTop].Assign(DropCtl.AnchorSide[akTop]); Control.AnchorSide[akBottom].Assign(DropCtl.AnchorSide[akBottom]); end else begin Control.AnchorSide[akLeft].Assign(DropCtl.AnchorSide[akLeft]); Control.AnchorSide[akRight].Assign(DropCtl.AnchorSide[akRight]); end; Control.Anchors:=[akLeft,akTop,akRight,akBottom]; Control.Parent:=DropCtl.Parent; // position DropCtl DropCtl.AnchorToNeighbour(DropCtlAnchor,0,Splitter); //debugln('TAnchoredDockManager.InsertControl BEFORE ALIGNING Control.Bounds=',DbgSName(Control),dbgs(Control.BoundsRect),' DropCtl.Bounds=',DbgSName(DropCtl),dbgs(DropCtl.BoundsRect),' Splitter.Bounds=',DbgSName(Splitter),dbgs(Splitter.BoundsRect)); finally if ParentDisabledAlign then DropCtl.Parent.EnableAlign; end; //debugln('TAnchoredDockManager.InsertControl END Control.Bounds=',DbgSName(Control),dbgs(Control.BoundsRect),' DropCtl.Bounds=',DbgSName(DropCtl),dbgs(DropCtl.BoundsRect),' Splitter.Bounds=',DbgSName(Splitter),dbgs(Splitter.BoundsRect)); end; alClient: begin // docking as page DebugLn('TAnchoredDockManager.InsertControl alClient DropCtl=',DbgSName(DropCtl),' Control=',DbgSName(Control)); if not (DropCtl.Parent is TLazDockPage) then begin // create a new TLazDockPages //DebugLn('TAnchoredDockManager.InsertControl Create TLazDockPages'); DockPages:=TLazDockPages.Create(nil); if DropCtl.Parent<>nil then begin // DockCtl is a child control // => replace the anchors to and from DockCtl with the new DockPages ReplaceAnchoredControl(DropCtl,DockPages); end else begin // DockCtl has no parent // => float DockPages DockPages.ManualFloat(DropCtl.BoundsRect); end; // add DockCtl as page to DockPages DockPages.Pages.Add(DropCtl.Caption); DropCtlPage:=DockPages.Page[0]; DropCtlPage.DisableAlign; try DropCtl.Parent:=DropCtlPage; DropCtl.AnchorClient(0); finally DropCtlPage.EnableAlign; end; end; // add Control as new page behind the page of DockCtl DropCtlPage:=DropCtl.Parent as TLazDockPage; DockPages:=DropCtlPage.PageControl as TLazDockPages; NewPageIndex:=DropCtlPage.PageIndex+1; DockPages.Pages.Insert(NewPageIndex,Control.Caption); NewPage:=DockPages.Page[NewPageIndex]; NewPage.DisableAlign; try Control.Parent:=NewPage; if Control is TCustomForm then TCustomForm(Control).WindowState:=wsNormal; Control.AnchorClient(0); finally NewPage.EnableAlign; end; end; else RaiseGDBException('TAnchoredDockManager.InsertControl TODO'); end; end; {------------------------------------------------------------------------------- procedure TAnchoredDockManager.UndockControl(Control: TControl); Removes a control from a docking form. It breaks all anchors and cleans up. The created gap will be tried to fill up. It removes TLazDockSplitter, TLazDockPage and TLazDockPages if they are no longer needed. -------------------------------------------------------------------------------} procedure TAnchoredDockManager.UndockControl(Control: TControl; Float: boolean); { Examples: Search Order: 1. A TLazDockSplitter dividing only two controls: Before: |------------- | +--+ | +--- | | | | | B | +--+ | +--- |------------- The splitter will be deleted and the right control will be anchored to the left. After: |------------- | +--- | | B | +--- |------------- 2. Four spiral splitters: Before: | A | ---------| | +--+ | C B | | | | | +--+ | | ---------- | D The left and right splitter will be combined to one. After: | A | -------| | C B | | |------ | D 3. No TLazDockSplitter. Control is the only child of a TLazDockPage In this case the page will be deleted. If the TLazDockPages has no childs left, it is recursively undocked. 4. No TLazDockSplitter, Control is the only child of a TLazDockForm. The TLazDockForm is deleted and the Control is floated. This normally means: A form will simply be placed on the desktop, other controls will be docked into their DockSite. 5. Otherwise: this control was not docked. } var a: TAnchorKind; AnchorControl: TControl; AnchorSplitter: TLazDockSplitter; i: Integer; Sibling: TControl; OldAnchorControls: array[TAnchorKind] of TControl; IsSpiralSplitter: Boolean; ParentControl: TWinControl; Done: Boolean; procedure DoFinallyForParent; var OldParentControl: TWinControl; NewBounds: TRect; NewOrigin: TPoint; begin try if Float then begin NewBounds:=Control.BoundsRect; NewOrigin:=Control.ControlOrigin; OffsetRect(NewBounds,NewOrigin.X,NewOrigin.Y); Control.ManualFloat(NewBounds); end else begin Control.Parent:=nil; end; finally if (ParentControl<>nil) then begin OldParentControl:=ParentControl; ParentControl:=nil; //DebugLn('DoFinallyForParent EnableAlign for ',DbgSName(OldParentControl)); OldParentControl.EnableAlign; //OldParentControl.WriteLayoutDebugReport('X '); end; end; end; begin if Control.Parent=nil then begin // already undocked RaiseGDBException('TAnchoredDockManager.UndockControl Control.Parent=nil'); end; ParentControl:=Control.Parent; ParentControl.DisableAlign; try // break anchors Control.Align:=alNone; for a:=Low(TAnchorKind) to High(TAnchorKind) do begin OldAnchorControls[a]:=Control.AnchorSide[a].Control; Control.AnchorSide[a].Control:=nil; end; Control.Anchors:=[akLeft,akTop]; Done:=false; if not Done then begin // check if there is a splitter, that has a side with only 'Control' // anchored to it. for a:=Low(TAnchorKind) to High(TAnchorKind) do begin AnchorControl:=OldAnchorControls[a]; if AnchorControl is TLazDockSplitter then begin AnchorSplitter:=TLazDockSplitter(AnchorControl); i:=ParentControl.ControlCount-1; while i>=0 do begin Sibling:=ParentControl.Controls[i]; if (Sibling.AnchorSide[a].Control=AnchorSplitter) then begin // Sibling is anchored with the same side to the splitter // => this splitter is needed, can not be deleted. //DebugLn('TAnchoredDockManager.UndockControl Splitter still needed: ',DbgSName(AnchorSplitter),'(',dbgs(AnchorSplitter.BoundsRect),') by ',DbgSName(Sibling)); break; end; dec(i); end; if i<0 then begin // this splitter is not needed anymore //DebugLn('TAnchoredDockManager.UndockControl Splitter not needed: ',DbgSName(AnchorSplitter),'(',dbgs(AnchorSplitter.BoundsRect),')'); DeleteSideSplitter(AnchorSplitter,OppositeAnchor[a], OldAnchorControls[OppositeAnchor[a]]); Done:=true; end; end; end; end; if not Done then begin // check if there are four spiral splitters around Control IsSpiralSplitter:=true; for a:=Low(TAnchorKind) to High(TAnchorKind) do begin AnchorControl:=OldAnchorControls[a]; if (AnchorControl=nil) or (not (AnchorControl is TLazDockSplitter)) then begin IsSpiralSplitter:=false; end; end; if IsSpiralSplitter then begin CombineSpiralSplitterPair(OldAnchorControls[akLeft] as TLazDockSplitter, OldAnchorControls[akRight] as TLazDockSplitter); Done:=true; end; end; if not Done then begin // check if Control is the only child of a TLazDockPage if (ParentControl.ControlCount=1) and (ParentControl is TLazDockPage) then begin DoFinallyForParent; DeletePage(TLazDockPage(Control.Parent)); end; end; if not Done then begin // check if Control is the only child of a TLazDockForm if (ParentControl.ControlCount=1) and (ParentControl is TLazDockForm) then begin DoFinallyForParent; DeleteDockForm(TLazDockForm(ParentControl)); end; end; if not Done then begin // otherwise: keep end; finally DoFinallyForParent; end; end; procedure TAnchoredDockManager.InsertControl(Control: TControl; InsertAt: TAlign; DropCtl: TControl); begin DockControl(Control, InsertAt, DropCtl); end; function TAnchoredDockManager.EnlargeControl(Control: TControl; Side: TAnchorKind; Simulate: boolean): boolean; { If Simulate=true then it will only test if control can be enlarged. Case A: Shrink one neighbour control, enlarge Control. Two splitters are resized. |#| |# |#| |# |#| Control |# |#| |# --+#+---------+# --> --+#| Control |# ===============# ===#| |# --------------+# --+#| |# A |# A|#| |# --------------+# --+#+---------+# ================== =================== Case B: Move one neighbour splitter, enlarge Control, resize one splitter, rotate the other splitter. |#| |#| |#| |#| |#| Control |#| |#| |#| --+#+---------+#+-- --> --+#| Control |#+-- =================== ===#| |#=== --------+#+-------- --+#| |#+-- |#| B |#| |#|B |#+-------- |#| |#+-- A |#========= A|#| |#=== |#+-------- |#| |#+-- |#| C |#| |#|C --------+#+-------- --+#+---------+#+-- =================== =================== } const MinControlSize = 20; var MainSplitter: TLazDockSplitter; Side2: TAnchorKind; Side3: TAnchorKind; Side2Anchor: TControl; Side3Anchor: TControl; Parent: TWinControl; i: Integer; Sibling: TControl; CurSplitter: TLazDockSplitter; Neighbour: TControl; ShrinkSide: TAnchorKind; ParentDisabledAlign: Boolean; EnlargeSplitter: TLazDockSplitter; RotateSplitter: TLazDockSplitter; procedure ParentDisableAlign; begin if ParentDisabledAlign then exit; ParentDisabledAlign:=true; Parent.DisableAlign; end; begin Result:=false; if Control=nil then exit; DebugLn(['TAnchoredDockManager.EnlargeControl Control=',DbgSName(Control),' Side=',AnchorNames[Side]]); if Side in [akLeft,akRight] then Side2:=akTop else Side2:=akLeft; Side3:=OppositeAnchor[Side2]; if not GetLazDockSplitter(Control,Side,MainSplitter) then exit; if not GetLazDockSplitterOrParent(Control,Side2,Side2Anchor) then exit; if not GetLazDockSplitterOrParent(Control,Side3,Side3Anchor) then exit; Parent:=Control.Parent; if (Side2Anchor=Parent) and (Side3Anchor=Parent) then exit; // search controls anchored to the MainSplitter on the other side Neighbour:=nil; for i:=0 to Parent.ControlCount-1 do begin Sibling:=Parent.Controls[i]; if (not GetLazDockSplitter(Sibling,OppositeAnchor[Side],CurSplitter)) or (CurSplitter<>MainSplitter) then continue; DebugLn(['TAnchoredDockManager.EnlargeControl neighbour Sibling=',DbgSName(Sibling)]); // Sibling is anchored to MainSplitter on the other side // check if it is at the same height as Control if Side in [akTop,akBottom] then begin if (Side2Anchor is TLazDockSplitter) then begin if (Sibling.Left+Sibling.WidthSide3Anchor.Left+Side3Anchor.Width) then continue; end else begin // Side3Anchor is Parent if Sibling.Left>Control.Left+Control.Width then continue; end; end else begin if (Side2Anchor is TLazDockSplitter) then begin if (Sibling.Top+Sibling.HeightSide3Anchor.Top+Side3Anchor.Height) then continue; end else begin // Side3Anchor is Parent if Sibling.Top>Control.Top+Control.Height then continue; end; end; if Neighbour=nil then Neighbour:=Sibling else if Sibling is TLazDockSplitter then begin if Neighbour is TLazDockSplitter then begin // two splitters means, there is at least one Neighbour which can not // be shrinked exit; end; Neighbour:=Sibling; end; end; if Neighbour=nil then exit; // no neighbour found DebugLn(['TAnchoredDockManager.EnlargeControl Neighbour=',DbgSName(Neighbour)]); ParentDisabledAlign:=false; try if Neighbour is TLazDockSplitter then begin // one splitter as Neighbour RotateSplitter:=TLazDockSplitter(Neighbour); DebugLn(['TAnchoredDockManager.EnlargeControl rotate splitter RotateSplitter=',DbgSName(RotateSplitter)]); // check that all anchored controls of this splitter can be shrinked for i:=0 to Parent.ControlCount-1 do begin Sibling:=Parent.Controls[i]; if Sibling=RotateSplitter then continue; if GetLazDockSplitter(Sibling,Side2,CurSplitter) and (CurSplitter=RotateSplitter) and (not NeighbourCanBeShrinked(Control,Sibling,Side2)) then begin // this Sibling is anchored with Side2 at RotateSplitter // but can not be shrinked exit; end; if GetLazDockSplitter(Sibling,Side3,CurSplitter) and (CurSplitter=RotateSplitter) and (not NeighbourCanBeShrinked(Control,Sibling,Side3)) then begin // this Sibling is anchored with Side3 at RotateSplitter // but can not be shrinked exit; end; end; { |#| |#| |#| |#| |#| Control |#| |#| |#| --+#+---------+#+-- --> --+#| Control |#+-- =================== ===#| |#=== --------+#+-------- --+#| |#+-- |#| B |#| |#|B |#+-------- |#| |#+-- A |#========= A|#| |#=== |#+-------- |#| |#+-- |#| C |#| |#|C --------+#+-------- --+#+---------+#+-- =================== =================== } Result:=true; if not Simulate then begin ParentDisableAlign; GetLazDockSplitter(Control,OppositeAnchor[Side2],EnlargeSplitter); // enlarge Control and its two side splitters Control.AnchorSame(Side,RotateSplitter); Side2Anchor.AnchorSame(Side,RotateSplitter); Side3Anchor.AnchorSame(Side,RotateSplitter); // shrink controls anchored to RotateSplitter for i:=0 to Parent.ControlCount-1 do begin Sibling:=Parent.Controls[i]; if Sibling=RotateSplitter then continue; if GetLazDockSplitter(Sibling,Side2,CurSplitter) and (CurSplitter=RotateSplitter) then begin // this Sibling is anchored with Side2 at RotateSplitter Sibling.AnchorToNeighbour(Side2,0,Side3Anchor); end; if GetLazDockSplitter(Sibling,Side3,CurSplitter) and (CurSplitter=RotateSplitter) then begin // this Sibling is anchored with Side3 at RotateSplitter Sibling.AnchorToNeighbour(Side3,0,Side2Anchor); end; end; // rotate RotateSplitter RotateSplitter.AnchorSide[Side].Control:=nil; RotateSplitter.AnchorSide[OppositeAnchor[Side]].Control:=nil; RotateSplitter.ResizeAnchor:=Side; RotateSplitter.AnchorToNeighbour(Side2,0,Side3Anchor); RotateSplitter.AnchorSame(Side3,MainSplitter); if Side in [akLeft,akRight] then RotateSplitter.Anchors:=RotateSplitter.Anchors-[akRight]+[akLeft] else RotateSplitter.Anchors:=RotateSplitter.Anchors-[akBottom]+[akTop]; // shrink MainSplitter MainSplitter.AnchorToNeighbour(Side2,0,Side2Anchor); // reanchor controls from MainSplitter to RotateSplitter for i:=0 to Parent.ControlCount-1 do begin Sibling:=Parent.Controls[i]; if GetLazDockSplitter(Sibling,Side,CurSplitter) and (CurSplitter=MainSplitter) then begin if Side in [akLeft,akRight] then begin if Sibling.Top>Control.Top then Sibling.AnchorSide[Side].Control:=RotateSplitter; end else begin if Sibling.Left>Control.Left then Sibling.AnchorSide[Side].Control:=RotateSplitter; end; end; end; end; end else begin // shrink a neighbour control DebugLn(['TAnchoredDockManager.EnlargeControl Shrink one control: Neighbour=',DbgSName(Neighbour)]); // check if Neighbour already shares a side with Control if (Neighbour.AnchorSide[Side2].Control<>Side2Anchor) and (Neighbour.AnchorSide[Side3].Control<>Side3Anchor) then begin { Neighbour is too broad. |#| |#| |#| Control |#| --+#+---------+#+-- =================== ------------------- Neighbour ------------------- } exit; end; // check if the Neighbour can be shrinked if NeighbourCanBeShrinked(Control,Neighbour,Side2) then begin ShrinkSide:=Side2; end else if NeighbourCanBeShrinked(Control,Neighbour,Side3) then begin ShrinkSide:=Side3; end else begin // Neighbour can not be shrinked exit; end; { EnlargeSplitter ^ |#| |# |#| |# |#| Control |# |#| |# --+#+---------+# --> --+#| Control |# MainSplitter <-- ===============# ===#| |# --------------+# --+#| |# Neighbour|# N|#| |# --------------+# --+#+---------+# ================== =================== } Result:=true; if not Simulate then begin ParentDisableAlign; GetLazDockSplitter(Control,OppositeAnchor[ShrinkSide],EnlargeSplitter); Neighbour.AnchorToNeighbour(ShrinkSide,0,EnlargeSplitter); MainSplitter.AnchorToNeighbour(ShrinkSide,0,EnlargeSplitter); EnlargeSplitter.AnchorSame(Side,Neighbour); Control.AnchorSame(Side,Neighbour); end; end; finally if ParentDisabledAlign then Parent.EnableAlign; end; end; procedure TAnchoredDockManager.LoadFromStream(Stream: TStream); begin RaiseGDBException('TAnchoredDockManager.LoadFromStream TODO'); end; procedure TAnchoredDockManager.PaintSite(DC: HDC); begin RaiseGDBException('TAnchoredDockManager.PaintSite TODO'); end; procedure TAnchoredDockManager.PositionDockRect(Client, DropCtl: TControl; DropAlign: TAlign; var DockRect: TRect); begin RaiseGDBException('TAnchoredDockManager.PositionDockRect TODO'); end; procedure TAnchoredDockManager.RemoveControl(Control: TControl); begin UndockControl(Control,false); end; procedure TAnchoredDockManager.ResetBounds(Force: Boolean); begin RaiseGDBException('TAnchoredDockManager.ResetBounds TODO'); end; procedure TAnchoredDockManager.SaveToStream(Stream: TStream); begin RaiseGDBException('TAnchoredDockManager.SaveToStream TODO'); end; procedure TAnchoredDockManager.SetReplacingControl(Control: TControl); begin RaiseGDBException('TAnchoredDockManager.SetReplacingControl TODO'); end; procedure TAnchoredDockManager.ReplaceAnchoredControl(OldControl, NewControl: TControl); var a: TAnchorKind; Side: TAnchorSide; i: Integer; Sibling: TControl; begin if OldControl.Parent<>nil then begin NewControl.Parent.DisableAlign; try // put NewControl on the same Parent with the same bounds NewControl.Parent:=nil; NewControl.Align:=alNone; NewControl.BoundsRect:=OldControl.BoundsRect; NewControl.Parent:=OldControl.Parent; // copy all four AnchorSide for a:=Low(TAnchorKind) to High(TAnchorKind) do NewControl.AnchorSide[a].Assign(OldControl.AnchorSide[a]); // bend all Anchors from OldControl to NewControl for i:=0 to OldControl.Parent.ControlCount-1 do begin Sibling:=OldControl.Parent.Controls[i]; if (Sibling=NewControl) or (Sibling=OldControl) then continue; for a:=Low(TAnchorKind) to High(TAnchorKind) do begin Side:=Sibling.AnchorSide[a]; if Side.Control=OldControl then begin Side.Control:=NewControl; end; end; end; // remove OldControl from its Parent OldControl.Parent:=nil; finally NewControl.Parent.EnableAlign; end; end else begin NewControl.Parent:=nil; NewControl.Align:=alNone; NewControl.BoundsRect:=OldControl.BoundsRect; end; end; function TAnchoredDockManager.GetSplitterWidth(Splitter: TControl): integer; begin Result:=Splitter.Constraints.MinMaxWidth(SplitterSize); end; function TAnchoredDockManager.GetSplitterHeight(Splitter: TControl): integer; begin Result:=Splitter.Constraints.MinMaxHeight(SplitterSize); end; { TLazDockPage } function TLazDockPage.GetPageControl: TLazDockPages; begin Result:=Parent as TLazDockPages; end; { TLazDockForm } procedure TLazDockForm.SetMainControl(const AValue: TControl); var NewValue: TControl; begin if (AValue<>nil) and (not IsParentOf(AValue)) then raise Exception.Create('invalid main control'); NewValue:=AValue; if NewValue=nil then NewValue:=FindMainControlCandidate; if FMainControl=NewValue then exit; FMainControl:=NewValue; if FMainControl<>nil then FMainControl.FreeNotification(Self); UpdateCaption; end; procedure TLazDockForm.Notification(AComponent: TComponent; Operation: TOperation); begin if (Operation=opRemove) then begin if AComponent=FMainControl then MainControl:=nil; end; inherited Notification(AComponent, Operation); end; procedure TLazDockForm.InsertControl(AControl: TControl; Index: integer); var NewMainConrtrol: TControl; begin inherited InsertControl(AControl, Index); if FMainControl=nil then begin NewMainConrtrol:=FindMainControlCandidate; if NewMainConrtrol<>nil then MainControl:=NewMainConrtrol; end; end; function TLazDockForm.CloseQuery: boolean; // query all top level forms, if form can close function QueryForms(ParentControl: TWinControl): boolean; var i: Integer; AControl: TControl; begin for i:=0 to ParentControl.ControlCount-1 do begin AControl:=ParentControl.Controls[i]; if (AControl is TWinControl) then begin if (AControl is TCustomForm) then begin // a top level form: query and do not ask childs if (not TCustomForm(AControl).CloseQuery) then exit(false); end else if not QueryForms(TWinControl(AControl)) then // search childs for forms exit(false); end; end; Result:=true; end; begin Result:=inherited CloseQuery; if Result then Result:=QueryForms(Self); end; procedure TLazDockForm.UpdateCaption; begin if FMainControl<>nil then Caption:=FMainControl.Caption else Caption:=''; end; function TLazDockForm.FindMainControlCandidate: TControl; var BestLevel: integer; procedure FindCandidate(ParentControl: TWinControl; Level: integer); var i: Integer; AControl: TControl; ResultIsForm, ControlIsForm: boolean; begin for i:=0 to ParentControl.ControlCount-1 do begin AControl:=ParentControl.Controls[i]; if (AControl.Name<>'') and (not (AControl is TLazDockForm)) and (not (AControl is TLazDockSplitter)) and (not (AControl is TLazDockPages)) and (not (AControl is TLazDockPage)) then begin // this is a candidate // prefer forms and top level controls if (Application<>nil) and (Application.MainForm=AControl) then begin // the MainForm is the best control Result:=Application.MainForm; BestLevel:=-1; exit; end; ResultIsForm:=Result is TCustomForm; ControlIsForm:=AControl is TCustomForm; if (Result=nil) or ((not ResultIsForm) and ControlIsForm) or ((ResultIsForm=ControlIsForm) and (Level