mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-14 05:41:16 +02:00

LCL: TWinControl: refreshing RawImage data after painting on canvas. Issue #25448 ........ git-svn-id: branches/fixes_1_6@53794 -
8587 lines
301 KiB
PHP
8587 lines
301 KiB
PHP
{%MainUnit ../controls.pp}
|
|
|
|
{******************************************************************************
|
|
TWinControl
|
|
******************************************************************************
|
|
|
|
*****************************************************************************
|
|
This file is part of the Lazarus Component Library (LCL)
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
}
|
|
|
|
{$IFOPT C-}
|
|
// Uncomment for local trace
|
|
// {$C+}
|
|
// {$DEFINE ASSERT_IS_ON}
|
|
{$ENDIF}
|
|
|
|
{off $DEFINE VerboseAutoSizeCtrlData}
|
|
{off $DEFINE VerboseMouseBugfix}
|
|
{off $DEFINE VerboseCanAutoSize}
|
|
{off $DEFINE VerboseIntfSizing}
|
|
{off $DEFINE VerboseClientRectBugFix}
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
const CheckClientRectName = 'LCLInterfaceRadioGroup';
|
|
{$ENDIF}
|
|
|
|
{off $DEFINE VerboseSizeMsg}
|
|
{off $DEFINE CHECK_POSITION}
|
|
{$IFDEF CHECK_POSITION}
|
|
const CheckPostionClassName = 'xxxTBreakPropertyDlg';
|
|
const CheckPostionName = 'FakeStatusBar';
|
|
const CheckPostionParentName = 'xxxEnvVarsPage';
|
|
|
|
function CheckPosition(AControl: TControl): boolean;
|
|
begin
|
|
Result:=(CompareText(AControl.ClassName,CheckPostionClassName)=0)
|
|
or (CompareText(AControl.Name,CheckPostionName)=0)
|
|
or ((AControl.Parent<>nil)
|
|
and (CompareText(AControl.Parent.Name,CheckPostionParentName)=0));
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function IsNotAligned(AControl: TControl): boolean;
|
|
begin
|
|
Result:=(AControl.Align=alNone)
|
|
and (AControl.Anchors=[akLeft,akTop])
|
|
and (AControl.AnchorSide[akLeft].Control=nil)
|
|
and (AControl.AnchorSide[akTop].Control=nil);
|
|
end;
|
|
|
|
function IsNotAligned(AControl: TControl; ASide: TAnchorKind): boolean;
|
|
begin
|
|
Result:=(AControl.Align=alNone);
|
|
if not Result then Exit;
|
|
if ASide in [akLeft, akRight] then
|
|
Result:=(AControl.Anchors*[akLeft, akRight]=[akLeft])
|
|
and (AControl.AnchorSide[akLeft].Control=nil)
|
|
else
|
|
Result:=(AControl.Anchors*[akTop, akBottom]=[akTop])
|
|
and (AControl.AnchorSide[akBottom].Control=nil);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Autosizing Helper classes
|
|
-------------------------------------------------------------------------------}
|
|
type
|
|
TAutoSizeBoxOrientation = (asboHorizontal, asboVertical);
|
|
|
|
PAutoSizeBox = ^TAutoSizeBox;
|
|
|
|
{ TAutoSizeBox
|
|
A TAutoSizeBox is a node in a tree.
|
|
A TAutoSizeBox can be a cell. Then it is a leaf in the tree and can have a
|
|
Control.
|
|
A TAutoSizeBox can be a row or column. Then it has only one Children array.
|
|
A TAutoSizeBox can be a table. Then it has both Children arrays.
|
|
}
|
|
|
|
TAutoSizeBox = class
|
|
public
|
|
Control: TControl; // the Control of a leaf node
|
|
MinimumSize: array[TAutoSizeBoxOrientation] of integer;
|
|
MaximumSize: array[TAutoSizeBoxOrientation] of integer; // 0 means inifinte
|
|
PreferredSize: array[TAutoSizeBoxOrientation] of integer;// without theme space
|
|
LeftTop: array[TAutoSizeBoxOrientation] of integer;
|
|
BorderLeftTop: array[TAutoSizeBoxOrientation] of integer;
|
|
BorderRightBottom: array[TAutoSizeBoxOrientation] of integer;
|
|
Parent: array[TAutoSizeBoxOrientation] of TAutoSizeBox;
|
|
Index: array[TAutoSizeBoxOrientation] of Integer; // index in parent or grandparent
|
|
ChildCount: array[TAutoSizeBoxOrientation] of Integer;
|
|
Children: array[TAutoSizeBoxOrientation] of PAutoSizeBox;
|
|
NewControlBounds: TRect;
|
|
|
|
// for nodes
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
procedure SetControl(AControl: TControl);
|
|
procedure ApplyChildSizingBorders(ChildSizing: TControlChildSizing);
|
|
|
|
// for rows and columns
|
|
procedure AllocateChildsArray(Orientation: TAutoSizeBoxOrientation;
|
|
NewChildCount: Integer);
|
|
procedure InitSums;
|
|
procedure SumLine(Orientation: TAutoSizeBoxOrientation;
|
|
DoInit: boolean);
|
|
procedure ResizeChildren(ChildSizing: TControlChildSizing;
|
|
Orientation: TAutoSizeBoxOrientation;
|
|
TargetSize: integer);
|
|
procedure ComputeLeftTops(Orientation: TAutoSizeBoxOrientation);
|
|
|
|
// for tables
|
|
procedure AllocateTable(ColCount, RowCount: Integer);
|
|
procedure SetTableControls(ListOfControls: TFPList;
|
|
ChildSizing: TControlChildSizing;
|
|
BiDiMode: TBiDiMode);
|
|
procedure SumTable;
|
|
procedure ResizeTable(ChildSizing: TControlChildSizing;
|
|
TargetWidth, TargetHeight: integer);
|
|
procedure AlignToRight(TargetWidth: integer);
|
|
procedure ComputeTableControlBounds(ChildSizing: TControlChildSizing;
|
|
BiDiMode: TBiDiMode);
|
|
function SetTableControlBounds(ChildSizing: TControlChildSizing
|
|
): boolean;// true if changed
|
|
function AlignControlsInTable(ListOfControls: TFPList;
|
|
ChildSizing: TControlChildSizing;
|
|
BiDiMode: TBiDiMode;
|
|
TargetWidth, TargetHeight: integer;
|
|
Apply: boolean): boolean;// true if changed
|
|
|
|
// debugging
|
|
procedure WriteDebugReport(const Title: string);
|
|
end;
|
|
|
|
{ TAutoSizeCtrlData
|
|
This class is used by the auto size algorithm, to compute the preferred
|
|
size of a control given the preferred sizes of its children.
|
|
Hints about the algorithm:
|
|
First it builds a graph of dependencies. That means, for every side
|
|
(Left,Top,Right,Bottom) of each child control the anchor control and
|
|
space is calculated. Anchor means here direct and indirect anchors.
|
|
Indirect anchors are defined by the Align property.
|
|
For example a control with Align=alTop is anchored left to the parent,
|
|
right to the parent and top to either the parent or another alTop control.
|
|
Then it searches for circles and other invalid combinations and repairs
|
|
them.
|
|
}
|
|
|
|
TAutoSizeCtrlData = class;
|
|
|
|
TAutoSizeSideDataState = (
|
|
assdfInvalid,
|
|
assdfComputing,
|
|
assdfUncomputable,// e.g. if [akLeft,akRight]*Anchors = []
|
|
assdfValid
|
|
);
|
|
TAutoSizeSideDistDirection = (
|
|
assddLeftTop,
|
|
assddRightBottom
|
|
);
|
|
|
|
TAutoSizeSideData = record
|
|
CtrlData: TAutoSizeCtrlData;
|
|
Side: TAnchorSideReference;
|
|
Space: integer;
|
|
Distance: array[TAutoSizeSideDistDirection] of integer;
|
|
DistanceState: array[TAutoSizeSideDistDirection] of TAutoSizeSideDataState;
|
|
end;
|
|
|
|
TAutoSizeCtrlData = class
|
|
private
|
|
FChilds: TAvgLvlTree;// tree of TAutoSizeCtrlData
|
|
function GetChildren(AControl: TControl): TAutoSizeCtrlData;
|
|
procedure DoMoveNonAlignedChildren(Side: TAnchorKind;
|
|
var MoveDiff: integer; FindMinimum: boolean);
|
|
procedure SetupNonAlignedChildren(MoveNonAlignedChildrenLeft,
|
|
MoveNonAlignedChildrenTop: boolean);
|
|
procedure AlignChildren;
|
|
procedure SetupSpace;
|
|
function ComputePositions: boolean;// false if recomputation is needed (a property changed)
|
|
public
|
|
Control: TControl; // the Control of a leaf node
|
|
WinControl: TWinControl;// the Control as TWinControl (else nil)
|
|
ChildCount: integer;
|
|
Visible: boolean;//= Control.IsControlVisible
|
|
PreferredSize: array[TAutoSizeBoxOrientation] of integer;// without theme space
|
|
Borders: array[TAnchorKind] of integer;
|
|
AdjustedClientBorders: array[TAnchorKind] of integer;// the borderspace created by WinControl.AdjustClientRect
|
|
Sides: array[TAnchorKind] of TAutoSizeSideData;
|
|
BaseBounds: TRect;
|
|
BaseParentClientSize: TSize;
|
|
constructor Create(AControl: TControl; IsParent: boolean = true);
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
procedure ComputePreferredClientArea(MoveNonAlignedChildrenLeft,
|
|
MoveNonAlignedChildrenTop: boolean;
|
|
out MoveNonAlignedToLeft, MoveNonAlignedToTop,
|
|
PreferredClientWidth, PreferredClientHeight: integer);
|
|
procedure FixControlProperties(Child: TControl);
|
|
procedure ClearSides;
|
|
procedure SetFixedLeftTop(ChildData: TAutoSizeCtrlData; Side: TAnchorKind;
|
|
NewLeftTop: integer);
|
|
property Children[AControl: TControl]: TAutoSizeCtrlData read GetChildren; default;
|
|
procedure WriteDebugReport(const Title, Prefix: string; OnlyVisible: boolean = true);
|
|
end;
|
|
|
|
const
|
|
SizeBoxOrthogonal: array[TAutoSizeBoxOrientation] of TAutoSizeBoxOrientation
|
|
= (asboVertical,asboHorizontal);
|
|
{AutoSizeSideDataStateNames: array[TAutoSizeSideDataState] of shortstring = (
|
|
'assdfInvalid',
|
|
'assdfComputing',
|
|
'assdfUncomputable',
|
|
'assdfValid'
|
|
);}
|
|
{$IFNDEF DisableChecks}
|
|
AutoSizeSideDistDirectionNames: array[TAutoSizeSideDistDirection] of shortstring = (
|
|
'assddLeftTop',
|
|
'assddRightBottom'
|
|
);
|
|
{$ENDIF}
|
|
|
|
function CompareAutoSizeCtrlDatas(Data1, Data2: Pointer): integer;
|
|
var
|
|
Control1: TControl;
|
|
Control2: TControl;
|
|
begin
|
|
Control1:=TAutoSizeCtrlData(Data1).Control;
|
|
Control2:=TAutoSizeCtrlData(Data2).Control;
|
|
if Pointer(Control1)>Pointer(Control2) then
|
|
Result:=1
|
|
else if Pointer(Control1)<Pointer(Control2) then
|
|
Result:=-1
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
function CompareControlWithAutoSizeCtrlData(AControl, AData: Pointer): integer;
|
|
var
|
|
Control1: TControl;
|
|
Control2: TControl;
|
|
begin
|
|
Control1:=TControl(AControl);
|
|
Control2:=TAutoSizeCtrlData(AData).Control;
|
|
if Pointer(Control1)>Pointer(Control2) then
|
|
Result:=1
|
|
else if Pointer(Control1)<Pointer(Control2) then
|
|
Result:=-1
|
|
else
|
|
Result:=0;
|
|
end;
|
|
|
|
|
|
{ TAutoSizeCtrlData }
|
|
|
|
function TAutoSizeCtrlData.GetChildren(AControl: TControl): TAutoSizeCtrlData;
|
|
var
|
|
AVLNode: TAvgLvlTreeNode;
|
|
begin
|
|
if AControl=nil then exit(nil);
|
|
if AControl=Control then RaiseGDBException('TAutoSizeCtrlData.GetChilds');
|
|
if FChilds=nil then
|
|
FChilds:=TAvgLvlTree.Create(@CompareAutoSizeCtrlDatas);
|
|
AVLNode:=FChilds.FindKey(AControl,@CompareControlWithAutoSizeCtrlData);
|
|
if AVLNode<>nil then
|
|
Result:=TAutoSizeCtrlData(AVLNode.Data)
|
|
else begin
|
|
Result:=TAutoSizeCtrlData.Create(AControl,false);
|
|
FChilds.Add(Result);
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.AlignChildren;
|
|
var
|
|
AlignList: TFPList;
|
|
AlignBoundaryControls: array[TAnchorKind] of TAutoSizeCtrlData;
|
|
|
|
procedure DoAlign(TheAlign: TAlign);
|
|
var
|
|
Child: TControl;
|
|
i: Integer;
|
|
ChildData: TAutoSizeCtrlData;
|
|
a: TAnchorKind;
|
|
begin
|
|
WinControl.CreateControlAlignList(TheAlign, AlignList, nil);
|
|
for i := 0 to AlignList.Count - 1 do
|
|
begin
|
|
Child := TControl(AlignList[i]);
|
|
ChildData := Children[Child];
|
|
//DebugLn('DoAlign ',DbgSName(Child),' ',dbgs(Child.Align));
|
|
|
|
for a := Low(TAnchorKind) to High(TAnchorKind) do
|
|
if a in AnchorAlign[TheAlign] then
|
|
begin
|
|
ChildData.Sides[a].CtrlData := AlignBoundaryControls[a];
|
|
if (a in [akLeft, akTop]) = (ChildData.Sides[a].CtrlData = Self) then
|
|
ChildData.Sides[a].Side := asrLeft
|
|
else
|
|
ChildData.Sides[a].Side := asrRight;
|
|
//DebugLn('DoAlign ',DbgSName(Child),' ',dbgs(a),' ',dbgs(a,ChildData.Sides[a].Side));
|
|
end;
|
|
|
|
case TheAlign of
|
|
alTop: AlignBoundaryControls[akTop] := ChildData;
|
|
alBottom: AlignBoundaryControls[akBottom] := ChildData;
|
|
alLeft: AlignBoundaryControls[akLeft] := ChildData;
|
|
alRight: AlignBoundaryControls[akRight] := ChildData;
|
|
alClient: ; // Delphi compatibility: multiple alClient controls overlap
|
|
end;
|
|
{DebugLn(['DoAlign AlignBoundaryControls:',
|
|
' Left=',DbgSName(AlignBoundaryControls[akLeft].Control),
|
|
' Top=',DbgSName(AlignBoundaryControls[akTop].Control),
|
|
' Right=',DbgSName(AlignBoundaryControls[akRight].Control),
|
|
' Bottom=',DbgSName(AlignBoundaryControls[akBottom].Control) ]);}
|
|
end;
|
|
end;
|
|
|
|
var
|
|
a: TAnchorKind;
|
|
begin
|
|
if ChildCount = 0 then exit;
|
|
AlignList := TFPList.Create;
|
|
try
|
|
// align and anchor child controls
|
|
for a := Low(TAnchorKind) to High(TAnchorKind) do
|
|
AlignBoundaryControls[a] := Self;
|
|
DoAlign(alTop);
|
|
DoAlign(alBottom);
|
|
DoAlign(alLeft);
|
|
DoAlign(alRight);
|
|
DoAlign(alClient);
|
|
finally
|
|
AlignList.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.SetupSpace;
|
|
var
|
|
i: Integer;
|
|
Child: TControl;
|
|
ChildData: TAutoSizeCtrlData;
|
|
a: TAnchorKind;
|
|
SiblingData: TAutoSizeCtrlData;
|
|
NewSpace: LongInt;
|
|
begin
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if ChildData.Sides[a].CtrlData=Self then begin
|
|
// aligned or anchored to parent
|
|
if a in [akLeft,akRight] then begin
|
|
ChildData.Sides[a].Space:=Max(WinControl.ChildSizing.LeftRightSpacing,
|
|
ChildData.Borders[a]);
|
|
end else begin
|
|
ChildData.Sides[a].Space:=Max(WinControl.ChildSizing.TopBottomSpacing,
|
|
ChildData.Borders[a]);
|
|
end;
|
|
inc(ChildData.Sides[a].Space,AdjustedClientBorders[a]);
|
|
end else if ChildData.Sides[a].CtrlData<>nil then begin
|
|
SiblingData:=ChildData.Sides[a].CtrlData;
|
|
// aligned or anchored to a sibling
|
|
if a in [akLeft,akTop] then begin
|
|
NewSpace:=ChildData.Borders[a];
|
|
if ChildData.Sides[a].Side=asrRight then begin
|
|
NewSpace:=Max(NewSpace,WinControl.ChildSizing.HorizontalSpacing);
|
|
if a=akLeft then
|
|
NewSpace:=Max(NewSpace,SiblingData.Borders[akRight])
|
|
else
|
|
NewSpace:=Max(NewSpace,SiblingData.Borders[akBottom]);
|
|
end else if ChildData.Sides[a].Side=asrLeft then
|
|
|
|
else if ChildData.Sides[a].Side=asrCenter then
|
|
NewSpace:=0;
|
|
ChildData.Sides[a].Space:=NewSpace;
|
|
end else begin
|
|
NewSpace:=ChildData.Borders[a];
|
|
if ChildData.Sides[a].Side=asrTop then begin
|
|
NewSpace:=Max(NewSpace,WinControl.ChildSizing.VerticalSpacing);
|
|
if a=akRight then
|
|
NewSpace:=Max(NewSpace,SiblingData.Borders[akLeft])
|
|
else
|
|
NewSpace:=Max(NewSpace,SiblingData.Borders[akTop]);
|
|
end else if ChildData.Sides[a].Side=asrBottom then
|
|
|
|
else if ChildData.Sides[a].Side=asrCenter then
|
|
NewSpace:=0;
|
|
ChildData.Sides[a].Space:=NewSpace;
|
|
end;
|
|
end else if a in Child.Anchors then begin
|
|
// anchored to parent via BaseBounds
|
|
if a in [akLeft,akTop] then begin
|
|
ChildData.Sides[a].Side:=asrRight;
|
|
end else begin
|
|
ChildData.Sides[a].Side:=asrLeft;
|
|
end;
|
|
case a of
|
|
akTop: ChildData.Sides[a].Space:=ChildData.BaseBounds.Top;
|
|
akLeft: ChildData.Sides[a].Space:=ChildData.BaseBounds.Left;
|
|
akRight: ChildData.Sides[a].Space:=
|
|
ChildData.BaseParentClientSize.cx-ChildData.BaseBounds.Right;
|
|
akBottom: ChildData.Sides[a].Space:=
|
|
ChildData.BaseParentClientSize.cy-ChildData.BaseBounds.Bottom;
|
|
end;
|
|
end else begin
|
|
// not anchored => use borderspacing
|
|
if a in [akLeft,akTop] then
|
|
ChildData.Sides[a].Side:=asrRight
|
|
else
|
|
ChildData.Sides[a].Side:=asrLeft;
|
|
if a in [akLeft,akRight] then begin
|
|
ChildData.Sides[a].Space:=
|
|
Max(WinControl.ChildSizing.LeftRightSpacing,
|
|
ChildData.Borders[a]);
|
|
end else begin
|
|
ChildData.Sides[a].Space:=
|
|
Max(WinControl.ChildSizing.TopBottomSpacing,
|
|
ChildData.Borders[a]);
|
|
end;
|
|
inc(ChildData.Sides[a].Space,AdjustedClientBorders[a]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TAutoSizeCtrlData.ComputePositions: boolean;
|
|
type
|
|
TComputeResult = (
|
|
crSuccess,
|
|
crCircle,
|
|
crFixedCircled
|
|
);
|
|
|
|
function ComputePosition(ChildData: TAutoSizeCtrlData; Side: TAnchorKind;
|
|
Direction: TAutoSizeSideDistDirection): TComputeResult;
|
|
var
|
|
OppositeSide: TAnchorKind;
|
|
NewDist: LongInt;
|
|
SiblingData: TAutoSizeCtrlData;
|
|
NeededSiblingSides: TAnchors;
|
|
a: TAnchorKind;
|
|
Child: TControl;
|
|
IsSideLeftTop, IsOutwards, IsParentInwards: boolean;
|
|
CurAnchors: TAnchors;
|
|
CurSize: LongInt;
|
|
FoundSides: TAnchors;
|
|
AddPreferredSize: Boolean;
|
|
begin
|
|
if ChildData.Sides[Side].DistanceState[Direction]
|
|
in [assdfValid,assdfUncomputable]
|
|
then
|
|
exit(crSuccess); // already computed
|
|
if ChildData.Sides[Side].DistanceState[Direction]=assdfComputing then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['TAutoSizeCtrlData.ComputePositions.ComputePosition CIRCLE detected ',DbgSName(ChildData.Control),' ',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
|
|
{$ENDIF}
|
|
exit(crCircle); // there is a circle
|
|
end;
|
|
if ChildData.Sides[Side].DistanceState[Direction]<>assdfInvalid then
|
|
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition <>assdfInvalid');
|
|
|
|
// mark as computing
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfComputing;
|
|
OppositeSide:=OppositeAnchor[Side];
|
|
|
|
// try to find good distances to the client area for this side
|
|
Child:=ChildData.Control;
|
|
CurAnchors:=Child.Anchors;
|
|
if Child.Align in [alLeft,alTop,alRight,alBottom,alClient] then
|
|
CurAnchors:=CurAnchors+AnchorAlign[Child.Align];
|
|
if (Side in CurAnchors) then begin
|
|
// this side is anchored
|
|
SiblingData:=ChildData.Sides[Side].CtrlData;
|
|
NewDist:=0;
|
|
if (SiblingData=nil) or (SiblingData=Self) then begin
|
|
// this side is anchored to parent
|
|
// Note: SiblingData=nil can happen, if the reference control
|
|
// is not visible => use parent as default anchor
|
|
case ChildData.Sides[Side].Side of
|
|
asrLeft,asrRight: // asrTop=asrLeft,asrBottom=asrRight
|
|
begin
|
|
IsSideLeftTop:=(Side in [akLeft,akTop]);
|
|
IsOutwards:=(Direction=assddLeftTop)=IsSideLeftTop;
|
|
IsParentInwards:=(SiblingData=nil)
|
|
or ((ChildData.Sides[Side].Side=asrLeft)=IsSideLeftTop);
|
|
if not IsParentInwards then begin
|
|
// for example: left side is anchored to right side of parent
|
|
//DebugLn(['ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' parent outside anchored, Direction=',AutoSizeSideDistDirectionNames[Direction],' => assdfUncomputable']);
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
|
|
end else if IsOutwards then begin
|
|
// for example: left side is anchored to left side of parent
|
|
// and left distance is needed
|
|
ChildData.Sides[Side].Distance[Direction]:=ChildData.Sides[Side].Space;
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
|
|
end else begin
|
|
// for example: left side is anchored to left side of parent,
|
|
// right distance is needed
|
|
AddPreferredSize:=true;
|
|
if OppositeSide in CurAnchors then begin
|
|
// compute opposite side first
|
|
Result:=ComputePosition(ChildData,OppositeSide,Direction);
|
|
if Result<>crSuccess then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['ComputePosition FAILED opposite side: ',DbgSName(Child),' ',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
if ChildData.Sides[OppositeSide].DistanceState[Direction]<>assdfValid
|
|
then begin
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
|
|
exit;
|
|
end;
|
|
NewDist:=ChildData.Sides[OppositeSide].Distance[Direction];
|
|
if (ChildData.Sides[OppositeSide].CtrlData<>nil)
|
|
and (ChildData.Sides[OppositeSide].CtrlData<>Self)
|
|
then begin
|
|
// opposite side is anchored to a sibling
|
|
if ((OppositeSide in [akLeft,akTop])
|
|
and (ChildData.Sides[OppositeSide].Side<>asrRight))
|
|
or ((OppositeSide in [akRight,akBottom])
|
|
and (ChildData.Sides[OppositeSide].Side<>asrLeft))
|
|
then
|
|
AddPreferredSize:=false;
|
|
end;
|
|
end else begin
|
|
NewDist:=ChildData.Sides[OppositeSide].Space;
|
|
end;
|
|
if AddPreferredSize then begin
|
|
if Side in [akLeft,akRight] then
|
|
inc(NewDist,ChildData.PreferredSize[asboHorizontal])
|
|
else
|
|
inc(NewDist,ChildData.PreferredSize[asboVertical]);
|
|
end;
|
|
ChildData.Sides[Side].Distance[Direction]:=NewDist;
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
|
|
end;
|
|
end;
|
|
asrCenter:
|
|
begin
|
|
//DebugLn(['ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' parent anchored, Direction=',AutoSizeSideDistDirectionNames[Direction],' => assdfUncomputable']);
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
|
|
end;
|
|
else
|
|
RaiseGDBException('');
|
|
end;
|
|
end else begin
|
|
// this side is anchored to a sibling
|
|
// first compute needed sides of sibling
|
|
NeededSiblingSides:=[];
|
|
case ChildData.Sides[Side].Side of
|
|
asrLeft: // Note: asrLeft=asrTop
|
|
if Side in [akLeft,akRight] then begin
|
|
Include(NeededSiblingSides,akLeft);
|
|
end else begin
|
|
Include(NeededSiblingSides,akTop);
|
|
end;
|
|
asrRight: // Note: asrRight=asrBottom
|
|
if Side in [akLeft,akRight] then begin
|
|
Include(NeededSiblingSides,akRight);
|
|
end else begin
|
|
Include(NeededSiblingSides,akBottom);
|
|
end;
|
|
asrCenter:
|
|
if Side in [akLeft,akRight] then begin
|
|
NeededSiblingSides:=NeededSiblingSides+[akLeft,akRight];
|
|
end else begin
|
|
NeededSiblingSides:=NeededSiblingSides+[akTop,akBottom];
|
|
end;
|
|
end;
|
|
FoundSides:=[];
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if a in NeededSiblingSides then begin
|
|
Result:=ComputePosition(SiblingData,a,Direction);
|
|
if (Result=crCircle)
|
|
and ((Child.Align in [alNone,alCustom])
|
|
or (not (Side in AnchorAlign[Child.Align]))) then
|
|
begin
|
|
// there is a circle and it can be broken => break it
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['ComputePosition breaking CIRCLE ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' ',dbgs(a),' ',AutoSizeSideDistDirectionNames[Direction]]);
|
|
{$ENDIF}
|
|
Child.Anchors:=Child.Anchors-[Side];
|
|
Result:=crFixedCircled;
|
|
end;
|
|
if Result<>crSuccess then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['ComputePosition FAILED sibling dependency: ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' a=',dbgs(a),' ',AutoSizeSideDistDirectionNames[Direction]]);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
if SiblingData.Sides[a].DistanceState[Direction]=assdfValid then
|
|
Include(FoundSides,a);
|
|
end;
|
|
end;
|
|
if FoundSides=[] then begin
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
|
|
exit(crSuccess);
|
|
end;
|
|
|
|
// this side is anchored to a sibling and some needed sibling sides are valid
|
|
case ChildData.Sides[Side].Side of
|
|
asrLeft,asrRight: // asrLeft=asrTop,asrRight=asrBottom
|
|
begin
|
|
if ChildData.Sides[Side].Side=asrLeft then begin
|
|
if Side in [akLeft,akRight] then
|
|
NewDist:=SiblingData.Sides[akLeft].Distance[Direction]
|
|
else
|
|
NewDist:=SiblingData.Sides[akTop].Distance[Direction];
|
|
end else begin
|
|
if Side in [akLeft,akRight] then
|
|
NewDist:=SiblingData.Sides[akRight].Distance[Direction]
|
|
else
|
|
NewDist:=SiblingData.Sides[akBottom].Distance[Direction];
|
|
end;
|
|
if (Direction=assddLeftTop)=(Side in [akLeft,akTop]) then
|
|
inc(NewDist,ChildData.Sides[Side].Space)
|
|
else
|
|
dec(NewDist,ChildData.Sides[Side].Space);
|
|
//DebugLn(['ComputePosition ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction],' NewDist=',NewDist]);
|
|
end;
|
|
asrCenter:
|
|
if Side in [akLeft,akRight] then begin
|
|
if FoundSides=[akLeft,akRight] then begin
|
|
NewDist:=((SiblingData.Sides[akLeft].Distance[Direction]
|
|
+SiblingData.Sides[akRight].Distance[Direction]) div 2);
|
|
end else if (FoundSides=[akLeft]) then begin
|
|
NewDist:=SiblingData.Sides[akLeft].Distance[Direction]
|
|
+(SiblingData.PreferredSize[asboHorizontal] div 2);
|
|
end else begin
|
|
NewDist:=SiblingData.Sides[akRight].Distance[Direction]
|
|
-(SiblingData.PreferredSize[asboHorizontal] div 2);
|
|
end;
|
|
//DebugLn(['ComputePosition BEFORE ',DbgSName(Child),' center to ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' FoundSides=',dbgs(FoundSides),' NewDist=',NewDist,' Direction=',AutoSizeSideDistDirectionNames[Direction],' PreferredSize=',ChildData.PreferredSize[asboHorizontal]]);
|
|
dec(NewDist,ChildData.PreferredSize[asboHorizontal] div 2);
|
|
// use at least the size of the child
|
|
if (Side=akLeft)=(Direction=assddRightBottom) then
|
|
NewDist:=Max(NewDist,ChildData.PreferredSize[asboHorizontal]);
|
|
//DebugLn(['ComputePosition AFTER ',DbgSName(Child),' center to ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' FoundSides=',dbgs(FoundSides),' NewDist=',NewDist,' Direction=',AutoSizeSideDistDirectionNames[Direction],' PreferredSize=',ChildData.PreferredSize[asboHorizontal]]);
|
|
end else begin
|
|
if FoundSides=[akTop,akBottom] then begin
|
|
NewDist:=((SiblingData.Sides[akTop].Distance[Direction]
|
|
+SiblingData.Sides[akBottom].Distance[Direction]) div 2);
|
|
end else if (FoundSides=[akTop]) then begin
|
|
NewDist:=SiblingData.Sides[akTop].Distance[Direction]
|
|
+(SiblingData.PreferredSize[asboVertical] div 2);
|
|
end else begin
|
|
NewDist:=SiblingData.Sides[akBottom].Distance[Direction]
|
|
-(SiblingData.PreferredSize[asboVertical] div 2);
|
|
end;
|
|
dec(NewDist,ChildData.PreferredSize[asboVertical] div 2);
|
|
// use at least the size of the child
|
|
if (Side=akTop)=(Direction=assddRightBottom) then
|
|
NewDist:=Max(NewDist,ChildData.PreferredSize[asboVertical]);
|
|
end;
|
|
end;
|
|
ChildData.Sides[Side].Distance[Direction]:=NewDist;
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
|
|
|
|
if (OppositeSide in CurAnchors)
|
|
and ((Direction=assddLeftTop) <> (Side in [akLeft,akTop])) then begin
|
|
// the opposite side is anchored too
|
|
// use the maximum of both anchors
|
|
Result:=ComputePosition(ChildData,OppositeSide,Direction);
|
|
if Result<>crSuccess then begin
|
|
//DebugLn(['ComputePosition (side anchored) FAILED computing opposite side: ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
|
|
exit;
|
|
end;
|
|
case ChildData.Sides[OppositeSide].DistanceState[Direction] of
|
|
assdfValid:
|
|
begin
|
|
// opposite side +- preferred size
|
|
NewDist:=ChildData.Sides[OppositeSide].Distance[Direction];
|
|
CurSize:=0;
|
|
if ((OppositeSide in [akLeft,akTop])
|
|
and (ChildData.Sides[OppositeSide].Side=asrRight))
|
|
or ((OppositeSide in [akRight,akBottom])
|
|
and (ChildData.Sides[OppositeSide].Side=asrLeft))
|
|
then begin
|
|
if Side in [akLeft,akRight] then
|
|
CurSize:=ChildData.PreferredSize[asboHorizontal]
|
|
else
|
|
CurSize:=ChildData.PreferredSize[asboVertical];
|
|
end;
|
|
inc(NewDist,CurSize);
|
|
// check if opposite side needs a bigger distance
|
|
if ChildData.Sides[Side].Distance[Direction]<NewDist then
|
|
ChildData.Sides[Side].Distance[Direction]:=NewDist;
|
|
end;
|
|
assdfUncomputable: ; // no problem, there is already a value
|
|
else
|
|
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition assdfValid,assdfUncomputable');
|
|
end;
|
|
end;
|
|
end;
|
|
end else if (OppositeSide in CurAnchors)
|
|
and ((Direction=assddLeftTop) <> (Side in [akLeft,akTop])) then begin
|
|
// this side is not anchored, but the opposite is
|
|
// e.g. control is anchored to the right
|
|
// => compute the opposite side first
|
|
Result:=ComputePosition(ChildData,OppositeSide,Direction);
|
|
if Result<>crSuccess then begin
|
|
//DebugLn(['ComputePosition (side not anchored) FAILED computing opposite side: ',DbgSName(Child),' - ',DbgSName(SiblingData.Control),' Side=',dbgs(Side),' ',AutoSizeSideDistDirectionNames[Direction]]);
|
|
exit;
|
|
end;
|
|
case ChildData.Sides[OppositeSide].DistanceState[Direction] of
|
|
assdfValid:
|
|
begin
|
|
// opposite side +- preferred size
|
|
NewDist:=ChildData.Sides[OppositeSide].Distance[Direction];
|
|
if Side in [akLeft,akRight] then
|
|
CurSize:=ChildData.PreferredSize[asboHorizontal]
|
|
else
|
|
CurSize:=ChildData.PreferredSize[asboVertical];
|
|
inc(NewDist,CurSize);
|
|
ChildData.Sides[Side].Distance[Direction]:=NewDist;
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
|
|
end;
|
|
assdfUncomputable:
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
|
|
else
|
|
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition assdfValid,assdfUncomputable');
|
|
end;
|
|
end else begin
|
|
// not anchored
|
|
if (Direction=assddLeftTop) = (Side in [akLeft,akTop]) then begin
|
|
NewDist:=ChildData.Sides[Side].Space;
|
|
ChildData.Sides[Side].Distance[Direction]:=NewDist;
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfValid;
|
|
end else begin
|
|
//DebugLn(['ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' not anchored, Direction=',AutoSizeSideDistDirectionNames[Direction],' => assdfUncomputable']);
|
|
ChildData.Sides[Side].DistanceState[Direction]:=assdfUncomputable;
|
|
end;
|
|
end;
|
|
if not (ChildData.Sides[Side].DistanceState[Direction]
|
|
in [assdfUncomputable,assdfValid])
|
|
then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['TAutoSizeCtrlData.ComputePositions.ComputePosition ',DbgSName(Child),' Side=',dbgs(Side),' Direction=',AutoSizeSideDistDirectionNames[Direction]]);
|
|
{$ENDIF}
|
|
raise Exception.Create('TAutoSizeCtrlData.ComputePositions.ComputePosition assdfValid,assdfUncomputable');
|
|
end;
|
|
Result:=crSuccess;
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
Child: TControl;
|
|
ChildData: TAutoSizeCtrlData;
|
|
a: TAnchorKind;
|
|
begin
|
|
Result:=false;
|
|
// for every side try to find a good distance to the client area
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
if not ChildData.Visible then continue;
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if ComputePosition(ChildData,a,assddLeftTop)<>crSuccess then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['TAutoSizeCtrlData.ComputePositions Failed to compute LeftTop ',DbgSName(Child),' ',dbgs(a)]);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
if ComputePosition(ChildData,a,assddRightBottom)<>crSuccess then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['TAutoSizeCtrlData.ComputePositions Failed to compute RightBottom ',DbgSName(Child),' ',dbgs(a)]);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
//WriteDebugReport('ComputePositons',' ');
|
|
Result:=true;
|
|
end;
|
|
|
|
constructor TAutoSizeCtrlData.Create(AControl: TControl; IsParent: boolean);
|
|
const
|
|
BigInteger = High(Integer) div 4;
|
|
var
|
|
CurBorders: TRect;
|
|
a: TAnchorKind;
|
|
AdjustedClientRect: TRect;
|
|
r: TRect;
|
|
begin
|
|
//DebugLn(['TAutoSizeCtrlData.Create ',DbgSName(AControl)]);
|
|
Control:=AControl;
|
|
if Control is TWinControl then begin
|
|
WinControl:=TWinControl(Control);
|
|
ChildCount:=WinControl.ControlCount;
|
|
end else
|
|
ChildCount:=0;
|
|
Visible:=Control.IsControlVisible;
|
|
Control.BorderSpacing.GetSpaceAround(CurBorders);
|
|
Borders[akLeft]:=CurBorders.Left;
|
|
Borders[akTop]:=CurBorders.Top;
|
|
Borders[akRight]:=CurBorders.Right;
|
|
Borders[akBottom]:=CurBorders.Bottom;
|
|
BaseBounds:=Control.BaseBounds;
|
|
if (BaseBounds.Left=BaseBounds.Right)
|
|
and (BaseBounds.Top=BaseBounds.Bottom) then
|
|
BaseBounds:=Control.BoundsRect;
|
|
BaseParentClientSize:=Control.BaseParentClientSize;
|
|
|
|
if (WinControl<>nil) and IsParent then begin
|
|
AdjustedClientRect:=Rect(0,0,BigInteger,BigInteger);
|
|
WinControl.AdjustClientRect(AdjustedClientRect);
|
|
AdjustedClientBorders[akLeft]:=AdjustedClientRect.Left;
|
|
AdjustedClientBorders[akTop]:=AdjustedClientRect.Top;
|
|
AdjustedClientBorders[akRight]:=BigInteger-AdjustedClientRect.Right;
|
|
AdjustedClientBorders[akBottom]:=BigInteger-AdjustedClientRect.Bottom;
|
|
end else begin
|
|
for a:=low(TAnchorKind) to high(TAnchorKind) do
|
|
AdjustedClientBorders[a]:=0;
|
|
if (BaseParentClientSize.cx=0) and (BaseParentClientSize.cy=0) then begin
|
|
r:=Control.Parent.GetLogicalClientRect;
|
|
BaseParentClientSize.cx:=r.Right;
|
|
BaseParentClientSize.cy:=r.Bottom;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
destructor TAutoSizeCtrlData.Destroy;
|
|
begin
|
|
Clear;
|
|
FreeAndNil(FChilds);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.Clear;
|
|
begin
|
|
ClearSides;
|
|
if FChilds<>nil then
|
|
FChilds.FreeAndClear;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.DoMoveNonAlignedChildren(Side: TAnchorKind;
|
|
var MoveDiff: integer; FindMinimum: boolean);
|
|
var
|
|
i: Integer;
|
|
Child: TControl;
|
|
MoveDiffValid: Boolean;
|
|
ChildData: TAutoSizeCtrlData;
|
|
AddSpace: LongInt;
|
|
Position: Integer;
|
|
begin
|
|
MoveDiffValid:=false;
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
if not ChildData.Visible then continue;
|
|
if IsNotAligned(Child, Side) then begin
|
|
// this is a non aligned control
|
|
//DebugLn(['TAutoSizeCtrlData.DoMoveNonAlignedChilds Child=',DbgSName(Child),' Side=',dbgs(Side)]);
|
|
if FindMinimum then begin
|
|
AddSpace:=Child.BorderSpacing.GetSideSpace(Side);
|
|
if Side=akLeft then
|
|
AddSpace:=Max(AddSpace,WinControl.ChildSizing.LeftRightSpacing)
|
|
else
|
|
AddSpace:=Max(AddSpace,WinControl.ChildSizing.TopBottomSpacing);
|
|
Position:=Child.GetSidePosition(Side)
|
|
-AddSpace
|
|
-AdjustedClientBorders[Side];
|
|
if (not MoveDiffValid) or (MoveDiff>Position) then
|
|
begin
|
|
MoveDiff:=Position;
|
|
MoveDiffValid:=true;
|
|
end;
|
|
end else begin
|
|
SetFixedLeftTop(ChildData,Side,Child.GetSidePosition(Side)-MoveDiff);
|
|
end;
|
|
end else if (Child.Align=alCustom)
|
|
and (Side in AnchorAlign[alCustom]) then begin
|
|
if FindMinimum then begin
|
|
// no auto move
|
|
end else begin
|
|
// don't move alCustom, but use them for bounds computation
|
|
SetFixedLeftTop(ChildData,Side,Child.GetSidePosition(Side));
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.SetupNonAlignedChildren(MoveNonAlignedChildrenLeft,
|
|
MoveNonAlignedChildrenTop: boolean);
|
|
var
|
|
ChildSizing: TControlChildSizing;
|
|
Box: TAutoSizeBox;
|
|
y: Integer;
|
|
RowBox: TAutoSizeBox;
|
|
x: Integer;
|
|
ControlBox: TAutoSizeBox;
|
|
Child: TControl;
|
|
NewBounds: TRect;
|
|
ChildData: TAutoSizeCtrlData;
|
|
MoveDiff: Integer;
|
|
AlignList: TFPList;
|
|
r: TRect;
|
|
i: Integer;
|
|
begin
|
|
if ChildCount=0 then exit;
|
|
if WinControl.ChildSizing.Layout=cclNone then begin
|
|
// move the non-aligned controls (i.e. not aligned or fixed anchored)
|
|
// Find the leftmost and topmost of those controls
|
|
MoveDiff:=0;
|
|
DoMoveNonAlignedChildren(akLeft,MoveDiff,true);
|
|
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea akLeft MoveDiff=',MoveDiff]);
|
|
if not MoveNonAlignedChildrenLeft then MoveDiff:=0;
|
|
DoMoveNonAlignedChildren(akLeft,MoveDiff,false);
|
|
MoveDiff:=0;
|
|
DoMoveNonAlignedChildren(akTop,MoveDiff,true);
|
|
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea akTop MoveDiff=',MoveDiff]);
|
|
if not MoveNonAlignedChildrenTop then MoveDiff:=0;
|
|
DoMoveNonAlignedChildren(akTop,MoveDiff,false);
|
|
end else begin
|
|
// there is an automatic layout for non aligned children
|
|
// use the layout engine, but with static values
|
|
ChildSizing:=nil;
|
|
Box:=nil;
|
|
AlignList:=TFPList.Create;
|
|
try
|
|
for i:=0 to WinControl.ControlCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
if Child.IsControlVisible and IsNotAligned(Child) then
|
|
AlignList.Add(Child);
|
|
end;
|
|
if AlignList.Count=0 then exit;
|
|
ChildSizing:=TControlChildSizing.Create(nil);
|
|
Box:=TAutoSizeBox.Create;
|
|
// copy current ChildSizing ...
|
|
ChildSizing.Assign(WinControl.ChildSizing);
|
|
// ... and change it to static layout (i.e. independent of parent size)
|
|
ChildSizing.ShrinkHorizontal:=crsAnchorAligning;
|
|
ChildSizing.EnlargeHorizontal:=crsAnchorAligning;
|
|
ChildSizing.ShrinkVertical:=crsAnchorAligning;
|
|
ChildSizing.EnlargeVertical:=crsAnchorAligning;
|
|
// compute static layout
|
|
r:=WinControl.GetLogicalClientRect;
|
|
Box.AlignControlsInTable(AlignList,ChildSizing,WinControl.BiDiMode,
|
|
r.Right,r.Bottom,false);
|
|
//Box.WriteDebugReport('TAutoSizeCtrlData.SetupNonAlignedChilds');
|
|
// transfer the coords of the layout
|
|
for y:=0 to Box.ChildCount[asboVertical]-1 do begin
|
|
RowBox:=Box.Children[asboVertical][y];
|
|
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
|
|
ControlBox:=RowBox.Children[asboHorizontal][x];
|
|
Child:=ControlBox.Control;
|
|
if Child=nil then continue;
|
|
NewBounds:=ControlBox.NewControlBounds;
|
|
//DebugLn(['TAutoSizeCtrlData.SetupNonAlignedChilds ',DbgSName(Child),' ',dbgs(NewBounds)]);
|
|
ChildData:=Children[Child];
|
|
// set left
|
|
SetFixedLeftTop(ChildData,akLeft,NewBounds.Left);
|
|
// set width
|
|
ChildData.PreferredSize[asboHorizontal]:=NewBounds.Right-NewBounds.Left;
|
|
// set top
|
|
SetFixedLeftTop(ChildData,akTop,NewBounds.Top);
|
|
// set height
|
|
ChildData.PreferredSize[asboVertical]:=NewBounds.Bottom-NewBounds.Top;
|
|
end;
|
|
end;
|
|
finally
|
|
ChildSizing.Free;
|
|
Box.Free;
|
|
AlignList.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.ComputePreferredClientArea(
|
|
MoveNonAlignedChildrenLeft, MoveNonAlignedChildrenTop: boolean; out
|
|
MoveNonAlignedToLeft, MoveNonAlignedToTop, PreferredClientWidth,
|
|
PreferredClientHeight: integer);
|
|
{ if MoveNonAlignedChilds=true then all non-aligned children will be moved in
|
|
parallel, so that at least one child is positioned left most and one child
|
|
is positioned top most.
|
|
|
|
Type of controls:
|
|
1. layout: the left and top side of the control has only designed position
|
|
and Parent.ChildSizing.Layout <> cclNone.
|
|
That means: Align=alNone, Anchors=[akLeft,akTop],
|
|
AnchorSide[akLeft/akTop].Control=nil, Parent.ChildSizing.Layout <> cclNone
|
|
2. non-aligned: the left+top side of the control has only a designed position.
|
|
That means: Align=alNone, akLeft is set, AnchorSide[akLeft].Control=nil
|
|
and Parent.ChildSizing.Layout=cclNone
|
|
Same for akTop.
|
|
3. Aligned: Align<>alNone
|
|
These are put consecutively into the remaining space.
|
|
BorderSpacing and AdjustClientRect defines the space.
|
|
The aligned sides automatically set the Anchors and the AnchorSide.Control
|
|
to nil.
|
|
alLeft,alRight,alTop,alBottom have one free side, which can be anchored.
|
|
4. centered: akLeft and akRight are not set
|
|
5. one side anchored: akLeft is set and akRight is not
|
|
OR akRight is set and akLeft is not
|
|
5.1 anchored to a side (asrLeft,asrRight)
|
|
5.2 anchored to a center (asrCenter)
|
|
6. both sides anchored: akLeft and akRight not
|
|
Note: asrCenter is not allowed here
|
|
|
|
Circles and invalid combinations will be automatically fixed.
|
|
}
|
|
|
|
procedure InitPreferredSizes;
|
|
var
|
|
i: Integer;
|
|
Child: TControl;
|
|
ChildData: TAutoSizeCtrlData;
|
|
CurAnchors: TAnchors;
|
|
CurPreferredWidth: integer;
|
|
CurPreferredHeight: integer;
|
|
UseCurrentWidth: Boolean;
|
|
UseCurrentHeight: Boolean;
|
|
NewWidth: LongInt;
|
|
NewHeight: LongInt;
|
|
begin
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
if ChildData.Visible then begin
|
|
CurAnchors:=Child.Anchors;
|
|
if Child.Align in [alLeft,alRight,alTop,alBottom,alClient] then
|
|
CurAnchors:=CurAnchors+AnchorAlign[Child.Align];
|
|
// check if the current Width and/or Height of the Child control can be
|
|
// used. For example: The current Width can be used, if it is independent
|
|
// of the parent's width.
|
|
UseCurrentWidth:=true;
|
|
if Child.AutoSize
|
|
or ([akLeft,akRight]*CurAnchors=[akLeft,akRight]) then
|
|
UseCurrentWidth:=false;
|
|
UseCurrentHeight:=true;
|
|
if Child.AutoSize
|
|
or ([akTop,akBottom]*CurAnchors=[akTop,akBottom]) then
|
|
UseCurrentHeight:=false;
|
|
|
|
if (not UseCurrentWidth) or (not UseCurrentHeight) then
|
|
Child.GetPreferredSize(CurPreferredWidth,CurPreferredHeight,true,true);
|
|
|
|
//if Child.Name='OtherInfoGroupBox' then debugln(['InitPreferredSizes ',DbgSName(Child),' Bounds=',dbgs(Child.BoundsRect),' Anchors=',dbgs(Child.Anchors),' CurAnchors=',dbgs(CurAnchors),' UseW=',UseCurrentWidth,' UseH=',UseCurrentHeight,' Pref=',CurPreferredWidth,'x',CurPreferredHeight]);
|
|
if UseCurrentWidth then
|
|
NewWidth:=Child.Width
|
|
else if (CurPreferredWidth>0)
|
|
or ((CurPreferredWidth=0) and (csAutoSize0x0 in Child.ControlStyle)) then
|
|
NewWidth:=CurPreferredWidth
|
|
else
|
|
NewWidth:=Max(1,Child.GetDefaultWidth);
|
|
NewWidth:=Child.Constraints.MinMaxWidth(NewWidth);
|
|
|
|
if UseCurrentHeight then
|
|
NewHeight:=Child.Height
|
|
else if (CurPreferredHeight>0)
|
|
or ((CurPreferredHeight=0) and (csAutoSize0x0 in Child.ControlStyle)) then
|
|
NewHeight:=CurPreferredHeight
|
|
else
|
|
NewHeight:=Max(1,Child.GetDefaultHeight);
|
|
NewHeight:=Child.Constraints.MinMaxHeight(NewHeight);
|
|
end else begin
|
|
NewWidth:=0;
|
|
NewHeight:=0;
|
|
end;
|
|
|
|
ChildData.PreferredSize[asboHorizontal]:=NewWidth;
|
|
ChildData.PreferredSize[asboVertical]:=NewHeight;
|
|
//DebugLn(['InitPreferredSizes Child=',DbgSName(Child),' PrefSize=',NewWidth,',',NewHeight]);
|
|
end;
|
|
end;
|
|
|
|
procedure GetSideAnchor(ChildData: TAutoSizeCtrlData; a: TAnchorKind);
|
|
var
|
|
Child: TControl;
|
|
ReferenceControl: TControl;
|
|
ReferenceSide: TAnchorSideReference;
|
|
Position: Integer;
|
|
begin
|
|
Child:=ChildData.Control;
|
|
Child.AnchorSide[a].GetSidePosition(ReferenceControl,ReferenceSide,Position);
|
|
//DebugLn(['GetSideAnchor Child=',DbgSName(Child),', a=',dbgs(a),' ReferenceControl=',DbgSName(ReferenceControl),' ReferenceSide=',dbgs(a,ReferenceSide)]);
|
|
if ReferenceControl=nil then begin
|
|
// invalid anchor
|
|
// => anchor to parent
|
|
ChildData.Sides[a].CtrlData:=Self;
|
|
if a in [akLeft,akTop] then
|
|
ChildData.Sides[a].Side:=asrLeft
|
|
else
|
|
ChildData.Sides[a].Side:=asrRight;
|
|
exit;
|
|
end;
|
|
if ReferenceControl=Control then
|
|
ChildData.Sides[a].CtrlData:=Self
|
|
else if (ReferenceControl<>nil) and (ReferenceControl.Parent=Control) then
|
|
ChildData.Sides[a].CtrlData:=Children[ReferenceControl];
|
|
ChildData.Sides[a].Side:=ReferenceSide;
|
|
//if ChildData.Sides[a].CtrlData<>nil then DebugLn(['GetSideAnchor Child=',DbgSName(Child),', a=',dbgs(a),' ReferenceControl=',DbgSName(ChildData.Sides[a].CtrlData.Control),' ReferenceSide=',dbgs(a,ChildData.Sides[a].Side)]);
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
VisibleCount: Integer;
|
|
Child: TControl;
|
|
ChildData: TAutoSizeCtrlData;
|
|
a: TAnchorKind;
|
|
CurNeededClientWH: Integer;
|
|
begin
|
|
PreferredClientWidth:=0;
|
|
PreferredClientHeight:=0;
|
|
MoveNonAlignedToLeft:=0;
|
|
MoveNonAlignedToTop:=0;
|
|
|
|
if ChildCount=0 then exit;
|
|
|
|
// fix control properties
|
|
// check if there are visible children
|
|
VisibleCount:=0;
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
FixControlProperties(Child);
|
|
ChildData:=Children[Child];
|
|
if ChildData.Visible then
|
|
inc(VisibleCount);
|
|
end;
|
|
//DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea ',DbgSName(Control),' VisibleCount=',VisibleCount]);
|
|
if VisibleCount=0 then begin
|
|
// nothing to do
|
|
exit;
|
|
end;
|
|
|
|
InitPreferredSizes;
|
|
|
|
repeat
|
|
// init dependencies
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
ChildData.ClearSides;
|
|
if not ChildData.Visible then continue;
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
ChildData.Sides[a].Side:=asrLeft;
|
|
if (Child.Align in [alLeft,alRight,alTop,alBottom,alClient])
|
|
and (a in AnchorAlign[Child.Align]) then begin
|
|
// this is an aligned side
|
|
// => the dependencies will be setup later in AlignChilds
|
|
end else if a in Child.Anchors then begin
|
|
// this is an anchored side
|
|
GetSideAnchor(ChildData,a);
|
|
end else begin
|
|
// this is a dangling side
|
|
end;
|
|
end;
|
|
end;
|
|
//WriteDebugReport('anchored','');
|
|
|
|
SetupNonAlignedChildren(MoveNonAlignedChildrenLeft,MoveNonAlignedChildrenTop);
|
|
//WriteDebugReport('nonaligned','');
|
|
// setup the dependencies for Aligned controls
|
|
AlignChildren;
|
|
//WriteDebugReport('aligned','');
|
|
|
|
// setup space for dependencies
|
|
SetupSpace;
|
|
{$IFDEF VerboseAutoSizeCtrlData}
|
|
WriteDebugReport('Space completed','');
|
|
{$ENDIF}
|
|
|
|
// calculate the needed positions for all children
|
|
until ComputePositions;
|
|
|
|
{$IFDEF VerboseAutoSizeCtrlData}
|
|
if WinControl.ClassName='TScrollBox' then
|
|
WriteDebugReport('Positions completed','');
|
|
{$ENDIF}
|
|
|
|
// compute needed clientwidth/clientheight
|
|
for i:=0 to ChildCount-1 do begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
if not ChildData.Visible then continue;
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if (ChildData.Sides[a].DistanceState[assddLeftTop]=assdfValid)
|
|
and (ChildData.Sides[a].DistanceState[assddRightBottom]=assdfValid)
|
|
then begin
|
|
CurNeededClientWH:=ChildData.Sides[a].Distance[assddLeftTop]
|
|
+ChildData.Sides[a].Distance[assddRightBottom];
|
|
if a in [akLeft,akRight] then begin
|
|
if PreferredClientWidth<CurNeededClientWH then
|
|
PreferredClientWidth:=CurNeededClientWH;
|
|
end else begin
|
|
if PreferredClientHeight<CurNeededClientWH then
|
|
PreferredClientHeight:=CurNeededClientWH;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// compute needed MoveNonAlignedToLeft,MoveNonAlignedToTop
|
|
if MoveNonAlignedChildrenLeft or MoveNonAlignedChildrenTop then
|
|
begin
|
|
MoveNonAlignedToLeft:=Low(integer);
|
|
MoveNonAlignedToTop:=Low(integer);
|
|
for i:=0 to ChildCount-1 do
|
|
begin
|
|
Child:=WinControl.Controls[i];
|
|
ChildData:=Children[Child];
|
|
if not Child.IsControlVisible then continue;
|
|
if IsNotAligned(Child, akLeft) then
|
|
begin
|
|
if MoveNonAlignedChildrenLeft
|
|
and (ChildData.Sides[akLeft].DistanceState[assddLeftTop]=assdfValid) then
|
|
MoveNonAlignedToLeft:=Max(MoveNonAlignedToLeft,
|
|
Child.Left-ChildData.Sides[akLeft].Distance[assddLeftTop]);
|
|
{ the below is only correct, if PreferredClientWidth is realized.
|
|
if (ChildData.Sides[akLeft].DistanceState[assddRightBottom]=assdfValid) then
|
|
MoveNonAlignedToLeft:=Min(MoveNonAlignedToLeft,
|
|
Child.Left
|
|
-(PreferredClientWidth
|
|
-ChildData.Sides[akLeft].Distance[assddRightBottom]));}
|
|
end;
|
|
if IsNotAligned(Child, akTop) then
|
|
begin
|
|
if MoveNonAlignedChildrenTop
|
|
and (ChildData.Sides[akTop].DistanceState[assddLeftTop]=assdfValid) then
|
|
MoveNonAlignedToTop:=Max(MoveNonAlignedToTop,
|
|
Child.Top-ChildData.Sides[akTop].Distance[assddLeftTop]);
|
|
{ the below is only correct, if PreferredClientWidth is realized.
|
|
if (ChildData.Sides[akTop].DistanceState[assddRightBottom]=assdfValid) then
|
|
MoveNonAlignedToTop:=Min(MoveNonAlignedToTop,
|
|
Child.Top
|
|
-(PreferredClientHeight
|
|
-ChildData.Sides[akTop].Distance[assddRightBottom]));}
|
|
end;
|
|
end;
|
|
if MoveNonAlignedToLeft=Low(integer) then MoveNonAlignedToLeft:=0;
|
|
if MoveNonAlignedToTop=Low(integer) then MoveNonAlignedToTop:=0;
|
|
end;
|
|
|
|
{$IFDEF VerboseAutoSizeCtrlData}
|
|
//if WinControl.ClassName='TProjectVersionInfoOptionsFrame' then
|
|
DebugLn(['TAutoSizeCtrlData.ComputePreferredClientArea END ',DbgSName(Control),' PreferredClientWidth/height=',PreferredClientWidth,',',PreferredClientHeight]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.FixControlProperties(Child: TControl);
|
|
var
|
|
a: TAnchorKind;
|
|
begin
|
|
// check that all anchor-controls are siblings or the parent
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if Child.AnchorSide[a].Control=nil then continue;
|
|
if Child.AnchorSide[a].Control=Control then continue;
|
|
if (Child.AnchorSide[a].Control=Child)
|
|
or (Child.AnchorSide[a].Control.Parent<>Control) then begin
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['TAutoSizeCtrlData.FixControlProperties ',DbgSName(Child),' a=',dbgs(a),' old=',DbgSName(Child.AnchorSide[a].Control),' new=nil']);
|
|
{$ENDIF}
|
|
Child.AnchorSide[a].Control:=nil;
|
|
end;
|
|
end;
|
|
|
|
if Child.Align in [alLeft,alRight,alTop,alBottom,alClient] then begin
|
|
// the aligned sides must be anchored
|
|
Child.Anchors:=Child.Anchors+AnchorAlign[Child.Align];
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if a in AnchorAlign[Child.Align] then begin
|
|
// the aligned sides can not be anchored to a control
|
|
{$IFNDEF DisableChecks}
|
|
if Child.AnchorSide[a].Control<>nil then
|
|
DebugLn(['TAutoSizeCtrlData.FixControlProperties aligned sides can not be anchored ',DbgSName(Child),' a=',dbgs(a),' old=',DbgSName(Child.AnchorSide[a].Control),' new=nil']);
|
|
{$ENDIF}
|
|
Child.AnchorSide[a].Control:=nil;
|
|
end;
|
|
if Child.AnchorSide[a].Side=asrCenter then begin
|
|
// an aligned control can not be centered
|
|
{$IFNDEF DisableChecks}
|
|
DebugLn(['TAutoSizeCtrlData.FixControlProperties aligned control can not be centered ',DbgSName(Child),' a=',dbgs(a)]);
|
|
{$ENDIF}
|
|
Child.AnchorSide[a].Side:=asrLeft;
|
|
if not (a in AnchorAlign[Child.Align]) then begin
|
|
Child.Anchors:=Child.Anchors-[a];
|
|
Child.AnchorSide[a].Control:=nil;
|
|
end;
|
|
end;
|
|
end;
|
|
end else begin
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
if (a in Child.Anchors)
|
|
and (Child.AnchorSide[a].Side=asrCenter) then begin
|
|
if Child.AnchorSide[a].Control<>nil then begin
|
|
// the control should be centered relative to another control
|
|
if a in [akLeft,akTop] then begin
|
|
// un-anchor the other side
|
|
{$IFNDEF DisableChecks}
|
|
if OppositeAnchor[a] in Child.Anchors then
|
|
DebugLn(['TAutoSizeCtrlData.FixControlProperties control is center-anchored -> unanchor opposite side: ',DbgSName(Child),' a=',dbgs(a)]);
|
|
{$ENDIF}
|
|
Child.Anchors:=Child.Anchors-[OppositeAnchor[a]];
|
|
Child.AnchorSide[OppositeAnchor[a]].Control:=nil;
|
|
end else begin
|
|
// the centering was setup via the right,bottom
|
|
// => normalize it to center via the Left,Top
|
|
DebugLn(['TAutoSizeCtrlData.FixControlProperties control is center-anchored -> normalize it to use Left,Top instead of Bottom,Right: ',DbgSName(Child),' a=',dbgs(a)]);
|
|
Child.AnchorSide[OppositeAnchor[a]].Control:=Child.AnchorSide[a].Control;
|
|
Child.AnchorSide[OppositeAnchor[a]].Side:=asrCenter;
|
|
Child.AnchorSide[a].Control:=nil;
|
|
Child.AnchorSide[a].Side:=asrLeft;
|
|
Child.Anchors:=Child.Anchors+[OppositeAnchor[a]]-[a];
|
|
end;
|
|
end else begin
|
|
// the asrCenter is not active => ok
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.ClearSides;
|
|
var
|
|
a: TAnchorKind;
|
|
d: TAutoSizeSideDistDirection;
|
|
begin
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
FillChar(Sides[a],SizeOf(TAutoSizeSideData),0);
|
|
for d:=Low(TAutoSizeSideDistDirection) to High(TAutoSizeSideDistDirection) do
|
|
Sides[a].DistanceState[d]:=assdfInvalid;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.SetFixedLeftTop(ChildData: TAutoSizeCtrlData;
|
|
Side: TAnchorKind; NewLeftTop: integer);
|
|
begin
|
|
ChildData.Sides[Side].CtrlData:=Self;
|
|
ChildData.Sides[Side].Side:=asrLeft;
|
|
ChildData.Sides[Side].Space:=NewLeftTop;
|
|
ChildData.Sides[Side].Distance[assddLeftTop]:=NewLeftTop;
|
|
ChildData.Sides[Side].DistanceState[assddLeftTop]:=assdfValid;
|
|
end;
|
|
|
|
procedure TAutoSizeCtrlData.WriteDebugReport(const Title, Prefix: string;
|
|
OnlyVisible: boolean);
|
|
|
|
function GetDistance(a: TAnchorKind; d: TAutoSizeSideDistDirection): string;
|
|
begin
|
|
case Sides[a].DistanceState[d] of
|
|
assdfInvalid: Result:='invalid';
|
|
assdfComputing: Result:='computing';
|
|
assdfUncomputable: Result:='uncomputable';
|
|
assdfValid: Result:=dbgs(Sides[a].Distance[d]);
|
|
else Result:='???';
|
|
end;
|
|
end;
|
|
|
|
function GetSideControl(a: TAnchorKind): string;
|
|
begin
|
|
if Sides[a].CtrlData<>nil then
|
|
Result:=DbgSName(Sides[a].CtrlData.Control)
|
|
else
|
|
Result:='nil';
|
|
end;
|
|
|
|
var
|
|
a: TAnchorKind;
|
|
i: Integer;
|
|
begin
|
|
if Title<>'' then
|
|
DebugLn([Prefix,'TAutoSizeCtrlData.WriteDebugReport ',Title]);
|
|
DebugLn([Prefix,' Control=',DbgSName(Control),' ChildCount=',ChildCount,' Visible=',Visible,' Anchors=',dbgs(Control.Anchors),' Align=',dbgs(Control.Align)]);
|
|
Debugln([Prefix,' PreferredSize=',PreferredSize[asboHorizontal],',',PreferredSize[asboVertical]]);
|
|
DebugLn([Prefix,' Borders=l=',Borders[akLeft],',t=',Borders[akTop],',r=',Borders[akRight],',b=',Borders[akBottom]]);
|
|
DebugLn([Prefix,' AdjustedClientBorders=l=',AdjustedClientBorders[akLeft],',t=',AdjustedClientBorders[akTop],',r=',AdjustedClientBorders[akRight],',b=',AdjustedClientBorders[akBottom]]);
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do begin
|
|
DebugLn([Prefix,' Side ',dbgs(a),' Control=',GetSideControl(a),
|
|
' RefSide=',dbgs(a,Sides[a].Side),
|
|
' Space=',Sides[a].Space,
|
|
' DistLT=',GetDistance(a,assddLeftTop),
|
|
' DistBR=',GetDistance(a,assddRightBottom)]);
|
|
end;
|
|
for i:=0 to ChildCount-1 do
|
|
if WinControl.Controls[i].Visible or (not OnlyVisible) then
|
|
Children[WinControl.Controls[i]].WriteDebugReport('',Prefix+dbgs(i)+': ');
|
|
end;
|
|
|
|
{ TAutoSizeBox }
|
|
|
|
procedure TAutoSizeBox.SetControl(AControl: TControl);
|
|
var
|
|
Border: TRect;
|
|
AutoSize0x0: Boolean;
|
|
IsPrefWidthValid: Boolean;
|
|
IsPrefHeightValid: Boolean;
|
|
begin
|
|
Control:=AControl;
|
|
MinimumSize[asboHorizontal]:=Control.Constraints.EffectiveMinWidth;
|
|
MinimumSize[asboVertical]:=Control.Constraints.EffectiveMinHeight;
|
|
MaximumSize[asboHorizontal]:=Control.Constraints.EffectiveMaxWidth;
|
|
MaximumSize[asboVertical]:=Control.Constraints.EffectiveMaxHeight;
|
|
Control.GetPreferredSize(PreferredSize[asboHorizontal],
|
|
PreferredSize[asboVertical],
|
|
true, // without constraints
|
|
true // with theme space
|
|
);
|
|
//DebugLn(['TAutoSizeBox.SetControl ',DbgSName(Control),' ',PreferredSize[asboHorizontal]]);
|
|
AutoSize0x0:=csAutoSize0x0 in Control.ControlStyle;
|
|
IsPrefWidthValid:=(PreferredSize[asboHorizontal]>0)
|
|
or (AutoSize0x0 and (PreferredSize[asboHorizontal]=0));
|
|
IsPrefHeightValid:=(PreferredSize[asboVertical]>0)
|
|
or (AutoSize0x0 and (PreferredSize[asboVertical]=0));
|
|
|
|
// apply constraints
|
|
if IsPrefWidthValid then
|
|
PreferredSize[asboHorizontal]:=
|
|
Control.Constraints.MinMaxWidth(PreferredSize[asboHorizontal]);
|
|
if IsPrefHeightValid then
|
|
PreferredSize[asboVertical]:=
|
|
Control.Constraints.MinMaxHeight(PreferredSize[asboVertical]);
|
|
|
|
if IsPrefWidthValid
|
|
and (Control.AutoSize or (Control.BorderSpacing.CellAlignHorizontal<>ccaFill))
|
|
then begin
|
|
// the control.width is fixed to its preferred width
|
|
MaximumSize[asboHorizontal]:=PreferredSize[asboHorizontal];
|
|
end;
|
|
if IsPrefHeightValid
|
|
and (Control.AutoSize or (Control.BorderSpacing.CellAlignVertical<>ccaFill))
|
|
then begin
|
|
// the control.height is fixed to its preferred height
|
|
MaximumSize[asboVertical]:=PreferredSize[asboVertical];
|
|
end;
|
|
|
|
// if no preferred size is valid use the class defaults
|
|
if not IsPrefWidthValid then
|
|
PreferredSize[asboHorizontal]:=
|
|
Control.Constraints.MinMaxWidth(Control.GetControlClassDefaultSize.CX);
|
|
if not IsPrefHeightValid then
|
|
PreferredSize[asboVertical]:=
|
|
Control.Constraints.MinMaxHeight(Control.GetControlClassDefaultSize.CX);
|
|
|
|
//DebugLn(['TAutoSizeBox.SetControl ',DbgSName(Control),' ',PreferredSize[asboHorizontal]]);
|
|
Control.BorderSpacing.GetSpaceAround(Border);
|
|
BorderLeftTop[asboHorizontal]:=Border.Left;
|
|
BorderLeftTop[asboVertical]:=Border.Top;
|
|
BorderRightBottom[asboHorizontal]:=Border.Right;
|
|
BorderRightBottom[asboVertical]:=Border.Bottom;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.AllocateChildsArray(Orientation: TAutoSizeBoxOrientation;
|
|
NewChildCount: Integer);
|
|
var
|
|
Size: Integer;
|
|
begin
|
|
Size:=NewChildCount*SizeOf(Pointer);
|
|
ReallocMem(Children[Orientation],Size);
|
|
if Size>0 then
|
|
FillChar(Children[Orientation][0],Size,0);
|
|
ChildCount[Orientation]:=NewChildCount;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.AllocateTable(ColCount, RowCount: Integer);
|
|
{ This creates a ColCount x RowCount number of cells,
|
|
and a Row of Columns and a Column of Rows.
|
|
|
|
+-++-++-++-+ +----------+
|
|
| || || || | | |
|
|
| || || || | +----------+
|
|
| || || || | +----------+
|
|
| || || || | | |
|
|
| || || || | +----------+
|
|
| || || || | +----------+
|
|
| || || || | | |
|
|
+-++-++-++-+ +----------+
|
|
|
|
}
|
|
var
|
|
x, y: Integer;
|
|
RowBox: TAutoSizeBox;
|
|
ColBox: TAutoSizeBox;
|
|
CellBox: TAutoSizeBox;
|
|
begin
|
|
AllocateChildsArray(asboHorizontal,ColCount);
|
|
AllocateChildsArray(asboVertical,RowCount);
|
|
// create columns
|
|
for x:=0 to ColCount-1 do begin
|
|
ColBox:=TAutoSizeBox.Create;
|
|
Children[asboHorizontal][x]:=ColBox;
|
|
ColBox.AllocateChildsArray(asboVertical,RowCount);
|
|
ColBox.Parent[asboHorizontal]:=Self;
|
|
ColBox.Index[asboHorizontal]:=x;
|
|
ColBox.Index[asboVertical]:=-1;
|
|
end;
|
|
// create rows
|
|
for y:=0 to RowCount-1 do begin
|
|
RowBox:=TAutoSizeBox.Create;
|
|
Children[asboVertical][y]:=RowBox;
|
|
RowBox.AllocateChildsArray(asboHorizontal,ColCount);
|
|
RowBox.Parent[asboVertical]:=Self;
|
|
RowBox.Index[asboHorizontal]:=-1;
|
|
RowBox.Index[asboVertical]:=y;
|
|
end;
|
|
// create cells
|
|
for y:=0 to RowCount-1 do begin
|
|
RowBox:=Children[asboVertical][y];
|
|
for x:=0 to ColCount-1 do begin
|
|
ColBox:=Children[asboHorizontal][x];
|
|
CellBox:=TAutoSizeBox.Create;
|
|
RowBox.Children[asboHorizontal][x]:=CellBox;
|
|
ColBox.Children[asboVertical][y]:=CellBox;
|
|
CellBox.Parent[asboHorizontal]:=RowBox;
|
|
CellBox.Parent[asboVertical]:=ColBox;
|
|
CellBox.Index[asboHorizontal]:=x;
|
|
CellBox.Index[asboVertical]:=y;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.SetTableControls(ListOfControls: TFPList;
|
|
ChildSizing: TControlChildSizing; BiDiMode: TBiDiMode);
|
|
var
|
|
i: Integer;
|
|
Row: LongInt;
|
|
Col: LongInt;
|
|
ChildControl: TControl;
|
|
ChildBox: TAutoSizeBox;
|
|
RowCount: LongInt;
|
|
ColCount: Integer;
|
|
LineMax: LongInt;
|
|
begin
|
|
// allocate table
|
|
case ChildSizing.Layout of
|
|
cclLeftToRightThenTopToBottom:
|
|
begin
|
|
ColCount:=Max(1,Min(ChildSizing.ControlsPerLine,ListOfControls.Count));
|
|
RowCount:=((ListOfControls.Count-1) div ColCount)+1;
|
|
end;
|
|
cclTopToBottomThenLeftToRight:
|
|
begin
|
|
RowCount:=Max(1,min(ChildSizing.ControlsPerLine,ListOfControls.Count));
|
|
ColCount:=((ListOfControls.Count-1) div RowCount)+1;
|
|
end;
|
|
else
|
|
raise Exception.Create('TAutoSizeBox.SetTableControls TODO');
|
|
end;
|
|
AllocateTable(ColCount,RowCount);
|
|
|
|
// set controls
|
|
for i:=0 to ListOfControls.Count-1 do begin
|
|
ChildControl:=TControl(ListOfControls[i]);
|
|
case ChildSizing.Layout of
|
|
cclLeftToRightThenTopToBottom:
|
|
begin
|
|
LineMax:=ChildCount[asboHorizontal];
|
|
Row:=i div LineMax;
|
|
Col:=i mod LineMax;
|
|
if (BiDiMode=bdRightToLeft) then
|
|
Col:=LineMax-Col-1;
|
|
ChildBox:=Children[asboHorizontal][Col].Children[asboVertical][Row];
|
|
ChildBox.SetControl(ChildControl);
|
|
ChildBox.ApplyChildsizingBorders(ChildSizing);
|
|
end;
|
|
cclTopToBottomThenLeftToRight:
|
|
begin
|
|
LineMax:=ChildCount[asboVertical];
|
|
Col:=i div LineMax;
|
|
Row:=i mod LineMax;
|
|
if (BiDiMode=bdRightToLeft) then
|
|
Col:=LineMax-Col-1;
|
|
ChildBox:=Children[asboVertical][Row].Children[asboHorizontal][Col];
|
|
ChildBox.SetControl(ChildControl);
|
|
ChildBox.ApplyChildsizingBorders(ChildSizing);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.ApplyChildSizingBorders(ChildSizing: TControlChildSizing);
|
|
var
|
|
MinBorder: LongInt;
|
|
begin
|
|
// left border
|
|
if (Parent[asboHorizontal]=nil) or (Index[asboHorizontal]=0) then
|
|
MinBorder:=ChildSizing.LeftRightSpacing
|
|
else
|
|
MinBorder:=ChildSizing.HorizontalSpacing;
|
|
BorderLeftTop[asboHorizontal]:=Max(BorderLeftTop[asboHorizontal],MinBorder);
|
|
|
|
// right border
|
|
if (Parent[asboHorizontal]=nil)
|
|
or (Index[asboHorizontal]=Parent[asboHorizontal].ChildCount[asboHorizontal]-1)
|
|
then
|
|
MinBorder:=ChildSizing.LeftRightSpacing
|
|
else
|
|
MinBorder:=ChildSizing.HorizontalSpacing;
|
|
BorderRightBottom[asboHorizontal]:=Max(BorderRightBottom[asboHorizontal],
|
|
MinBorder);
|
|
|
|
// top border
|
|
if (Parent[asboVertical]=nil) or (Index[asboVertical]=0) then
|
|
MinBorder:=ChildSizing.TopBottomSpacing
|
|
else
|
|
MinBorder:=ChildSizing.VerticalSpacing;
|
|
BorderLeftTop[asboVertical]:=Max(BorderLeftTop[asboVertical],MinBorder);
|
|
|
|
// bottom border
|
|
if (Parent[asboVertical]=nil)
|
|
or (Index[asboVertical]=Parent[asboVertical].ChildCount[asboVertical]-1)
|
|
then
|
|
MinBorder:=ChildSizing.TopBottomSpacing
|
|
else
|
|
MinBorder:=ChildSizing.VerticalSpacing;
|
|
BorderRightBottom[asboVertical]:=Max(BorderRightBottom[asboVertical],
|
|
MinBorder);
|
|
end;
|
|
|
|
procedure TAutoSizeBox.InitSums;
|
|
|
|
procedure Init(o: TAutoSizeBoxOrientation);
|
|
var
|
|
FirstChild: TAutoSizeBox;
|
|
begin
|
|
if ChildCount[o]>0 then begin
|
|
FirstChild:=Children[o][0];
|
|
MaximumSize[o]:=FirstChild.MaximumSize[o];
|
|
MinimumSize[o]:=FirstChild.MinimumSize[o];
|
|
PreferredSize[o]:=FirstChild.PreferredSize[o];
|
|
BorderLeftTop[o]:=FirstChild.BorderLeftTop[o];
|
|
BorderRightBottom[o]:=FirstChild.BorderRightBottom[o];
|
|
end else begin
|
|
MaximumSize[o]:=0;
|
|
MinimumSize[o]:=0;
|
|
PreferredSize[o]:=0;
|
|
BorderLeftTop[o]:=0;
|
|
BorderRightBottom[o]:=0;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Init(asboHorizontal);
|
|
Init(asboVertical);
|
|
end;
|
|
|
|
procedure TAutoSizeBox.SumLine(Orientation: TAutoSizeBoxOrientation;
|
|
DoInit: boolean);
|
|
// total orientated minimum is the sum of all minimums plus borders
|
|
// total orientated maximum is the sum of all maximums plus borders
|
|
// total orientated preferred is the sum of all preferred plus borders
|
|
// total orthogonal minimum is the maximum of all minimums
|
|
// total orthogonal maximum is the minimum of all maximums
|
|
// total orthogonal preferred is the maximum of all preferred
|
|
var
|
|
i: Integer;
|
|
Orthogonal: TAutoSizeBoxOrientation;
|
|
CurChild: TAutoSizeBox;
|
|
CurBorder: integer;
|
|
LastChild: TAutoSizeBox;
|
|
begin
|
|
if DoInit then InitSums;
|
|
Orthogonal:=SizeBoxOrthogonal[Orientation];
|
|
if ChildCount[Orientation]>0 then begin
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
CurChild:=Children[Orientation][i];
|
|
|
|
// add border in Orientation
|
|
CurBorder:=CurChild.BorderLeftTop[Orientation];
|
|
if i>0 then
|
|
CurBorder:=Max(Children[Orientation][i-1].BorderRightBottom[Orientation],
|
|
CurBorder);
|
|
if MaximumSize[Orientation]>0 then begin
|
|
inc(MaximumSize[Orientation],CurBorder);
|
|
end;
|
|
inc(MinimumSize[Orientation],CurBorder);
|
|
inc(PreferredSize[Orientation],CurBorder);
|
|
// add item size in Orientation
|
|
if MaximumSize[Orientation]>0 then begin
|
|
if CurChild.MaximumSize[Orientation]>0 then
|
|
inc(MaximumSize[Orientation],CurChild.MaximumSize[Orientation])
|
|
else
|
|
MaximumSize[Orientation]:=0;
|
|
end;
|
|
inc(MinimumSize[Orientation],CurChild.MinimumSize[Orientation]);
|
|
inc(PreferredSize[Orientation],CurChild.PreferredSize[Orientation]);
|
|
|
|
// maximize in Orthogonal
|
|
if MaximumSize[Orthogonal]>0 then begin
|
|
if CurChild.MaximumSize[Orthogonal]>0 then
|
|
MaximumSize[Orthogonal]:=Max(MaximumSize[Orthogonal],
|
|
CurChild.MaximumSize[Orthogonal])
|
|
else
|
|
MaximumSize[Orthogonal]:=0;
|
|
end;
|
|
MinimumSize[Orthogonal]:=Max(MinimumSize[Orthogonal],
|
|
CurChild.MinimumSize[Orthogonal]);
|
|
PreferredSize[Orthogonal]:=Max(PreferredSize[Orthogonal],
|
|
CurChild.PreferredSize[Orthogonal]);
|
|
BorderLeftTop[Orthogonal]:=Max(BorderLeftTop[Orthogonal],
|
|
CurChild.BorderLeftTop[Orthogonal]);
|
|
BorderRightBottom[Orthogonal]:=Max(BorderRightBottom[Orthogonal],
|
|
CurChild.BorderRightBottom[Orthogonal]);
|
|
end;
|
|
|
|
// last border
|
|
LastChild:=Children[Orientation][ChildCount[Orientation]-1];
|
|
BorderRightBottom[Orientation]:=LastChild.BorderRightBottom[Orientation];
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.SumTable;
|
|
var
|
|
x: Integer;
|
|
ColBox: TAutoSizeBox;
|
|
y: Integer;
|
|
RowBox: TAutoSizeBox;
|
|
begin
|
|
// sum items in rows
|
|
for y:=0 to ChildCount[asboVertical]-1 do begin
|
|
RowBox:=Children[asboVertical][y];
|
|
RowBox.SumLine(asboHorizontal,true);
|
|
end;
|
|
// sum items in columns
|
|
for x:=0 to ChildCount[asboHorizontal]-1 do begin
|
|
ColBox:=Children[asboHorizontal][x];
|
|
ColBox.SumLine(asboVertical,true);
|
|
end;
|
|
// sum rows
|
|
SumLine(asboVertical,true);
|
|
// sum columns
|
|
SumLine(asboHorizontal,false);
|
|
end;
|
|
|
|
procedure TAutoSizeBox.ComputeLeftTops(Orientation: TAutoSizeBoxOrientation);
|
|
var
|
|
i: Integer;
|
|
Child: TAutoSizeBox;
|
|
CurLeftTop: Integer;
|
|
s: LongInt;
|
|
begin
|
|
CurLeftTop:=0;
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if i=0 then
|
|
inc(CurLeftTop,Child.BorderLeftTop[Orientation]);
|
|
Child.LeftTop[Orientation]:=CurLeftTop;
|
|
inc(CurLeftTop,Child.PreferredSize[Orientation]);
|
|
s:=Child.BorderRightBottom[Orientation];
|
|
if i<ChildCount[Orientation]-1 then
|
|
s:=Max(s,Children[Orientation][i+1].BorderLeftTop[Orientation]);
|
|
inc(CurLeftTop,s);
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.ResizeChildren(ChildSizing: TControlChildSizing;
|
|
Orientation: TAutoSizeBoxOrientation; TargetSize: integer);
|
|
type
|
|
TResizeFactor = record
|
|
Scale: double;
|
|
Offset: integer;
|
|
end;
|
|
var
|
|
EnlargeStyle: TChildControlResizeStyle;
|
|
ShrinkStyle: TChildControlResizeStyle;
|
|
CurSize: LongInt;
|
|
|
|
function GetChildTotalSize: integer;
|
|
// computes the total preferred size of all children of this Orientation
|
|
var
|
|
i: Integer;
|
|
Child: TAutoSizeBox;
|
|
s: LongInt;
|
|
begin
|
|
Result:=0;
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if i=0 then
|
|
inc(Result,Child.BorderLeftTop[Orientation]);
|
|
if Child.PreferredSize[Orientation]<1 then
|
|
Child.PreferredSize[Orientation]:=1;
|
|
inc(Result,Child.PreferredSize[Orientation]);
|
|
s:=Child.BorderRightBottom[Orientation];
|
|
if i<ChildCount[Orientation]-1 then
|
|
s:=Max(s,Children[Orientation][i+1].BorderLeftTop[Orientation]);
|
|
inc(Result,s);
|
|
end;
|
|
end;
|
|
|
|
procedure GetChildMaxResize(out Factor: TResizeFactor;
|
|
out ResizeableCount: integer);
|
|
// returns the number of children/gaps, that can grow (ResizeableCount)
|
|
// and the maximum factor, by which the children/gaps can grow (TResizeFactor)
|
|
var
|
|
i: Integer;
|
|
CurScale: Double;
|
|
CurOffset: LongInt;
|
|
Child: TAutoSizeBox;
|
|
begin
|
|
Factor.Scale:=0;
|
|
Factor.Offset:=0;
|
|
ResizeableCount:=0;
|
|
case EnlargeStyle of
|
|
|
|
crsAnchorAligning:
|
|
exit; // no resizing
|
|
|
|
crsScaleChilds,crsHomogenousChildResize:
|
|
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if (Child.MaximumSize[Orientation]>0)
|
|
and (Child.PreferredSize[Orientation]>=Child.MaximumSize[Orientation])
|
|
then begin
|
|
// this child can not be further enlarged
|
|
continue;
|
|
end;
|
|
inc(ResizeableCount);
|
|
|
|
case EnlargeStyle of
|
|
|
|
crsScaleChilds, crsHomogenousChildResize:
|
|
begin
|
|
if Child.MaximumSize[Orientation]=0 then begin
|
|
CurScale:=double(TargetSize);
|
|
CurOffset:=TargetSize;
|
|
end else begin
|
|
CurScale:=double(Child.MaximumSize[Orientation])
|
|
/Child.PreferredSize[Orientation];
|
|
CurOffset:=Child.MaximumSize[Orientation]
|
|
-Child.PreferredSize[Orientation];
|
|
end;
|
|
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
|
|
Factor.Scale:=CurScale;
|
|
Factor.Offset:=CurOffset;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
crsHomogenousSpaceResize:
|
|
if ChildCount[Orientation]>0 then begin
|
|
Factor.Scale:=double(TargetSize);
|
|
Factor.Offset:=TargetSize;
|
|
ResizeableCount:=ChildCount[Orientation]+1;
|
|
end;
|
|
|
|
else
|
|
raise Exception.Create('TAutoSizeBox.ResizeChilds');
|
|
|
|
end;
|
|
end;
|
|
|
|
procedure EnlargeChilds(const Factor: TResizeFactor);
|
|
var
|
|
i: Integer;
|
|
Child: TAutoSizeBox;
|
|
DiffSize: Integer;
|
|
NewSize: LongInt;
|
|
OldSize: LongInt;
|
|
begin
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
if TargetSize=CurSize then break;
|
|
|
|
Child:=Children[Orientation][i];
|
|
if (Child.MaximumSize[Orientation]<0)
|
|
and (Child.PreferredSize[Orientation]>=Child.MaximumSize[Orientation])
|
|
then begin
|
|
// this child can not be further enlarged
|
|
continue;
|
|
end;
|
|
|
|
case EnlargeStyle of
|
|
|
|
crsScaleChilds:
|
|
begin
|
|
// scale PreferredSize
|
|
DiffSize:=TargetSize-CurSize;
|
|
OldSize:=Child.PreferredSize[Orientation];
|
|
NewSize:=round(double(OldSize)*Factor.Scale);
|
|
NewSize:=Min(OldSize+DiffSize,Max(OldSize+1,NewSize));
|
|
inc(CurSize,NewSize-OldSize);
|
|
Child.PreferredSize[Orientation]:=NewSize;
|
|
end;
|
|
|
|
crsHomogenousChildResize:
|
|
begin
|
|
// add to PreferredSize
|
|
DiffSize:=TargetSize-CurSize;
|
|
OldSize:=Child.PreferredSize[Orientation];
|
|
NewSize:=Min(OldSize+Factor.Offset,OldSize+DiffSize);
|
|
inc(CurSize,NewSize-OldSize);
|
|
Child.PreferredSize[Orientation]:=NewSize;
|
|
end;
|
|
|
|
crsHomogenousSpaceResize:
|
|
begin
|
|
if i=0 then begin
|
|
// add to left/top border
|
|
DiffSize:=TargetSize-CurSize;
|
|
OldSize:=Child.BorderLeftTop[Orientation];
|
|
NewSize:=Min(OldSize+Factor.Offset,OldSize+DiffSize);
|
|
inc(CurSize,NewSize-OldSize);
|
|
Child.BorderLeftTop[Orientation]:=NewSize;
|
|
end;
|
|
// add to right/bottom border
|
|
DiffSize:=TargetSize-CurSize;
|
|
OldSize:=Child.BorderRightBottom[Orientation];
|
|
NewSize:=Min(OldSize+Factor.Offset,OldSize+DiffSize);
|
|
inc(CurSize,NewSize-OldSize);
|
|
Child.BorderRightBottom[Orientation]:=NewSize;
|
|
if i<ChildCount[Orientation]-1 then
|
|
Child.BorderLeftTop[Orientation]:=NewSize;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure GetChildMinResize(out Factor: TResizeFactor;
|
|
out ResizeableCount: integer);
|
|
// returns the number of children/gaps, that can shrink (ResizeableCount)
|
|
// and the maximum factor, by which the children/gaps can shrink (TResizeFactor)
|
|
var
|
|
i: Integer;
|
|
CurScale: Double;
|
|
CurOffset: LongInt;
|
|
Child: TAutoSizeBox;
|
|
begin
|
|
Factor.Scale:=0;
|
|
Factor.Offset:=0;
|
|
ResizeableCount:=0;
|
|
case ShrinkStyle of
|
|
|
|
crsAnchorAligning:
|
|
exit; // no resizing
|
|
|
|
crsScaleChilds,crsHomogenousChildResize:
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if (Child.PreferredSize[Orientation]<=Child.MinimumSize[Orientation])
|
|
or (Child.PreferredSize[Orientation]<=1)
|
|
then begin
|
|
// this child can not be further shrinked
|
|
continue;
|
|
end;
|
|
inc(ResizeableCount);
|
|
|
|
case ShrinkStyle of
|
|
|
|
crsScaleChilds:
|
|
begin
|
|
CurScale:=double(Child.MinimumSize[Orientation])
|
|
/Child.PreferredSize[Orientation];
|
|
CurOffset:=Child.PreferredSize[Orientation]
|
|
-Child.MinimumSize[Orientation];
|
|
if (Factor.Offset=0) or (Factor.Scale<CurScale) then begin
|
|
Factor.Scale:=CurScale;
|
|
Factor.Offset:=CurOffset;
|
|
end;
|
|
end;
|
|
|
|
crsHomogenousChildResize:
|
|
begin
|
|
CurScale:=double(Child.MinimumSize[Orientation])
|
|
/Child.PreferredSize[Orientation];
|
|
CurOffset:=Child.PreferredSize[Orientation]
|
|
-Child.MinimumSize[Orientation];
|
|
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
|
|
Factor.Scale:=CurScale;
|
|
Factor.Offset:=CurOffset;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
crsHomogenousSpaceResize:
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if i=0 then begin
|
|
CurScale:=double(TargetSize);
|
|
CurOffset:=Child.BorderLeftTop[Orientation];
|
|
if CurOffset>0 then begin
|
|
inc(ResizeableCount);
|
|
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
|
|
Factor.Scale:=CurScale;
|
|
Factor.Offset:=CurOffset;
|
|
end;
|
|
end;
|
|
end;
|
|
CurScale:=double(TargetSize);
|
|
CurOffset:=Child.BorderRightBottom[Orientation];
|
|
if CurOffset>0 then begin
|
|
inc(ResizeableCount);
|
|
if (Factor.Offset=0) or (Factor.Offset>CurOffset) then begin
|
|
Factor.Scale:=CurScale;
|
|
Factor.Offset:=CurOffset;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
else
|
|
raise Exception.Create('TAutoSizeBox.ResizeChilds');
|
|
|
|
end;
|
|
end;
|
|
|
|
procedure ShrinkChilds(const Factor: TResizeFactor);
|
|
var
|
|
i: Integer;
|
|
Child: TAutoSizeBox;
|
|
DiffSize: Integer;
|
|
NewSize: LongInt;
|
|
OldSize: LongInt;
|
|
begin
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if (Child.PreferredSize[Orientation]<=1)
|
|
or (Child.PreferredSize[Orientation]<=Child.MinimumSize[Orientation])
|
|
then begin
|
|
// this child can not be further shrinked
|
|
continue;
|
|
end;
|
|
|
|
case ShrinkStyle of
|
|
|
|
crsScaleChilds:
|
|
begin
|
|
// scale PreferredSize
|
|
DiffSize:=CurSize-TargetSize;
|
|
OldSize:=Child.PreferredSize[Orientation];
|
|
NewSize:=Min(round(OldSize*Factor.Scale),OldSize-1);
|
|
NewSize:=Max(Max(1,NewSize),OldSize-DiffSize);
|
|
dec(CurSize,OldSize-NewSize);
|
|
Child.PreferredSize[Orientation]:=NewSize;
|
|
end;
|
|
|
|
crsHomogenousChildResize:
|
|
begin
|
|
// add to PreferredSize
|
|
DiffSize:=CurSize-TargetSize;
|
|
OldSize:=Child.PreferredSize[Orientation];
|
|
NewSize:=OldSize-Factor.Offset;
|
|
NewSize:=Max(Max(NewSize,1),OldSize-DiffSize);
|
|
dec(CurSize,OldSize-NewSize);
|
|
Child.PreferredSize[Orientation]:=NewSize;
|
|
end;
|
|
|
|
crsHomogenousSpaceResize:
|
|
begin
|
|
if i=0 then begin
|
|
// add to left/top border
|
|
DiffSize:=CurSize-TargetSize;
|
|
OldSize:=Child.BorderLeftTop[Orientation];
|
|
NewSize:=Max(Max(0,OldSize-Factor.Offset),OldSize-DiffSize);
|
|
dec(CurSize,OldSize-NewSize);
|
|
Child.BorderLeftTop[Orientation]:=NewSize;
|
|
end;
|
|
// add to right/bottom border
|
|
DiffSize:=CurSize-TargetSize;
|
|
OldSize:=Child.BorderRightBottom[Orientation];
|
|
NewSize:=Max(Max(0,OldSize-Factor.Offset),OldSize-DiffSize);
|
|
dec(CurSize,OldSize-NewSize);
|
|
Child.BorderRightBottom[Orientation]:=NewSize;
|
|
if i<ChildCount[Orientation]-1 then
|
|
Child.BorderLeftTop[Orientation]:=NewSize;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
MaxResizeFactorPerItem, MinResizeFactorPerItem, CurScale: TResizeFactor;
|
|
ResizeableCount: integer;
|
|
i: Integer;
|
|
begin
|
|
CurSize:=GetChildTotalSize;
|
|
//DebugLn('TAutoSizeBox.ResizeChilds CurSize=',dbgs(CurSize),' TargetSize=',dbgs(TargetSize));
|
|
EnlargeStyle:=crsAnchorAligning;
|
|
ShrinkStyle:=crsAnchorAligning;
|
|
i:=0;
|
|
if TargetSize>CurSize then begin
|
|
// enlarge
|
|
if Orientation=asboHorizontal then
|
|
EnlargeStyle:=ChildSizing.EnlargeHorizontal
|
|
else
|
|
EnlargeStyle:=ChildSizing.EnlargeVertical;
|
|
while TargetSize>CurSize do begin
|
|
// shrink children
|
|
GetChildMaxResize(MaxResizeFactorPerItem,ResizeableCount);
|
|
if (ResizeableCount=0) or (MaxResizeFactorPerItem.Offset=0) then break;
|
|
|
|
CurScale.Scale:=(double(TargetSize)/CurSize);
|
|
if (MaxResizeFactorPerItem.Scale>0)
|
|
and (MaxResizeFactorPerItem.Scale<CurScale.Scale) then
|
|
CurScale.Scale:=MaxResizeFactorPerItem.Scale;
|
|
|
|
CurScale.Offset:=((TargetSize-CurSize-1) div ResizeableCount)+1;
|
|
// note: the above formula makes sure, that Offset>0
|
|
if (MaxResizeFactorPerItem.Offset>0)
|
|
and (MaxResizeFactorPerItem.Offset<CurScale.Offset) then
|
|
CurScale.Offset:=MaxResizeFactorPerItem.Offset;
|
|
|
|
EnlargeChilds(CurScale);
|
|
inc(i);
|
|
if i>1000 then RaiseGDBException('TAutoSizeBox.ResizeChilds consistency error');
|
|
end;
|
|
end else if TargetSize<CurSize then begin
|
|
// shrink
|
|
if Orientation=asboHorizontal then
|
|
ShrinkStyle:=ChildSizing.ShrinkHorizontal
|
|
else
|
|
ShrinkStyle:=ChildSizing.ShrinkVertical;
|
|
while TargetSize<CurSize do begin
|
|
GetChildMinResize(MinResizeFactorPerItem,ResizeableCount);
|
|
if (ResizeableCount=0) or (MinResizeFactorPerItem.Offset=0) then break;
|
|
|
|
CurScale.Scale:=(double(TargetSize)/CurSize);
|
|
if (MinResizeFactorPerItem.Scale>0)
|
|
and (MinResizeFactorPerItem.Scale>CurScale.Scale) then
|
|
CurScale.Scale:=MinResizeFactorPerItem.Scale;
|
|
|
|
CurScale.Offset:=((CurSize-TargetSize-1) div ResizeableCount)+1;
|
|
// note: the above formula makes sure, that Offset>0
|
|
if (MinResizeFactorPerItem.Offset>0)
|
|
and (MinResizeFactorPerItem.Offset>CurScale.Offset) then
|
|
CurScale.Offset:=MinResizeFactorPerItem.Offset;
|
|
|
|
ShrinkChilds(CurScale);
|
|
inc(i);
|
|
if i>1000 then RaiseGDBException('TAutoSizeBox.ResizeChilds consistency error');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.ResizeTable(ChildSizing: TControlChildSizing;
|
|
TargetWidth, TargetHeight: integer);
|
|
begin
|
|
// resize rows and columns
|
|
ResizeChildren(ChildSizing,asboHorizontal,TargetWidth);
|
|
ComputeLeftTops(asboHorizontal);
|
|
ResizeChildren(ChildSizing,asboVertical,TargetHeight);
|
|
ComputeLeftTops(asboVertical);
|
|
end;
|
|
|
|
procedure TAutoSizeBox.AlignToRight(TargetWidth: integer);
|
|
|
|
function GetChildTotalSize(Orientation: TAutoSizeBoxOrientation): integer;
|
|
// computes the total preferred size of all children of this Orientation
|
|
var
|
|
i: Integer;
|
|
Child: TAutoSizeBox;
|
|
begin
|
|
Result:=0;
|
|
for i:=0 to ChildCount[Orientation]-1 do begin
|
|
Child:=Children[Orientation][i];
|
|
if i=0 then
|
|
inc(Result,Child.BorderLeftTop[Orientation]);
|
|
if Child.PreferredSize[Orientation]<1 then
|
|
Child.PreferredSize[Orientation]:=1;
|
|
inc(Result,Child.PreferredSize[Orientation]);
|
|
inc(Result,Child.BorderRightBottom[Orientation]);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Orientation: TAutoSizeBoxOrientation;
|
|
i: Integer;
|
|
Child: TAutoSizeBox;
|
|
dx: Integer;
|
|
begin
|
|
Orientation:=asboHorizontal;
|
|
dx:=TargetWidth-GetChildTotalSize(Orientation);
|
|
|
|
for i:=ChildCount[Orientation]-1 downto 0 do begin
|
|
Child:=Children[Orientation][i];
|
|
inc(Child.LeftTop[Orientation],dx);
|
|
end;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.ComputeTableControlBounds(
|
|
ChildSizing: TControlChildSizing; BiDiMode: TBiDiMode);
|
|
var
|
|
y: Integer;
|
|
RowBox: TAutoSizeBox;
|
|
x: Integer;
|
|
ColBox: TAutoSizeBox;
|
|
ControlBox: TAutoSizeBox;
|
|
CurControl: TControl;
|
|
NewBounds: TRect;
|
|
CellBounds: TRect;
|
|
NewWidth: LongInt;
|
|
NewHeight: LongInt;
|
|
begin
|
|
//WriteDebugReport;
|
|
for y:=0 to ChildCount[asboVertical]-1 do begin
|
|
RowBox:=Children[asboVertical][y];
|
|
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
|
|
ControlBox:=RowBox.Children[asboHorizontal][x];
|
|
ColBox:=ControlBox.Parent[asboVertical];
|
|
CurControl:=ControlBox.Control;
|
|
if CurControl=nil then continue;
|
|
CellBounds:=Bounds(ColBox.LeftTop[asboHorizontal],
|
|
RowBox.LeftTop[asboVertical],
|
|
ColBox.PreferredSize[asboHorizontal],
|
|
RowBox.PreferredSize[asboVertical]);
|
|
NewBounds.Left:=CellBounds.Left;
|
|
NewBounds.Top:=CellBounds.Top;
|
|
NewWidth:=ControlBox.PreferredSize[asboHorizontal];
|
|
NewHeight:=ControlBox.PreferredSize[asboVertical];
|
|
if (NewWidth<ColBox.PreferredSize[asboHorizontal]) then begin
|
|
// column is bigger than preferred width of the control
|
|
//DebugLn('TAutoSizeBox.SetTableControlBounds ',DbgSName(CurControl),' ',dbgs(ord(CurControl.BorderSpacing.CellAlignHorizontal)));
|
|
case CurControl.BorderSpacing.CellAlignHorizontal of
|
|
ccaFill: NewWidth:=CellBounds.Right-CellBounds.Left;
|
|
ccaLeftTop,ccaRightBottom:
|
|
if (CurControl.BorderSpacing.CellAlignHorizontal=ccaRightBottom)
|
|
=(BidiMode=bdLeftToRight)
|
|
then
|
|
NewBounds.Left:=CellBounds.Right-NewWidth;
|
|
ccaCenter: NewBounds.Left:=NewBounds.Left
|
|
+(CellBounds.Right-CellBounds.Left-NewWidth) div 2;
|
|
end;
|
|
end else if (NewWidth>ColBox.PreferredSize[asboHorizontal]) then begin
|
|
// column is smaller than preferred width of the control
|
|
if ChildSizing.ShrinkHorizontal
|
|
in [crsScaleChilds,crsHomogenousChildResize]
|
|
then
|
|
NewWidth:=CellBounds.Right-CellBounds.Left;
|
|
end;
|
|
if (NewHeight<ColBox.PreferredSize[asboVertical]) then begin
|
|
// column is bigger than preferred height of the control
|
|
case CurControl.BorderSpacing.CellAlignVertical of
|
|
ccaFill: NewHeight:=CellBounds.Bottom-CellBounds.Top;
|
|
ccaLeftTop: ;
|
|
ccaRightBottom: NewBounds.Top:=CellBounds.Bottom-NewHeight;
|
|
ccaCenter: NewBounds.Top:=NewBounds.Top
|
|
+(CellBounds.Bottom-CellBounds.Top-NewHeight) div 2;
|
|
end;
|
|
end else if (NewHeight>ColBox.PreferredSize[asboVertical]) then begin
|
|
// column is smaller than preferred height of the control
|
|
if ChildSizing.ShrinkVertical
|
|
in [crsScaleChilds,crsHomogenousChildResize]
|
|
then
|
|
NewHeight:=CellBounds.Bottom-CellBounds.Top;
|
|
end;
|
|
|
|
NewBounds.Right:=NewBounds.Left+NewWidth;
|
|
NewBounds.Bottom:=NewBounds.Top+NewHeight;
|
|
ControlBox.NewControlBounds:=NewBounds;
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(CurControl) then
|
|
DebugLn(['TAutoSizeBox.ComputeTableControlBounds ',DbgSName(CurControl),
|
|
' CellBounds=',dbgs(CellBounds),
|
|
' Preferred=',ControlBox.PreferredSize[asboHorizontal],'x',ControlBox.PreferredSize[asboVertical],
|
|
' NewBounds=',dbgs(NewBounds)]);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TAutoSizeBox.SetTableControlBounds(ChildSizing: TControlChildSizing
|
|
): boolean;
|
|
var
|
|
y: Integer;
|
|
RowBox: TAutoSizeBox;
|
|
x: Integer;
|
|
ControlBox: TAutoSizeBox;
|
|
CurControl: TControl;
|
|
NewBounds: TRect;
|
|
OldBounds: TRect;
|
|
begin
|
|
Result:=false;
|
|
//WriteDebugReport;
|
|
for y:=0 to ChildCount[asboVertical]-1 do begin
|
|
RowBox:=Children[asboVertical][y];
|
|
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
|
|
ControlBox:=RowBox.Children[asboHorizontal][x];
|
|
CurControl:=ControlBox.Control;
|
|
if CurControl=nil then continue;
|
|
NewBounds:=ControlBox.NewControlBounds;
|
|
OldBounds:=CurControl.BoundsRect;
|
|
if not CompareRect(@NewBounds,@OldBounds) then begin
|
|
Result:=true;
|
|
CurControl.SetBoundsKeepBase(NewBounds.Left,
|
|
NewBounds.Top,
|
|
NewBounds.Right-NewBounds.Left,
|
|
NewBounds.Bottom-NewBounds.Top);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TAutoSizeBox.AlignControlsInTable(ListOfControls: TFPList;
|
|
ChildSizing: TControlChildSizing; BiDiMode: TBiDiMode;
|
|
TargetWidth, TargetHeight: integer;
|
|
Apply: boolean): boolean;
|
|
// true if a control was modified
|
|
begin
|
|
SetTableControls(ListOfControls,ChildSizing,BiDiMode);
|
|
//WriteDebugReport('after SetTableControls');
|
|
SumTable;
|
|
//WriteDebugReport('after SumTable');
|
|
ResizeTable(ChildSizing,TargetWidth,TargetHeight);
|
|
//WriteDebugReport('after ResizeTable');
|
|
if BiDiMode=bdRightToLeft then
|
|
AlignToRight(TargetWidth);
|
|
//WriteDebugReport('after AlignToRight');
|
|
ComputeTableControlBounds(ChildSizing,BiDiMode);
|
|
//WriteDebugReport('after ComputeTableControlBounds');
|
|
Result:=Apply and SetTableControlBounds(ChildSizing);
|
|
end;
|
|
|
|
procedure TAutoSizeBox.WriteDebugReport(const Title: string);
|
|
var
|
|
y: Integer;
|
|
RowBox: TAutoSizeBox;
|
|
x: Integer;
|
|
CellBox: TAutoSizeBox;
|
|
ColBox: TAutoSizeBox;
|
|
begin
|
|
DebugLn('TAutoSizeBox.WriteDebugReport '+Title
|
|
+' ChildCounts=',dbgs(ChildCount[asboHorizontal]),'x',dbgs(ChildCount[asboVertical]));
|
|
for y:=0 to ChildCount[asboVertical]-1 do begin
|
|
RowBox:=Children[asboVertical][y];
|
|
DbgOut(' Row='+dbgs(y),
|
|
' MinY='+dbgs(RowBox.MinimumSize[asboVertical]),
|
|
' MaxY='+dbgs(RowBox.MaximumSize[asboVertical]),
|
|
' PrefY='+dbgs(RowBox.PreferredSize[asboVertical]),
|
|
' BorderTop=',dbgs(RowBox.BorderLeftTop[asboVertical]),
|
|
' #Col='+dbgs(RowBox.ChildCount[asboHorizontal]));
|
|
for x:=0 to RowBox.ChildCount[asboHorizontal]-1 do begin
|
|
CellBox:=RowBox.Children[asboHorizontal][x];
|
|
DbgOut(' CellControl=',DbgSName(CellBox.Control),
|
|
' Min='+dbgs(CellBox.MinimumSize[asboHorizontal])+'x'+dbgs(CellBox.MinimumSize[asboVertical]),
|
|
' Max='+dbgs(CellBox.MaximumSize[asboHorizontal])+'x'+dbgs(CellBox.MaximumSize[asboVertical]),
|
|
' BorderLeft=',dbgs(CellBox.BorderLeftTop[asboHorizontal]),
|
|
' Pref='+dbgs(CellBox.PreferredSize[asboHorizontal])+'x'+dbgs(CellBox.PreferredSize[asboVertical]),
|
|
'');
|
|
end;
|
|
DebugLn;
|
|
end;
|
|
DbgOut(' Columns: ');
|
|
for x:=0 to ChildCount[asboHorizontal]-1 do begin
|
|
ColBox:=Children[asboHorizontal][x];
|
|
DbgOut(' Col='+dbgs(ColBox.Index[asboHorizontal]),
|
|
' Min='+dbgs(ColBox.MinimumSize[asboHorizontal]),
|
|
' Max='+dbgs(ColBox.MaximumSize[asboHorizontal]),
|
|
' Pref='+dbgs(ColBox.PreferredSize[asboHorizontal]),
|
|
'');
|
|
end;
|
|
DebugLn;
|
|
end;
|
|
|
|
destructor TAutoSizeBox.Destroy;
|
|
var
|
|
o: TAutoSizeBoxOrientation;
|
|
begin
|
|
// unlink from parent
|
|
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
|
|
if Parent[o]<>nil then
|
|
Parent[o].Children[o][Index[o]]:=nil;
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TAutoSizeBox.Clear;
|
|
var
|
|
o: TAutoSizeBoxOrientation;
|
|
i: Integer;
|
|
begin
|
|
// free all children
|
|
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
|
|
for i:=0 to ChildCount[o]-1 do
|
|
Children[o][i].Free;
|
|
// free children arrays
|
|
for o:=Low(TAutoSizeBoxOrientation) to high(TAutoSizeBoxOrientation) do
|
|
ReallocMem(Children[o],0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.AutoSizePhases: TControlAutoSizePhases;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.AutoSizePhases: TControlAutoSizePhases;
|
|
begin
|
|
if Parent<>nil then
|
|
Result:=Parent.AutoSizePhases
|
|
else begin
|
|
Result:=[];
|
|
if ([wcfCreatingHandle,wcfCreatingChildHandles]*FWinControlFlags<>[]) then
|
|
Include(Result,caspCreatingHandles);
|
|
if fAutoSizingAll then
|
|
Include(Result,caspComputingBounds);
|
|
if wcfRealizingBounds in FWinControlFlags then
|
|
Include(Result,caspRealizingBounds);
|
|
if wcfUpdateShowing in FWinControlFlags then
|
|
Include(Result,caspShowing);
|
|
if FAutoSizingLockCount>0 then
|
|
Include(Result,caspChangingProperties);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.AutoSizeDelayed: boolean;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.AutoSizeDelayed: boolean;
|
|
begin
|
|
Result:=(csDestroyingHandle in ControlState)
|
|
or (inherited AutoSizeDelayed);
|
|
//if Result then debugln('TWinControl.AutoSizeDelayed A ',DbgSName(Self),' wcfCreatingChildHandles=',dbgs(wcfCreatingChildHandles in FWinControlFlags),' csLoading=',dbgs(csLoading in ComponentState));
|
|
{$IFDEF VerboseCanAutoSize}
|
|
if Result {and AutoSize} then begin
|
|
if not HandleAllocated then
|
|
debugln('TWinControl.AutoSizeDelayed Self='+DbgSName(Self)+' not HandleAllocated');
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
function TWinControl.AutoSizeDelayedReport: string;
|
|
begin
|
|
if csDestroyingHandle in ControlState then
|
|
Result:='csDestroyingHandle'
|
|
else
|
|
Result:=inherited AutoSizeDelayedReport;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl AutoSizeDelayedHandle
|
|
|
|
Returns true if AutoSize should be skipped / delayed because of its handle.
|
|
A TWinControl needs a parent handle.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.AutoSizeDelayedHandle: Boolean;
|
|
begin
|
|
Result := (Parent = nil) and (ParentWindow = 0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl AdjustClientRect
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.AdjustClientRect(var ARect: TRect);
|
|
begin
|
|
// Can be overriden.
|
|
// It's called often, so don't put expensive code here, or cache the result
|
|
end;
|
|
|
|
procedure TWinControl.GetAdjustedLogicalClientRect(out ARect: TRect);
|
|
begin
|
|
if not (wcfAdjustedLogicalClientRectValid in FWinControlFlags) then begin
|
|
FAdjustClientRect:=GetLogicalClientRect;
|
|
AdjustClientRect(FAdjustClientRect);
|
|
Include(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
|
|
end;
|
|
ARect:=FAdjustClientRect;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CreateControlAlignList
|
|
|
|
Creates a list of controls that need to be aligned via TheAlign.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CreateControlAlignList(TheAlign: TAlign;
|
|
AlignList: TFPList; StartControl: TControl);
|
|
|
|
function InsertBefore(Control1, Control2: TControl; AAlign: TAlign): Boolean;
|
|
begin
|
|
case AAlign of
|
|
alTop: begin
|
|
Result := (Control1.Top < Control2.Top)
|
|
or ( (Control1.Top = Control2.Top)
|
|
and (Control1.FBaseBounds.Top < Control2.FBaseBounds.Top));
|
|
end;
|
|
alLeft: begin
|
|
Result := (Control1.Left < Control2.Left)
|
|
or ( (Control1.Left = Control2.Left)
|
|
and (Control1.FBaseBounds.Left < Control2.FBaseBounds.Left));
|
|
end;
|
|
// contrary to VCL, LCL uses > for alBottom, alRight
|
|
// Maybe it is a bug in the VCL.
|
|
// This results in first control is put rightmost/bottommost
|
|
alBottom: begin
|
|
Result := ((Control1.Top + Control1.Height) > (Control2.Top + Control2.Height))
|
|
or ( ((Control1.Top + Control1.Height) = (Control2.Top + Control2.Height))
|
|
and (Control1.FBaseBounds.Bottom > Control2.FBaseBounds.Bottom));
|
|
end;
|
|
alRight: begin
|
|
Result := ((Control1.Left + Control1.Width) > (Control2.Left + Control2.Width))
|
|
or ( ((Control1.Left + Control1.Width) = (Control2.Left + Control2.Width))
|
|
and (Control1.FBaseBounds.Right > Control2.FBaseBounds.Right));
|
|
end;
|
|
alCustom: begin
|
|
// CustomAlignInsertBefore returns true when Control2 is inserted before Control1
|
|
// We return true when Control1 is inserted before Control2
|
|
// So swap controls
|
|
Result := CustomAlignInsertBefore(Control2, Control1);
|
|
end;
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
I, X: Integer;
|
|
Control: TControl;
|
|
begin
|
|
AlignList.Clear;
|
|
|
|
// first add the current control
|
|
if (StartControl <> nil) and (StartControl.Align = TheAlign) and
|
|
((TheAlign = alNone) or StartControl.IsControlVisible) then
|
|
AlignList.Add(StartControl);
|
|
|
|
// then add all other
|
|
for I := 0 to FAlignOrder.Count - 1 do
|
|
begin
|
|
Control := TControl(FAlignOrder[I]);
|
|
|
|
if (Control.Align = TheAlign) and Control.IsControlVisible then
|
|
begin
|
|
if Control = StartControl then Continue;
|
|
|
|
X := 0;
|
|
while (X < AlignList.Count) and
|
|
not InsertBefore(Control, TControl(AlignList[X]), TheAlign) do
|
|
Inc(X);
|
|
AlignList.Insert(X, Control);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl AlignControls
|
|
|
|
Align child controls
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.AlignControls(AControl: TControl;
|
|
var RemainingClientRect: TRect);
|
|
{ $DEFINE CHECK_POSITION}
|
|
var
|
|
AlignList: TFPList;
|
|
BoundsMutated: boolean;
|
|
LastBoundsMutated: TControl;
|
|
LastBoundsMutatedOld: TRect;
|
|
ParentClientWidth: integer;
|
|
ParentClientHeight: integer;
|
|
RemainingBorderSpace: TRect; // borderspace around RemainingClientRect
|
|
// e.g. Right=3 means borderspace of 3
|
|
|
|
function NeedAlignWork: Boolean;
|
|
var
|
|
I: Integer;
|
|
CurControl: TControl;
|
|
begin
|
|
Result := True;
|
|
for I := ControlCount - 1 downto 0 do
|
|
begin
|
|
CurControl:=Controls[I];
|
|
if (CurControl.Align <> alNone)
|
|
or (CurControl.Anchors <> [akLeft, akTop])
|
|
or (CurControl.AnchorSide[akLeft].Control<>nil)
|
|
or (CurControl.AnchorSide[akTop].Control<>nil)
|
|
or (ChildSizing.Layout<>cclNone)
|
|
then Exit;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
function Anchored(Align: TAlign; Anchors: TAnchors): Boolean;
|
|
begin
|
|
case Align of
|
|
alLeft: Result := akLeft in Anchors;
|
|
alTop: Result := akTop in Anchors;
|
|
alRight: Result := akRight in Anchors;
|
|
alBottom: Result := akBottom in Anchors;
|
|
alClient: Result := Anchors = [akLeft, akTop, akRight, akBottom];
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
|
|
procedure DoPosition(Control: TControl; AAlign: TAlign; AControlIndex: Integer);
|
|
var
|
|
NewLeft, NewTop, NewWidth, NewHeight: Integer;
|
|
ParentBaseClientSize: TSize;
|
|
CurBaseBounds: TRect;
|
|
NewRight: Integer;// temp variable, not always valid, use with care !
|
|
NewBottom: Integer;// temp variable, not always valid, use with care !
|
|
|
|
MinWidth: Integer;
|
|
MaxWidth: Integer;
|
|
MinHeight: Integer;
|
|
MaxHeight: Integer;
|
|
CurRemainingClientRect: TRect;
|
|
CurRemainingBorderSpace: TRect; // borderspace around RemainingClientRect
|
|
// e.g. Right=3 means borderspace of 3
|
|
ChildAroundSpace: TRect;
|
|
AnchorSideCacheValid: array[TAnchorKind] of boolean;
|
|
AnchorSideCache: array[TAnchorKind] of integer;
|
|
CurAnchors: TAnchors;
|
|
CurAlignAnchors: TAnchors;
|
|
OldBounds: TRect;
|
|
NewBounds: TRect;
|
|
|
|
AlignInfo: TAlignInfo; // alCustom
|
|
PrefWidth: integer;
|
|
PrefHeight: integer;
|
|
|
|
function ConstraintWidth(NewWidth: integer): Integer;
|
|
begin
|
|
Result:=NewWidth;
|
|
if (MaxWidth>=MinWidth) and (Result>MaxWidth) and (MaxWidth>0) then
|
|
Result:=MaxWidth;
|
|
if Result<MinWidth then Result:=MinWidth;
|
|
end;
|
|
|
|
procedure ConstraintWidth(var NewLeft, NewWidth: integer);
|
|
var
|
|
ConWidth: LongInt;
|
|
begin
|
|
ConWidth:=ConstraintWidth(NewWidth);
|
|
if ConWidth<>NewWidth then begin
|
|
if [akLeft,akRight]*CurAnchors=[akRight] then
|
|
// move left side, keep right
|
|
inc(NewLeft,NewWidth-ConWidth);
|
|
NewWidth:=ConWidth;
|
|
end;
|
|
end;
|
|
|
|
function ConstraintHeight(NewHeight: integer): Integer;
|
|
begin
|
|
Result:=NewHeight;
|
|
if (MaxHeight>=MinHeight) and (Result>MaxHeight) and (MaxHeight>0) then
|
|
Result:=MaxHeight;
|
|
if Result<MinHeight then Result:=MinHeight;
|
|
end;
|
|
|
|
procedure ConstraintHeight(var NewTop, NewHeight: integer);
|
|
var
|
|
ConHeight: LongInt;
|
|
begin
|
|
ConHeight:=ConstraintHeight(NewHeight);
|
|
if ConHeight<>NewHeight then begin
|
|
if [akTop,akBottom]*CurAnchors=[akBottom] then
|
|
// move top side, keep bottom
|
|
inc(NewTop,NewHeight-ConHeight);
|
|
NewHeight:=ConHeight;
|
|
end;
|
|
end;
|
|
|
|
procedure InitAnchorSideCache;
|
|
var
|
|
a: TAnchorKind;
|
|
begin
|
|
for a:=Low(TAnchorKind) to High(TAnchorKind) do
|
|
AnchorSideCacheValid[a]:=false;
|
|
end;
|
|
|
|
function GetAnchorSidePosition(Kind: TAnchorKind;
|
|
DefaultPosition: Integer): integer;
|
|
// calculates the position in pixels of a side due to anchors
|
|
// For example: if akLeft is set, it returns the coordinate for the left anchor
|
|
var
|
|
CurAnchorSide: TAnchorSide;
|
|
ReferenceControl: TControl;
|
|
ReferenceSide: TAnchorSideReference;
|
|
Position: Integer;
|
|
begin
|
|
if AnchorSideCacheValid[Kind] then begin
|
|
Result:=AnchorSideCache[Kind];
|
|
exit;
|
|
end;
|
|
Result:=DefaultPosition;
|
|
CurAnchorSide:=Control.AnchorSide[Kind];
|
|
//if CheckPosition(Control) and (Kind=akLeft) then debugln(['GetAnchorSidePosition A Self=',DbgSName(Self),' Control=',DbgSName(Control),' CurAnchorSide.Control=',DbgSName(CurAnchorSide.Control),' Spacing=',Control.BorderSpacing.GetSpace(Kind)]);
|
|
CurAnchorSide.GetSidePosition(ReferenceControl,ReferenceSide,Position);
|
|
if ReferenceControl<>nil then begin
|
|
//DebugLn(['GetAnchorSidePosition ',DbgSName(Control),' ReferenceControl=',DbgSName(ReferenceControl)]);
|
|
Result:=Position;
|
|
end;
|
|
//if CheckPosition(Control) and (Kind=akRight) then begin
|
|
// debugln('GetAnchorSidePosition B Self=',DbgSName(Self),' Control=',DbgSName(Control),' Result=',dbgs(Result),' ReferenceControl=',dbgsName(ReferenceControl));
|
|
// if ReferenceControl<>nil then DebugLn(['GetAnchorSidePosition ReferenceControl.BoundsRect=',dbgs(ReferenceControl.BoundsRect)]);
|
|
//end;
|
|
AnchorSideCacheValid[Kind]:=true;
|
|
AnchorSideCache[Kind]:=Result;
|
|
if ReferenceSide=asrTop then ;
|
|
end;
|
|
|
|
begin
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Control) then
|
|
with Control do
|
|
DebugLn('[TWinControl.AlignControls.DoPosition] A Control=',dbgsName(Control),' ',
|
|
dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height),
|
|
' recalculate the anchors=',dbgs(Control.Anchors <> AnchorAlign[AAlign]),
|
|
' Align=',DbgS(AAlign));
|
|
{$ENDIF}
|
|
|
|
with Control do begin
|
|
// get constraints
|
|
MinWidth:=Constraints.EffectiveMinWidth;
|
|
if MinWidth<0 then MinWidth:=0;
|
|
MaxWidth:=Constraints.EffectiveMaxWidth;
|
|
MinHeight:=Constraints.EffectiveMinHeight;
|
|
if MinHeight<0 then MinHeight:=0;
|
|
MaxHeight:=Constraints.EffectiveMaxHeight;
|
|
|
|
// get anchors set by Align
|
|
CurAlignAnchors:=[];
|
|
if Align in [alLeft,alRight,alBottom,alTop,alClient] then
|
|
CurAlignAnchors:=AnchorAlign[Align];
|
|
CurAnchors:=Anchors+CurAlignAnchors;
|
|
|
|
// get default bounds
|
|
NewLeft:=Left;
|
|
NewTop:=Top;
|
|
NewWidth:=Width;
|
|
NewHeight:=Height;
|
|
if AutoSize then begin
|
|
GetPreferredSize(PrefWidth,PrefHeight);
|
|
if PrefWidth>0 then NewWidth:=PrefWidth;
|
|
if PrefHeight>0 then NewHeight:=PrefHeight;
|
|
end;
|
|
ConstraintWidth(NewLeft,NewWidth);
|
|
ConstraintHeight(NewTop,NewHeight);
|
|
end;
|
|
|
|
InitAnchorSideCache;
|
|
|
|
{ Recalculate the anchors
|
|
|
|
Use Anchors to ensure that a control maintains its current position
|
|
relative to an edge of its parent or another sibling.
|
|
This is controlled with the AnchorSide properties.
|
|
|
|
1. If AnchorSide[].Control is not set, the distance is kept relative to
|
|
the edges of the client area of its parent.
|
|
When its parent is resized, the control holds its position relative to the
|
|
edges to which it is anchored.
|
|
If a control is anchored to opposite edges of its parent, the control
|
|
stretches when its parent is resized. For example, if a control has its
|
|
Anchors property set to [akLeft,akRight], the control stretches when the
|
|
width of its parent changes.
|
|
Anchors is enforced only when the parent is resized. Thus, for example,
|
|
if a control is anchored to opposite edges of a form at design time and
|
|
the form is created in a maximized state, the control is not stretched
|
|
because the form is not resized after the control is created.
|
|
|
|
2. If AnchorSide[].Control is set, the BorderSpace properties defines the
|
|
distance to another sibling (i.e. AnchorSide[].Control).
|
|
}
|
|
if (AAlign = alNone) or (Control.Anchors <> CurAlignAnchors)
|
|
then begin
|
|
// at least one side is anchored without align
|
|
|
|
// Get the base bounds. The base bounds are the user defined bounds
|
|
// without automatic aligning and/or anchoring
|
|
|
|
// get base size of parents client area
|
|
ParentBaseClientSize:=Control.FBaseParentClientSize;
|
|
if (ParentBaseClientSize.cx=0)
|
|
and (ParentBaseClientSize.cy=0) then
|
|
ParentBaseClientSize:=Size(ParentClientWidth,ParentClientHeight);
|
|
|
|
// get base bounds of Control
|
|
CurBaseBounds:=Control.FBaseBounds;
|
|
if not (cfBaseBoundsValid in FControlFlags) then
|
|
CurBaseBounds:=Control.BoundsRect;
|
|
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in ComponentState then
|
|
if CheckPosition(Control) then
|
|
DebugLn('[TWinControl.AlignControls.DoPosition] Before Anchoring ',
|
|
' Self='+DbgSName(Self),' Control='+DbgSName(Control),
|
|
' CurBaseBounds='+dbgs(CurBaseBounds.Left)+','+dbgs(CurBaseBounds.Top)+','+dbgs(CurBaseBounds.Right-CurBaseBounds.Left)+','+dbgs(CurBaseBounds.Bottom-CurBaseBounds.Top),
|
|
' ParentBaseClientSize='+dbgs(ParentBaseClientSize.cx)+','+dbgs(ParentBaseClientSize.cy),
|
|
' ControlParent.Client='+dbgs(ParentClientWidth)+','+dbgs(ParentClientHeight),
|
|
' NewBounds='+dbgs(NewLeft)+','+dbgs(NewTop)+','+dbgs(NewWidth)+','+dbgs(NewHeight),
|
|
'');
|
|
{$ENDIF}
|
|
|
|
if akLeft in CurAnchors then begin
|
|
// keep distance to left side of parent or another sibling
|
|
NewLeft:=GetAnchorSidePosition(akLeft,CurBaseBounds.Left);
|
|
if akRight in CurAnchors then begin
|
|
// keep distance to right side of parent or another sibling
|
|
// -> change the width
|
|
NewRight:=ParentClientWidth
|
|
-(ParentBaseClientSize.cx-CurBaseBounds.Right);
|
|
if (not (akRight in CurAlignAnchors))
|
|
and (akRight in Control.Anchors) then
|
|
NewRight:=GetAnchorSidePosition(akRight,NewRight);
|
|
NewWidth:=ConstraintWidth(NewRight-NewLeft);
|
|
end else begin
|
|
// do not anchor to the right
|
|
// -> keep new width
|
|
end;
|
|
end else begin
|
|
// do not anchor to the left
|
|
if akRight in CurAnchors then begin
|
|
// keep distance to right side of parent
|
|
// and keep new width
|
|
NewRight:=ParentClientWidth
|
|
-(ParentBaseClientSize.cx-CurBaseBounds.Right);
|
|
if (not (akRight in CurAlignAnchors))
|
|
and (akRight in Control.Anchors) then
|
|
NewRight:=GetAnchorSidePosition(akRight,NewRight);
|
|
NewLeft:=NewRight-NewWidth;
|
|
end else begin
|
|
// do not anchor to the right
|
|
// -> keep new width and scale center position.
|
|
NewLeft:=MulDiv(ParentClientWidth,
|
|
(CurBaseBounds.Left+CurBaseBounds.Right) div 2,
|
|
ParentBaseClientSize.cx)
|
|
-(NewWidth div 2);
|
|
end;
|
|
end;
|
|
|
|
if akTop in CurAnchors then begin
|
|
// keep distance to top side of parent
|
|
NewTop:=GetAnchorSidePosition(akTop,CurBaseBounds.Top);
|
|
if akBottom in CurAnchors then begin
|
|
// keep distance to bottom side of parent
|
|
// -> change the height
|
|
NewBottom:=ParentClientHeight
|
|
-(ParentBaseClientSize.cy-CurBaseBounds.Bottom);
|
|
if (not (akBottom in CurAlignAnchors))
|
|
and (akBottom in Control.Anchors) then
|
|
NewBottom:=GetAnchorSidePosition(akBottom,NewBottom);
|
|
NewHeight:=ConstraintHeight(NewBottom-NewTop);
|
|
end else begin
|
|
// do not anchor to the bottom
|
|
// -> keep new height
|
|
end;
|
|
end else begin
|
|
// do not anchor to the top
|
|
if akBottom in CurAnchors then begin
|
|
// keep distance to bottom side of parent
|
|
// and keep new height
|
|
NewBottom:=ParentClientHeight
|
|
-(ParentBaseClientSize.cy-CurBaseBounds.Bottom);
|
|
if (not (akBottom in CurAlignAnchors))
|
|
and (akBottom in Control.Anchors) then
|
|
NewBottom:=GetAnchorSidePosition(akBottom,NewBottom);
|
|
NewTop:=NewBottom-NewHeight;
|
|
end else begin
|
|
// do not anchor to the bottom
|
|
// -> keep new height and scale center position.
|
|
NewTop:=MulDiv(ParentClientHeight,
|
|
(CurBaseBounds.Top+CurBaseBounds.Bottom) div 2,
|
|
ParentBaseClientSize.cy)
|
|
-(NewHeight div 2);
|
|
end;
|
|
end;
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in ComponentState then
|
|
if CheckPosition(Control) then
|
|
with Control do begin
|
|
DebugLn(['[TWinControl.AlignControls.DoPosition] After Anchoring',
|
|
' Self=',DbgSName(Self),
|
|
' Align=',DbgS(AAlign),
|
|
' Control=',dbgsName(Control),
|
|
' Old= l=',Left,',t=',Top,',w=',Width,',h=',Height,
|
|
' New= l=',NewLeft,',t=',NewTop,',w=',NewWidth,',h=',NewHeight,
|
|
'']);
|
|
DebugLn(['DoPosition akRight=',akRight in CurAnchors,' ',GetAnchorSidePosition(akRight,NewLeft+NewWidth)]);
|
|
end;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
// set min size to stop circling (this should not be needed. But if someone
|
|
// plays/fixes the above code, new bugs can enter and there are far too many
|
|
// combinations to test, and so the LCL can loop for some applications.
|
|
// Prevent this, so users can at least report a bug.)
|
|
if NewWidth<0 then NewWidth:=0;
|
|
if NewHeight<0 then NewHeight:=0;
|
|
|
|
case AAlign of
|
|
alLeft,alTop,alRight,alBottom,alClient: begin
|
|
{ Realign
|
|
|
|
Use Align to align a control to the top, bottom, left, right of a
|
|
form or panel and have it remain there even if the size of the form,
|
|
panel, or component that contains the control changes. When the parent
|
|
is resized, an aligned control also resizes so that it continues to span
|
|
the top, bottom, left, or right edge of the parent (more exact:
|
|
span the remaining client area of its parent).
|
|
}
|
|
NewRight:=NewLeft+NewWidth;
|
|
NewBottom:=NewTop+NewHeight;
|
|
|
|
// calculate current RemainingClientRect for the current Control
|
|
CurRemainingClientRect:=RemainingClientRect;
|
|
CurRemainingBorderSpace:=RemainingBorderSpace;
|
|
Control.BorderSpacing.GetSpaceAround(ChildAroundSpace);
|
|
AdjustBorderSpace(CurRemainingClientRect,CurRemainingBorderSpace,
|
|
ChildAroundSpace);
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Control) then
|
|
DebugLn('DoPosition Before aligning ',dbgsName(Control),' akRight in AnchorAlign[AAlign]=',DbgS(akRight in AnchorAlign[AAlign]),
|
|
' akLeft in Control.Anchors=',DbgS(akLeft in Control.Anchors),
|
|
//' ARect=',ARect.Left,',',ARect.Top,',',ARect.Right,',',ARect.Bottom,
|
|
' New=',DbgS(NewLeft,NewTop,NewRight,NewBottom));
|
|
{$ENDIF}
|
|
|
|
if akLeft in AnchorAlign[AAlign]
|
|
then begin
|
|
if (akRight in CurAnchors)
|
|
then begin
|
|
// left align and keep right border
|
|
NewLeft:=CurRemainingClientRect.Left;
|
|
NewRight:=NewLeft+ConstraintWidth(NewRight-NewLeft);
|
|
end
|
|
else begin
|
|
// left align and right border free to move (-> keep width)
|
|
dec(NewRight,NewLeft-CurRemainingClientRect.Left);
|
|
NewLeft:=CurRemainingClientRect.Left;
|
|
end;
|
|
end;
|
|
|
|
if akTop in AnchorAlign[AAlign]
|
|
then begin
|
|
if (akBottom in CurAnchors)
|
|
then begin
|
|
// top align and keep bottom border
|
|
NewTop:=CurRemainingClientRect.Top;
|
|
NewBottom:=NewTop+ConstraintHeight(NewBottom-NewTop);
|
|
end
|
|
else begin
|
|
// top align and bottom border is free to move (-> keep height)
|
|
dec(NewBottom,NewTop-CurRemainingClientRect.Top);
|
|
NewTop:=CurRemainingClientRect.Top;
|
|
end;
|
|
end;
|
|
|
|
if akRight in AnchorAlign[AAlign]
|
|
then begin
|
|
if (akLeft in CurAnchors)
|
|
then begin
|
|
// right align and keep left border
|
|
NewWidth:=ConstraintWidth(CurRemainingClientRect.Right-NewLeft);
|
|
if Align=alRight
|
|
then begin
|
|
// align to right (this overrides the keeping of left border)
|
|
NewRight:=CurRemainingClientRect.Right;
|
|
NewLeft:=NewRight-NewWidth;
|
|
end
|
|
else begin
|
|
// keep left border overrides keeping right border
|
|
NewRight:=NewLeft+NewWidth;
|
|
end;
|
|
end
|
|
else begin
|
|
// right align and left border free to move (-> keep width)
|
|
inc(NewLeft,CurRemainingClientRect.Right-NewRight);
|
|
NewRight:=CurRemainingClientRect.Right;
|
|
end;
|
|
end;
|
|
|
|
if akBottom in AnchorAlign[AAlign]
|
|
then begin
|
|
if (akTop in CurAnchors)
|
|
then begin
|
|
// bottom align and keep top border
|
|
NewHeight:=ConstraintHeight(CurRemainingClientRect.Bottom-NewTop);
|
|
if AAlign=alBottom
|
|
then begin
|
|
// align to bottom (this overrides the keeping of top border)
|
|
NewBottom:=CurRemainingClientRect.Bottom;
|
|
NewTop:=NewBottom-NewHeight;
|
|
end
|
|
else begin
|
|
// keeping top border overrides keeping bottom border
|
|
NewBottom:=NewTop+NewHeight;
|
|
end;
|
|
end
|
|
else begin
|
|
// bottom align and top border free to move (-> keep height)
|
|
inc(NewTop,CurRemainingClientRect.Bottom-NewBottom);
|
|
NewBottom:=CurRemainingClientRect.Bottom;
|
|
end;
|
|
end;
|
|
|
|
NewWidth:=Max(0,NewRight-NewLeft);
|
|
NewHeight:=Max(0,NewBottom-NewTop);
|
|
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in Control.ComponentState then
|
|
if CheckPosition(Control) then
|
|
with Control do
|
|
DebugLn('[TWinControl.AlignControls.DoPosition] After Aligning',
|
|
' ',Name,':',ClassName,
|
|
' Align=',DbgS(AAlign),
|
|
' Control=',Name,':',ClassName,
|
|
' Old=',DbgS(Left,Top,Width,Height),
|
|
' New=',DbgS(NewLeft,NewTop,NewWidth,NewHeight),
|
|
//' ARect=',ARect.Left,',',ARect.Top,',',ARect.Right-ARect.Left,',',ARect.Bottom-ARect.Top,
|
|
'');
|
|
{$ENDIF}
|
|
end;
|
|
alCustom: begin
|
|
AlignInfo.AlignList := AlignList;
|
|
AlignInfo.Align := alCustom;
|
|
AlignInfo.ControlIndex := AControlIndex;
|
|
CustomAlignPosition(Control, NewLeft, NewTop, NewWidth, NewHeight, RemainingClientRect, AlignInfo);
|
|
end;
|
|
end;
|
|
|
|
// apply the constraints
|
|
NewWidth:=ConstraintWidth(NewWidth);
|
|
NewHeight:=ConstraintHeight(NewHeight);
|
|
NewRight:=NewLeft+NewWidth;
|
|
NewBottom:=NewTop+NewHeight;
|
|
|
|
// set the new bounds
|
|
if (Control.Left <> NewLeft) or (Control.Top <> NewTop)
|
|
or (Control.Width <> NewWidth) or (Control.Height <> NewHeight) then begin
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in Control.ComponentState then
|
|
if CheckPosition(Control) then
|
|
with Control do
|
|
DebugLn('[TWinControl.AlignControls.DoPosition] NEW BOUNDS Control=',DbgSName(Control),
|
|
' New=l=',dbgs(NewLeft)+',t='+dbgs(NewTop)+',w='+dbgs(NewWidth)+',h='+dbgs(NewHeight));
|
|
{$ENDIF}
|
|
// lock the base bounds, so that the new automatic bounds do not override
|
|
// the user settings
|
|
OldBounds:=Control.BoundsRect;
|
|
Control.SetBoundsKeepBase(NewLeft, NewTop, NewWidth, NewHeight);
|
|
//DebugLn(['DoPosition ',DbgSName(Control),' ',cfAutoSizeNeeded in Control.FControlFlags]);
|
|
NewBounds:=Control.BoundsRect;
|
|
BoundsMutated:=not CompareRect(@OldBounds,@NewBounds);
|
|
if BoundsMutated then begin
|
|
LastBoundsMutated:=Control;
|
|
LastBoundsMutatedOld:=OldBounds;
|
|
end;
|
|
// Sometimes SetBounds change the bounds. For example due to constraints.
|
|
// update the new bounds
|
|
with Control do
|
|
begin
|
|
NewLeft:=Left;
|
|
NewTop:=Top;
|
|
NewWidth:=Width;
|
|
NewHeight:=Height;
|
|
end;
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in Control.ComponentState then
|
|
if CheckPosition(Control) then
|
|
with Control do
|
|
DebugLn('[TWinControl.AlignControls.DoPosition] AFTER SETBOUND Control=',DbgSName(Control),' Bounds=',DbgS(Control.BoundsRect));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
// adjust the remaining client area
|
|
case AAlign of
|
|
alTop:
|
|
begin
|
|
RemainingClientRect.Top:=Min(NewTop+NewHeight,RemainingClientRect.Bottom);
|
|
RemainingBorderSpace.Top:=0;
|
|
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
|
|
0,Max(ChildSizing.VerticalSpacing,ChildAroundSpace.Bottom),0,0);
|
|
end;
|
|
alBottom:
|
|
begin
|
|
RemainingClientRect.Bottom:=Max(NewTop,RemainingClientRect.Top);
|
|
RemainingBorderSpace.Bottom:=0;
|
|
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
|
|
0,0,0,Max(ChildSizing.VerticalSpacing,ChildAroundSpace.Top));
|
|
end;
|
|
alLeft:
|
|
begin
|
|
RemainingClientRect.Left:=Min(NewLeft+NewWidth,RemainingClientRect.Right);
|
|
RemainingBorderSpace.Left:=0;
|
|
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
|
|
Max(ChildSizing.HorizontalSpacing,ChildAroundSpace.Right),0,0,0);
|
|
end;
|
|
alRight:
|
|
begin
|
|
RemainingClientRect.Right:=Max(NewLeft,RemainingClientRect.Left);
|
|
RemainingBorderSpace.Right:=0;
|
|
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
|
|
0,0,Max(ChildSizing.HorizontalSpacing,ChildAroundSpace.Left),0);
|
|
end;
|
|
alClient:
|
|
begin
|
|
// For VCL compatibility alClient should *not* reduce the free space,
|
|
// so that several alClient controls can overlap. This can be used
|
|
// for example to simulate a two page control and edit both pages
|
|
// at designtime with SendToBack.
|
|
// At runtime programs should use Visible instead of BringToFront to
|
|
// reduce overhead.
|
|
// See bug 10380.
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Control) then
|
|
with Control do
|
|
DebugLn('[TWinControl.AlignControls.DoPosition] END Control=',
|
|
Name,':',ClassName,
|
|
' ',DbgS(Left,Top,Width,Height),
|
|
' Align=',DbgS(AAlign),
|
|
//' ARect=',ARect.Left,',',ARect.Top,',',ARect.Right-ARect.Left,',',ARect.Bottom-ARect.Top,
|
|
'');
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure DoAlign(AAlign: TAlign);
|
|
var
|
|
I: Integer;
|
|
Control: TControl;
|
|
begin
|
|
//DebugLn(['DoAlign ',DbgSName(Self),' ',dbgs(AALign),' ClientRect=',dbgs(ClientRect),' ControlCount=',ControlCount]);
|
|
CreateControlAlignList(AAlign,AlignList,AControl);
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
if AlignList.Count>0 then
|
|
begin
|
|
DbgOut('[TWinControl.AlignControls.DoAlign] Self=',DbgSName(Self),' Control=',dbgsName(AControl),
|
|
' current align=',DbgS(AAlign),' AlignList=[');
|
|
for i:=0 to AlignList.Count-1 do
|
|
begin
|
|
if i>0 then DbgOut(',');
|
|
DbgOut(DbgSName(TObject(AlignList[i])));
|
|
end;
|
|
DebugLn(']');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
// let override handle them
|
|
if DoAlignChildControls(AAlign, AControl, AlignList, RemainingClientRect) then
|
|
exit;
|
|
// remove controls that are positioned by other means
|
|
if (AAlign = alNone) and (AutoSize or (ChildSizing.Layout <> cclNone)) then
|
|
for I := AlignList.Count - 1 downto 0 do
|
|
begin
|
|
Control := TControl(AlignList[I]);
|
|
if IsNotAligned(Control) then AlignList.Delete(I);
|
|
end;
|
|
// anchor/align control
|
|
for I := 0 to AlignList.Count - 1 do
|
|
DoPosition(TControl(AlignList[I]), AAlign, I);
|
|
end;
|
|
|
|
procedure DoAlignNotAligned;
|
|
// All controls, not aligned by their own properties, can be auto aligned.
|
|
var
|
|
i: Integer;
|
|
Control: TControl;
|
|
begin
|
|
// check if ChildSizing aligning is enabled
|
|
if (ChildSizing.Layout = cclNone) then
|
|
exit;
|
|
|
|
/// collect all 'not aligned' controls
|
|
AlignList.Clear;
|
|
for i := 0 to ControlCount - 1 do
|
|
begin
|
|
Control := Controls[i];
|
|
if IsNotAligned(Control) and Control.IsControlVisible then
|
|
AlignList.Add(Control);
|
|
end;
|
|
//debugln('DoAlignNotAligned ',DbgSName(Self),' AlignList.Count=',dbgs(AlignList.Count));
|
|
if AlignList.Count = 0 then exit;
|
|
|
|
LastBoundsMutated := nil;
|
|
AlignNonAlignedControls(AlignList, BoundsMutated);
|
|
end;
|
|
|
|
var
|
|
i: Integer;
|
|
OldRemainingClientRect: TRect;
|
|
OldRemainingBorderSpace: TRect;
|
|
MaxTries: LongInt;
|
|
r: TRect;
|
|
begin
|
|
//DebugLn(['TWinControl.AlignControls ',DbgSName(Self),' ',not (wcfAligningControls in FWinControlFlags)]);
|
|
if wcfAligningControls in FWinControlFlags then exit;
|
|
Include(FWinControlFlags,wcfAligningControls);
|
|
try
|
|
//if csDesigning in ComponentState then begin
|
|
//DebugLn('[TWinControl.AlignControls] ',Name,':',Classname,' ',Left,',',Top,',',Width,',',Height,' AlignWork=',NeedAlignWork,' ControlCount=',ControlCount);
|
|
//if AControl<>nil then DebugLn(' AControl=',AControl.Name,':',AControl.ClassName);
|
|
//end;
|
|
// first let the DockManager align controls
|
|
if DockSite and UseDockManager and (DockManager<>nil) then
|
|
DockManager.ResetBounds(false);
|
|
AdjustClientRect(RemainingClientRect);
|
|
r:=GetLogicalClientRect;
|
|
ParentClientWidth:=r.Right;
|
|
ParentClientHeight:=r.Bottom;
|
|
|
|
if NeedAlignWork then
|
|
begin
|
|
//DebugLn(['TWinControl.AlignControls ',DbgSName(Self),' RemainingClientRect=',dbgs(RemainingClientRect),' ',dbgs(ClientRect)]);
|
|
RemainingBorderSpace:=Rect(0,0,0,0);
|
|
// adjust RemainingClientRect by ChildSizing properties
|
|
AdjustBorderSpace(RemainingClientRect,RemainingBorderSpace,
|
|
ChildSizing.LeftRightSpacing,ChildSizing.TopBottomSpacing,
|
|
ChildSizing.LeftRightSpacing,ChildSizing.TopBottomSpacing);
|
|
//DebugLn('[TWinControl.AlignControls] ',Name,':',Classname,' ',Left,',',Top,',',Width,',',Height,' ClientRect=',RemainingClientRect.Left,',',RemainingClientRect.Top,',',RemainingClientRect.Right,',',RemainingClientRect.Bottom);
|
|
AlignList := TFPList.Create;
|
|
try
|
|
// Auto aligning/anchoring can be very interdependent.
|
|
// In worst case the n-2 depends on the n-1, the n-3 depends on n-2
|
|
// and so forth. This is allowed, so do up to n loop step.
|
|
// Do not more, to avoid endless loops, if there are circle
|
|
// dependencies.
|
|
MaxTries:=ControlCount;
|
|
{$IFDEF CHECK_POSITION}inc(MaxTries);{$ENDIF}
|
|
for i:=1 to MaxTries do begin
|
|
// align and anchor child controls
|
|
BoundsMutated:=false;
|
|
OldRemainingClientRect:=RemainingClientRect;
|
|
OldRemainingBorderSpace:=RemainingBorderSpace;
|
|
DoAlign(alTop);
|
|
DoAlign(alBottom);
|
|
DoAlign(alLeft);
|
|
DoAlign(alRight);
|
|
DoAlign(alClient);
|
|
DoAlign(alCustom);
|
|
DoAlign(alNone);
|
|
DoAlignNotAligned;
|
|
if not BoundsMutated then break;
|
|
if (i=ControlCount+1) then begin
|
|
DebugLn(['TWinControl.AlignControls ENDLESS LOOP STOPPED ',DbgSName(Self),' i=',i]);
|
|
if LastBoundsMutated<>nil then
|
|
DebugLn(['TWinControl.AlignControls LAST CHANGED: ',DbgSName(LastBoundsMutated),' Old=',dbgs(LastBoundsMutatedOld),' Now=',dbgs(LastBoundsMutated.BoundsRect)]);
|
|
end;
|
|
// update again
|
|
RemainingClientRect:=OldRemainingClientRect;
|
|
RemainingBorderSpace:=OldRemainingBorderSpace;
|
|
end;
|
|
finally
|
|
AlignList.Free;
|
|
end;
|
|
end;
|
|
ControlsAligned;
|
|
finally
|
|
Exclude(FWinControlFlags,wcfAligningControls);
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.CustomAlignInsertBefore(AControl1, AControl2: TControl): Boolean;
|
|
begin
|
|
Result := Assigned(FOnAlignInsertBefore)
|
|
and FOnAlignInsertBefore(Self, AControl1, AControl2);
|
|
end;
|
|
|
|
procedure TWinControl.CustomAlignPosition(AControl: TControl; var ANewLeft, ANewTop,
|
|
ANewWidth, ANewHeight: Integer; var AlignRect: TRect; AlignInfo: TAlignInfo);
|
|
begin
|
|
if Assigned(FOnAlignPosition)
|
|
then FOnAlignPosition(Self, AControl, ANewLeft, ANewTop, ANewWidth, ANewHeight, AlignRect, AlignInfo);
|
|
end;
|
|
|
|
function TWinControl.DoAlignChildControls(TheAlign: TAlign; AControl: TControl;
|
|
AControlList: TFPList; var ARect: TRect): Boolean;
|
|
begin
|
|
Result:=false;
|
|
end;
|
|
|
|
procedure TWinControl.DoChildSizingChange(Sender: TObject);
|
|
begin
|
|
//debugln('TWinControl.DoChildSizingChange ',DbgSName(Self));
|
|
if ControlCount=0 then exit;
|
|
InvalidatePreferredSize;
|
|
ReAlign;
|
|
end;
|
|
|
|
procedure TWinControl.InvalidatePreferredChildSizes;
|
|
var
|
|
AControl: TControl;
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to ControlCount-1 do begin
|
|
AControl:=Controls[i];
|
|
Exclude(AControl.FControlFlags,cfPreferredSizeValid);
|
|
Exclude(AControl.FControlFlags,cfPreferredMinSizeValid);
|
|
if AControl is TWinControl then
|
|
Exclude(TWinControl(AControl).FWinControlFlags,wcfAdjustedLogicalClientRectValid);
|
|
if AControl is TWinControl then
|
|
TWinControl(AControl).InvalidatePreferredChildSizes;
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure TWinControl.DoAutoSize;
|
|
|
|
Shrink or enlarge to fit children.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoAutoSize;
|
|
var
|
|
HasVisibleChilds: boolean;
|
|
|
|
procedure GetMoveDiffForNonAlignedChilds(const CurClientRect: TRect;
|
|
out dx, dy: integer);
|
|
// how much can non-aligned-children be moved up and left
|
|
// non-aligned-children: no fixed anchoring or autosizing,
|
|
// (Align=alNone, visible, AnchorSide[].Control=nil)
|
|
// borderspacing is used
|
|
// e.g. dx=10 means all non-align-children should be moved 10 pixels to the left
|
|
var
|
|
NewClientWidth, NewClientHeight: integer;
|
|
Layout: TAutoSizeCtrlData;
|
|
begin
|
|
if ChildSizing.Layout<>cclNone then begin
|
|
dx:=0;
|
|
dy:=0;
|
|
exit;
|
|
end;
|
|
|
|
// get the move requirements for the child controls
|
|
Layout:=nil;
|
|
try
|
|
Layout:=TAutoSizeCtrlData.Create(Self);
|
|
Layout.ComputePreferredClientArea(
|
|
not (csAutoSizeKeepChildLeft in ControlStyle),
|
|
not (csAutoSizeKeepChildTop in ControlStyle),
|
|
dx,dy,NewClientWidth,NewClientHeight);
|
|
if (NewClientWidth<>0) or (NewClientHeight<>0) then ;
|
|
//if (dx<>0) or (dy<>0) then DebugLn(['GetMoveDiffForNonAlignedChilds ',DbgSName(Self),' dx=',dx,' dy=',dy]);
|
|
finally
|
|
Layout.Free;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
AControl: TControl;
|
|
PreferredWidth: LongInt;
|
|
PreferredHeight: LongInt;
|
|
CurClientRect: TRect;
|
|
WidthIsFixed: boolean;
|
|
HeightIsFixed: boolean;
|
|
NewLeft: LongInt;
|
|
NewTop: LongInt;
|
|
CurAnchors: TAnchors;
|
|
dx: Integer;
|
|
dy: Integer;
|
|
NewChildBounds: TRect;
|
|
OldChildBounds: TRect;
|
|
begin
|
|
{$IFDEF VerboseAllAutoSize}
|
|
debugln('TWinControl.DoAutoSize ',DbgSName(Self));
|
|
{$ENDIF}
|
|
if not (caspComputingBounds in AutoSizePhases) then begin
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAutoSize DELAYED AutoSizePhases=',dbgs(AutoSizePhases)]);
|
|
{$ENDIF}
|
|
AdjustSize;
|
|
exit;
|
|
end;
|
|
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DoAutoSize'){$ENDIF};
|
|
try
|
|
// test if resizing is possible
|
|
HasVisibleChilds:=false;
|
|
for i:=0 to ControlCount-1 do
|
|
if Controls[i].IsControlVisible then begin
|
|
HasVisibleChilds:=true;
|
|
break;
|
|
end;
|
|
|
|
CurAnchors:=Anchors;
|
|
if Align<>alNone then CurAnchors:=CurAnchors+AnchorAlign[Align];
|
|
WidthIsFixed:=WidthIsAnchored;
|
|
HeightIsFixed:=HeightIsAnchored;
|
|
|
|
// move free children as much as possible to left and top (all free children the same)
|
|
if HasVisibleChilds then begin
|
|
CurClientRect:=GetLogicalClientRect;
|
|
AdjustClientRect(CurClientRect);
|
|
// get minimum left, top of non aligned children
|
|
GetMoveDiffForNonAlignedChilds(CurClientRect,dx,dy);
|
|
//DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' CurClientRect=',dbgs(CurClientRect)]);
|
|
|
|
if (dx<>0) or (dy<>0) then begin
|
|
// move all free children to left and top of client area
|
|
//DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' dx=',dbgs(dx),' dy=',dbgs(dy),' CurClientRect=',dbgs(CurClientRect),' CurAnchors=',dbgs(CurAnchors),' IsFixed: w=',WidthIsFixed,' h=',HeightIsFixed]);
|
|
for I := 0 to ControlCount - 1 do begin
|
|
AControl:=Controls[I];
|
|
if not AControl.IsControlVisible then continue;
|
|
if AControl.Align<>alNone then continue;
|
|
//DebugLn(['TWinControl.DoAutoSize BEFORE ',DbgSName(AControl),' ',dbgs(AControl.BoundsRect)]);
|
|
NewChildBounds:=AControl.BoundsRect;
|
|
if (akLeft in AControl.Anchors)
|
|
and (AControl.AnchorSide[akLeft].Control=nil) then begin
|
|
dec(NewChildBounds.Left,dx);
|
|
if not (akRight in AControl.Anchors) then
|
|
dec(NewChildBounds.Right,dx);
|
|
end;
|
|
if (akTop in AControl.Anchors)
|
|
and (AControl.AnchorSide[akTop].Control=nil) then begin
|
|
dec(NewChildBounds.Top,dy);
|
|
if not (akBottom in AControl.Anchors) then
|
|
dec(NewChildBounds.Bottom,dy);
|
|
end;
|
|
// Important: change the BaseBounds too, otherwise the changes will be undone by AlignControls
|
|
OldChildBounds:=AControl.BoundsRect;
|
|
if not CompareRect(@OldChildBounds,@NewChildBounds) then begin
|
|
//DebugLn(['TWinControl.DoAutoSize moving child: ',DbgSName(AControl),' Old=',dbgs(OldChildBounds),' New=',dbgs(NewChildBounds)]);
|
|
AControl.BoundsRect:=NewChildBounds;
|
|
//DebugLn(['TWinControl.DoAutoSize AFTER ',DbgSName(AControl),' ',dbgs(AControl.BoundsRect)]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// autosize control to preferred size
|
|
if (not WidthIsFixed) or (not HeightIsFixed) then begin
|
|
GetPreferredSize(PreferredWidth,PreferredHeight,
|
|
false,// with constraints
|
|
true // with theme space
|
|
);
|
|
//if ControlCount>0 then DebugLn(['TWinControl.DoAutoSize ',DbgSName(Self),' PreferredWidth=',PreferredWidth,' PreferredHeight=',PreferredHeight,' ControlCount=',ControlCount]);
|
|
end else begin
|
|
PreferredWidth:=0;
|
|
PreferredHeight:=0;
|
|
end;
|
|
if WidthIsFixed or (PreferredWidth<0)
|
|
or ((PreferredWidth=0) and (not (csAutoSize0x0 in ControlStyle))) then
|
|
PreferredWidth:=Constraints.MinMaxWidth(Width);
|
|
if HeightIsFixed or (PreferredHeight<0)
|
|
or ((PreferredHeight=0) and (not (csAutoSize0x0 in ControlStyle))) then
|
|
PreferredHeight:=Constraints.MinMaxHeight(Height);
|
|
|
|
// set new size
|
|
{$IF defined(VerboseAutoSize) or defined(VerboseAllAutoSize)}
|
|
debugln(['TWinControl.DoAutoSize A ',DbgSName(Self),' Cur=',Width,'x',Height,' Prefer=',PreferredWidth,'x',PreferredHeight,' WidgetClass=',WidgetSetClass.ClassName,' Fixed=',WidthIsFixed,'x',HeightIsFixed]);
|
|
{$ENDIF}
|
|
if (PreferredWidth<>Width) or (PreferredHeight<>Height) then begin
|
|
// adjust Left/Top as well to reduce auto sizing overhead
|
|
NewLeft:=Left;
|
|
NewTop:=Top;
|
|
if akRight in CurAnchors then
|
|
inc(NewLeft,Width-PreferredWidth);
|
|
if akBottom in CurAnchors then
|
|
inc(NewTop,Height-PreferredHeight);
|
|
//if CompareText(Name,'NewUnitOkButton')=0 then
|
|
//debugln(['DoAutoSize Resize ',DbgSName(Self),' Old=',dbgs(BoundsRect),' New=',dbgs(Bounds(NewLeft,NewTop,PreferredWidth,PreferredHeight)),' WidthIsFixed=',WidthIsFixed,' HeightIsFixed=',HeightIsFixed,' Align=',dbgs(Align),' Anchors=',dbgs(Anchors)]);
|
|
SetBoundsKeepBase(NewLeft,NewTop,PreferredWidth,PreferredHeight);
|
|
end;
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DoAutoSize'){$ENDIF};
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.DoAllAutoSize;
|
|
|
|
function CheckHandleAllocated(AWinControl: TWinControl): boolean;
|
|
// true if a handle was missing
|
|
var
|
|
i: Integer;
|
|
ChildWinControl: TWinControl;
|
|
begin
|
|
if AWinControl.HandleObjectShouldBeVisible and (not AWinControl.HandleAllocated) then
|
|
begin
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize CREATE HANDLE ',DbgSName(AWinControl)]);
|
|
{$ENDIF}
|
|
AWinControl.HandleNeeded;
|
|
Exit(True);
|
|
end;
|
|
Result := False;
|
|
for i := 0 to AWinControl.ControlCount - 1 do
|
|
begin
|
|
ChildWinControl := TWinControl(AWinControl.Controls[i]);
|
|
if (ChildWinControl is TWinControl) and CheckHandleAllocated(ChildWinControl) then
|
|
Result:=true;
|
|
end;
|
|
end;
|
|
|
|
procedure ClearRequests(AControl: TControl);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Exclude(AControl.FControlFlags,cfAutoSizeNeeded);
|
|
if AControl is TWinControl then
|
|
for i:=0 to TWinControl(AControl).ControlCount-1 do
|
|
ClearRequests(TWinControl(AControl).Controls[i]);
|
|
end;
|
|
|
|
procedure UpdateShowingRecursive(AWinControl: TWinControl;
|
|
OnlyChildren: boolean);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
// first make the children visible
|
|
if AWinControl.FControls<>nil then
|
|
for i:=0 to AWinControl.FControls.Count-1 do
|
|
if TObject(AWinControl.FControls[i]) is TWinControl then
|
|
UpdateShowingRecursive(TWinControl(AWinControl.FControls[i]),false);
|
|
// then make the control visible
|
|
if not OnlyChildren and AWinControl.HandleObjectShouldBeVisible and not AWinControl.Showing then
|
|
AWinControl.UpdateShowing;
|
|
end;
|
|
|
|
var
|
|
RealizeCounter: Integer;
|
|
UpdateShowingCounter: Integer;
|
|
begin
|
|
if wcfAllAutoSizing in FWinControlFlags then exit;
|
|
if AutoSizeDelayed then exit;
|
|
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize START ',DbgSName(Self),' ',dbgs(BoundsRect)]);
|
|
{$ENDIF}
|
|
Include(FWinControlFlags,wcfAllAutoSizing);
|
|
try
|
|
// create needed handles
|
|
if HandleObjectShouldBeVisible then begin
|
|
if CheckHandleAllocated(Self) then begin
|
|
// a new handle was created
|
|
end;
|
|
end else begin
|
|
// no autosize possible => remove needed flags
|
|
ClearRequests(Self);
|
|
exit;
|
|
end;
|
|
|
|
RealizeCounter:=0;
|
|
UpdateShowingCounter:=0;
|
|
while (not AutoSizeDelayed) do
|
|
begin
|
|
// compute all sizes for LCL objects without touching the widgetset
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize COMPUTE BOUNDS ',DbgSName(Self),' old=',dbgs(BoundsRect)]);
|
|
{$ENDIF}
|
|
inherited DoAllAutoSize;
|
|
if cfAutoSizeNeeded in FControlFlags then RaiseGDBException('');
|
|
AllAutoSized;
|
|
// send all new bounds to widgetset
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize REALIZE BOUNDS ',DbgSName(Self),' lclbounds=',dbgs(BoundsRect)]);
|
|
{$ENDIF}
|
|
inc(RealizeCounter);
|
|
if RealizeCounter=100 then
|
|
Include(FWinControlFlags,wcfKillIntfSetBounds);
|
|
RealizeBoundsRecursive;
|
|
if (cfAutoSizeNeeded in FControlFlags) then continue; // repeat computing bounds
|
|
RealizeCounter:=0;
|
|
inc(UpdateShowingCounter);
|
|
// make child handles visible
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize UPDATESHOWING children ',DbgSName(Self),' lclbounds=',dbgs(BoundsRect)]);
|
|
{$ENDIF}
|
|
Include(FWinControlFlags,wcfUpdateShowing);
|
|
try
|
|
UpdateShowingRecursive(Self,true);
|
|
finally
|
|
Exclude(FWinControlFlags,wcfUpdateShowing);
|
|
end;
|
|
// check if another turn is needed
|
|
if not (cfAutoSizeNeeded in FControlFlags) then break; // complete
|
|
end;
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize END ',DbgSName(Self),' ',dbgs(BoundsRect)]);
|
|
{$ENDIF}
|
|
finally
|
|
FWinControlFlags:=FWinControlFlags-[wcfAllAutoSizing,wcfKillIntfSetBounds];
|
|
end;
|
|
// make handle visible => this can trigger events like Form.OnShow where
|
|
// application does arbitrary stuff
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize UPDATESHOWING self ',DbgSName(Self),' lclbounds=',dbgs(BoundsRect)]);
|
|
{$ENDIF}
|
|
if not (wcfUpdateShowing in FWinControlFlags) then
|
|
begin
|
|
Include(FWinControlFlags, wcfUpdateShowing);
|
|
try
|
|
if HandleObjectShouldBeVisible and not Showing then
|
|
UpdateShowing
|
|
else begin
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.DoAllAutoSize not UPDATESHOWING self ',DbgSName(Self),' because HandleObjectShouldBeVisible=',HandleObjectShouldBeVisible,' Showing=',Showing]);
|
|
{$ENDIF}
|
|
end;
|
|
finally
|
|
Exclude(FWinControlFlags, wcfUpdateShowing);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.AllAutoSized;
|
|
begin
|
|
// see TCustomForm.AllAutoSized
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl BroadCast
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.BroadCast(var ToAllMessage);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to ControlCount - 1 do
|
|
begin
|
|
Controls[I].WindowProc(TLMessage(ToAllMessage));
|
|
if TLMessage(ToAllMessage).Result <> 0 then Exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.NotifyControls(Msg: Word);
|
|
var
|
|
ToAllMessage: TLMessage;
|
|
begin
|
|
ToAllMessage.Msg := Msg;
|
|
ToAllMessage.WParam := 0;
|
|
ToAllMessage.LParam := 0;
|
|
ToAllMessage.Result := 0;
|
|
Broadcast(ToAllMessage);
|
|
end;
|
|
|
|
procedure TWinControl.DefaultHandler(var AMessage);
|
|
begin
|
|
TWSWinControlClass(WidgetSetClass).DefaultWndHandler(Self, AMessage);
|
|
end;
|
|
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CanFocus
|
|
|
|
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.CanFocus: Boolean;
|
|
var
|
|
Control: TWinControl;
|
|
Form: TCustomForm;
|
|
begin
|
|
Result := False;
|
|
//Verify that every parent is enabled and visible before returning true.
|
|
Form := GetParentForm(Self);
|
|
if Form <> nil then
|
|
begin
|
|
Control := Self;
|
|
repeat
|
|
if Control = Form then break;
|
|
// test all except the Form if it is visible and enabled
|
|
if not (Control.IsControlVisible and Control.Enabled) then Exit;
|
|
Control := Control.Parent;
|
|
until False;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CanSetFocus
|
|
|
|
CanSetFocus should be prefered over CanFocus if used in CanSetFocus/SetFocus
|
|
combination
|
|
|
|
if MyControl.CanSetFocus then
|
|
MyControl.SetFocus;
|
|
|
|
because it checks also if the parent form can receive focus and thus prevents
|
|
the "cannot focus an invisible window" LCL exception.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.CanSetFocus: Boolean;
|
|
var
|
|
Control: TWinControl;
|
|
begin
|
|
Control := Self;
|
|
while True do
|
|
begin
|
|
// test if all are visible and enabled
|
|
if not (Control.IsControlVisible and Control.Enabled) then
|
|
Exit(False);
|
|
if not Assigned(Control.Parent) then
|
|
Break;
|
|
Control := Control.Parent;
|
|
end;
|
|
Result := Control is TCustomForm;//the very top parent must be a form
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CreateSubClass
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CreateSubClass(var Params: TCreateParams;
|
|
ControlClassName: PChar);
|
|
begin
|
|
// TODO: Check if we need this method
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DisableAlign
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DisableAlign;
|
|
begin
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DisableAlign'){$ENDIF};
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl DoAdjustClientRectChange
|
|
|
|
Asks the interface if clientrect has changed since last AlignControl
|
|
and calls AdjustSize on change.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoAdjustClientRectChange(const InvalidateRect: Boolean = True);
|
|
var
|
|
R: TRect;
|
|
begin
|
|
if InvalidateRect then
|
|
InvalidateClientRectCache(False);
|
|
R := GetClientRect;
|
|
AdjustClientRect(R);
|
|
//if CheckPosition(Self) then
|
|
//DebugLn(['TWinControl.DoAdjustClientRectChange ',DbgSName(Self),' new=',dbgs(r),' old=',dbgs(FAdjustClientRectRealized),' ',CompareRect(@r,@FAdjustClientRectRealized)]);
|
|
if not CompareRect(@R, @FAdjustClientRectRealized) then
|
|
begin
|
|
// client rect changed since last AlignControl
|
|
{$IF defined(VerboseAllAutoSize) or defined(VerboseClientRectBugFix) or defined(VerboseIntfSizing) or defined(VerboseOnResize)}
|
|
DebugLn(['TWinControl.DoAdjustClientRectChange ClientRect changed ',DbgSName(Self),
|
|
' Old=',Dbgs(FAdjustClientRectRealized),' New=',DbgS(R)]);
|
|
{$ENDIF}
|
|
FAdjustClientRectRealized := R;
|
|
AdjustSize;
|
|
Resize;
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl DoConstraintsChange
|
|
Params: Sender : TObject
|
|
|
|
Call inherited, then send the constraints to the interface
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoConstraintsChange(Sender : TObject);
|
|
begin
|
|
inherited DoConstraintsChange(Sender);
|
|
//debugln('TWinControl.DoConstraintsChange ',DbgSName(Self),' HandleAllocated=',dbgs(HandleAllocated));
|
|
if HandleAllocated then
|
|
TWSWinControlClass(WidgetSetClass).ConstraintsChange(Self);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl InvalidateClientRectCache(WithChildControls: boolean)
|
|
|
|
The clientrect is cached. Call this procedure to invalidate the cache, so that
|
|
next time the clientrect is fetched from the interface.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.InvalidateClientRectCache(WithChildControls: boolean);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
if Name=CheckClientRectName then begin
|
|
DebugLn(['TWinControl.InvalidateClientRectCache ',DbgSName(Self)]);
|
|
//DumpStack;
|
|
end;
|
|
{$ENDIF}
|
|
Exclude(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
|
|
Include(FWinControlFlags,wcfClientRectNeedsUpdate);
|
|
|
|
if WithChildControls then begin
|
|
// invalidate clients too
|
|
if Assigned(FControls) then
|
|
for I := 0 to FControls.Count - 1 do
|
|
if TObject(FControls.Items[I]) is TWinControl then
|
|
TWinControl(FControls.Items[I]).InvalidateClientRectCache(true);
|
|
end;
|
|
InvalidatePreferredSize;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl ClientRectNeedsInterfaceUpdate
|
|
|
|
The clientrect is cached. Check if cache is valid.
|
|
-------------------------------------------------------------------------------}
|
|
function TWinControl.ClientRectNeedsInterfaceUpdate: boolean;
|
|
var
|
|
InterfaceWidth, InterfaceHeight: integer;
|
|
IntfClientRect: TRect;
|
|
begin
|
|
if (not HandleAllocated) or (csDestroyingHandle in ControlState)
|
|
or (csDestroying in ComponentState)
|
|
then
|
|
exit(false);
|
|
if wcfClientRectNeedsUpdate in FWinControlFlags then
|
|
exit(true);
|
|
// get the current interface bounds
|
|
LCLIntf.GetWindowSize(Handle,InterfaceWidth,InterfaceHeight);
|
|
LCLIntf.GetClientRect(Handle,IntfClientRect);
|
|
// The LCL is not always in sync with the interface.
|
|
// Add the difference between LCL size and interface size to the
|
|
// interface clientrect
|
|
inc(IntfClientRect.Right,Width-InterfaceWidth);
|
|
inc(IntfClientRect.Bottom,Height-InterfaceHeight);
|
|
Result:=(FClientWidth<>IntfClientRect.Right)
|
|
or (FClientHeight<>IntfClientRect.Bottom);
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
if (Name=CheckClientRectName) and Result then
|
|
DebugLn(['TWinControl.ClientRectNeedsInterfaceUpdate ',DbgSName(Self),' ',dbgs(IntfClientRect)]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl DoSetBounds
|
|
Params: ALeft, ATop, AWidth, AHeight : integer
|
|
|
|
Anticipate the new clientwidth/height and call inherited
|
|
|
|
Normally the clientwidth/clientheight is adjusted automatically by the
|
|
interface. But it is up to interface when this will be done. The gtk for
|
|
example just puts resize requests into a queue. The LCL would resize the
|
|
children just after this procedure due to the clientrect. On complex forms with
|
|
lots of nested controls, this would result in thousands of resizes.
|
|
Changing the clientrect in the LCL to the most probable size reduces
|
|
unneccessary resizes.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoSetBounds(ALeft, ATop, AWidth, AHeight : integer);
|
|
var
|
|
OldWidth: LongInt;
|
|
OldHeight: LongInt;
|
|
begin
|
|
//DbgOut('[TWinControl.DoSetBounds] ',Name,':',ClassName,' OldHeight=',DbgS(FHeight),' NewHeight=',DbgS(AHeight));
|
|
OldWidth:=Width;
|
|
OldHeight:=Height;
|
|
inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
|
|
// adapt Clientrect
|
|
inc(FClientWidth,Width-OldWidth);
|
|
if FClientWidth<0 then FClientWidth:=0;
|
|
inc(FClientHeight,Height-OldHeight);
|
|
if FClientHeight<0 then FClientHeight:=0;
|
|
Exclude(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl EnableAlign
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.EnableAlign;
|
|
begin
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DisableAlign'){$ENDIF};
|
|
end;
|
|
|
|
procedure TWinControl.WriteLayoutDebugReport(const Prefix: string);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited WriteLayoutDebugReport(Prefix);
|
|
for i:=0 to ControlCount-1 do
|
|
Controls[i].WriteLayoutDebugReport(Prefix+' ');
|
|
end;
|
|
|
|
procedure TWinControl.AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
|
|
const AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited AutoAdjustLayout(AMode, AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth);
|
|
|
|
for i:=0 to ControlCount-1 do
|
|
Controls[i].AutoAdjustLayout(AMode, AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl.CanTab
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.CanTab: Boolean;
|
|
begin
|
|
Result := CanFocus and TWSWinControlClass(WidgetSetClass).CanFocus(Self);
|
|
end;
|
|
|
|
function TWinControl.DoDragMsg(ADragMessage: TDragMessage; APosition: TPoint; ADragObject: TDragObject; ATarget: TControl; ADocking: Boolean):LRESULT;
|
|
var
|
|
TargetControl: TControl;
|
|
begin
|
|
case ADragMessage of
|
|
dmFindTarget:
|
|
begin
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TWinControl.DoDragMsg dmFindTarget ',Name,':',ClassName,' Start DragMsg.DragRec^.Pos=',IntToStr(APosition.X),',',IntToStr(APosition.Y));
|
|
{$ENDIF}
|
|
TargetControl := ControlAtPos(ScreentoClient(APosition),
|
|
[capfAllowWinControls,capfRecursive]);
|
|
if TargetControl = nil then TargetControl := Self;
|
|
{$IFDEF VerboseDrag}
|
|
DebugLn('TWinControl.DoDragMsg dmFindTarget ',Name,':',ClassName,' end Result=',TargetControl.Name,':',TargetControl.ClassName);
|
|
{$ENDIF}
|
|
Result := LRESULT(TargetControl);
|
|
end;
|
|
else
|
|
Result := inherited;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetChildren
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.GetChildren(Proc: TGetChildProc; Root: TComponent);
|
|
var
|
|
I : Integer;
|
|
Control : TControl;
|
|
begin
|
|
for I := 0 to ControlCount-1 do
|
|
begin
|
|
Control := Controls[i];
|
|
if Control.Owner = Root then Proc(Control);
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function TWinControl.ChildClassAllowed(ChildClass: TClass): boolean;
|
|
|
|
Allow TControl as child.
|
|
-------------------------------------------------------------------------------}
|
|
function TWinControl.ChildClassAllowed(ChildClass: TClass): boolean;
|
|
begin
|
|
Result:=(ChildClass<>nil) and ChildClass.InheritsFrom(TControl);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl GetClientOrigin
|
|
Result: TPoint
|
|
|
|
returns the screen coordinate of the topleft coordinate 0,0 of the client area
|
|
Note that this value is the position as stored in the interface and is not
|
|
always in sync with the LCL. When a control is moved, the LCL sets the bounds
|
|
to the wanted position and sends a move message to the interface. It is up to
|
|
the interface to handle moves instantly or queued.
|
|
-------------------------------------------------------------------------------}
|
|
function TWinControl.GetClientOrigin: TPoint;
|
|
var
|
|
AControl: TWinControl;
|
|
begin
|
|
Result.X := 0;
|
|
Result.Y := 0;
|
|
if HandleAllocated then
|
|
begin
|
|
// get the interface idea where the client area is on the screen
|
|
LCLIntf.ClientToScreen(Handle, Result);
|
|
// adjust the result by all bounds, that are not yet sent to the interface
|
|
AControl := Self;
|
|
repeat
|
|
inc(Result.X, AControl.Left - AControl.FBoundsRealized.Left);
|
|
inc(Result.Y, AControl.Top - AControl.FBoundsRealized.Top);
|
|
AControl := AControl.Parent;
|
|
until AControl = nil;
|
|
end else
|
|
if Parent <> nil then
|
|
Result := inherited GetClientOrigin;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl GetClientRect
|
|
Result: TRect
|
|
|
|
returns the client area. Starting at 0,0.
|
|
-------------------------------------------------------------------------------}
|
|
function TWinControl.GetClientRect: TRect;
|
|
|
|
procedure StoreClientRect(NewClientRect: TRect);
|
|
var
|
|
ClientSizeChanged: boolean;
|
|
begin
|
|
if wcfClientRectNeedsUpdate in FWinControlFlags then begin
|
|
ClientSizeChanged:=(FClientWidth<>NewClientRect.Right)
|
|
or (FClientHeight<>NewClientRect.Bottom);
|
|
if ClientSizeChanged then begin
|
|
FClientWidth:=NewClientRect.Right;
|
|
FClientHeight:=NewClientRect.Bottom;
|
|
{$IF defined(VerboseNewAutoSize) or defined(CHECK_POSITION)}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
DebugLn(['StoreClientRect ',Name,':',ClassName,' ',FClientWidth,',',FClientHeight,' HandleAllocated=',HandleAllocated]);
|
|
{$ENDIF}
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
DebugLn(['StoreClientRect ',DbgSName(Self),' ',FClientWidth,',',FClientHeight,' HandleAllocated=',HandleAllocated,' wcfBoundsRealized=',wcfBoundsRealized in FWinControlFlags]);
|
|
{$ENDIF}
|
|
Exclude(FWinControlFlags,wcfAdjustedLogicalClientRectValid);
|
|
end;
|
|
Exclude(FWinControlFlags,wcfClientRectNeedsUpdate);
|
|
end;
|
|
end;
|
|
|
|
procedure GetDefaults(var r: TRect);
|
|
begin
|
|
r:=inherited GetClientRect;
|
|
if csLoading in ComponentState then begin
|
|
if cfClientWidthLoaded in FControlFlags then
|
|
r.Right:=FLoadedClientSize.cx;
|
|
if cfClientHeightLoaded in FControlFlags then
|
|
r.Bottom:=FLoadedClientSize.cy;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
InterfaceWidth, InterfaceHeight: integer;
|
|
begin
|
|
if wcfClientRectNeedsUpdate in FWinControlFlags then begin
|
|
//DebugLn(['TWinControl.GetClientRect ',DbgSName(Self),' ',HandleAllocated,' ',wcfBoundsRealized in FWinControlFlags]);
|
|
if TWSWinControlClass(WidgetSetClass).GetDefaultClientRect(Self,
|
|
Left, Top, Width, Height, Result)
|
|
then begin
|
|
// the LCL interface provided a ClientRect
|
|
end
|
|
else if HandleAllocated then
|
|
begin
|
|
// update clientrect from interface
|
|
LCLIntf.GetClientRect(Handle, Result);
|
|
// the LCL is not always in sync with the interface
|
|
// -> adjust client rect based on LCL bounds
|
|
// for example: if the Width in LCL differ from the Width of the Interface
|
|
// object, then adjust the clientwidth accordingly
|
|
// this often anticipates later LM_SIZE messages from the interface
|
|
// and reduces resizes
|
|
LCLIntf.GetWindowSize(Handle, InterfaceWidth, InterfaceHeight);
|
|
{$IF defined(VerboseNewAutoSize) or defined(CHECK_POSITION)}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
debugln('TWinControl.GetClientRect ',DbgSName(Self),' Interface=',dbgs(InterfaceWidth),',',dbgs(InterfaceHeight),' Result=',dbgs(Result),' Bounds=',dbgs(BoundsRect));
|
|
{$ENDIF}
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
//if Name=CheckClientRectName then
|
|
debugln('TWinControl.GetClientRect ',DbgSName(Self),' InterfaceSize=',dbgs(InterfaceWidth),',',dbgs(InterfaceHeight),' Result=',dbgs(Result),' Bounds=',dbgs(BoundsRect));
|
|
{$ENDIF}
|
|
if (Width<>InterfaceWidth)
|
|
or (Height<>InterfaceHeight) then
|
|
begin
|
|
// the LCL is not in sync with the interface
|
|
if wcfBoundsRealized in FWinControlFlags then
|
|
begin
|
|
// no bounds were sent yet to the interface and it didn't initialize
|
|
// them on its own
|
|
// => the client bounds from the interface are not yet ready
|
|
// they will probably change
|
|
// to avoid resizes it is better use the defaults
|
|
GetDefaults(Result);
|
|
end else begin
|
|
// -> adjust client rect based on LCL bounds
|
|
// for example: if the Width in LCL differ from the Width of the Interface
|
|
// object, then adjust the clientwidth accordingly
|
|
// this often anticipates later LM_SIZE messages from the interface
|
|
// and reduces resizes
|
|
inc(Result.Right,Width-InterfaceWidth);
|
|
inc(Result.Bottom,Height-InterfaceHeight);
|
|
end;
|
|
end;
|
|
end else begin
|
|
// no handle and no interface help => use defaults
|
|
GetDefaults(Result);
|
|
end;
|
|
Result.Right:=Max(Result.Left,Result.Right);
|
|
Result.Bottom:=Max(Result.Top,Result.Bottom);
|
|
StoreClientRect(Result);
|
|
|
|
{r:=inherited GetClientRect;
|
|
if (r.Left<>Result.Left)
|
|
or (r.Top<>Result.Top)
|
|
or (r.Right<>Result.Right)
|
|
or (r.Bottom<>Result.Bottom) then begin
|
|
//DebugLn(' TWinControl.GetClientRect ',Name,':',ClassName,
|
|
// ' Old=',r.Left,',',r.Top,',',r.Right,',',r.Bottom,
|
|
// ' New=',Result.Left,',',Result.Top,',',Result.Right,',',Result.Bottom
|
|
// );
|
|
end;}
|
|
|
|
end else begin
|
|
Result:=Rect(0,0,FClientWidth,FClientHeight);
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
TWinControl GetControlOrigin
|
|
Result: TPoint
|
|
|
|
Returns the screen coordinate of the topleft coordinate 0,0 of the control
|
|
area. (The topleft pixel of the control on the screen)
|
|
Note that this value is the position as stored in the interface and is not
|
|
always in sync with the LCL. When a control is moved, the LCL sets the bounds
|
|
to the wanted position and sends a move message to the interface. It is up to
|
|
the interface to handle moves instantly or queued.
|
|
-------------------------------------------------------------------------------}
|
|
function TWinControl.GetControlOrigin: TPoint;
|
|
var
|
|
AControl: TWinControl;
|
|
IntfBounds: TRect;
|
|
begin
|
|
if HandleAllocated then
|
|
begin
|
|
// get the interface idea where the client area is on the screen
|
|
LCLIntf.GetWindowRect(Handle,IntfBounds);
|
|
Result.X := IntfBounds.Left;
|
|
Result.Y := IntfBounds.Top;
|
|
// adjust the result by all bounds, that are not yet sent to the interface
|
|
AControl := Self;
|
|
repeat
|
|
inc(Result.X, AControl.Left - AControl.FBoundsRealized.Left);
|
|
inc(Result.Y, AControl.Top - AControl.FBoundsRealized.Top);
|
|
AControl := AControl.Parent;
|
|
until AControl = nil;
|
|
end else
|
|
Result:=inherited GetControlOrigin;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.GetChildrenRect(Scrolled: boolean): TRect;
|
|
|
|
Returns the Client rectangle relative to the controls left, top.
|
|
If Scrolled is true, the rectangle is moved by the current scrolling values
|
|
(for an example see TScrollingWincontrol).
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetChildrenRect(Scrolled: boolean): TRect;
|
|
var
|
|
ScrolledOffset: TPoint;
|
|
begin
|
|
if HandleAllocated then begin
|
|
LCLIntf.GetClientBounds(Handle,Result);
|
|
if Scrolled then begin
|
|
ScrolledOffset:=GetClientScrollOffset;
|
|
inc(Result.Left,ScrolledOffset.X);
|
|
inc(Result.Top,ScrolledOffset.Y);
|
|
inc(Result.Right,ScrolledOffset.X);
|
|
inc(Result.Bottom,ScrolledOffset.Y);
|
|
end;
|
|
end else
|
|
Result:=inherited GetChildrenRect(Scrolled);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl SetBorderStyle
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetBorderStyle(NewStyle: TBorderStyle);
|
|
begin
|
|
FBorderStyle := NewStyle;
|
|
if HandleAllocated then
|
|
TWSWinControlClass(WidgetSetClass).SetBorderStyle(Self, NewStyle);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl SetBorderWidth
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetBorderWidth(Value: TBorderWidth);
|
|
begin
|
|
if FBorderWidth = Value then exit;
|
|
FBorderWidth := Value;
|
|
Perform(CM_BORDERCHANGED, 0, 0);
|
|
end;
|
|
|
|
procedure TWinControl.SetParentWindow(const AValue: HWND);
|
|
begin
|
|
if (ParentWindow = AValue) or Assigned(Parent) then Exit;
|
|
FParentWindow := AValue;
|
|
if HandleAllocated then
|
|
if (AValue <> 0) then
|
|
LCLIntf.SetParent(Handle, AValue)
|
|
else
|
|
DestroyHandle;
|
|
UpdateControlState;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl.SetChildZPosition
|
|
|
|
Set the position of the child control in the TWinControl(s)
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetChildZPosition(const AChild: TControl;
|
|
const APosition: Integer);
|
|
var
|
|
OldPos, NewPos: Integer;
|
|
IsWinControl: boolean;
|
|
i: Integer;
|
|
WinControls: TFPList;
|
|
begin
|
|
if AChild = nil
|
|
then begin
|
|
DebugLn('WARNING: TWinControl.SetChildZPosition: Child = nil');
|
|
Exit;
|
|
end;
|
|
|
|
IsWinControl := AChild is TWincontrol;
|
|
|
|
if FControls = nil then
|
|
begin
|
|
DebugLn('WARNING: TWinControl.SetChildZPosition: Unknown child');
|
|
Exit;
|
|
end;
|
|
OldPos := FControls.IndexOf(AChild);
|
|
if OldPos<0 then begin
|
|
DebugLn('WARNING: TWinControl.SetChildZPosition: Not a child');
|
|
Exit;
|
|
end;
|
|
|
|
NewPos := APosition;
|
|
|
|
if NewPos < 0 then
|
|
NewPos := 0;
|
|
if NewPos >= FControls.Count then
|
|
NewPos := FControls.Count - 1;
|
|
|
|
if NewPos = OldPos then Exit;
|
|
|
|
FControls.Move(OldPos, NewPos);
|
|
|
|
if IsWinControl then
|
|
begin
|
|
if HandleAllocated and TWinControl(AChild).HandleAllocated then
|
|
begin
|
|
// ignore children without handle
|
|
WinControls:=TFPList.Create;
|
|
try
|
|
for i:=FControls.Count-1 downto 0 do
|
|
begin
|
|
if (TObject(FControls[i]) is TWinControl) then
|
|
begin
|
|
WinControls.Add(FControls[i]);
|
|
end else begin
|
|
if i<OldPos then dec(OldPos);
|
|
if i<NewPos then dec(NewPos);
|
|
end;
|
|
end;
|
|
TWSWinControlClass(WidgetSetClass).SetChildZPosition(Self,
|
|
TWinControl(AChild), OldPos, NewPos, WinControls);
|
|
finally
|
|
WinControls.Free;
|
|
end;
|
|
end;
|
|
end
|
|
else begin
|
|
AChild.InvalidateControl(AChild.IsVisible, True, True);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl.SetTabOrder
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetTabOrder(NewTabOrder: TTabOrder);
|
|
begin
|
|
if csLoading in ComponentState then
|
|
FTabOrder := NewTabOrder
|
|
else
|
|
UpdateTabOrder(NewTabOrder);
|
|
end;
|
|
|
|
procedure TWinControl.SetTabStop(NewTabStop: Boolean);
|
|
begin
|
|
if FTabStop = NewTabStop then
|
|
Exit;
|
|
FTabStop := NewTabStop;
|
|
UpdateTabOrder(FTabOrder);
|
|
Perform(CM_TABSTOPCHANGED, 0, 0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TControl UpdateTabOrder
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.UpdateTabOrder(NewTabOrder: TTabOrder);
|
|
var
|
|
Count: Integer;
|
|
begin
|
|
if FParent <> nil then
|
|
begin
|
|
FTabOrder := GetTabOrder;
|
|
Count := ListCount(FParent.FTabList);
|
|
if NewTabOrder < 0 then
|
|
NewTabOrder := Count;
|
|
if FTabOrder = -1 then
|
|
Inc(Count);
|
|
if NewTabOrder > Count then
|
|
NewTabOrder := Count;
|
|
if NewTabOrder <> FTabOrder then
|
|
begin
|
|
if FTabOrder <> - 1 then
|
|
ListDelete(FParent.FTabList,FTabOrder);
|
|
if NewTabOrder <> -1 then
|
|
begin
|
|
if NewTabOrder = Count then
|
|
ListAdd(FParent.FTabList,Self)
|
|
else
|
|
ListInsert(FParent.FTabList,NewTabOrder,Self);
|
|
FTabOrder := NewTabOrder;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
procedure TWinControl.SendMoveSizeMessages(SizeChanged, PosChanged: boolean);
|
|
|
|
Send Move and Size messages through the LCL message paths. This simulates the
|
|
VCL behaviour and has no real effect.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.SendMoveSizeMessages(SizeChanged, PosChanged: boolean);
|
|
var
|
|
SizeMsg : TLMSize;
|
|
MoveMsg : TLMMove;
|
|
//Flags: UINT;
|
|
begin
|
|
if (not HandleAllocated)
|
|
or ((not SizeChanged) and (not PosChanged)) then exit;
|
|
|
|
Perform(LM_WindowposChanged, 0, 0);
|
|
|
|
if SizeChanged then
|
|
begin
|
|
with SizeMsg do
|
|
begin
|
|
Msg := LM_SIZE;
|
|
SizeType := 6; // force realign
|
|
Width := FWidth;
|
|
Height := FHeight;
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
DebugLn(' [TControl.SendMoveSizeMessages] ',Name,':',ClassName,' SizeMsg Width=',DbgS(Width),' Height=',DbgS(Height));
|
|
{$ENDIF}
|
|
end;
|
|
WindowProc(TLMessage(SizeMsg));
|
|
end;
|
|
|
|
if PosChanged then
|
|
begin
|
|
with MoveMsg do
|
|
begin
|
|
Msg:= LM_MOVE;
|
|
MoveType:= 1;
|
|
XPos:= FLeft;
|
|
YPos:= FTop;
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
DebugLn(' [TControl.SendMoveSizeMessages] ',Name,':',ClassName,' MoveMsg XPos=',Dbgs(XPos),' YPos=',Dbgs(YPos));
|
|
{$ENDIF}
|
|
end;
|
|
WindowProc(TLMessage(MoveMsg));
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl UpdateShowing
|
|
|
|
Check control's handle visibility.
|
|
If handle should become visible the handle and child handles are created.
|
|
The
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.UpdateShowing;
|
|
|
|
procedure ChangeShowing(bShow: Boolean);
|
|
begin
|
|
if FShowing = bShow then Exit;
|
|
FShowing := bShow;
|
|
try
|
|
{$IFDEF VerboseShowing}
|
|
DebugLn(['ChangeShowing ',DbgSName(Self),' new FShowing=',FShowing]);
|
|
{$ENDIF}
|
|
Perform(CM_SHOWINGCHANGED, 0, 0); // see TWinControl.CMShowingChanged
|
|
finally
|
|
if FShowing<>(wcfHandleVisible in FWinControlFlags) then
|
|
begin
|
|
FShowing := wcfHandleVisible in FWinControlFlags;
|
|
DebugLn(['TWinControl.UpdateShowing.ChangeShowing failed for ',DbgSName(Self),', Showing reset to ',FShowing]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
bShow: Boolean;
|
|
n: Integer;
|
|
begin
|
|
bShow := HandleObjectShouldBeVisible;
|
|
|
|
if bShow then
|
|
begin
|
|
if not HandleAllocated then CreateHandle;
|
|
if Assigned(FControls) then
|
|
begin
|
|
for n := 0 to FControls.Count - 1 do
|
|
if TObject(FControls[n]) is TWinControl then
|
|
TWinControl(FControls[n]).UpdateShowing;
|
|
end;
|
|
end;
|
|
if not HandleAllocated then
|
|
begin
|
|
{$IFDEF VerboseShowing}
|
|
if bShow then
|
|
DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' handle not allocated']);
|
|
{$ENDIF}
|
|
Exit;
|
|
end;
|
|
|
|
if FShowing = bShow then Exit;
|
|
//DebugLn(['TWinControl.UpdateShowing ',dbgsName(Self),' FShowing=',dbgs(FShowing),' bShow=',dbgs(bShow), ' IsWindowVisible=', IsWindowVisible(FHandle)]);
|
|
if bShow then
|
|
begin
|
|
// the Handle should become visible
|
|
// delay this until all other autosizing has been processed
|
|
if AutoSizeDelayed or (not (caspShowing in AutoSizePhases)) then
|
|
begin
|
|
{$IFDEF VerboseShowing}
|
|
if AutoSizeDelayed then DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' SKIPPING because AutoSizeDelayed: ',AutoSizeDelayedReport]);
|
|
if (not (caspShowing in AutoSizePhases)) then DebugLn(['TWinControl.UpdateShowing ',DbgSName(Self),' SKIPPING because wrong phase']);
|
|
{$ENDIF}
|
|
exit;
|
|
end;
|
|
end;
|
|
ChangeShowing(bShow);
|
|
end;
|
|
|
|
procedure TWinControl.Update;
|
|
begin
|
|
if HandleAllocated then UpdateWindow(Handle);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl Focused
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.Focused: Boolean;
|
|
begin
|
|
Result := CanTab and (HandleAllocated and (FindOwnerControl(GetFocus)=Self));
|
|
end;
|
|
|
|
function TWinControl.PerformTab(ForwardTab: boolean): boolean;
|
|
var
|
|
NewFocus: TWinControl;
|
|
ParentForm: TCustomForm;
|
|
begin
|
|
Result := True;
|
|
ParentForm := GetParentForm(Self);
|
|
if ParentForm = nil then
|
|
Exit;
|
|
NewFocus := ParentForm.FindNextControl(Self, ForwardTab, True, False);
|
|
if NewFocus = nil then
|
|
Exit;
|
|
|
|
NewFocus.SetFocus;
|
|
Result := NewFocus.Focused;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl SelectNext
|
|
|
|
Find next control (Tab control or Child control).
|
|
Like VCL the CurControl parameter is ignored.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SelectNext(CurControl: TWinControl; GoForward,
|
|
CheckTabStop: Boolean);
|
|
begin
|
|
CurControl := FindNextControl(CurControl, GoForward,
|
|
CheckTabStop, not CheckTabStop);
|
|
if CurControl <> nil then CurControl.SetFocus;
|
|
end;
|
|
|
|
procedure TWinControl.SetTempCursor(Value: TCursor);
|
|
begin
|
|
if not HandleAllocated then exit;
|
|
TWSWinControlClass(WidgetSetClass).SetCursor(Self, Screen.Cursors[Value]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl FindChildControl
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.FindChildControl(const ControlName: String): TControl;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if FControls <> nil then
|
|
for I := 0 to FControls.Count - 1 do begin
|
|
Result:=TControl(FControls[I]);
|
|
if CompareText(Result.Name, ControlName) = 0 then
|
|
exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TWinControl.FlipChildren(AllLevels: Boolean);
|
|
var
|
|
i: Integer;
|
|
FlipControls: TFPList;
|
|
CurControl: TControl;
|
|
begin
|
|
if ControlCount = 0 then exit;
|
|
FlipControls := TFPList.Create;
|
|
|
|
DisableAlign;
|
|
try
|
|
// Collect all controls with Align Right and Left
|
|
for i := 0 to ControlCount - 1 do begin
|
|
CurControl:=Controls[i];
|
|
if CurControl.Align in [alLeft,alRight] then
|
|
FlipControls.Add(CurControl);
|
|
end;
|
|
// flip the rest
|
|
DoFlipChildren;
|
|
// reverse Right and Left alignments
|
|
while FlipControls.Count > 0 do begin
|
|
CurControl:=TControl(FlipControls[FlipControls.Count-1]);
|
|
if CurControl.Align=alLeft then
|
|
CurControl.Align:=alRight
|
|
else if CurControl.Align=alRight then
|
|
CurControl.Align:=alLeft;
|
|
FlipControls.Delete(FlipControls.Count - 1);
|
|
end;
|
|
finally
|
|
FlipControls.Free;
|
|
EnableAlign;
|
|
end;
|
|
|
|
// flip recursively
|
|
if AllLevels then begin
|
|
for i := 0 to ControlCount - 1 do begin
|
|
CurControl:=Controls[i];
|
|
if CurControl is TWinControl then
|
|
TWinControl(CurControl).FlipChildren(true);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.ScaleBy(Multiplier, Divider: Integer);
|
|
begin
|
|
ChangeScale(Multiplier, Divider);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------}
|
|
{ TWinControl FindNextControl }
|
|
{------------------------------------------------------------------------------}
|
|
function TWinControl.FindNextControl(CurrentControl: TWinControl; GoForward,
|
|
CheckTabStop, CheckParent: Boolean): TWinControl;
|
|
var
|
|
List: TFPList;
|
|
Next: TWinControl;
|
|
I, J: Longint;
|
|
begin
|
|
try
|
|
Result := nil;
|
|
List := TFPList.Create;
|
|
GetTabOrderList(List);
|
|
//for i:=0 to List.Count-1 do
|
|
// debugln(['TWinControl.FindNextControl TabOrderList ',dbgs(i),' ',DbgSName(TObject(List[i]))]);
|
|
if List.Count > 0 then
|
|
begin
|
|
J := List.IndexOf(CurrentControl);
|
|
if J < 0 then
|
|
begin
|
|
if GoForward then
|
|
J := List.Count - 1
|
|
else
|
|
J := 0;
|
|
end;
|
|
//DebugLn(['TWinControl.FindNextControl A ',DbgSName(CurrentControl),' ',dbgs(J),
|
|
// ' GoForward='+dbgs(GoForward)+' CheckTabStop='+dbgs(CheckTabStop)+' CheckParent='+dbgs(CheckParent)]);
|
|
I := J;
|
|
repeat
|
|
if GoForward then
|
|
begin
|
|
Inc(I);
|
|
if I >= List.Count then
|
|
I := 0;
|
|
end else
|
|
begin
|
|
Dec(I);
|
|
if I < 0 then
|
|
I := List.Count - 1;
|
|
end;
|
|
|
|
Next := TWinControl(List[I]);
|
|
{ DebugLn(['TWinControl.FindNextControl B ',Next.Name,' ',dbgs(I),
|
|
' ChckTabStop='+dbgs(CheckTabStop)+' TabStop='+dbgs(Next.TabStop)
|
|
+' ChckParent='+dbgs(CheckParent)+' Parent=Self='+dbgs(Next.Parent = Self)
|
|
+' Enabled='+dbgs(Next.Enabled)
|
|
+' TestTab='+dbgs(((Not CheckTabStop) or Next.TabStop))
|
|
+' TestPar='+dbgs(((not CheckParent) or (Next.Parent = Self)))
|
|
+' TestEnVi='+dbgs(Next.Enabled and Next.IsVisible)]);}
|
|
if (((not CheckTabStop) or Next.TabStop)
|
|
and ((not CheckParent) or (Next.Parent = Self)))
|
|
and (Next.Enabled and Next.IsVisible) then
|
|
Result := Next;
|
|
|
|
// if we reached the start then exit because we traversed the loop and
|
|
// did not find any control
|
|
if I = J then
|
|
break;
|
|
until (Result <> nil);
|
|
//DebugLn(['TWinControl.FindNextControl END ',DbgSName(Result),' I=',dbgs(I)]);
|
|
end;
|
|
finally
|
|
List.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.SelectFirst;
|
|
var
|
|
Form : TCustomForm;
|
|
Control : TWinControl;
|
|
begin
|
|
Form := GetParentForm(Self);
|
|
if Form <> nil then begin
|
|
Control := FindNextControl(nil, true, true, false);
|
|
if Control = nil then
|
|
Control := FindNextControl(nil, true, false, false);
|
|
if Control <> nil then
|
|
Form.ActiveControl := Control;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.FixupTabList;
|
|
var
|
|
I, J: Integer;
|
|
Control: TWinControl;
|
|
List: TFPList;
|
|
WinControls: TFPList;
|
|
begin
|
|
if FControls <> nil then
|
|
begin
|
|
List := TFPList.Create;
|
|
WinControls:=TFPList.Create;
|
|
try
|
|
for i:=0 to FControls.Count-1 do
|
|
if TObject(FControls[i]) is TWinControl then
|
|
WinControls.Add(FControls[i]);
|
|
List.Count := WinControls.Count;
|
|
for I := 0 to WinControls.Count - 1 do
|
|
begin
|
|
Control := TWinControl(WinControls[I]);
|
|
J := Control.FTabOrder;
|
|
if (J >= 0) and (J < WinControls.Count) then
|
|
List[J] := Control;
|
|
end;
|
|
for I := 0 to List.Count - 1 do
|
|
begin
|
|
Control := TWinControl(List[I]);
|
|
if Control <> nil then
|
|
Control.UpdateTabOrder(TTabOrder(I));
|
|
end;
|
|
finally
|
|
List.Free;
|
|
WinControls.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetTabOrderList
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.GetTabOrderList(List: TFPList);
|
|
var
|
|
I: Integer;
|
|
lWinControl: TWinControl;
|
|
begin
|
|
if FTabList <> nil then
|
|
for I := 0 to FTabList.Count - 1 do
|
|
begin
|
|
lWinControl := TWinControl(FTabList[I]);
|
|
// The tab order list should exclude injected LCL-CustomDrawn controls
|
|
if lWinControl.CanFocus and (not LCLIntf.IsCDIntfControl(lWinControl)) then
|
|
List.Add(lWinControl);
|
|
lWinControl.GetTabOrderList(List);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl IsControlMouseMsg
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.IsControlMouseMsg(var TheMessage): Boolean;
|
|
var
|
|
MouseMessage: TLMMouse absolute TheMessage;
|
|
MouseEventMessage: TLMMouseEvent;
|
|
Control: TControl;
|
|
ScrolledOffset, P: TPoint;
|
|
ClientBounds: TRect;
|
|
begin
|
|
{ CaptureControl = nil means that widgetset has captured input, but it does
|
|
not know anything about TControl controls }
|
|
if (FindOwnerControl(GetCapture) = Self) and (CaptureControl <> nil) then
|
|
begin
|
|
Control := nil;
|
|
//DebugLn(['TWinControl.IsControlMouseMsg A ', DbgSName(CaptureControl), ', ',DbgSName(CaptureControl.Parent),', Self: ', DbgSName(Self)]);
|
|
if (CaptureControl.Parent = Self) then
|
|
Control := CaptureControl;
|
|
end
|
|
else
|
|
begin
|
|
// do query wincontrol children, in case they overlap
|
|
Control := ControlAtPos(SmallPointToPoint(MouseMessage.Pos),
|
|
[capfAllowWinControls]);
|
|
if Control is TWinControl then
|
|
begin
|
|
// there is a TWinControl child at this position
|
|
// TWinControl children get their own messages
|
|
// => ignore here
|
|
Control := nil;
|
|
end;
|
|
end;
|
|
|
|
//DebugLn(['TWinControl.IsControlMouseMsg B ',DbgSName(Self),' Control=',DbgSName(Control),' Msg=',TheMessage.Msg]);
|
|
Result := False;
|
|
if Control <> nil then
|
|
begin
|
|
// map mouse coordinates to control
|
|
ScrolledOffset := GetClientScrollOffset;
|
|
|
|
P.X := MouseMessage.XPos - Control.Left + ScrolledOffset.X;
|
|
P.Y := MouseMessage.YPos - Control.Top + ScrolledOffset.Y;
|
|
if (Control is TWinControl) and TWinControl(Control).HandleAllocated then
|
|
begin
|
|
// map coordinates to client area of control
|
|
LCLIntf.GetClientBounds(TWinControl(Control).Handle, ClientBounds);
|
|
dec(P.X, ClientBounds.Left);
|
|
dec(P.Y, ClientBounds.Top);
|
|
{$IFDEF VerboseMouseBugfix}
|
|
DebugLn(['TWinControl.IsControlMouseMsg ',Name,' -> ',Control.Name,
|
|
' MsgPos=',MouseMessage.Pos.X,',',MouseMessage.Pos.Y,
|
|
' Control=',Control.Left,',',Control.Top,
|
|
' ClientBounds=',ClientBounds.Left,',',ClientBounds.Top,
|
|
' Scrolled=',GetClientScrollOffset.X,',',GetClientScrollOffset.Y,
|
|
' P=',P.X,',',P.Y]
|
|
);
|
|
{$ENDIF}
|
|
end;
|
|
if MouseMessage.Msg = LM_MOUSEWHEEL then
|
|
begin
|
|
MouseEventMessage := TLMMouseEvent(TheMessage);
|
|
MouseEventMessage.X := P.X;
|
|
MouseEventMessage.Y := P.Y;
|
|
Control.Dispatch(MouseEventMessage);
|
|
MouseMessage.Result := MouseEventMessage.Result;
|
|
Result := (MouseMessage.Result <> 0);
|
|
end
|
|
else
|
|
begin
|
|
MouseMessage.Result := Control.Perform(MouseMessage.Msg, WParam(MouseMessage.Keys),
|
|
LParam(Integer(PointToSmallPoint(P))));
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.FontChanged(Sender: TObject);
|
|
begin
|
|
if HandleAllocated and ([csLoading, csDestroying] * ComponentState = []) then
|
|
begin
|
|
TWSWinControlClass(WidgetSetClass).SetFont(Self, Font);
|
|
Exclude(FWinControlFlags, wcfFontChanged);
|
|
end
|
|
else
|
|
Include(FWinControlFlags, wcfFontChanged);
|
|
inherited FontChanged(Sender);
|
|
NotifyControls(CM_PARENTFONTCHANGED);
|
|
end;
|
|
|
|
procedure TWinControl.SetColor(Value: TColor);
|
|
begin
|
|
if Value = Color then Exit;
|
|
inherited SetColor(Value);
|
|
if BrushCreated then
|
|
if Color = clDefault then
|
|
FBrush.Color := GetDefaultColor(dctBrush)
|
|
else
|
|
FBrush.Color := Color;
|
|
if HandleAllocated and ([csLoading,csDestroying]*ComponentState=[]) then
|
|
begin
|
|
TWSWinControlClass(WidgetSetClass).SetColor(Self);
|
|
Exclude(FWinControlFlags, wcfColorChanged);
|
|
end
|
|
else
|
|
Include(FWinControlFlags, wcfColorChanged);
|
|
NotifyControls(CM_PARENTCOLORCHANGED);
|
|
end;
|
|
|
|
procedure TWinControl.PaintHandler(var TheMessage: TLMPaint);
|
|
|
|
function ControlMustBeClipped(AControl: TControl): boolean;
|
|
begin
|
|
Result := (csOpaque in AControl.ControlStyle) and AControl.IsVisible;
|
|
end;
|
|
|
|
var
|
|
I, Clip, SaveIndex: Integer;
|
|
DC: HDC;
|
|
PS: TPaintStruct; //defined in LCLIntf.pp
|
|
ControlsNeedsClipping: boolean;
|
|
CurControl: TControl;
|
|
begin
|
|
//DebugLn('[TWinControl.PaintHandler] ',Name,':',ClassName,' DC=',DbgS(TheMessage.DC,8));
|
|
if (csDestroying in ComponentState) or (not HandleAllocated) then exit;
|
|
|
|
{$IFDEF VerboseResizeFlicker}
|
|
DebugLn('TWinControl.PaintHandler A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
{$IFDEF VerboseDsgnPaintMsg}
|
|
if csDesigning in ComponentState then
|
|
DebugLn('TWinControl.PaintHandler A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
|
|
//DebugLn(Format('Trace:> [TWinControl.PaintHandler] %s --> Msg.DC: 0x%x', [ClassName, TheMessage.DC]));
|
|
DC := TheMessage.DC;
|
|
if DC = 0 then
|
|
DC := BeginPaint(Handle, PS);
|
|
|
|
try
|
|
// check if child controls need clipping
|
|
//if Name='GroupBox1' then
|
|
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' B');
|
|
ControlsNeedsClipping:=false;
|
|
if FControls<>nil then
|
|
for I := 0 to FControls.Count - 1 do
|
|
if ControlMustBeClipped(TControl(FControls[I])) then begin
|
|
ControlsNeedsClipping:=true;
|
|
break;
|
|
end;
|
|
// exclude child controls and send new paint message
|
|
//if Name='GroupBox1' then
|
|
//debugln(['TWinControl.PaintHandler ControlsNeedsClipping=',ControlsNeedsClipping,' ControlCount=',ControlCount]);
|
|
if not ControlsNeedsClipping then begin
|
|
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' no clipping ...');
|
|
PaintWindow(DC)
|
|
end else
|
|
begin
|
|
SaveIndex := SaveDC(DC);
|
|
Clip := SimpleRegion;
|
|
for I := 0 to FControls.Count - 1 do begin
|
|
CurControl:=TControl(FControls[I]);
|
|
if ControlMustBeClipped(CurControl) then
|
|
with CurControl do begin
|
|
//DebugLn('TWinControl.PaintHandler Exclude Child ',DbgSName(Self),' Control=',DbgSName(CurControl),'(',dbgs(CurControl.BoundsRect),')');
|
|
Clip := ExcludeClipRect(DC, Left, Top, Left + Width, Top + Height);
|
|
if Clip = NullRegion then Break;
|
|
end;
|
|
end;
|
|
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' with clipping ...');
|
|
if Clip <> NullRegion then
|
|
PaintWindow(DC);
|
|
RestoreDC(DC, SaveIndex);
|
|
end;
|
|
// paint controls
|
|
//DebugLn('[TWinControl.PaintHandler] ',DbgSName(Self),' PaintControls ...');
|
|
if FDockSite and FUseDockManager and Assigned(DockManager) then
|
|
DockManager.PaintSite(DC);
|
|
PaintControls(DC, nil);
|
|
finally
|
|
if TheMessage.DC = 0 then
|
|
EndPaint(Handle, PS);
|
|
end;
|
|
//DebugLn(Format('Trace:< [TWinControl.PaintHandler] %s', [ClassName]));
|
|
//DebugLn('[TWinControl.PaintHandler] END ',Name,':',ClassName,' DC=',DbgS(Message.DC,8));
|
|
end;
|
|
|
|
procedure TWinControl.PaintControls(DC: HDC; First: TControl);
|
|
var
|
|
I, Count, SaveIndex: Integer;
|
|
// FrameBrush: HBRUSH;
|
|
TempControl : TControl;
|
|
{off $Define VerboseControlDCOrigin}
|
|
{$IFDEF VerboseControlDCOrigin}
|
|
P: TPoint;
|
|
{$ENDIF}
|
|
begin
|
|
{$ifdef DEBUG_WINDOW_ORG}
|
|
DebugLn(':> [TWinControl.PaintControls] A');
|
|
{$endif}
|
|
|
|
//DebugLn('[TWinControl.PaintControls] ',Name,':',ClassName,' DC=',DbgS(DC,8));
|
|
if (csDestroying in ComponentState)
|
|
or ((DC=0) and (not HandleAllocated)) then
|
|
exit;
|
|
|
|
{$IFDEF VerboseDsgnPaintMsg}
|
|
if csDesigning in ComponentState then
|
|
DebugLn('TWinControl.PaintControls A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
|
|
// Controls that are not TWinControl, have no handle of their own, and so
|
|
// they are repainted as part of the parent:
|
|
if FControls <> nil then
|
|
begin
|
|
{$ifdef DEBUG_WINDOW_ORG}
|
|
DebugLn(':> [TWinControl.PaintControls] B');
|
|
{$endif}
|
|
I := 0;
|
|
if First <> nil then
|
|
begin
|
|
I := FControls.IndexOf(First);
|
|
if I < 0 then I := 0;
|
|
end;
|
|
//debugln(['TWinControl.PaintControls ',DbgSName(Self),' ClientRect=',dbgs(ClientRect)]);
|
|
Count := FControls.Count;
|
|
while I < Count do
|
|
begin
|
|
TempControl := TControl(FControls.Items[I]);
|
|
{$ifdef DEBUG_WINDOW_ORG}
|
|
if Name='GroupBox1' then
|
|
DebugLn(
|
|
Format(':> [TWinControl.PaintControls] C DC=%d TempControl=%s Left=%d Top=%d Width=%d Height=%d IsVisible=%s RectVisible=%s',
|
|
[DC, DbgSName(TempControl),
|
|
TempControl.Left, TempControl.Top, TempControl.Width, TempControl.Height,
|
|
dbgs(IsVisible),
|
|
dbgs(RectVisible(DC, TempControl.BoundsRect))
|
|
]));
|
|
{$endif}
|
|
if not (TempControl is TWinControl) then begin
|
|
//DebugLn('TWinControl.PaintControls B Self=',Self.Name,':',Self.ClassName,' Control=',TempControl.Name,':',TempControl.ClassName,' ',TempControl.Left,',',TempControl.Top,',',TempControl.Width,',',TempControl.Height);
|
|
with TempControl do
|
|
if IsVisible
|
|
and RectVisible(DC, TempControl.BoundsRect) then
|
|
begin
|
|
if csPaintCopy in Self.ControlState then
|
|
Include(FControlState, csPaintCopy);
|
|
SaveIndex := SaveDC(DC);
|
|
|
|
{$ifdef DEBUG_WINDOW_ORG}
|
|
DebugLn(
|
|
Format(':> [TWinControl.PaintControls] Control=%s Left=%d Top=%d Width=%d Height=%d',
|
|
[Self.Name, Left, Top, Width, Height]));
|
|
{$endif}
|
|
|
|
MoveWindowOrg(DC, Left, Top);
|
|
{$IFDEF VerboseControlDCOrigin}
|
|
DebugLn('TWinControl.PaintControls B Self=',DbgSName(Self),' Control=',DbgSName(TempControl),' ',dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height));
|
|
{$ENDIF}
|
|
IntersectClipRect(DC, 0, 0, Width, Height);
|
|
{$IFDEF VerboseControlDCOrigin}
|
|
DebugLn('TWinControl.PaintControls C');
|
|
P:=Point(-1,-1);
|
|
GetWindowOrgEx(DC,@P);
|
|
debugln(' DCOrigin=',dbgs(P));
|
|
{$ENDIF}
|
|
Perform(LM_PAINT, WParam(DC), 0);
|
|
{$IFDEF VerboseControlDCOrigin}
|
|
DebugLn('TWinControl.PaintControls D TempControl=',DbgSName(TempControl));
|
|
{$ENDIF}
|
|
RestoreDC(DC, SaveIndex);
|
|
Exclude(FControlState, csPaintCopy);
|
|
end;
|
|
end;
|
|
Inc(I);
|
|
end;
|
|
end;
|
|
//DebugLn('[TWinControl.PaintControls] END ',Name,':',ClassName,' DC=',DbgS(DC,8));
|
|
end;
|
|
|
|
procedure TWinControl.PaintWindow(DC: HDC);
|
|
var
|
|
Message: TLMessage;
|
|
begin
|
|
//DebugLn('[TWinControl.PaintWindow] ',Name,':',Classname,' DC=',DbgS(DC));
|
|
if (csDestroying in ComponentState)
|
|
or ((DC=0) and (not HandleAllocated)) then
|
|
exit;
|
|
|
|
{$IFDEF VerboseDsgnPaintMsg}
|
|
if csDesigning in ComponentState then
|
|
DebugLn('TWinControl.PaintWindow A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
|
|
Message.Msg := LM_PAINT;
|
|
Message.WParam := WParam(DC);
|
|
Message.LParam := 0;
|
|
Message.Result := 0;
|
|
DefaultHandler(Message);
|
|
end;
|
|
|
|
procedure TWinControl.CreateBrush;
|
|
begin
|
|
if BrushCreated then exit;
|
|
FBrush := TBrush.Create;
|
|
if Color = clDefault then
|
|
FBrush.Color := GetDefaultColor(dctBrush)
|
|
else
|
|
FBrush.Color := Color;
|
|
end;
|
|
|
|
procedure TWinControl.ScaleControls(Multiplier, Divider: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to ControlCount - 1 do
|
|
Controls[i].ChangeScale(Multiplier, Divider);
|
|
end;
|
|
|
|
procedure TWinControl.ChangeScale(Multiplier, Divider: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if Multiplier <> Divider then
|
|
begin
|
|
DisableAlign;
|
|
try
|
|
ScaleControls(Multiplier, Divider);
|
|
inherited;
|
|
for i := 0 to ControlCount - 1 do
|
|
Controls[i].UpdateAnchorRules;
|
|
finally
|
|
EnableAlign;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.EraseBackground;
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.EraseBackground(DC: HDC);
|
|
var
|
|
ARect: TRect;
|
|
begin
|
|
if DC = 0 then Exit;
|
|
ARect := Rect(0, 0, Width, Height);
|
|
FillRect(DC, ARect, HBRUSH(Brush.Reference.Handle));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.IntfUTF8KeyPress(var UTF8Key: TUTF8Char;
|
|
RepeatCount: integer; SystemKey: boolean): boolean;
|
|
|
|
Called by the interface after the navigation and specials keys are handled
|
|
(e.g. after KeyDown but before KeyPress).
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.IntfUTF8KeyPress(var UTF8Key: TUTF8Char;
|
|
RepeatCount: integer; SystemKey: boolean): boolean;
|
|
begin
|
|
IncLCLRefCount;
|
|
try
|
|
Result := (RepeatCount > 0) and not SystemKey and DoUTF8KeyPress(UTF8Key);
|
|
finally
|
|
DecLCLRefCount;
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.IntfGetDropFilesTarget: TWinControl;
|
|
begin
|
|
Result:=Self;
|
|
repeat
|
|
Result:=GetFirstParentForm(Result);
|
|
if Result=nil then exit;
|
|
if TCustomForm(Result).AllowDropFiles then exit;
|
|
Result:=Result.Parent;
|
|
until Result=nil;
|
|
end;
|
|
|
|
procedure TWinControl.PaintTo(DC: HDC; X, Y: Integer);
|
|
begin
|
|
if HandleAllocated then
|
|
TWSWinControlClass(WidgetSetClass).PaintTo(Self, DC, X, Y);
|
|
end;
|
|
|
|
procedure TWinControl.PaintTo(ACanvas: TCanvas; X, Y: Integer);
|
|
begin
|
|
PaintTo(ACanvas.Handle, X, Y);
|
|
ACanvas.Changed;
|
|
end;
|
|
|
|
procedure TWinControl.SetShape(AShape: TBitmap);
|
|
begin
|
|
if not HandleAllocated then
|
|
Exit;
|
|
|
|
if (AShape <> nil) and (AShape.Width = Width) and (AShape.Height = Height) then
|
|
TWSWinControlClass(WidgetSetClass).SetShape(Self, AShape.Handle)
|
|
else
|
|
if AShape = nil then
|
|
TWSWinControlClass(WidgetSetClass).SetShape(Self, 0)
|
|
end;
|
|
|
|
procedure TWinControl.SetShape(AShape: TRegion);
|
|
begin
|
|
LCLIntf.SetWindowRgn(Handle, AShape.Reference.Handle, True);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl ControlAtPos
|
|
Params: const Pos : TPoint
|
|
AllowDisabled: Boolean
|
|
Results: TControl
|
|
|
|
Searches a child (not grand child) control, which client area contains Pos.
|
|
Pos is relative to the ClientOrigin.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.ControlAtPos(const Pos: TPoint; AllowDisabled: Boolean): TControl;
|
|
begin
|
|
Result := ControlAtPos(Pos, AllowDisabled, False);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl ControlAtPos
|
|
Params: const Pos : TPoint
|
|
AllowDisabled, AllowWinControls: Boolean
|
|
Results: TControl
|
|
|
|
Searches a child (not grand child) control, which client area contains Pos.
|
|
Pos is relative to the ClientOrigin.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.ControlAtPos(const Pos: TPoint;
|
|
AllowDisabled, AllowWinControls: Boolean): TControl;
|
|
var
|
|
Flags: TControlAtPosFlags;
|
|
begin
|
|
Flags := [capfOnlyClientAreas];
|
|
if AllowDisabled then Include(Flags, capfAllowDisabled);
|
|
if AllowWinControls then Include(Flags, capfAllowWinControls);
|
|
Result := ControlAtPos(Pos, Flags);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl ControlAtPos
|
|
Params: const Pos : TPoint
|
|
Flags: TControlAtPosFlags
|
|
Results: TControl
|
|
|
|
Searches a child (not grand child) control, which contains Pos.
|
|
Pos is relative to the ClientOrigin.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.ControlAtPos(const Pos: TPoint;
|
|
Flags: TControlAtPosFlags): TControl;
|
|
var
|
|
I: Integer;
|
|
P: TPoint;
|
|
LControl: TControl;
|
|
ClientBounds: TRect;
|
|
|
|
function GetControlAtPos(AControl: TControl): Boolean;
|
|
var
|
|
ControlPos: TPoint;
|
|
ControlClientBounds: TRect;
|
|
begin
|
|
with AControl do
|
|
begin
|
|
// MG: Delphi checks for PtInRect(ClientRect,P). But the client area is
|
|
// not always at 0,0, so I guess this is a bug in the VCL.
|
|
|
|
ControlPos := Point(P.X - Left, P.Y - Top);
|
|
Result := (ControlPos.X >= 0) and (ControlPos.Y >= 0) and
|
|
(ControlPos.X < Width) and (ControlPos.Y < Height);
|
|
if Result and (capfOnlyClientAreas in Flags) then
|
|
begin
|
|
ControlClientBounds := GetChildrenRect(false);
|
|
Result:=PtInRect(ControlClientBounds, ControlPos);
|
|
end;
|
|
|
|
Result := Result
|
|
and (
|
|
(
|
|
(csDesigning in ComponentState)
|
|
and not (csNoDesignVisible in ControlStyle)
|
|
// Here was a VCL bug: VCL checks if control is Visible,
|
|
// which should be ignored at designtime
|
|
)
|
|
or
|
|
(
|
|
(not (csDesigning in ComponentState))
|
|
and
|
|
(Visible)
|
|
and
|
|
(Enabled or (capfAllowDisabled in Flags))
|
|
and
|
|
(Perform(CM_HITTEST, 0,
|
|
LParam(Integer(PointToSmallPoint(ControlPos)))) <> 0)
|
|
)
|
|
);
|
|
{$IFDEF VerboseMouseBugfix}
|
|
//if Result then
|
|
DebugLn(['GetControlAtPos ',Name,':',ClassName,
|
|
' Pos=',Pos.X,',',Pos.Y,
|
|
' P=',P.X,',',P.Y,
|
|
' ControlPos=',dbgs(ControlPos),
|
|
' ClientBounds=',ClientBounds.Left,',',ClientBounds.Top,',',ClientBounds.Right,',',ClientBounds.Bottom,
|
|
// ' OnlyCl=',OnlyClientAreas,
|
|
' Result=',Result]);
|
|
{$ENDIF}
|
|
if Result then
|
|
LControl := AControl;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
ScrolledOffset: TPoint;
|
|
OldClientOrigin: TPoint;
|
|
NewClientOrigin: TPoint;
|
|
NewPos: TPoint;
|
|
begin
|
|
//debugln(['TWinControl.ControlAtPos START ',DbgSName(Self),' P=',dbgs(Pos)]);
|
|
|
|
// check if Pos in visible client area
|
|
ClientBounds := GetClientRect;
|
|
ScrolledOffset := GetClientScrollOffset;
|
|
if capfHasScrollOffset in Flags then
|
|
begin
|
|
{ ClientBounds do not include scrolling offset }
|
|
inc(ClientBounds.Left, ScrolledOffset.x);
|
|
inc(ClientBounds.Right, ScrolledOffset.x);
|
|
inc(ClientBounds.Top, ScrolledOffset.y);
|
|
inc(ClientBounds.Bottom, ScrolledOffset.y);
|
|
end;
|
|
|
|
if not PtInRect(ClientBounds, Pos) then
|
|
begin
|
|
//debugln(['TWinControl.ControlAtPos OUT OF CLIENTBOUNDS ',DbgSName(Self),' P=',dbgs(Pos),' ClientBounds=',dbgs(ClientBounds)]);
|
|
Result := nil;
|
|
exit;
|
|
end;
|
|
|
|
// map Pos to logical client area
|
|
P := Pos;
|
|
if not (capfHasScrollOffset in Flags) then
|
|
begin
|
|
inc(P.X, ScrolledOffset.X);
|
|
inc(P.Y, ScrolledOffset.Y);
|
|
end;
|
|
|
|
LControl := nil;
|
|
if FControls<>nil then
|
|
begin
|
|
// check wincontrols
|
|
if (capfAllowWinControls in Flags) then
|
|
for I := FControls.Count - 1 downto 0 do
|
|
if (TObject(FControls[i]) is TWinControl)
|
|
and GetControlAtPos(TControl(FControls[I])) then
|
|
Break;
|
|
// check controls
|
|
if (LControl = nil) then
|
|
for I := FControls.Count - 1 downto 0 do
|
|
if (not (TObject(FControls[i]) is TWinControl))
|
|
and GetControlAtPos(TControl(FControls[I])) then
|
|
Break;
|
|
end;
|
|
Result := LControl;
|
|
|
|
// check recursive sub children
|
|
if (capfRecursive in Flags) and (Result is TWinControl) and
|
|
(TWinControl(Result).ControlCount > 0) then
|
|
begin
|
|
// in LCL ClientOrigin contains the scroll offset. At least this is so
|
|
// for win32 and gtk2
|
|
OldClientOrigin := ClientOrigin;
|
|
NewClientOrigin := TWinControl(Result).ClientOrigin;
|
|
NewPos := Pos;
|
|
NewPos.X := NewPos.X - NewClientOrigin.X + OldClientOrigin.X;
|
|
NewPos.Y := NewPos.Y - NewClientOrigin.Y + OldClientOrigin.Y;
|
|
LControl := TWinControl(Result).ControlAtPos(NewPos, Flags + [capfHasScrollOffset]);
|
|
//debugln(['TWinControl.RECURSED ControlAtPos Result=',DbgSName(Result),' LControl=',DbgSName(LControl),' ',dbgs(NewPos),' AllowDisabled=',AllowDisabled,' OnlyClientAreas=',OnlyClientAreas]);
|
|
if LControl <> nil then
|
|
Result := LControl;
|
|
end;
|
|
//debugln(['TWinControl.ControlAtPos END ',DbgSName(Self),' P=',dbgs(Pos),' Result=',DbgSName(Result)]);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function TWinControl.GetControlIndex(AControl: TControl): integer;
|
|
|
|
|
|
-------------------------------------------------------------------------------}
|
|
function TWinControl.GetControlIndex(AControl: TControl): integer;
|
|
begin
|
|
if FControls <> nil then
|
|
Result := FControls.IndexOf(AControl)
|
|
else
|
|
Result := -1;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
function TWinControl.GetControlIndex(AControl: TControl): integer;
|
|
|
|
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetControlIndex(AControl: TControl; NewIndex: integer);
|
|
begin
|
|
SetChildZPosition(AControl, NewIndex);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DestroyHandle
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DestroyHandle;
|
|
var
|
|
i: integer;
|
|
AWinControl: TWinControl;
|
|
begin
|
|
//DebugLn(['TWinControl.DestroyHandle START ',DbgSName(Self)]);
|
|
if not HandleAllocated then begin
|
|
DebugLn('Warning: TWinControl.DestroyHandle ',Name,':',ClassName,' Handle not Allocated');
|
|
//RaiseGDBException('');
|
|
end;
|
|
|
|
// First destroy all children handles
|
|
//DebugLn(['TWinControl.DestroyHandle DESTROY CHILDS ',DbgSName(Self)]);
|
|
Include(FControlState, csDestroyingHandle);
|
|
try
|
|
if FControls <> nil then begin
|
|
for i:= 0 to FControls.Count - 1 do begin
|
|
//DebugLn([' ',i,' ',DbgSName(TObject(FWinControls[i]))]);
|
|
AWinControl:=TWinControl(FControls[i]);
|
|
if (AWinControl is TWinControl) and AWinControl.HandleAllocated then
|
|
AWinControl.DestroyHandle;
|
|
end;
|
|
end;
|
|
//DebugLn(['TWinControl.DestroyHandle DESTROY SELF ',DbgSName(Self)]);
|
|
DestroyWnd;
|
|
finally
|
|
Exclude(FControlState, csDestroyingHandle);
|
|
end;
|
|
//DebugLn(['TWinControl.DestroyHandle END ',DbgSName(Self)]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl WndPRoc
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WndProc(var Message: TLMessage);
|
|
var
|
|
Form: TCustomForm;
|
|
begin
|
|
//debugln(['TWinControl.WndProc ',DbgSName(Self),' ',Message.Msg]);
|
|
//DebugLn(Format('Trace:[TWinControl.WndPRoc] %s(%s) --> Message = %d', [ClassName, Name, Message.Msg]));
|
|
case Message.Msg of
|
|
LM_SETFOCUS:
|
|
begin
|
|
//DebugLn(Format('Trace:[TWinControl.WndPRoc] %s --> LM_SETFOCUS', [ClassName]));
|
|
{$IFDEF VerboseFocus}
|
|
DebugLn('TWinControl.WndProc LM_SetFocus ',DbgSName(Self));
|
|
{$ENDIF}
|
|
Form := GetParentForm(Self);
|
|
if Assigned(Form) and not (csDestroyingHandle in ControlState) and not (csDestroying in ComponentState) then
|
|
begin
|
|
if not Form.SetFocusedControl(Self) then
|
|
begin
|
|
{$IFDEF VerboseFocus}
|
|
DebugLn('TWinControl.WndProc LM_SetFocus ',DbgSName(Self),' form=',DbgSName(Form),' Form.SetFocusedControl FAILED');
|
|
{$ENDIF}
|
|
Exit;
|
|
end;
|
|
Message.Result := 0;
|
|
end;
|
|
{$IFDEF VerboseFocus}
|
|
DebugLn('TWinControl.WndProc AFTER form LM_SetFocus ',DbgSName(Self));
|
|
{$ENDIF}
|
|
end;
|
|
|
|
LM_KILLFOCUS:
|
|
begin
|
|
//DebugLn(Format('Trace:[TWinControl.WndPRoc] %s --> _KILLFOCUS', [ClassName]));
|
|
if csFocusing in ControlState then
|
|
begin
|
|
{$IFDEF VerboseFocus}
|
|
DebugLn('TWinControl.WndProc LM_KillFocus during focusing ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
Exit;
|
|
end;
|
|
Message.Result:=0;
|
|
end;
|
|
|
|
LM_NCHITTEST:
|
|
begin
|
|
inherited WndPRoc(Message);
|
|
if (Message.Result = HTTRANSPARENT) and
|
|
Assigned(ControlAtPos(ScreenToClient(SmallPointToPoint(TLMNCHitTest(Message).Pos)), False)) then
|
|
Message.Result := HTCLIENT;
|
|
Exit;
|
|
end;
|
|
|
|
// exclude only LM_MOUSEENTER, LM_MOUSELEAVE
|
|
LM_MOUSEFIRST..LM_MOUSELAST,
|
|
LM_MOUSEFIRST2..LM_RBUTTONQUADCLK,
|
|
LM_XBUTTONTRIPLECLK..LM_MOUSELAST2:
|
|
begin
|
|
{$IFDEF VerboseMouseBugfix}
|
|
DebugLn('TWinControl.WndPRoc A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
//if Message.Msg=LM_RBUTTONUP then begin DebugLn(['TWinControl.WndProc ',DbgSName(Self)]); DumpStack end;
|
|
DoBeforeMouseMessage;
|
|
if IsControlMouseMSG(Message) then
|
|
Exit
|
|
else
|
|
begin
|
|
if FDockSite and FUseDockManager and Assigned(DockManager) then
|
|
DockManager.MessageHandler(Self, Message);
|
|
end;
|
|
{$IFDEF VerboseMouseBugfix}
|
|
DebugLn('TWinControl.WndPRoc B ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
LM_KEYFIRST..LM_KEYLAST:
|
|
if Dragging then Exit;
|
|
|
|
LM_CANCELMODE:
|
|
if (FindOwnerControl(GetCapture) = Self)
|
|
and (CaptureControl <> nil)
|
|
and (CaptureControl.Parent = Self)
|
|
then CaptureControl.Perform(LM_CANCELMODE,0,0);
|
|
CM_MOUSEENTER,
|
|
CM_MOUSELEAVE:
|
|
begin
|
|
if FDockSite and FUseDockManager and Assigned(DockManager) then
|
|
DockManager.MessageHandler(Self, Message);
|
|
end;
|
|
CM_TEXTCHANGED, CM_VISIBLECHANGED, LM_SIZE, LM_MOVE:
|
|
begin
|
|
// forward message to the dock manager is we are docked
|
|
if (HostDockSite <> nil) and (HostDockSite.UseDockManager) and
|
|
Assigned(HostDockSite.DockManager) then
|
|
HostDockSite.DockManager.MessageHandler(Self, Message);
|
|
end;
|
|
end;
|
|
|
|
inherited WndProc(Message);
|
|
end;
|
|
|
|
procedure TWinControl.WSSetText(const AText: String);
|
|
begin
|
|
TWSWinControlClass(WidgetSetClass).SetText(Self, AText);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.DoAddDockClient(Client: TControl; const ARect: TRect);
|
|
|
|
Default method for adding a dock client. Become the new parent and break
|
|
old anchored controls.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoAddDockClient(Client: TControl; const ARect: TRect);
|
|
begin
|
|
//DebugLn(['TWinControl.DoAddDockClient ',DbgSName(Self),' Client=',DbgSName(Client),' OldParent=',DbgSName(Client.Parent),' Client.AnchoredControlCount=',Client.AnchoredControlCount]);
|
|
Client.Parent := Self;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.DockOver(Source: TDragDockObject; X, Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
|
|
Called to check whether this control allows docking and where.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DockOver(Source: TDragDockObject; X, Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
begin
|
|
if State = dsDragMove then
|
|
PositionDockRect(Source);
|
|
DoDockOver(Source, X, Y, State, Accept);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.DoDockOver(Source: TDragDockObject; X, Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoDockOver(Source: TDragDockObject; X, Y: Integer;
|
|
State: TDragState; var Accept: Boolean);
|
|
begin
|
|
if Assigned(FOnDockOver) then
|
|
FOnDockOver(Self, Source, X, Y, State, Accept);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.DoRemoveDockClient(Client: TControl);
|
|
|
|
Called to remove client from dock list.
|
|
This method exists for descendent overrides.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoRemoveDockClient(Client: TControl);
|
|
begin
|
|
// empty (this method exists for descendent overrides)
|
|
{$IFDEF VerboseDocking}
|
|
DebugLn(['TWinControl.DoRemoveDockClient ',DbgSName(Self),' ',DbgSName(Client)]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.DoUnDock(NewTarget: TWinControl; Client: TControl
|
|
): Boolean;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.DoUnDock(NewTarget: TWinControl; Client: TControl;
|
|
KeepDockSiteSize: Boolean): Boolean;
|
|
var
|
|
NewBounds: TRect;
|
|
begin
|
|
{$IFDEF VerboseDocking}
|
|
DebugLn('TWinControl.DoUnDock ',Name,' NewTarget=',DbgSName(NewTarget),' Client=',DbgSName(Client));
|
|
{$ENDIF}
|
|
Result := True;
|
|
if Assigned(FOnUnDock) then
|
|
begin
|
|
FOnUnDock(Self, Client, NewTarget, Result);
|
|
if not Result then
|
|
Exit;
|
|
end;
|
|
|
|
if not KeepDockSiteSize then
|
|
begin
|
|
NewBounds := BoundsRect;
|
|
case Client.Align of
|
|
alLeft:
|
|
inc(NewBounds.Left, Client.Width);
|
|
alTop:
|
|
inc(NewBounds.Top, Client.Height);
|
|
alRight:
|
|
dec(NewBounds.Right, Client.Width);
|
|
alBottom:
|
|
dec(NewBounds.Bottom, Client.Height);
|
|
end;
|
|
SetBoundsKeepBase(NewBounds.Left, NewBounds.Top,
|
|
NewBounds.Right - NewBounds.Left,
|
|
NewBounds.Bottom - NewBounds.Top);
|
|
end;
|
|
|
|
Result := Result and DoUndockClientMsg(NewTarget, Client);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.GetSiteInfo(Client: TControl; var InfluenceRect: TRect;
|
|
MousePos: TPoint; var CanDock: Boolean);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.GetSiteInfo(Client: TControl; var InfluenceRect: TRect;
|
|
MousePos: TPoint; var CanDock: Boolean);
|
|
const
|
|
ADockMargin = 10;
|
|
begin
|
|
GetWindowRect(Handle, InfluenceRect);
|
|
//Margins to test docking (enlarged surface for test)
|
|
InfluenceRect.Left := InfluenceRect.Left-ADockMargin;
|
|
InfluenceRect.Top := InfluenceRect.Top-ADockMargin;
|
|
InfluenceRect.Right := InfluenceRect.Right+ADockMargin;
|
|
InfluenceRect.Bottom := InfluenceRect.Bottom+ADockMargin;
|
|
|
|
if Assigned(FOnGetSiteInfo) then
|
|
FOnGetSiteInfo(Self, Client, InfluenceRect, MousePos, CanDock);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.GetParentHandle: HWND;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetParentHandle: HWND;
|
|
begin
|
|
if Parent <> nil then
|
|
Result := Parent.Handle
|
|
else
|
|
Result := ParentWindow;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.GetTopParentHandle: HWND;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetTopParentHandle: HWND;
|
|
var
|
|
AWinControl: TWinControl;
|
|
begin
|
|
AWinControl := Self;
|
|
while AWinControl.Parent <> nil do
|
|
AWinControl := AWinControl.Parent;
|
|
if AWinControl.ParentWindow = 0 then
|
|
Result := AWinControl.Handle
|
|
else
|
|
Result := AWinControl.ParentWindow;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.ReloadDockedControl(const AControlName: string;
|
|
var AControl: TControl);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.ReloadDockedControl(const AControlName: string;
|
|
var AControl: TControl);
|
|
begin
|
|
AControl := Owner.FindComponent(AControlName) as TControl;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.CreateDockManager: TDockManager;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.CreateDockManager: TDockManager;
|
|
begin
|
|
if (DockManager = nil) and DockSite and UseDockManager then
|
|
// this control can dock other controls, so it needs a TDockManager
|
|
Result := DefaultDockManagerClass.Create(Self)
|
|
else
|
|
Result := DockManager;
|
|
end;
|
|
|
|
procedure TWinControl.SetDockManager(AMgr: TDockManager);
|
|
begin
|
|
//use FDockManager only here!
|
|
if Assigned(DockManager) and (DockManager <> AMgr) then
|
|
if FDockManager.AutoFreeByControl then
|
|
FDockManager.Free;
|
|
FDockManager := AMgr; //can be nil
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.SetUseDockManager(const AValue: Boolean);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetUseDockManager(const AValue: Boolean);
|
|
begin
|
|
if FUseDockManager=AValue then exit;
|
|
FUseDockManager:=AValue;
|
|
if FUseDockManager and ([csDesigning,csDestroying]*ComponentState=[])
|
|
and (DockManager=nil) then
|
|
DockManager := CreateDockManager;
|
|
end;
|
|
|
|
procedure TWinControl.DoFloatMsg(ADockSource: TDragDockObject);
|
|
var
|
|
WasVisible: Boolean;
|
|
begin
|
|
if FloatingDockSiteClass = ClassType then
|
|
begin
|
|
WasVisible := Visible;
|
|
try
|
|
Dock(nil, ADockSource.DockRect);
|
|
finally
|
|
if WasVisible then BringToFront;
|
|
end;
|
|
end
|
|
else
|
|
inherited DoFloatMsg(ADockSource);
|
|
end;
|
|
|
|
function TWinControl.GetDockCaption(AControl: TControl): String;
|
|
begin
|
|
Result := AControl.GetDefaultDockCaption;
|
|
DoGetDockCaption(AControl, Result);
|
|
end;
|
|
|
|
procedure TWinControl.UpdateDockCaption(Exclude: TControl);
|
|
begin
|
|
{ Called when this is a hostdocksite and either the list of docked clients have
|
|
changed or one of their captions.
|
|
Exclude an currently undocking control. }
|
|
end;
|
|
|
|
procedure TWinControl.DoGetDockCaption(AControl: TControl; var ACaption: String);
|
|
begin
|
|
if Assigned(FOnGetDockCaption) then
|
|
OnGetDockCaption(Self, AControl, ACaption);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.MainWndProc(var Message : TLMessage);
|
|
|
|
The message handler of this wincontrol.
|
|
Only needed by controls, which needs features not yet supported by the LCL.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.MainWndProc(var Msg: TLMessage);
|
|
begin
|
|
//DebugLn(Format('Trace:[TWinControl.MainWndPRoc] %s(%s) --> Message = %d', [ClassName, Name, Msg.Msg]));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl SetFocus
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetFocus;
|
|
var
|
|
Form: TCustomForm;
|
|
begin
|
|
{$IFDEF VerboseFocus}
|
|
DebugLn('[TWinControl.SetFocus] ',Name,':',ClassName,' Visible=',dbgs(Visible),' HandleAllocated=',dbgs(HandleAllocated));
|
|
{$ENDIF}
|
|
Form := GetParentForm(Self);
|
|
if Form <> nil then
|
|
Form.FocusControl(Self)
|
|
else
|
|
if IsVisible and HandleAllocated then
|
|
LCLIntf.SetFocus(Handle);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl KeyDown
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.KeyDown(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
if Assigned(FOnKeyDown) then FOnKeyDown(Self, Key, Shift);
|
|
if Key <> 0 then
|
|
DoCallKeyEventHandler(chtOnKeyDown, Key, Shift);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl KeyDownBeforeInterface
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.KeyDownBeforeInterface(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
KeyDown(Key, Shift);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl KeyDownAfterInterface
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.KeyDownAfterInterface(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl KeyPress
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.KeyPress(var Key: char);
|
|
begin
|
|
if Assigned(FOnKeyPress) then FOnKeyPress(Self, Key);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl UTF8KeyPress
|
|
|
|
Called before KeyPress.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.UTF8KeyPress(var UTF8Key: TUTF8Char);
|
|
begin
|
|
if Assigned(FOnUTF8KeyPress) then FOnUTF8KeyPress(Self, UTF8Key);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl KeyUp
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.KeyUp(var Key: Word; Shift : TShiftState);
|
|
begin
|
|
if Assigned(FOnKeyUp) then FOnKeyUp(Self, Key, Shift);
|
|
end;
|
|
|
|
procedure TWinControl.KeyUpBeforeInterface(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
//debugln('TWinControl.KeyUpBeforeInterface ',DbgSName(Self));
|
|
KeyUp(Key,Shift);
|
|
end;
|
|
|
|
procedure TWinControl.KeyUpAfterInterface(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
//debugln('TWinControl.KeyUpAfterInterface ',DbgSName(Self));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DoKeyDownBeforeInterface
|
|
|
|
returns true if handled
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.DoKeyDownBeforeInterface(var Message: TLMKey; IsRecurseCall: Boolean): Boolean;
|
|
|
|
function IsShortCut: Boolean;
|
|
var
|
|
AParent: TWinControl;
|
|
APopupMenu: TPopupMenu;
|
|
begin
|
|
Result := False;
|
|
// check popup menu
|
|
APopupMenu := PopupMenu;
|
|
if Assigned(APopupMenu) and APopupMenu.IsShortCut(Message) then
|
|
Exit(True);
|
|
|
|
if IsRecurseCall then
|
|
Exit;
|
|
|
|
// let each parent form handle shortcuts
|
|
AParent := Parent;
|
|
while Assigned(AParent) do
|
|
begin
|
|
if (AParent is TCustomForm) and TCustomForm(AParent).IsShortcut(Message) then
|
|
Exit(True);
|
|
AParent := AParent.Parent;
|
|
end;
|
|
|
|
// let application handle shortcut
|
|
if Assigned(Application) and Application.IsShortcut(Message) then
|
|
Exit(True);
|
|
end;
|
|
|
|
var
|
|
F: TCustomForm;
|
|
ShiftState: TShiftState;
|
|
AParent: TWinControl;
|
|
begin
|
|
//debugln('TWinControl.DoKeyDown ',DbgSName(Self),' ShiftState=',dbgs(KeyDataToShiftState(Message.KeyData)),' CharCode=',dbgs(Message.CharCode));
|
|
Result := True;
|
|
|
|
with Message do
|
|
begin
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
ShiftState := KeyDataToShiftState(KeyData);
|
|
|
|
if not IsRecurseCall then
|
|
begin
|
|
// let application handle the key
|
|
if Assigned(Application) then
|
|
begin
|
|
Application.NotifyKeyDownBeforeHandler(Self, CharCode, ShiftState);
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
end;
|
|
|
|
// let each parent form with keypreview handle the key
|
|
AParent := Parent;
|
|
while Assigned(AParent) do
|
|
begin
|
|
if (AParent is TCustomForm) then
|
|
begin
|
|
F := TCustomForm(AParent);
|
|
if (F.KeyPreview) and (F.DoKeyDownBeforeInterface(Message, True)) then Exit;
|
|
end;
|
|
AParent := AParent.Parent;
|
|
end;
|
|
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
ShiftState := KeyDataToShiftState(KeyData);
|
|
|
|
// let drag object handle the key
|
|
if DragManager.IsDragging then
|
|
begin
|
|
DragManager.KeyDown(CharCode, ShiftState);
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
end;
|
|
end;
|
|
|
|
// let user handle the key
|
|
if not (csNoStdEvents in ControlStyle) then
|
|
begin
|
|
KeyDownBeforeInterface(CharCode, ShiftState);
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
end;
|
|
|
|
// check the shortcuts
|
|
if IsShortCut then Exit;
|
|
end;
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
function TWinControl.ChildKey(var Message: TLMKey): boolean;
|
|
begin
|
|
if Assigned(Parent) then
|
|
Result := Parent.ChildKey(Message)
|
|
else
|
|
Result := false;
|
|
end;
|
|
|
|
function TWinControl.DialogChar(var Message: TLMKey): boolean;
|
|
var
|
|
I: integer;
|
|
begin
|
|
// broadcast to children
|
|
Result := False;
|
|
for I := 0 to ControlCount - 1 do
|
|
begin
|
|
// for Delphi compatibility send it to all controls,
|
|
// even those that can not focus or are disabled
|
|
Result := Controls[I].DialogChar(Message);
|
|
if Result then Exit;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DoRemainingKeyDown
|
|
|
|
Returns True if key handled
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.DoRemainingKeyDown(var Message: TLMKeyDown): Boolean;
|
|
var
|
|
ShiftState: TShiftState;
|
|
begin
|
|
Result := True;
|
|
|
|
ShiftState := KeyDataToShiftState(Message.KeyData);
|
|
|
|
// let parent(s) handle key from child key
|
|
if Assigned(Parent) and Parent.ChildKey(Message) then
|
|
Exit;
|
|
|
|
// handle LCL special keys
|
|
ControlKeyDown(Message.CharCode, ShiftState);
|
|
if Message.CharCode = VK_UNKNOWN then Exit;
|
|
|
|
//DebugLn('TWinControl.WMKeyDown ',Name,':',ClassName);
|
|
if not (csNoStdEvents in ControlStyle) then
|
|
begin
|
|
KeyDownAfterInterface(Message.CharCode, ShiftState);
|
|
if Message.CharCode = VK_UNKNOWN then Exit;
|
|
// Note: Message.CharCode can now be different or even 0
|
|
end;
|
|
|
|
// let application handle the remaining key
|
|
if Assigned(Application) then
|
|
Application.NotifyKeyDownHandler(Self, Message.CharCode, ShiftState);
|
|
if Message.CharCode = VK_UNKNOWN then Exit;
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DoKeyPress
|
|
|
|
Returns True if key handled
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.DoKeyPress(var Message : TLMKey): Boolean;
|
|
var
|
|
F: TCustomForm;
|
|
C: char;
|
|
AParent: TWinControl;
|
|
begin
|
|
Result := True;
|
|
|
|
// let each parent form with keypreview handle the key
|
|
AParent := Parent;
|
|
while (AParent <> nil) do
|
|
begin
|
|
if (AParent is TCustomForm) then
|
|
begin
|
|
F := TCustomForm(AParent);
|
|
if F.KeyPreview and F.DoKeyPress(Message) then Exit;
|
|
end;
|
|
AParent := AParent.Parent;
|
|
end;
|
|
|
|
if not (csNoStdEvents in ControlStyle) then
|
|
with Message do
|
|
begin
|
|
C := Char(CharCode);
|
|
KeyPress(C);
|
|
CharCode := Ord(C);
|
|
if Char(CharCode) = #0 then Exit;
|
|
end;
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DoRemainingKeyPress
|
|
|
|
Returns True if key handled
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.SendDialogChar(var Message : TLMKey): Boolean;
|
|
var
|
|
ParentForm: TCustomForm;
|
|
begin
|
|
Result := False;
|
|
ParentForm := GetParentForm(Self);
|
|
if ParentForm <> nil then
|
|
begin
|
|
Result := ParentForm.DialogChar(Message);
|
|
if Result then
|
|
Message.CharCode := VK_UNKNOWN;
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.DoRemainingKeyUp(var Message: TLMKeyDown): Boolean;
|
|
var
|
|
ShiftState: TShiftState;
|
|
begin
|
|
//debugln('TWinControl.DoRemainingKeyUp ',DbgSName(Self));
|
|
Result := True;
|
|
|
|
ShiftState := KeyDataToShiftState(Message.KeyData);
|
|
|
|
// handle LCL special keys
|
|
ControlKeyUp(Message.CharCode,ShiftState);
|
|
if Message.CharCode=VK_UNKNOWN then exit;
|
|
|
|
if not (csNoStdEvents in ControlStyle) then
|
|
begin
|
|
KeyUpAfterInterface(Message.CharCode, ShiftState);
|
|
if Message.CharCode=VK_UNKNOWN then exit;
|
|
// Note: Message.CharCode can now be different or even 0
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DoUTF8KeyPress
|
|
|
|
Returns True if key handled
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.DoUTF8KeyPress(var UTF8Key: TUTF8Char): boolean;
|
|
var
|
|
AParent: TWinControl;
|
|
F: TCustomForm;
|
|
begin
|
|
Result := True;
|
|
|
|
// let each parent form with keypreview handle the key
|
|
AParent := Parent;
|
|
while Assigned(AParent) do
|
|
begin
|
|
if (AParent is TCustomForm) then
|
|
begin
|
|
F := TCustomForm(AParent);
|
|
if (F.KeyPreview) and F.DoUTF8KeyPress(UTF8Key) then Exit;
|
|
end;
|
|
AParent := AParent.Parent;
|
|
end;
|
|
|
|
if not (csNoStdEvents in ControlStyle) then
|
|
begin
|
|
UTF8KeyPress(UTF8Key);
|
|
if UTF8Key = '' then Exit;
|
|
end;
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl DoKeyUpBeforeInterface
|
|
|
|
Returns True if key handled
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.DoKeyUpBeforeInterface(var Message : TLMKey): Boolean;
|
|
var
|
|
F: TCustomForm;
|
|
ShiftState: TShiftState;
|
|
AParent: TWinControl;
|
|
begin
|
|
Result := True;
|
|
|
|
// let each parent form with keypreview handle the key
|
|
AParent:=Parent;
|
|
while (AParent<>nil) do begin
|
|
if (AParent is TCustomForm) then begin
|
|
F := TCustomForm(AParent);
|
|
if (F.KeyPreview)
|
|
and (F.DoKeyUpBeforeInterface(Message)) then Exit;
|
|
end;
|
|
AParent:=AParent.Parent;
|
|
end;
|
|
|
|
with Message do
|
|
begin
|
|
ShiftState := KeyDataToShiftState(KeyData);
|
|
|
|
if DragManager.IsDragging then
|
|
begin
|
|
DragManager.KeyUp(CharCode, ShiftState);
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
end;
|
|
|
|
if not (csNoStdEvents in ControlStyle)
|
|
then begin
|
|
KeyUpBeforeInterface(CharCode, ShiftState);
|
|
if CharCode = VK_UNKNOWN then Exit;
|
|
end;
|
|
|
|
// TODO
|
|
//if (CharCode = VK_APPS) and not (ssAlt in ShiftState) then
|
|
// CheckMenuPopup(SmallPoint(0, 0));
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl ControlKeyDown
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.ControlKeyDown(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
Application.ControlKeyDown(Self,Key,Shift);
|
|
end;
|
|
|
|
procedure TWinControl.ControlKeyUp(var Key: Word; Shift: TShiftState);
|
|
begin
|
|
//debugln('TWinControl.ControlKeyUp ',DbgSName(Self));
|
|
Application.ControlKeyUp(Self,Key,Shift);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CreateParams
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CreateParams(var Params : TCreateParams);
|
|
begin
|
|
FillChar(Params, SizeOf(Params),0);
|
|
Params.Caption := PChar(FCaption);
|
|
Params.Style := WS_CHILD or WS_CLIPSIBLINGS or WS_CLIPCHILDREN;
|
|
Params.ExStyle := 0;
|
|
if csAcceptsControls in ControlStyle then
|
|
Params.ExStyle := Params.ExStyle or WS_EX_CONTROLPARENT;
|
|
if BorderStyle = bsSingle then
|
|
Params.ExStyle := Params.ExStyle or WS_EX_CLIENTEDGE;
|
|
if TabStop then
|
|
Params.Style := Params.Style or WS_TABSTOP;
|
|
|
|
if (Parent <> nil) then
|
|
Params.WndParent := Parent.Handle
|
|
else
|
|
Params.WndParent := ParentWindow;
|
|
|
|
Params.X := Left;
|
|
Params.Y := Top;
|
|
Params.Width := Width;
|
|
Params.Height := Height;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl Invalidate
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.Invalidate;
|
|
begin
|
|
//DebugLn(['TWinControl.Invalidate ',DbgSName(Self),' HandleAllocated=',HandleAllocated]);
|
|
if HandleAllocated and ([csDestroying,csLoading]*ComponentState=[]) then
|
|
TWSWinControlClass(WidgetSetClass).Invalidate(Self);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl AddControl
|
|
|
|
Add Handle object to parents Handle object.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.AddControl;
|
|
begin
|
|
TWSControlClass(WidgetSetClass).AddControl(Self);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl Repaint
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.Repaint;
|
|
begin
|
|
if (not HandleAllocated) or (csDestroying in ComponentState) then exit;
|
|
{$IFDEF VerboseDsgnPaintMsg}
|
|
if csDesigning in ComponentState then
|
|
DebugLn('TWinControl.Repaint A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
Invalidate;
|
|
Update;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl Insert
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.Insert(AControl : TControl);
|
|
begin
|
|
Insert(AControl,ControlCount);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.Insert(AControl: TControl; Index: integer);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.Insert(AControl: TControl; Index: integer);
|
|
begin
|
|
if AControl = nil then exit;
|
|
if AControl.FParent<>nil then
|
|
raise EInvalidOperation.Create('control has already a parent');
|
|
|
|
if AControl = Self then
|
|
raise EInvalidOperation.Create(rsAControlCanNotHaveItselfAsParent);
|
|
|
|
ListInsert(FControls, Index, AControl);
|
|
if AControl is TWinControl then
|
|
begin
|
|
ListAdd(FTabList, AControl);
|
|
|
|
if (csDesigning in ComponentState) and (not (csLoading in ComponentState))
|
|
and AControl.CanTab then
|
|
TWinControl(AControl).TabStop := true;
|
|
end;
|
|
|
|
AControl.FParent := Self;
|
|
if AControl.FAutoSizingLockCount>0 then
|
|
begin
|
|
// the AControl has disabled autosizing => disable it for the parent=self too
|
|
//DebugLn([Space(FAutoSizingLockCount*2+2),'TWinControl.Insert ',DbgSName(Self),' Control=',DbgSName(AControl),' disable Parent']);
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TControl.DisableAutoSizing'){$ENDIF};
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl ReAlign
|
|
|
|
Realign all children
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.ReAlign;
|
|
begin
|
|
AdjustSize;
|
|
end;
|
|
|
|
procedure TWinControl.ScrollBy_WS(DeltaX, DeltaY: Integer);
|
|
begin
|
|
if HandleAllocated then
|
|
TWSWinControlClass(WidgetSetClass).ScrollBy(Self, DeltaX, DeltaY)
|
|
else
|
|
raise Exception.Create('TWinControl.ScrollBy_WS: Handle not allocated');
|
|
end;
|
|
|
|
procedure TWinControl.ScrollBy(DeltaX, DeltaY: Integer);
|
|
var
|
|
i: Integer;
|
|
begin
|
|
// scroll inner controls
|
|
DisableAutoSizing;
|
|
try
|
|
for i := 0 to ControlCount - 1 do
|
|
with Controls[i] do
|
|
SetBounds(Left + DeltaX, Top + DeltaY, Width, Height);
|
|
finally
|
|
EnableAutoSizing;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl Remove
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.Remove(AControl : TControl);
|
|
begin
|
|
if AControl <> nil then
|
|
begin
|
|
//DebugLn(Format('trace:[TWinControl.Remove] %s(%S) --> Remove: %s(%s)', [ClassName, Name, AControl.ClassName, AControl.Name]));
|
|
if AControl is TWinControl then
|
|
ListRemove(FTabList, AControl);
|
|
ListRemove(FControls, AControl);
|
|
ListRemove(FAlignOrder, AControl);
|
|
AControl.FParent := nil;
|
|
if AControl.FAutoSizingLockCount>0 then
|
|
begin
|
|
// AControl has disabled autosizing and thus for its parent=Self too
|
|
// end disable autosize for parent=self
|
|
//DebugLn([Space(FAutoSizingLockCount*2),'TWinControl.Remove ',DbgSName(Self),' Control=',DbgSName(AControl),' enable Parent']);
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TControl.DisableAutoSizing'){$ENDIF};
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.AlignNonAlignedControls(ListOfControls: TFPList;
|
|
var BoundsModified: Boolean);
|
|
{ All controls, not aligned/anchored by their own properties, can be auto aligned.
|
|
|
|
Example:
|
|
cclLeftToRightThenTopToBottom
|
|
|
|
+-----------------------------------+
|
|
|+---------------------------------+|
|
|
|| Control1 | Control2 | Control 3 ||
|
|
|+---------------------------------+|
|
|
|+---------------------------------+|
|
|
|| Control4 | Control5 | Control 6 ||
|
|
|+---------------------------------+|
|
|
|+---------------------+ |
|
|
|| Control7 | Control8 | |
|
|
|+---------------------+ |
|
|
+-----------------------------------+
|
|
}
|
|
var
|
|
Box: TAutoSizeBox;
|
|
r: TRect;
|
|
begin
|
|
// check if ChildSizing aligning is enabled
|
|
if (ChildSizing.Layout=cclNone) or (ListOfControls.Count=0) then
|
|
exit;
|
|
|
|
//debugln('TWinControl.AlignNonAlignedControls ',DbgSName(Self),' ListOfControls.Count=',dbgs(ListOfControls.Count),' ',dbgs(ord(ChildSizing.EnlargeHorizontal)));
|
|
|
|
Box:=TAutoSizeBox.Create;
|
|
try
|
|
r:=GetLogicalClientRect;
|
|
BoundsModified:=Box.AlignControlsInTable(ListOfControls,ChildSizing,BiDiMode,
|
|
r.Right,r.Bottom,true);
|
|
finally
|
|
Box.Free;
|
|
end;
|
|
end;
|
|
|
|
class procedure TWinControl.WSRegisterClass;
|
|
begin
|
|
inherited WSRegisterClass;
|
|
RegisterWinControl;
|
|
RegisterPropertyToSkip(TWinControl, 'ParentDoubleBuffered', 'VCL compatibility property', '');
|
|
RegisterPropertyToSkip(TWinControl, 'ImeMode', 'VCL compatibility property', '');
|
|
RegisterPropertyToSkip(TWinControl, 'ImeName', 'VCL compatibility property', '');
|
|
end;
|
|
|
|
function TWinControl.IsClientHeightStored: boolean;
|
|
begin
|
|
// The ClientHeight is needed to restore children anchored akBottom
|
|
Result:=ControlCount>0;
|
|
end;
|
|
|
|
function TWinControl.IsClientWidthStored: boolean;
|
|
begin
|
|
// The ClientWidth is needed to restore children anchored akRight
|
|
Result:=ControlCount>0;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl RemoveFocus
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.RemoveFocus(Removing : Boolean);
|
|
var
|
|
Form: TCustomForm;
|
|
begin
|
|
Form := GetParentForm(Self);
|
|
if Form <> nil then Form.DefocusControl(Self, Removing);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl UpdateControlState
|
|
|
|
Called by: RecreateWnd, TCustomTabControl.ShowCurrentPage,
|
|
TWinControl.SetParentWindow, TWinControl.InsertControl,
|
|
TWinControl.CMVisibleChanged
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.UpdateControlState;
|
|
begin
|
|
if HandleObjectShouldBeVisible then
|
|
AdjustSize // this will trigger DoAllAutoSize, which calls UpdateShowing
|
|
else
|
|
UpdateShowing; // hide immediately
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl InsertControl
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.InsertControl(AControl: TControl);
|
|
begin
|
|
InsertControl(AControl, ControlCount);
|
|
end;
|
|
|
|
procedure TWinControl.InsertControl(AControl: TControl; Index: integer);
|
|
begin
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InsertControl'){$ENDIF};
|
|
try
|
|
AControl.ValidateContainer(Self);
|
|
Perform(CM_CONTROLLISTCHANGE, WParam(AControl), LParam(True));
|
|
Insert(AControl,Index);
|
|
AControl.UpdateAlignIndex;
|
|
if not (csReading in AControl.ComponentState) then
|
|
begin
|
|
AControl.Perform(CM_PARENTCOLORCHANGED, 0, 0);
|
|
AControl.Perform(CM_PARENTSHOWHINTCHANGED, 0, 0);
|
|
AControl.Perform(CM_PARENTBIDIMODECHANGED, 0, 0);
|
|
AControl.Perform(CM_PARENTFONTCHANGED, 0, 0);
|
|
AControl.UpdateBaseBounds(false,true,false);
|
|
if AControl is TWinControl then
|
|
TWinControl(AControl).UpdateControlState
|
|
else
|
|
if HandleAllocated then
|
|
AControl.Invalidate;
|
|
//DebugLn('TWinControl.InsertControl ',Name,':',ClassName);
|
|
end;
|
|
AdjustSize;
|
|
Perform(CM_CONTROLCHANGE, WParam(AControl), LParam(True));
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InsertControl'){$ENDIF};
|
|
end;
|
|
//debugln(['TWinControl.InsertControl ',DbgSName(Self),' ',csDesigning in ComponentState,' ',DbgSName(AControl),' ',csDesigning in AControl.ComponentState]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl removeControl
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.RemoveControl(AControl: TControl);
|
|
var
|
|
AWinControl: TWinControl;
|
|
AGrControl: TGraphicControl;
|
|
begin
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.RemoveControl'){$ENDIF};
|
|
try
|
|
Perform(CM_CONTROLCHANGE, WParam(AControl), LParam(False));
|
|
if AControl is TWinControl then
|
|
begin
|
|
AWinControl := TWinControl(AControl);
|
|
AWinControl.RemoveFocus(True);
|
|
if AWinControl.HandleAllocated then
|
|
AWinControl.DestroyHandle;
|
|
end
|
|
else
|
|
begin
|
|
if AControl is TGraphicControl then
|
|
begin
|
|
AGrControl := TGraphicControl(AControl);
|
|
if (AGrControl.Canvas<>nil) then
|
|
TControlCanvas(AGrControl.Canvas).FreeHandle;
|
|
end;
|
|
if HandleAllocated then
|
|
AControl.InvalidateControl(AControl.IsVisible, False, True);
|
|
end;
|
|
Remove(AControl);
|
|
Perform(CM_CONTROLLISTCHANGE, WParam(AControl), LParam(False));
|
|
if not (csDestroying in ComponentState) then
|
|
begin
|
|
InvalidatePreferredSize;
|
|
AdjustSize;
|
|
end;
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.RemoveControl'){$ENDIF};
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.GetEnumeratorControls: TWinControlEnumerator;
|
|
begin
|
|
Result:=TWinControlEnumerator.Create(Self,true);
|
|
end;
|
|
|
|
function TWinControl.GetEnumeratorControlsReverse: TWinControlEnumerator;
|
|
begin
|
|
Result:=TWinControlEnumerator.Create(Self,false);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl AlignControl
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.AlignControl(AControl: TControl);
|
|
var
|
|
ARect: TRect;
|
|
NewRect: TRect;
|
|
begin
|
|
//if csDesigning in ComponentState then begin
|
|
// DbgOut('TWinControl.AlignControl ',Name,':',ClassName);
|
|
// if AControl<>nil then DebugLn(' AControl=',AControl.Name,':',AControl.ClassName) else DebugLn(' AControl=nil');;
|
|
//end;
|
|
if csDestroying in ComponentState then exit;
|
|
|
|
// only called by DoAllAutoSize, so no check needed
|
|
|
|
DisableAlign;
|
|
try
|
|
// store
|
|
ARect := GetClientRect;
|
|
AdjustClientRect(ARect);
|
|
FAdjustClientRectRealized:=ARect;
|
|
|
|
ARect:=GetLogicalClientRect;
|
|
AlignControls(AControl, ARect);
|
|
// some widgetsets updates their clientrect when the first child was moved
|
|
// do a second pass if ClientRect changed
|
|
NewRect:=GetLogicalClientRect;
|
|
if not CompareRect(@ARect,@NewRect) then
|
|
AlignControls(AControl, NewRect);
|
|
finally
|
|
EnableAlign;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.ContainsControl
|
|
Params: Control: the control to be checked
|
|
Returns: Self is a (super)parent of Control
|
|
|
|
Checks if Control is a child of Self
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.ContainsControl(Control: TControl): Boolean;
|
|
begin
|
|
while (Control <> nil) and (Control <> Self) do Control := Control.Parent;
|
|
Result := Control = Self;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetBorderStyle
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetBorderStyle: TBorderStyle;
|
|
begin
|
|
Result := TBorderStyle(FBorderStyle);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetBrush
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetBrush: TBrush;
|
|
begin
|
|
if not BrushCreated then
|
|
CreateBrush;
|
|
Result := FBrush;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetControl
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetControl(const Index: Integer): TControl;
|
|
begin
|
|
Result := TControl(FControls[Index]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetControlCount
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetControlCount: Integer;
|
|
begin
|
|
if FControls <> nil then
|
|
Result := FControls.Count
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TWinControl.GetDockClientCount: Integer;
|
|
begin
|
|
if FDockClients <> nil then
|
|
Result := FDockClients.Count
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TWinControl.GetDockClients(Index: Integer): TControl;
|
|
begin
|
|
if FDockClients <> nil then
|
|
Result := TControl(FDockClients[Index])
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl GetHandle
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetHandle: HWND;
|
|
begin
|
|
//if not HandleAllocated then DebugLn('TWinControl.GetHandle Creating handle on the fly: ',DbgSName(Self));
|
|
HandleNeeded;
|
|
Result := FHandle;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl SetHandle
|
|
Params: NewHandle
|
|
Returns: Nothing
|
|
-------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetHandle(NewHandle: HWND);
|
|
begin
|
|
//if (NewHandle=0) and (AnsiCompareText(ClassName,'TPAGE')=0) then
|
|
// RaiseGDBException('TWincontrol.SetHandle');
|
|
FHandle:=NewHandle;
|
|
InvalidatePreferredSize;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.Create
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Constructor for the class.
|
|
------------------------------------------------------------------------------}
|
|
constructor TWinControl.Create(TheOwner : TComponent);
|
|
begin
|
|
// do not set borderstyle, because TCustomForm needs to set it before calling
|
|
// inherited, to have it set before handle is created via streaming
|
|
// use property that bsNone is zero
|
|
//FBorderStyle := bsNone;
|
|
inherited Create(TheOwner);
|
|
FCompStyle := csWinControl;
|
|
FChildSizing:=TControlChildSizing.Create(Self);
|
|
FChildSizing.OnChange:=@DoChildSizingChange;
|
|
FBrush := nil; // Brush will be created on demand. Only few controls need it.
|
|
FTabOrder := -1;
|
|
FTabStop := False;
|
|
InvalidateClientRectCache(false);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CreateParented
|
|
------------------------------------------------------------------------------}
|
|
constructor TWinControl.CreateParented(AParentWindow: HWND);
|
|
begin
|
|
FParentWindow := AParentWindow;
|
|
Create(nil);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
TWinControl CreateParentedControl
|
|
------------------------------------------------------------------------------}
|
|
class function TWinControl.CreateParentedControl(AParentWindow: HWND
|
|
): TWinControl;
|
|
begin
|
|
Result := CreateParented(AParentWindow);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.Destroy
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Destructor for the class.
|
|
------------------------------------------------------------------------------}
|
|
destructor TWinControl.Destroy;
|
|
var
|
|
n: Integer;
|
|
Control: TControl;
|
|
begin
|
|
//DebugLn('[TWinControl.Destroy] A ',Name,':',ClassName);
|
|
// prevent parent to try to focus a to be destroyed control
|
|
if Parent <> nil then
|
|
RemoveFocus(true);
|
|
if HandleAllocated then
|
|
DestroyHandle;
|
|
//DebugLn('[TWinControl.Destroy] B ',Name,':',ClassName);
|
|
|
|
//for n:=0 to ComponentCount-1 do
|
|
// DebugLn(' n=',n,' ',Components[n].ClassName);
|
|
|
|
n := ControlCount;
|
|
|
|
while n > 0 do
|
|
begin
|
|
Control := Controls[n - 1];
|
|
//DebugLn('[TWinControl.Destroy] C ',Name,':',ClassName,' ',Control.Name,':',Control.ClassName);
|
|
Remove(Control); // this sets Control.Parent to nil
|
|
//DebugLn(['TWinControl.Destroy ',DbgSName(Control.HostDockSite)]);
|
|
if Control.HostDockSite = Self then
|
|
Control.HostDockSite := nil;
|
|
// don't free the control, controls are freed by the owner
|
|
n := ControlCount;
|
|
end;
|
|
|
|
// undock controls that use this as HostDockSite
|
|
while DockClientCount>0 do begin
|
|
Control:=DockClients[DockClientCount-1];
|
|
//DebugLn(['TWinControl.Destroy ',DbgSName(Self),' undocking ',DbgSName(Control)]);
|
|
Control.HostDockSite:=nil;
|
|
end;
|
|
|
|
FreeAndNil(FAlignOrder);
|
|
FreeThenNil(FBrush);
|
|
FreeThenNil(FChildSizing);
|
|
if (FDockManager<>nil) then
|
|
if FDockManager.AutoFreeByControl then
|
|
FreeThenNil(FDockManager)
|
|
else
|
|
FDockManager:=nil;
|
|
FreeThenNil(FDockClients);
|
|
FreeThenNil(FTabList);
|
|
//DebugLn('[TWinControl.Destroy] D ',Name,':',ClassName);
|
|
inherited Destroy;
|
|
//DebugLn('[TWinControl.Destroy] END ',Name,':',ClassName);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.DoEnter
|
|
Params: none
|
|
Returns: Nothing
|
|
|
|
Call user's callback for OnEnter.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoEnter;
|
|
begin
|
|
if Assigned(FOnEnter) then FOnEnter(Self);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.DoExit
|
|
Params: none
|
|
Returns: Nothing
|
|
|
|
Call user's callback for OnExit.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoExit;
|
|
begin
|
|
if Assigned(FOnExit) then FOnExit(Self);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.DoFlipChildren;
|
|
|
|
Flip children horizontally. That means mirroring the left position.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DoFlipChildren;
|
|
var
|
|
i: Integer;
|
|
CurControl: TControl;
|
|
AWidth: Integer;
|
|
begin
|
|
AWidth:=GetLogicalClientRect.Right;
|
|
DisableAlign;
|
|
for i:=0 to ControlCount-1 do begin
|
|
CurControl:=Controls[i];
|
|
CurControl.Left:=AWidth-CurControl.Left-CurControl.Width;
|
|
CurControl.Anchors := BidiFlipAnchors(CurControl, True);
|
|
end;
|
|
EnableAlign;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CMEnabledChanged
|
|
Params: Message
|
|
Returns: Nothing
|
|
|
|
Called when enabled is changed. Takes action to enable control
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CMEnabledChanged(var Message: TLMessage);
|
|
begin
|
|
if not Enabled and (Parent <> nil)
|
|
then RemoveFocus(False);
|
|
|
|
if HandleAllocated and not (csDesigning in ComponentState) then begin
|
|
//if (not Enabled) then debugln('TWinControl.CMEnabledChanged disable ',Name,':',CLassName);
|
|
EnableWindow(Handle, Enabled);
|
|
end;
|
|
inherited;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CMShowHintChanged
|
|
Params: Message
|
|
Returns: Nothing
|
|
|
|
Called when showhint is changed. Notifies children
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CMShowHintChanged(var Message: TLMessage);
|
|
begin
|
|
NotifyControls(CM_PARENTSHOWHINTCHANGED);
|
|
end;
|
|
|
|
procedure TWinControl.CMBiDiModeChanged(var Message: TLMessage);
|
|
begin
|
|
inherited CMBiDiModeChanged(Message);
|
|
NotifyControls(CM_PARENTBIDIMODECHANGED);
|
|
if HandleAllocated and (Message.wParam = 0) then
|
|
TWSWinControlClass(WidgetSetClass).SetBiDiMode(Self,
|
|
UseRightToLeftAlignment, UseRightToLeftReading, UseRightToLeftScrollBar);
|
|
AdjustSize;
|
|
end;
|
|
|
|
procedure TWinControl.CMBorderChanged(var Message: TLMessage);
|
|
begin
|
|
DoAdjustClientRectChange;
|
|
AdjustSize;
|
|
Invalidate;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMSetFocus
|
|
Params: Message
|
|
Returns: Nothing
|
|
|
|
SetFocus event handler
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMSetFocus(var Message: TLMSetFocus);
|
|
begin
|
|
{$IFDEF VerboseFocus}
|
|
DebugLn('TWinControl.WMSetFocus A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.LMKillFocus
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMKillFocus(var Message: TLMKillFocus);
|
|
var
|
|
ParentForm: TCustomForm;
|
|
begin
|
|
//DebugLn('TWinControl.WMKillFocus A ',Name,':',ClassName);
|
|
//DebugLn(Format('Trace: %s', [ClassName]));
|
|
if [csLoading,csDestroying,csDesigning]*ComponentState=[] then
|
|
begin
|
|
ParentForm := GetParentForm(Self);
|
|
if Assigned(ParentForm) and ParentForm.Active then
|
|
EditingDone;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMPaint
|
|
Params: Msg: The paint message
|
|
Returns: nothing
|
|
|
|
Paint event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMPaint(var Msg: TLMPaint);
|
|
var
|
|
DC,MemDC: HDC;
|
|
{$ifdef BUFFERED_WMPAINT}
|
|
MemBitmap, OldBitmap : HBITMAP;
|
|
MemWidth: Integer;
|
|
MemHeight: Integer;
|
|
{$ENDIF}
|
|
PS : TPaintStruct;
|
|
ClientBoundRect: TRect;
|
|
begin
|
|
//DebugLn('[TWinControl.WMPaint] ',DbgSName(Self),' ',DbgS(Msg.DC));
|
|
{$IFDEF VerboseResizeFlicker}
|
|
DebugLn('[TWinControl.WMPaint] ',DbgSName(Self),' Bounds=',dbgs(BoundsRect),' ClientRect=',dbgs(ClientRect));
|
|
{$ENDIF}
|
|
if ([csDestroying,csLoading]*ComponentState<>[]) or (not HandleAllocated) then
|
|
exit;
|
|
|
|
{$IFDEF VerboseDsgnPaintMsg}
|
|
if csDesigning in ComponentState then
|
|
DebugLn('TWinControl.WMPaint A ',Name,':',ClassName);
|
|
{$ENDIF}
|
|
|
|
//if Name='GroupBox1' then
|
|
// debugln(['TWinControl.WMPaint ',DbgSName(Self),' DoubleBuffered=',DoubleBuffered,' Msg.DC=',dbgs(Msg.DC),' csCustomPaint=',csCustomPaint in ControlState,' ControlCount=',ControlCount,' ClientRect=',dbgs(ClientRect)]);
|
|
if (Msg.DC <> 0) or not DoubleBuffered then
|
|
begin
|
|
if not (csCustomPaint in ControlState) and (ControlCount = 0) then
|
|
begin
|
|
DefaultHandler(Msg);
|
|
end
|
|
else
|
|
PaintHandler(Msg);
|
|
end
|
|
else begin
|
|
// NOTE: not every interface uses this part
|
|
//DebugLn('TWinControl.WMPaint Painting doublebuffered ',Name,':',classname);
|
|
{$ifdef BUFFERED_WMPAINT}
|
|
DC := GetDC(0);
|
|
MemWidth:=Width;
|
|
MemHeight:=Height;
|
|
MemBitmap := CreateCompatibleBitmap(DC, MemWidth, MemHeight);
|
|
ReleaseDC(0, DC);
|
|
MemDC := CreateCompatibleDC(0);
|
|
OldBitmap := SelectObject(MemDC, MemBitmap);
|
|
{$ENDIF}
|
|
try
|
|
// Fetch a DC of the whole Handle (including client area)
|
|
DC := BeginPaint(Handle, PS);
|
|
if DC=0 then exit;
|
|
{$ifNdef BUFFERED_WMPAINT}
|
|
MemDC := DC;
|
|
{$ENDIF}
|
|
// erase background
|
|
Include(FWinControlFlags,wcfEraseBackground);
|
|
Perform(LM_ERASEBKGND, WParam(MemDC), 0);
|
|
Exclude(FWinControlFlags,wcfEraseBackground);
|
|
// create a paint message to paint the child controls.
|
|
// WMPaint expects the DC origin to be equal to the client origin of its
|
|
// parent
|
|
// -> Move the DC Origin to the client origin
|
|
if not GetClientBounds(Handle,ClientBoundRect) then exit;
|
|
MoveWindowOrgEx(MemDC,ClientBoundRect.Left,ClientBoundRect.Top);
|
|
// handle the paint message
|
|
Msg.DC := MemDC;
|
|
Perform(LM_PAINT, WParam(MemDC), 0);
|
|
Msg.DC := 0;
|
|
// restore the DC origin
|
|
MoveWindowOrgEx(MemDC,-ClientBoundRect.Left,-ClientBoundRect.Top);
|
|
{$ifdef BUFFERED_WMPAINT}
|
|
BitBlt(DC, 0,0, MemWidth, MemHeight, MemDC, 0, 0, SRCCOPY);
|
|
{$ENDIF}
|
|
EndPaint(Handle, PS);
|
|
finally
|
|
Exclude(FWinControlFlags,wcfEraseBackground);
|
|
{$ifdef BUFFERED_WMPAINT}
|
|
SelectObject(MemDC, OldBitmap);
|
|
DeleteDC(MemDC);
|
|
DeleteObject(MemBitmap);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
//DebugLn(Format('Trace:< [TWinControl.WMPaint] %s', [ClassName]));
|
|
//DebugLn('[TWinControl.WMPaint] END ',Name,':',ClassName);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMDestroy
|
|
Params: Msg: The destroy message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMDestroy(var Message: TLMDestroy);
|
|
begin
|
|
//DebugLn(Format('Trace: [TWinControl.LMDestroy] %s', [ClassName]));
|
|
//DebugLn('TWinControl.WMDestroy ',Name,':',ClassName);
|
|
// Our widget/window doesn't exist anymore
|
|
Handle := 0;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMMove
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMMove(var Message: TLMMove);
|
|
var
|
|
NewWidth, NewHeight: Integer;
|
|
NewBoundsRealized: TRect;
|
|
TopParent: TControl;
|
|
|
|
procedure RaiseLoop;
|
|
begin
|
|
raise Exception.Create('TWinControl.WMMove loop detected: '+DbgSName(Self)+' BoundsRealized='+dbgs(FBoundsRealized)+' NewBoundsRealized='+dbgs(NewBoundsRealized));
|
|
end;
|
|
|
|
begin
|
|
{$IF defined (VerboseSizeMsg) or defined(VerboseIntfSizing)}
|
|
if (Message.MoveType and Move_SourceIsInterface)>0 then
|
|
DebugLn(['TWinControl.WMMove A ',DbgSName(Self),' Message=',Message.XPos,',',Message.YPos,
|
|
' BoundsRealized=',FBoundsRealized.Left,',',FBoundsRealized.Top,
|
|
' FromIntf=',Message.MoveType=Move_SourceIsInterface,
|
|
',',FBoundsRealized.Right-FBoundsRealized.Left,
|
|
'x',FBoundsRealized.Bottom-FBoundsRealized.Top]);
|
|
{$ENDIF}
|
|
NewWidth := Width;
|
|
NewHeight := Height;
|
|
if (Message.MoveType and Move_SourceIsInterface)>0 then
|
|
begin
|
|
if not (wcfBoundsRealized in FWinControlFlags) then exit;
|
|
// interface widget has moved
|
|
// -> update size and realized bounds
|
|
NewWidth := FBoundsRealized.Right - FBoundsRealized.Left;
|
|
NewHeight := FBoundsRealized.Bottom - FBoundsRealized.Top;
|
|
// skip size update when window is minimized
|
|
if HandleAllocated and (not IsIconic(Handle)) then
|
|
GetWindowSize(Handle, NewWidth, NewHeight);
|
|
|
|
NewBoundsRealized:=Bounds(Message.XPos, Message.YPos, NewWidth, NewHeight);
|
|
if CompareRect(@NewBoundsRealized,@FBoundsRealized) then exit;
|
|
|
|
TopParent:=GetTopParent;
|
|
if (TopParent is TWinControl) and (wcfKillIntfSetBounds in TWinControl(TopParent).FWinControlFlags) then
|
|
RaiseLoop;
|
|
|
|
FBoundsRealized := NewBoundsRealized;
|
|
if ([caspCreatingHandles,caspComputingBounds]*AutoSizePhases<>[]) then
|
|
begin
|
|
// while the LCL is creating handles the widgetset may send default bounds
|
|
// we have not yet told the widgetset the final bounds
|
|
// => the InvalidatePreferredSize and the InvalidateClientRectCache
|
|
// (invoked by the widgetset) may trigger a further loop in the auto
|
|
// size algorithm to take care of the new bounds
|
|
// => do not call SetBounds, as this will set the Bounds to the widgetset
|
|
// default values.
|
|
//DebugLn(['TWinControl.WMMove from intf ignored, because phases=',dbgs(AutoSizePhases),' boundsrealized=',wcfBoundsRealized in FWinControlFlags]);
|
|
exit;
|
|
end;
|
|
end;
|
|
if Assigned(Parent) then
|
|
SetBoundsKeepBase(Message.XPos, Message.YPos, NewWidth, NewHeight)
|
|
else
|
|
SetBounds(Message.XPos, Message.YPos, NewWidth, NewHeight);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMSize
|
|
Params: Message: TLMSize
|
|
Returns: nothing
|
|
|
|
Event handler for size messages. This is called, whenever width, height,
|
|
clientwidth or clientheight have changed.
|
|
If the source of the message is the interface, the new size is stored
|
|
in FBoundsRealized to avoid sending a size message back to the interface.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMSize(var Message: TLMSize);
|
|
var
|
|
NewLeft, NewTop: integer;
|
|
NewBoundsRealized: TRect;
|
|
TopParent: TControl;
|
|
OldClientSize: TSize;
|
|
NewClientSize: TSize;
|
|
|
|
procedure RaiseLoop;
|
|
var
|
|
s: String;
|
|
begin
|
|
s:='TWinControl.WMSize loop detected, the widgetset does not like the LCL bounds or sends unneeded wmsize messages: '+DbgSName(Self)+' BoundsRealized='+dbgs(FBoundsRealized)+' NewBoundsRealized='+dbgs(NewBoundsRealized);
|
|
if (OldClientSize.cx<>NewClientSize.cx)
|
|
or (OldClientSize.cy<>NewClientSize.cy)
|
|
then
|
|
s:=s+' OldClientSize='+dbgs(OldClientSize)+' NewClientSize='+dbgs(NewClientSize);
|
|
raise Exception.Create(s);
|
|
end;
|
|
|
|
begin
|
|
{$IF defined(VerboseSizeMsg) or defined(CHECK_POSITION) or defined(VerboseIntfSizing)}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
if (Message.SizeType and Size_SourceIsInterface) > 0 then
|
|
DebugLn(['TWinControl.WMSize A ',Name,':',ClassName,' Message=',Message.Width,',',Message.Height,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),
|
|
' WChg=',FBoundsRealized.Right-FBoundsRealized.Left<>Message.Width,
|
|
' HChg=',FBoundsRealized.Bottom-FBoundsRealized.Top<>Message.Height,
|
|
' FromIntf=',(Message.SizeType and Size_SourceIsInterface)>0,' ClientRectInvalid=',ClientRectNeedsInterfaceUpdate]);
|
|
{$ENDIF}
|
|
|
|
NewLeft := Left;
|
|
NewTop := Top;
|
|
if ((Message.SizeType and Size_SourceIsInterface) > 0) then
|
|
begin
|
|
// interface widget has resized
|
|
// -> update realized position and realized bounds
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.WMSize FromIntf ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),
|
|
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
|
|
{$ENDIF}
|
|
if not (wcfBoundsRealized in FWinControlFlags) then exit;
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
//if Name=CheckClientRectName then
|
|
DebugLn(['TWinControl.WMSize FromIntf ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),
|
|
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
|
|
{$ENDIF}
|
|
|
|
NewLeft := FBoundsRealized.Left;
|
|
NewTop := FBoundsRealized.Top;
|
|
if HandleAllocated then
|
|
GetWindowRelativePosition(Handle, NewLeft, NewTop);
|
|
//if CheckPosition(Self) then
|
|
//DebugLn(['TWinControl.WMSize GetWindowRelativePosition: ',DbgSName(Self),' ',NewLeft,',',NewTop,' ClientRectNeedsInterfaceUpdate=',ClientRectNeedsInterfaceUpdate]);
|
|
NewBoundsRealized := Bounds(NewLeft, NewTop, Message.Width, Message.Height);
|
|
OldClientSize := Size(0, 0);
|
|
NewClientSize := Size(0, 0);
|
|
if CompareRect(@NewBoundsRealized, @FBoundsRealized) then
|
|
begin
|
|
if not (wcfClientRectNeedsUpdate in FWinControlFlags) then
|
|
begin
|
|
OldClientSize := Size(FClientWidth, FClientHeight);
|
|
NewClientSize := Size(ClientWidth, ClientHeight);
|
|
if (OldClientSize.cx = NewClientSize.cx) and
|
|
(OldClientSize.cy = NewClientSize.cy) then
|
|
Exit;
|
|
end;
|
|
end;
|
|
{$IFDEF VerboseAllAutoSize}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
DebugLn(['TWinControl.WMSize Changed From Intf ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),
|
|
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags,
|
|
' ClientRectNeedsInterfaceUpdate=',ClientRectNeedsInterfaceUpdate]);
|
|
{$ENDIF}
|
|
|
|
TopParent := GetTopParent;
|
|
if (TopParent is TWinControl) and (wcfKillIntfSetBounds in TWinControl(TopParent).FWinControlFlags) then
|
|
RaiseLoop;
|
|
|
|
FBoundsRealized := NewBoundsRealized;
|
|
//DebugLn(['TWinControl.WMSize ',DbgSName(Self),' phases=',dbgs(AutoSizePhases)]);
|
|
if ([caspCreatingHandles, caspComputingBounds] * AutoSizePhases <> []) then
|
|
begin
|
|
// while the LCL is creating handles the widgetset may send default bounds
|
|
// we have not yet told the widgetset the final bounds
|
|
// => the InvalidatePreferredSize and the InvalidateClientRectCache
|
|
// (invoked by the widgetset) may trigger a further loop in the auto
|
|
// size algorithm to take care of the new bounds
|
|
// => do not call SetBounds, as this will set the Bounds to the widgetset
|
|
// default values.
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
// DebugLn(['TWinControl.WMSize from intf ignored, because phases=',dbgs(AutoSizePhases),' boundsrealized=',wcfBoundsRealized in FWinControlFlags]);
|
|
Exit;
|
|
end;
|
|
|
|
if Assigned(Parent) then
|
|
InvalidatePreferredSize;
|
|
end;
|
|
|
|
if Assigned(Parent) then
|
|
SetBoundsKeepBase(NewLeft, NewTop, Message.Width, Message.Height)
|
|
else
|
|
SetBounds(NewLeft, NewTop, Message.Width, Message.Height);
|
|
//if CheckPosition(Self) then
|
|
//debugln(['TWinControl.WMSize ',DbgSName(Self),' ClientRectNeedsInterfaceUpdate=',ClientRectNeedsInterfaceUpdate]);
|
|
if ((Message.SizeType and Size_SourceIsInterface) > 0) and ClientRectNeedsInterfaceUpdate then
|
|
DoAdjustClientRectChange;
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
if ((Message.SizeType and Size_SourceIsInterface) > 0) then
|
|
DebugLn(['TWinControl.WMSize END ',dbgsname(Self),' Message=',Message.Width,',',Message.Height,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),' ClientRect=',dbgs(ClientRect),
|
|
' ']);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMWindowPosChanged
|
|
Params: Message: TLMWindowPosChanged
|
|
Returns: nothing
|
|
|
|
Event handler for size/move messages. This is called, whenever left, top,
|
|
width, height, clientwidth or clientheight have changed.
|
|
If the source of the message is the interface, the new size is stored
|
|
in FBoundsRealized to avoid sending a SetBounds back to the interface.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMWindowPosChanged(var Message: TLMWindowPosChanged);
|
|
var
|
|
NewLeft, NewTop, NewWidth, NewHeight: integer;
|
|
NewBoundsRealized: TRect;
|
|
TopParent: TControl;
|
|
|
|
procedure RaiseLoop;
|
|
begin
|
|
raise Exception.Create('TWinControl.WMWindowPosChanged loop detected: '+DbgSName(Self)+' BoundsRealized='+dbgs(FBoundsRealized)+' NewBoundsRealized='+dbgs(NewBoundsRealized));
|
|
end;
|
|
|
|
begin
|
|
if not Assigned(Message.WindowPos) or
|
|
((Message.WindowPos^.flags and SWP_SourceIsInterface) = 0) then
|
|
begin
|
|
inherited WMWindowPosChanged(Message);
|
|
Exit;
|
|
end;
|
|
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(DbgSName(Self) + ' : ' + DbgSWindowPosFlags(Message.WindowPos^.flags));
|
|
{$ENDIF}
|
|
|
|
NewLeft := Message.WindowPos^.x;
|
|
NewTop := Message.WindowPos^.y;
|
|
NewWidth := Message.WindowPos^.cx;
|
|
NewHeight := Message.WindowPos^.cy;
|
|
|
|
{$IF defined(VerboseSizeMsg) or defined(CHECK_POSITION) or defined(VerboseIntfSizing)}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
{$ENDIF}
|
|
DebugLn(['TWinControl.WMWindowPosChanged START ',DbgSName(Self),' Message=',NewLeft,',',NewTop,',',NewWidth,'x',NewHeight,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),' FromIntf=',(Message.WindowPos^.flags and SWP_SourceIsInterface)>0,' ClientRectInvalid=',ClientRectNeedsInterfaceUpdate]);
|
|
{$ENDIF}
|
|
|
|
// interface widget has moved/resized
|
|
// -> update realized bounds
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.WMWindowPosChanged FROM INTF ',dbgsname(Self),' Message=',NewLeft,',',NewTop,',',NewWidth,'x',NewHeight,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),
|
|
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
|
|
{$ENDIF}
|
|
//DebugLn('TWinControl.WMSize B ',Name,':',ClassName,' ',NewLeft,',',NewTop);
|
|
NewBoundsRealized := Bounds(NewLeft, NewTop, NewWidth, NewHeight);
|
|
if CompareRect(@NewBoundsRealized,@FBoundsRealized)
|
|
and (not (wcfClientRectNeedsUpdate in FWinControlFlags)) then
|
|
exit;
|
|
|
|
{$IFDEF VerboseAllAutoSize}
|
|
DebugLn(['TWinControl.WMWindowPosChanged CHANGED BY INTF ',dbgsname(Self),' Message=',NewLeft,',',NewTop,',',NewWidth,'x',NewHeight,
|
|
' BoundsRealized=',dbgs(FBoundsRealized),
|
|
' wcfClientRectNeedsUpdate=',wcfClientRectNeedsUpdate in FWinControlFlags]);
|
|
{$ENDIF}
|
|
|
|
TopParent:=GetTopParent;
|
|
if (TopParent is TWinControl)
|
|
and (wcfKillIntfSetBounds in TWinControl(TopParent).FWinControlFlags)
|
|
then
|
|
RaiseLoop;
|
|
|
|
FBoundsRealized := NewBoundsRealized;
|
|
//DebugLn(['TWinControl.WMSize ',DbgSName(Self),' phases=',dbgs(AutoSizePhases)]);
|
|
if ([caspCreatingHandles,caspComputingBounds]*AutoSizePhases<>[])
|
|
or (not (wcfBoundsRealized in FWinControlFlags))
|
|
then begin
|
|
// while the LCL is creating handles the widgetset may send default bounds
|
|
// we have not yet told the widgetset the final bounds
|
|
// => the InvalidatePreferredSize and the InvalidateClientRectCache
|
|
// (invoked by the widgetset) may trigger a further loop in the auto
|
|
// size algorithm to take care of the new bounds
|
|
// => do not call SetBounds, as this will set the Bounds to the widgetset
|
|
// default values.
|
|
//DebugLn(['TWinControl.WMSize from intf ignored, because phases=',dbgs(AutoSizePhases),' boundsrealized=',wcfBoundsRealized in FWinControlFlags]);
|
|
exit;
|
|
end;
|
|
|
|
if Parent<>nil then
|
|
InvalidatePreferredSize;
|
|
|
|
if Parent<>nil then
|
|
SetBoundsKeepBase(NewLeft, NewTop, NewWidth, NewHeight)
|
|
else
|
|
SetBounds(NewLeft, NewTop, NewWidth, NewHeight);
|
|
if ((Message.WindowPos^.flags and SWP_SourceIsInterface) > 0)
|
|
and ClientRectNeedsInterfaceUpdate then
|
|
DoAdjustClientRectChange;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CNKeyDown
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CNKeyDown(var Message: TLMKeyDown);
|
|
begin
|
|
//DebugLn('TWinControl.CNKeyDown ',Name,':',ClassName);
|
|
if DoKeyDownBeforeInterface(Message, False) then
|
|
Message.Result := 1
|
|
else
|
|
{inherited}; // there is nothing to inherit
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.CNSysKeyDown(var Message: TLMKeyDown);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CNSysKeyDown(var Message: TLMKeyDown);
|
|
begin
|
|
if DoKeyDownBeforeInterface(Message, False) then
|
|
Message.Result := 1
|
|
else
|
|
{inherited}; // there is nothing to inherit
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.CNSysKeyUp(var Message: TLMKeyUp);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CNSysKeyUp(var Message: TLMKeyUp);
|
|
begin
|
|
if DoKeyUpBeforeInterface(Message) then
|
|
Message.Result := 1
|
|
else
|
|
{inherited}; // there is nothing to inherit
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CNKeyUp
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CNKeyUp(var Message: TLMKeyUp);
|
|
begin
|
|
if DoKeyUpBeforeInterface(Message) then
|
|
Message.Result := 1
|
|
else
|
|
{inherited}; // there is nothing to inherit
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CNChar
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
CNChar is sent by the interface before it has handled the keypress itself.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CNChar(var Message: TLMKeyUp);
|
|
var
|
|
c: TUTF8Char;
|
|
begin
|
|
//debugln('TWinControl.CNChar B ',DbgSName(Self),' ',dbgs(Message.CharCode),' ',dbgs(IntfSendsUTF8KeyPress));
|
|
if Widgetset.GetLCLCapability(lcSendsUTF8KeyPress) = LCL_CAPABILITY_NO then
|
|
begin
|
|
// current interface does not (yet) send UTF8 key press notifications
|
|
// -> emulate
|
|
if (Message.CharCode < %11000000) then
|
|
begin
|
|
c:=chr(Message.CharCode);
|
|
IntfUTF8KeyPress(c,1,false);
|
|
if (length(c)<>1) or (c[1]<>chr(Message.CharCode)) then
|
|
begin
|
|
// character changed
|
|
if length(c)=1 then
|
|
Message.CharCode:=ord(c[1])
|
|
else
|
|
Message.CharCode:=0;
|
|
end;
|
|
end;
|
|
if Message.CharCode=0 then
|
|
begin
|
|
Message.Result := 1;
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef VerboseKeyboard}
|
|
debugln('TWinControl.CNChar A ',DbgSName(Self),' ',dbgs(Message.CharCode),' ',dbgs(IntfSendsUTF8KeyPress));
|
|
{$endif}
|
|
|
|
if DoKeyPress(Message) then
|
|
Message.Result := 1
|
|
else
|
|
{inherited}; // there is nothing to inherit
|
|
end;
|
|
|
|
procedure TWinControl.WMSysChar(var Message: TLMKeyUp);
|
|
begin
|
|
if SendDialogChar(Message) then
|
|
Message.Result := 1
|
|
else
|
|
{inherited}; // there is nothing to inherit
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMNofity
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMNotify(var Message: TLMNotify);
|
|
begin
|
|
if not DoControlMsg(Message.NMHdr^.hwndfrom, Message) then
|
|
inherited;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMShowWindow
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMShowWindow(var Message: TLMShowWindow);
|
|
begin
|
|
// DebugLn(['TWinControl.LMShowWindow ', dbgsName(self)]);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMEnter
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMEnter(var Message: TLMEnter);
|
|
begin
|
|
//DebugLn(Format('Trace: TODO: [TWinControl.LMEnter] %s', [ClassName]));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMEraseBkgnd
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMEraseBkgnd(var Message: TLMEraseBkgnd);
|
|
begin
|
|
if (Message.DC <> 0) and (wcfEraseBackground in FWinControlFlags) then
|
|
begin
|
|
EraseBackground(Message.DC);
|
|
Message.Result := 1;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMExit
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMExit(var Message: TLMExit);
|
|
begin
|
|
//DebugLn(Format('Trace: TODO: [TWinControl.LMExit] %s', [ClassName]));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMChar
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
WMChar is sent by the interface after it has handled the keypress by itself.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMChar(var Message: TLMChar);
|
|
begin
|
|
//debugln('TWinControl.WMChar ',DbgSName(Self),' ',dbgs(Message.CharCode));
|
|
if SendDialogChar(Message) then
|
|
Message.Result := 1;
|
|
//DebugLn(Format('Trace:[TWinControl.WMChar] %s', [ClassName]));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMKeyDown
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
Event handler for keys not handled by the interface
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMKeyDown(var Message: TLMKeyDown);
|
|
begin
|
|
if DoRemainingKeyDown(Message) then
|
|
Message.Result := 1;
|
|
end;
|
|
|
|
procedure TWinControl.WMSysKeyDown(var Message: TLMKeyDown);
|
|
begin
|
|
if DoRemainingKeyDown(Message) then
|
|
Message.Result := 1;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.WMSysKeyUp(var Message: TLMKeyUp);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMSysKeyUp(var Message: TLMKeyUp);
|
|
begin
|
|
//debugln('TWinControl.WMSysKeyUp ',DbgSName(Self));
|
|
if DoRemainingKeyUp(Message) then
|
|
Message.Result := 1;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.WMKeyUp
|
|
Params: Msg: The message
|
|
Returns: nothing
|
|
|
|
event handler.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.WMKeyUp(var Message: TLMKeyUp);
|
|
begin
|
|
//debugln('TWinControl.WMKeyUp ',DbgSName(Self));
|
|
if DoRemainingKeyUp(Message) then
|
|
Message.Result := 1;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function: TWinControl.HandleAllocated
|
|
Params: None
|
|
Returns: True is handle is allocated
|
|
|
|
Checks if a handle is allocated. I.E. if the control is mapped
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.HandleAllocated : Boolean;
|
|
begin
|
|
HandleAllocated := (FHandle <> 0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CreateHandle
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Creates the handle ( = object) if not already done.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CreateHandle;
|
|
begin
|
|
if (not HandleAllocated) then CreateWnd;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CreateWnd
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Creates the interface object and assigns the handle
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CreateWnd;
|
|
var
|
|
Params: TCreateParams;
|
|
i: Integer;
|
|
AWinControl: TWinControl;
|
|
|
|
{ procedure WriteClientRect(const Prefix: string);
|
|
var r: TRect;
|
|
begin
|
|
LCLIntf.GetClientRect(Handle,r);
|
|
if csDesigning in ComponentState then
|
|
DebugLn('WriteClientRect ',Prefix,' ',Name,':',ClassName,' r=',r.Right,',',r.Bottom);
|
|
end;}
|
|
|
|
begin
|
|
//DebugLn('[TWinControl.CreateWnd] START ',DbgSName(Self));
|
|
if (csDestroying in ComponentState) or Assigned(Parent) and (csDestroying in Parent.ComponentState) then
|
|
begin
|
|
DebugLn('[TWinControl.CreateWnd] NOTE: csDestroying ',DbgSName(Self));
|
|
RaiseGDBException('');
|
|
exit;
|
|
end;
|
|
|
|
if wcfInitializing in FWinControlFlags then
|
|
begin
|
|
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while initializing');
|
|
RaiseGDBException('');
|
|
Exit;
|
|
end;
|
|
|
|
if wcfCreatingHandle in FWinControlFlags then
|
|
begin
|
|
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while creating handle');
|
|
RaiseGDBException('');
|
|
Exit;
|
|
end;
|
|
|
|
if wcfCreatingChildHandles in FWinControlFlags then
|
|
begin
|
|
DebugLn('[WARNING] Recursive call to CreateWnd for ',DbgSName(Self), ' while creating children');
|
|
RaiseGDBException('');
|
|
Exit;
|
|
end;
|
|
|
|
if [csLoading,csDesigning]*ComponentState=[csLoading] then
|
|
begin
|
|
DebugLn('[HINT] TWinControl.CreateWnd creating Handle during loading ',DbgSName(Self),' csDesigning=',dbgs(csDesigning in ComponentState));
|
|
//DumpStack;
|
|
//RaiseGDBException('');
|
|
end;
|
|
|
|
FBoundsRealized := Rect(0,0,0,0);
|
|
Exclude(FWinControlFlags, wcfBoundsRealized);
|
|
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.CreateWnd'){$ENDIF};
|
|
try
|
|
if Assigned(Parent) and not Parent.HandleAllocated then
|
|
begin
|
|
// first create the parent handle
|
|
Parent.HandleNeeded;
|
|
if HandleAllocated then exit;
|
|
DebugLn(['WARNING: TWinControl.CreateWnd: parent created handles, but not ours']);
|
|
end;
|
|
// Control is not visible at this moment. It will be shown in UpdateShowing
|
|
FShowing := False;
|
|
Exclude(FWinControlFlags, wcfHandleVisible);
|
|
|
|
Include(FWinControlFlags, wcfCreatingHandle);
|
|
try
|
|
CreateParams(Params);
|
|
with Params do
|
|
begin
|
|
if (WndParent = 0) and (Style and WS_CHILD <> 0) then
|
|
begin
|
|
DebugLn(['TWinControl.CreateWnd ',DbgSName(Self),' Parent=',DbgSName(Parent),' ERROR WndParent=0']);
|
|
raise EInvalidOperation.CreateFmt(rsControlHasNoParentWindow, [Name]);
|
|
end;
|
|
end;
|
|
|
|
//DebugLn(['TWinControl.CreateWnd Creating handle ... ',DbgSName(WidgetSetClass),' ',DbgSName(Self)]);
|
|
FHandle := TWSWinControlClass(WidgetSetClass).CreateHandle(Self, Params);
|
|
if not HandleAllocated then
|
|
begin
|
|
if WidgetSet.LCLPlatform=lpNoGUI then
|
|
RaiseGDBException('TWinControl.CreateWnd: The nogui widgetset does not support visual controls.')
|
|
else
|
|
RaiseGDBException('TWinControl.CreateWnd: Handle creation failed creating '+DbgSName(Self));
|
|
end;
|
|
//debugln('TWinControl.CreateWnd update constraints ... ',DbgSName(Self));
|
|
TWSWinControlClass(WidgetSetClass).SetBiDiMode(Self,
|
|
UseRightToLeftAlignment, UseRightToLeftReading, UseRightToLeftScrollBar);
|
|
|
|
Constraints.UpdateInterfaceConstraints;
|
|
InvalidateClientRectCache(False);
|
|
TWSWinControlClass(WidgetSetClass).ConstraintsChange(Self);
|
|
|
|
//WriteClientRect('A');
|
|
if Assigned(Parent) then
|
|
AddControl
|
|
else
|
|
if ParentWindow <> 0 then
|
|
LCLIntf.SetParent(FHandle, ParentWindow);
|
|
//WriteClientRect('B');
|
|
|
|
Include(FWinControlFlags, wcfInitializing);
|
|
//DebugLn(['TWinControl.CreateWnd initializing window ...']);
|
|
InitializeWnd;
|
|
|
|
finally
|
|
Exclude(FWinControlFlags, wcfInitializing);
|
|
Exclude(FWinControlFlags, wcfCreatingHandle);
|
|
end;
|
|
|
|
Include(FWinControlFlags, wcfCreatingChildHandles);
|
|
try
|
|
//DebugLn('[TWinControl.CreateWnd] ',Name,':',ClassName,' ',Left,',',Top,',',Width,',',Height);
|
|
//WriteClientRect('C');
|
|
|
|
if FControls <> nil then
|
|
begin
|
|
for i := 0 to FControls.Count - 1 do
|
|
begin
|
|
AWinControl := TWinControl(FControls.Items[i]);
|
|
//DebugLn(['TWinControl.CreateWnd create child handles self=',DbgSName(Self),' Child=',DbgSName(AWinControl)]);
|
|
if (AWinControl is TWinControl) and AWinControl.IsControlVisible then
|
|
AWinControl.HandleNeeded;
|
|
end;
|
|
end;
|
|
|
|
ChildHandlesCreated;
|
|
finally
|
|
Exclude(FWinControlFlags, wcfCreatingChildHandles);
|
|
end;
|
|
|
|
InvalidatePreferredSize;
|
|
if Assigned(FControls) then
|
|
for i := 0 to FControls.Count - 1 do
|
|
TControl(FControls[i]).InvalidatePreferredSize;
|
|
// size this control
|
|
AdjustSize;
|
|
finally
|
|
//DebugLn(['TWinControl.CreateWnd created ',DbgSName(Self),' enable autosizing ...']);
|
|
(* If an error occured and FHandle was not created,
|
|
then EnableAutoSizing must not be called.
|
|
EnableAutoSizing will need the Handle, and trigger another attempt to create it.
|
|
This leads to an endless loop/recursion.
|
|
As a side effect the current control will be left in autosize-disabled *)
|
|
if FHandle <> 0 then
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.CreateWnd'){$ENDIF};
|
|
end;
|
|
|
|
//DebugLn('[TWinControl.CreateWnd] END ',Name,':',Classname);
|
|
//WriteClientRect('D');
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.InitializeWnd
|
|
Params: none
|
|
Returns: Nothing
|
|
|
|
Gets called after the window is created, but before the child controls are
|
|
created. Place cached property code here.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.InitializeWnd;
|
|
var
|
|
CachedText: string;
|
|
begin
|
|
//DebugLn(Format('Trace:[TWinControl.InitializeWnd] %s', [ClassName]));
|
|
// set all cached properties
|
|
|
|
//DebugLn('[TWinControl.InitializeWnd] ',Name,':',ClassName,':', FCaption,' ',dbgs(Left),',',dbgs(Top),',',dbgs(Width),',',dbgs(Height));
|
|
|
|
// First set the WinControl property some interfaces depends on it
|
|
SetProp(Handle,'WinControl',TWinControl(Self));
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InitializeWnd'){$ENDIF};
|
|
try
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
DebugLn('[TWinControl.InitializeWnd] A ',DbgSName(Self),
|
|
' OldRelBounds=',dbgs(FBoundsRealized),
|
|
' -> NewBounds=',dbgs(BoundsRect));
|
|
{$ENDIF}
|
|
|
|
if wcfColorChanged in FWinControlFlags then
|
|
begin
|
|
// replace by update style call
|
|
TWSWinControlClass(WidgetSetClass).SetColor(Self);
|
|
Exclude(FWinControlFlags, wcfColorChanged);
|
|
end;
|
|
if wcfFontChanged in FWinControlFlags then
|
|
begin
|
|
// replace by update style call
|
|
TWSWinControlClass(WidgetSetClass).SetFont(Self,Font);
|
|
Exclude(FWinControlFlags, wcfFontChanged);
|
|
end;
|
|
|
|
if not (csDesigning in ComponentState) then
|
|
EnableWindow(Handle, Enabled);
|
|
|
|
// Delay the setting of text until it is completely loaded
|
|
if not (csLoading in ComponentState) then
|
|
begin
|
|
if GetCachedText(CachedText) then
|
|
WSSetText(CachedText);
|
|
InvalidatePreferredSize;
|
|
end;
|
|
|
|
if csDesigning in ComponentState then
|
|
TWSWinControlClass(WidgetSetClass).SetCursor(Self, Screen.Cursors[crDefault])
|
|
else
|
|
TWSWinControlClass(WidgetSetClass).SetCursor(Self, Screen.Cursors[Cursor]);
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.InitializeWnd'){$ENDIF};
|
|
end;
|
|
// send pending OnResize
|
|
{$IFDEF VerboseOnResize}
|
|
debugln(['TWinControl.InitializeWnd ',DbgSName(Self),' calling Resize ...']);
|
|
{$ENDIF}
|
|
Resize;
|
|
end;
|
|
|
|
procedure TWinControl.FinalizeWnd;
|
|
var
|
|
S: string;
|
|
begin
|
|
if not HandleAllocated then
|
|
RaiseGDBException('TWinControl.FinalizeWnd Handle already destroyed');
|
|
// make sure our text is saved
|
|
if TWSWinControlClass(WidgetSetClass).GetText(Self, S) then
|
|
FCaption := S;
|
|
// if color has changed make sure it will be restored
|
|
if FColor <> {$ifdef UseCLDefault}clDefault{$else}clWindow{$endif} then
|
|
Include(FWinControlFlags,wcfColorChanged);
|
|
RemoveProp(Handle,'WinControl');
|
|
FAdjustClientRectRealized := Rect(0,0,0,0);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.ParentFormHandleInitialized;
|
|
|
|
Called after all children handles of the ParentForm are created.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.ParentFormHandleInitialized;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited ParentFormHandleInitialized;
|
|
// tell all controls about the final end of the handle creation phase
|
|
if FControls<>nil then begin
|
|
for i:=0 to FControls.Count-1 do
|
|
TControl(FControls[i]).ParentFormHandleInitialized;
|
|
end;
|
|
//debugln('TWinControl.ParentFormHandleInitialized A ',DbgSName(Self));
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.ChildHandlesCreated;
|
|
|
|
Called after all children handles are created.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.ChildHandlesCreated;
|
|
begin
|
|
Exclude(FWinControlFlags,wcfCreatingChildHandles);
|
|
end;
|
|
|
|
function TWinControl.GetMouseCapture: Boolean;
|
|
begin
|
|
Result:=HandleAllocated and (GetCaptureControl=Self);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.ParentHandlesAllocated: boolean;
|
|
|
|
Checks if all Handles of all Parents are created.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.ParentHandlesAllocated: boolean;
|
|
var
|
|
CurControl: TWinControl;
|
|
begin
|
|
Result:=false;
|
|
CurControl:=Self;
|
|
while CurControl<>nil do begin
|
|
if (not CurControl.HandleAllocated)
|
|
or (csDestroying in CurControl.ComponentState)
|
|
or (csDestroyingHandle in CurControl.ControlState) then
|
|
exit;
|
|
CurControl:=CurControl.Parent;
|
|
end;
|
|
Result:=true;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.Loaded;
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.Loaded;
|
|
var
|
|
CachedText: string;
|
|
i: Integer;
|
|
AChild: TControl;
|
|
LoadedClientSize: TSize;
|
|
CurControl: TWinControl;
|
|
begin
|
|
//DebugLn(['TWinControl.Loaded START ',DbgSName(Self),' cfWidthLoaded=',cfWidthLoaded in FControlFlags,' cfHeightLoaded=',cfHeightLoaded in FControlFlags,' ']);
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.Loaded'){$ENDIF};
|
|
try
|
|
//DebugLn(['TWinControl.Loaded ',DbgSName(Self),' cfWidthLoaded=',cfWidthLoaded in FControlFlags,' cfHeightLoaded=',cfHeightLoaded in FControlFlags,' ']);
|
|
if cfClientWidthLoaded in FControlFlags then
|
|
LoadedClientSize.cx:=FLoadedClientSize.cx
|
|
else begin
|
|
CurControl:=Self;
|
|
while CurControl<>nil do begin
|
|
LoadedClientSize.cx:=CurControl.ClientWidth;
|
|
if LoadedClientSize.cx>0 then break;
|
|
LoadedClientSize.cx:=CurControl.Width;
|
|
if LoadedClientSize.cx>0 then break;
|
|
CurControl:=CurControl.Parent;
|
|
end;
|
|
end;
|
|
if cfClientHeightLoaded in FControlFlags then
|
|
LoadedClientSize.cy:=FLoadedClientSize.cy
|
|
else begin
|
|
CurControl:=Self;
|
|
while CurControl<>nil do begin
|
|
LoadedClientSize.cy:=CurControl.ClientHeight;
|
|
if LoadedClientSize.cy>0 then break;
|
|
LoadedClientSize.cy:=CurControl.Height;
|
|
if LoadedClientSize.cy>0 then break;
|
|
CurControl:=CurControl.Parent;
|
|
end;
|
|
end;
|
|
for i:=0 to ControlCount-1 do begin
|
|
AChild:=Controls[i];
|
|
if AChild=nil then ;
|
|
AChild.FBaseParentClientSize:=LoadedClientSize;
|
|
//DebugLn(['TWinControl.Loaded Self=',DbgSName(Self),' AChild=',AChild,' AChild.FBaseParentClientSize=',dbgs(AChild.FBaseParentClientSize)]);
|
|
end;
|
|
if HandleAllocated then
|
|
begin
|
|
// Set cached caption
|
|
if GetCachedText(CachedText) then
|
|
WSSetText(CachedText);
|
|
InvalidatePreferredSize;
|
|
|
|
if wcfColorChanged in FWinControlFlags then
|
|
begin
|
|
TWSWinControlClass(WidgetSetClass).SetColor(Self);
|
|
NotifyControls(CM_PARENTCOLORCHANGED);
|
|
Exclude(FWinControlFlags, wcfColorChanged);
|
|
end;
|
|
if wcfFontChanged in FWinControlFlags then
|
|
begin
|
|
TWSWinControlClass(WidgetSetClass).SetFont(Self,Font);
|
|
NotifyControls(CM_PARENTFONTCHANGED);
|
|
FWinControlFlags:=FWinControlFlags-[wcfFontChanged];
|
|
end;
|
|
end;
|
|
|
|
inherited Loaded;
|
|
|
|
FixupTabList;
|
|
|
|
finally
|
|
//DebugLn(['TWinControl.Loaded enableautosizing ',DbgSName(Self),' ',dbgs(BoundsRect)]);
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.Loaded'){$ENDIF};
|
|
//DebugLn(['TWinControl.Loaded END ',DbgSName(Self),' ',dbgs(BoundsRect)]);
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.FormEndUpdated;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
inherited FormEndUpdated;
|
|
for i:=0 to ControlCount-1 do
|
|
Controls[i].FormEndUpdated;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.DestroyWnd
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Destroys the interface object.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DestroyWnd;
|
|
var
|
|
i: integer;
|
|
begin
|
|
if HandleAllocated then
|
|
begin
|
|
//DebugLn(['TWinControl.DestroyWnd ',DbgSName(Self)]);
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DestroyWnd'){$ENDIF};
|
|
try
|
|
FinalizeWnd;
|
|
|
|
if FControls <> nil then
|
|
for i := 0 to FControls.Count - 1 do
|
|
TControl(FControls[i]).DoOnParentHandleDestruction;
|
|
|
|
TWSWinControlClass(WidgetSetClass).DestroyHandle(Self);
|
|
Handle := 0;
|
|
Exclude(FWinControlFlags,wcfBoundsRealized);
|
|
// Maybe handle is not needed at moment but later it will be created once
|
|
// again. To propely initialize control after we need to restore color
|
|
// and font. Request update.
|
|
FWinControlFlags := FWinControlFlags + [wcfColorChanged, wcfFontChanged];
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.DestroyWnd'){$ENDIF};
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.HandleNeeded
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
Description of the procedure for the class.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.HandleNeeded;
|
|
begin
|
|
if (not HandleAllocated) and (not (csDestroying in ComponentState)) then
|
|
begin
|
|
if Parent = Self
|
|
then begin
|
|
//DebugLn(Format('Trace:[TWinControl.HandleNeeded] Somebody set Parent := Self in %s. DONT DO THAT !!', [Classname]));
|
|
end
|
|
else begin
|
|
if (Parent <> nil) then
|
|
begin
|
|
Parent.HandleNeeded;
|
|
// has parent triggered us to create our handle ?
|
|
if HandleAllocated then
|
|
exit;
|
|
end;
|
|
end;
|
|
CreateHandle;
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.BrushCreated: Boolean;
|
|
begin
|
|
Result := Assigned(FBrush);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.BeginUpdateBounds
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
increases the BoundsLockCount
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.BeginUpdateBounds;
|
|
begin
|
|
inc(FBoundsLockCount);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TControl.EndUpdateBounds
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
decreases the BoundsLockCount
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.EndUpdateBounds;
|
|
begin
|
|
if FBoundsLockCount <= 0 then
|
|
raise EInvalidOperation.CreateFmt('TWinControl.EndUpdateBounds %s too many calls.', [DbgSName(Self)]);
|
|
dec(FBoundsLockCount);
|
|
if FBoundsLockCount = 0 then
|
|
SetBounds(Left, Top, Width, Height);
|
|
end;
|
|
|
|
procedure TWinControl.LockRealizeBounds;
|
|
begin
|
|
inc(FRealizeBoundsLockCount);
|
|
end;
|
|
|
|
procedure TWinControl.UnlockRealizeBounds;
|
|
begin
|
|
if FRealizeBoundsLockCount<=0 then
|
|
RaiseGDBException('UnlockRealizeBounds');
|
|
dec(FRealizeBoundsLockCount);
|
|
if (FRealizeBoundsLockCount=0)
|
|
and (not AutoSizeDelayed) and (caspRealizingBounds in AutoSizePhases)
|
|
then
|
|
RealizeBounds;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.DockDrop(DockObject: TDragDockObject; X, Y: Integer);
|
|
|
|
Docks the DockObject.Control onto this control.
|
|
X, Y are only default values. More important is the DockObject.DropAlign
|
|
property, which defines how to align DockObject.Control.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.DockDrop(DragDockObject: TDragDockObject; X, Y: Integer);
|
|
begin
|
|
if DoDockClientMsg(DragDockObject, Point(X, Y)) and Assigned(FOnDockDrop) then
|
|
FOnDockDrop(Self, DragDockObject, X, Y);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TControl.GetIsResizing
|
|
Params: None
|
|
Returns: Nothing
|
|
|
|
decreases the BoundsLockCount
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetIsResizing: boolean;
|
|
begin
|
|
Result:=BoundsLockCount>0;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.GetTabOrder: TTabOrder;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetTabOrder: TTabOrder;
|
|
begin
|
|
if FParent <> nil then
|
|
Result := ListIndexOf(FParent.FTabList,Self)
|
|
else
|
|
Result := FTabOrder;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
function TWinControl.GetVisibleDockClientCount: Integer;
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetVisibleDockClientCount: Integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := 0;
|
|
if FDockClients=nil then exit;
|
|
for i:=FDockClients.Count-1 downto 0 do
|
|
if TControl(FDockClients[I]).Visible then inc(Result);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.SetChildSizing(const AValue: TControlChildSizing);
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetChildSizing(const AValue: TControlChildSizing);
|
|
begin
|
|
if (FChildSizing=AValue) then exit;
|
|
FChildSizing.Assign(AValue);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.SetDockSite(const NewDockSite: Boolean);
|
|
|
|
If NewDockSite=true it means, this control can dock other controls.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetDockSite(const NewDockSite: Boolean);
|
|
begin
|
|
if FDockSite=NewDockSite then exit;
|
|
FDockSite := NewDockSite;
|
|
if not (csDesigning in ComponentState) then begin
|
|
DragManager.RegisterDockSite(Self,NewDockSite);
|
|
if not NewDockSite then begin
|
|
FreeAndNil(FDockClients);
|
|
FDockClients := nil;
|
|
DockManager := nil;
|
|
end
|
|
else begin
|
|
if FDockClients = nil then FDockClients := TFPList.Create;
|
|
DockManager := CreateDockManager;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.DoDockClientMsg(DragDockObject: TDragDockObject;
|
|
aPosition: TPoint): boolean;
|
|
var
|
|
DestRect: TRect;
|
|
Form: TCustomForm;
|
|
begin
|
|
with DragDockObject do begin
|
|
DestRect := DockRect;
|
|
DisableAlign;
|
|
try
|
|
{$IFDEF VerboseDocking}
|
|
Debugln(['TWinControl.DoDockClientMsg ',DbgSName(Self),' Control=',DbgSName(DragDockObject.Control),' DestRect=',dbgs(DestRect)]);
|
|
{$ENDIF}
|
|
DragDockObject.Control.Dock(Self, DestRect);
|
|
if FUseDockManager and (DockManager <> nil) then
|
|
DockManager.InsertControl(DragDockObject);
|
|
finally
|
|
EnableAlign;
|
|
end;
|
|
Form := GetParentForm(Self);
|
|
if Form <> nil then Form.BringToFront;
|
|
Result := true;
|
|
end;
|
|
end;
|
|
|
|
function TWinControl.DoUndockClientMsg(NewTarget, Client: TControl): boolean;
|
|
begin
|
|
Result := True;
|
|
{$IFDEF VerboseDocking}
|
|
DebugLn(['TWinControl.DoUnDockClientMsg ',DbgSName(Self),' Client=',DbgSName(Client),' Client.Parent=',DbgSName(Client.Parent)]);
|
|
{$ENDIF}
|
|
if FUseDockManager and (DockManager <> nil) then
|
|
DockManager.RemoveControl(Client);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.SetBounds
|
|
Params: aLeft, aTop, aWidth, aHeight
|
|
Returns: Nothing
|
|
|
|
Sets the bounds of the control.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
|
|
|
|
procedure CheckDesignBounds;
|
|
begin
|
|
if FRealizeBoundsLockCount > 0 then Exit;
|
|
// the user changed the bounds
|
|
if AWidth < 0 then
|
|
raise EInvalidOperation.CreateFmt('TWinControl.SetBounds (%s): Negative width %d not allowed.', [DbgSName(Self), AWidth]);
|
|
if AHeight < 0 then
|
|
raise EInvalidOperation.CreateFmt('TWinControl.SetBounds (%s): Negative height %d not allowed.', [DbgSName(Self), AHeight]);
|
|
end;
|
|
|
|
var
|
|
NewBounds, OldBounds: TRect;
|
|
begin
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in ComponentState then
|
|
if CheckPosition(Self) then
|
|
DebugLn(['[TWinControl.SetBounds] START ',DbgSName(Self),
|
|
' Old=',dbgs(Bounds(Left,Top,Width,Height)),
|
|
' -> New=',dbgs(Bounds(ALeft,ATop,AWidth,AHeight)),
|
|
' Lock=',BoundsLockCount,
|
|
' Realized=',dbgs(FBoundsRealized)
|
|
]);
|
|
{$ENDIF}
|
|
if BoundsLockCount <> 0 then
|
|
Exit;
|
|
OldBounds := BoundsRect;
|
|
NewBounds := Bounds(ALeft, ATop, AWidth, AHeight);
|
|
|
|
if not CompareRect(@NewBounds, @OldBounds) then
|
|
begin
|
|
if [csDesigning,csDestroying,csLoading]*ComponentState=[csDesigning] then
|
|
CheckDesignBounds;
|
|
// LCL bounds are not up2date -> process new bounds
|
|
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.SetBounds'){$ENDIF};
|
|
try
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in ComponentState then
|
|
if CheckPosition(Self) then
|
|
DebugLn(['[TWinControl.SetBounds] Set LCL Bounds ',DbgSName(Self),
|
|
' OldBounds=',Dbgs(Bounds(Left,Top,Width,Height)),
|
|
' -> New=',Dbgs(Bounds(ALeft,ATop,AWidth,AHeight))]);
|
|
{$ENDIF}
|
|
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
|
|
//DebugLn(['TWinControl.SetBounds ',DbgSName(Self),' FUseDockManager=',FUseDockManager,' ',DbgSName(DockManager)]);
|
|
finally
|
|
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TWinControl.SetBounds'){$ENDIF};
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
procedure TWinControl.CalculatePreferredSize(var PreferredWidth,
|
|
PreferredHeight: integer; WithThemeSpace" Boolean);
|
|
|
|
Calculates the default/preferred width and height for a TWinControl, which is
|
|
used by the LCL autosizing algorithms as default size. Only positive values
|
|
are valid. Negative or 0 are treated as undefined and the LCL uses other sizes
|
|
instead (exception: csAutoSize0x0).
|
|
TWinControl overrides this:
|
|
If there are children, their total preferred size is calculated.
|
|
If this value can not be computed (e.g. the children depend too much on their
|
|
parent clientrect), then the interface is asked for the preferred size.
|
|
For example the preferred size of a TButton is the size, where the label fits
|
|
exactly. This depends heavily on the current theme and widgetset.
|
|
|
|
This value is independent of constraints and siblings, only the inner parts
|
|
are relevant.
|
|
|
|
WithThemeSpace: If true, adds space for stacking. For example: TRadioButton
|
|
has a minimum size. But for stacking multiple TRadioButtons there should be
|
|
some space around. This space is theme dependent, so it passed parameter to
|
|
the widgetset.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CalculatePreferredSize(var PreferredWidth,
|
|
PreferredHeight: integer; WithThemeSpace: Boolean);
|
|
|
|
{$IFDEF VerboseCalculatePreferredSize}
|
|
procedure trav(aControl: TControl; Prefix: string);
|
|
var
|
|
w: integer;
|
|
h: integer;
|
|
i: Integer;
|
|
begin
|
|
if not aControl.IsVisible then exit;
|
|
if aControl<>Self then begin
|
|
aControl.GetPreferredSize(w,h,true,true);
|
|
debugln([Prefix,'Child ',DbgSName(aControl),' ',dbgs(aControl.BoundsRect),' Pref=',w,'x',h]);
|
|
end;
|
|
if aControl is TWinControl then
|
|
for i:=0 to TWinControl(aControl).ControlCount-1 do
|
|
trav(TWinControl(aControl).Controls[i],Prefix+' ');
|
|
end;
|
|
|
|
function IsVerbose: boolean;
|
|
begin
|
|
Result:=(Name='MainScrollBox');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
var
|
|
Layout: TAutoSizeCtrlData;
|
|
NewClientWidth: Integer;
|
|
NewClientHeight: Integer;
|
|
CurClientRect: TRect;
|
|
NewMoveLeft, NewMoveRight: integer;
|
|
FrameWidth: integer;
|
|
FrameHeight: integer;
|
|
begin
|
|
inherited CalculatePreferredSize(PreferredWidth,PreferredHeight,WithThemeSpace);
|
|
|
|
if HandleAllocated then begin
|
|
TWSWinControlClass(WidgetSetClass).GetPreferredSize(Self,
|
|
PreferredWidth, PreferredHeight, WithThemeSpace);
|
|
{$IFDEF VerboseCalculatePreferredSize}
|
|
if IsVerbose then debugln(['TWinControl.CalculatePreferredSize Widget ',DbgSName(Self),' ',DbgSName(WidgetSetClass),' Pref=',PreferredWidth,'x',PreferredHeight]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
if ControlCount>0 then begin
|
|
// Beware: ControlCount>0 does not mean that there are visible children
|
|
|
|
// get the size requirements for the child controls
|
|
Layout:=nil;
|
|
try
|
|
Layout:=TAutoSizeCtrlData.Create(Self);
|
|
Layout.ComputePreferredClientArea(false,false,NewMoveLeft,NewMoveRight,
|
|
NewClientWidth,NewClientHeight);
|
|
//if (Name='OtherInfoGroupBox') or (Name='ProjectVersionInfoOptionsFrame') then
|
|
// debugln(['TWinControl.CalculatePreferredSize NewClientWidth=',NewClientWidth,' NewClientHeight=',NewClientHeight]);
|
|
if (NewMoveLeft<>0) or (NewMoveRight<>0) then ;
|
|
finally
|
|
Layout.Free;
|
|
end;
|
|
|
|
// add clientarea frame
|
|
GetPreferredSizeClientFrame(FrameWidth,FrameHeight);
|
|
|
|
{$IF defined(VerboseAutoSize) or defined(VerboseAllAutoSize) or defined(VerboseCalculatePreferredSize)}
|
|
{$IFDEF VerboseCalculatePreferredSize}
|
|
if IsVerbose then
|
|
trav(Self,' ');
|
|
if IsVerbose then
|
|
{$ENDIF}
|
|
//if (Name='OtherInfoGroupBox') or (Name='ProjectVersionInfoOptionsFrame') then
|
|
debugln(['TWinControl.CalculatePreferredSize ',DbgSName(Self),
|
|
' HandleAllocated=',HandleAllocated,
|
|
' Cur=',Width,'x',Height,
|
|
' Client=',ClientWidth,'x',ClientHeight,
|
|
' PrefClient=',NewClientWidth,'x',NewClientHeight]);
|
|
{$ENDIF}
|
|
if NewClientWidth>0 then
|
|
PreferredWidth:=Max(PreferredWidth,NewClientWidth+FrameWidth);
|
|
if NewClientHeight>0 then
|
|
PreferredHeight:=Max(PreferredHeight,NewClientHeight+FrameHeight);
|
|
end;
|
|
|
|
// add borderspacing
|
|
if (PreferredWidth>0)
|
|
or ((PreferredWidth=0) and (csAutoSize0x0 in ControlStyle)) then
|
|
inc(PreferredWidth,BorderSpacing.InnerBorder*2);
|
|
if (PreferredHeight>0)
|
|
or ((PreferredHeight=0) and (csAutoSize0x0 in ControlStyle)) then
|
|
inc(PreferredHeight,BorderSpacing.InnerBorder*2);
|
|
{$IF defined(VerboseAutoSize) or defined(VerboseCalculatePreferredSize)}
|
|
{$IFDEF VerboseCalculatePreferredSize}
|
|
if IsVerbose then
|
|
{$ENDIF}
|
|
debugln(['TWinControl.CalculatePreferredSize ',DbgSName(Self),
|
|
' HandleAllocated=',dbgs(HandleAllocated),
|
|
' ClientFrame=',FrameWidth,'x',FrameHeight,
|
|
' Preferred=',dbgs(PreferredWidth),'x',dbgs(PreferredHeight)]);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
procedure TWinControl.GetPreferredSizeClientFrame(out aWidth, aHeight: integer);
|
|
begin
|
|
aWidth:=Width-ClientWidth;
|
|
aHeight:=Height-ClientHeight;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.RealGetText
|
|
Params: None
|
|
Returns: The text
|
|
|
|
Gets the text/caption of a control
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.RealGetText: TCaption;
|
|
begin
|
|
Result := '';
|
|
{$IFDEF VerboseTWinControlRealText}
|
|
DebugLn(['TWinControl.RealGetText ',DbgSName(Self),' HandleAllocated=',HandleAllocated,' csLoading=',csLoading in ComponentState,' ']);
|
|
if not HandleAllocated
|
|
or (csLoading in ComponentState) then begin
|
|
DebugLn(['TWinControl.RealGetText using inherited RealGetText']);
|
|
Result := inherited RealGetText;
|
|
end else begin
|
|
DebugLn(['TWinControl.RealGetText using ',DbgSName(WidgetSetClass),' GetText']);
|
|
if (not TWSWinControlClass(WidgetSetClass).GetText(Self, Result)) then begin
|
|
DebugLn(['TWinControl.RealGetText FAILED, using RealGetText']);
|
|
Result := inherited RealGetText;
|
|
end;
|
|
end;
|
|
DebugLn(['TWinControl.RealGetText Result="',Result,'"']);
|
|
{$ELSE}
|
|
if not HandleAllocated
|
|
or (csLoading in ComponentState)
|
|
or (not TWSWinControlClass(WidgetSetClass).GetText(Self, Result))
|
|
then Result := inherited RealGetText;
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.GetTextLen
|
|
Params: None
|
|
Returns: The length of the text
|
|
|
|
Gets the length of the text/caption of a control
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetTextLen: Integer;
|
|
begin
|
|
Result := 0;
|
|
if not HandleAllocated
|
|
or (csLoading in ComponentState)
|
|
or not TWSWinControlClass(WidgetSetClass).GetTextLen(Self, Result)
|
|
then Result := inherited GetTextLen;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.RealSetText
|
|
Params: Value: the text to be set
|
|
Returns: Nothing
|
|
|
|
Sets the text/caption of a control
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.RealSetText(const AValue: TCaption);
|
|
begin
|
|
{$IFDEF VerboseTWinControlRealText}
|
|
DebugLn(['TWinControl.RealSetText ',DbgSName(Self),' AValue="',AValue,'" HandleAllocated=',HandleAllocated,' csLoading=',csLoading in ComponentState]);
|
|
{$ENDIF}
|
|
if HandleAllocated and (not (csLoading in ComponentState)) then
|
|
begin
|
|
WSSetText(AValue);
|
|
InvalidatePreferredSize;
|
|
inherited RealSetText(AValue);
|
|
AdjustSize;
|
|
end
|
|
else inherited RealSetText(AValue);
|
|
{$IFDEF VerboseTWinControlRealText}
|
|
DebugLn(['TWinControl.RealSetText ',DbgSName(Self),' END']);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.GetDeviceContext
|
|
Params: WindowHandle: the windowhandle of this control
|
|
Returns: a Devicecontext
|
|
|
|
Get the devicecontext for this WinControl.
|
|
------------------------------------------------------------------------------}
|
|
function TWinControl.GetDeviceContext(var WindowHandle: HWND): HDC;
|
|
begin
|
|
Result := GetDC(Handle);
|
|
//DebugLn('[TWinControl.GetDeviceContext] ',ClassName,' DC=',DbgS(Result,8),' Handle=',DbgS(FHandle));
|
|
if Result = 0 then
|
|
raise EOutOfResources.CreateFmt(rsErrorCreatingDeviceContext, [Name, ClassName]);
|
|
|
|
WindowHandle := Handle;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CMVisibleChanged
|
|
Params: Message : not used
|
|
Returns: nothing
|
|
|
|
Performs actions when visibility has changed
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CMVisibleChanged(var Message : TLMessage);
|
|
begin
|
|
if not FVisible and Assigned(Parent) then
|
|
RemoveFocus(False);
|
|
|
|
if not (csDesigning in ComponentState) or (csNoDesignVisible in ControlStyle) then
|
|
UpdateControlState;
|
|
end;
|
|
|
|
procedure TWinControl.CMEnter(var Message: TLMessage);
|
|
begin
|
|
DoEnter;
|
|
end;
|
|
|
|
procedure TWinControl.CMExit(var Message: TLMessage);
|
|
begin
|
|
DoExit;
|
|
end;
|
|
|
|
procedure TWinControl.WMContextMenu(var Message: TLMContextMenu);
|
|
var
|
|
Child: TControl;
|
|
begin
|
|
// Check if at the click place we have a control and if so then pass the
|
|
// message to it.
|
|
// Don't check csDesigning here - let a child control check it.
|
|
if (Message.Result <> 0) then
|
|
Exit;
|
|
|
|
if Message.XPos <> -1 then
|
|
begin
|
|
// don't allow disabled and don't search wincontrols - they receive their
|
|
// message themself
|
|
Child := ControlAtPos(ScreenToClient(SmallPointToPoint(Message.Pos)), [capfHasScrollOffset]);
|
|
if Assigned(Child) then
|
|
with Message do
|
|
begin
|
|
Result := Child.Perform(Msg, WParam(hWnd), LParam(Integer(Pos)));
|
|
if (Result <> 0) then
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
procedure TWinControl.DoSendShowHideToInterface;
|
|
var
|
|
NewVisible: Boolean;
|
|
begin
|
|
NewVisible := HandleObjectShouldBeVisible;
|
|
if NewVisible <> (wcfHandleVisible in FWinControlFlags) then
|
|
begin
|
|
if NewVisible then
|
|
Include(FWinControlFlags, wcfHandleVisible)
|
|
else
|
|
Exclude(FWinControlFlags, wcfHandleVisible);
|
|
{$IF defined(VerboseNewAutoSize) or defined(VerboseIntfSizing) or defined(VerboseShowing)}
|
|
DebugLn(['TWinControl.DoSendShowHideToInterface ',DbgSName(Self),' FBoundsRealized=',dbgs(FBoundsRealized),' New=',HandleObjectShouldBeVisible]);
|
|
{$ENDIF}
|
|
TWSWinControlClass(WidgetSetClass).ShowHide(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.ControlsAligned;
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TWinControl.DoSendBoundsToInterface;
|
|
var
|
|
NewBounds: TRect;
|
|
OldClientRect: TRect;
|
|
NewClientRect: TRect;
|
|
{$IF defined(VerboseResizeFlicker) or defined(VerboseSizeMsg)}
|
|
OldBounds: TRect;
|
|
{$ENDIF}
|
|
begin
|
|
if (Parent = nil) and (not HandleObjectShouldBeVisible) then
|
|
begin
|
|
{ do not move invisible forms
|
|
Reason: It is common to do this:
|
|
Form1:=TForm1.Create(nil);
|
|
Form1.Top:=100;
|
|
Form1.Left:=100;
|
|
Form1.Show;
|
|
This moves the form around and confuses some windowmanagers.
|
|
Only send the last bounds. }
|
|
Exit;
|
|
end;
|
|
NewBounds := Bounds(Left, Top, Width, Height);
|
|
{$IF defined(VerboseResizeFlicker) or defined(VerboseSizeMsg)}
|
|
if HandleAllocated then begin
|
|
GetWindowRelativePosition(Handle,OldBounds.Left,OldBounds.Top);
|
|
GetWindowSize(Handle,OldBounds.Right,OldBounds.Bottom);
|
|
inc(OldBounds.Right,OldBounds.Left);
|
|
inc(OldBounds.Bottom,OldBounds.Top);
|
|
end else
|
|
OldBounds:=NewBounds;
|
|
DebugLn(['[TWinControl.DoSendBoundsToInterface] ',DbgSName(Self),
|
|
' Old=',dbgs(OldBounds),
|
|
' New=',dbgs(NewBounds),
|
|
' PosChanged=',(OldBounds.Left<>NewBounds.Left) or (OldBounds.Top<>NewBounds.Top),
|
|
' SizeChanged=w',(OldBounds.Right-OldBounds.Left<>NewBounds.Right-NewBounds.Left),
|
|
',h', (OldBounds.Bottom-OldBounds.Top<>NewBounds.Bottom-NewBounds.Top),
|
|
' CurClient=',FClientWidth,'x',FClientHeight
|
|
]);
|
|
{$ENDIF}
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then
|
|
DebugLn('[TWinControl.DoSendBoundsToInterface] A ',DbgSName(Self),
|
|
' OldRelBounds=',dbgs(FBoundsRealized),
|
|
' -> NewBounds=',dbgs(NewBounds),
|
|
' ClientRect=',dbgs(ClientRect));
|
|
{$ENDIF}
|
|
|
|
{$IFDEF VerboseClientRectBugFix}
|
|
//if Name=CheckClientRectName then
|
|
DebugLn('[TWinControl.DoSendBoundsToInterface] A ',DbgSName(Self),
|
|
' OldRelBounds=',dbgs(FBoundsRealized),
|
|
' -> NewBounds=',dbgs(NewBounds)
|
|
//,' Parent.Bounds=',dbgs(Parent.BoundsRect)
|
|
//,' Parent.ClientRect=',dbgs(Parent.ClientRect)
|
|
);
|
|
{$ENDIF}
|
|
|
|
{$IFDEF VerboseIntfSizing}
|
|
if Visible then begin
|
|
DebugLn('[TWinControl.DoSendBoundsToInterface] A ',DbgSName(Self),
|
|
' OldRelBounds=',dbgs(FBoundsRealized),
|
|
' -> NewBounds=',dbgs(NewBounds));
|
|
end;
|
|
{$ENDIF}
|
|
FBoundsRealized := NewBounds;
|
|
OldClientRect := ClientRect; // during a resize this is the anticipated new ClientRect
|
|
Include(FWinControlFlags, wcfBoundsRealized); // Note: set before calling widgetset, because used in WMSize
|
|
//if Parent=nil then DebugLn(['TWinControl.DoSendBoundsToInterface ',DbgSName(Self),' ',dbgs(BoundsRect)]);
|
|
// this can trigger WMSize messages
|
|
TWSWinControlClass(WidgetSetClass).SetBounds(Self, Left, Top, Width, Height);
|
|
NewClientRect := ClientRect;
|
|
if Visible and (not CompareRect(@OldClientRect,@NewClientRect)) then
|
|
begin
|
|
// the widgetset has changed the clientrect in an unexpected way
|
|
{$IFDEF VerboseIntfSizing}
|
|
debugln(['TWinControl.DoSendBoundsToInterface WS has changed ClientRect in an unexpected way: ',
|
|
DbgSName(Self),' Bounds=',dbgs(BoundsRect),' ExpectedClientRect=',dbgs(OldClientRect),' New=',dbgs(NewClientRect)]);
|
|
{$ENDIF}
|
|
//DebugLn(['TWinControl.DoSendBoundsToInterface ',DbgSName(Self),' Bounds=',dbgs(BoundsRect),' OldClientRect=',dbgs(OldClientRect),' NewClientRect=',dbgs(NewClientRect)]);
|
|
AdjustSize;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.RealizeBounds;
|
|
|
|
procedure Check;
|
|
var
|
|
c: TWinControl;
|
|
begin
|
|
c:=Self;
|
|
while c<>nil do begin
|
|
DebugLn(['Check ',DbgSName(c),' ',c.HandleAllocated,
|
|
' wcfCreatingHandle=',wcfCreatingHandle in FWinControlFlags,
|
|
' wcfInitializing=',wcfInitializing in FWinControlFlags,
|
|
' wcfCreatingChildHandles=',wcfCreatingChildHandles in FWinControlFlags,
|
|
'']);
|
|
c:=c.Parent;
|
|
end;
|
|
RaiseGDBException('');
|
|
end;
|
|
|
|
var
|
|
NewBounds: TRect;
|
|
begin
|
|
NewBounds:=Bounds(Left, Top, Width, Height);
|
|
if HandleAllocated
|
|
and ([csLoading,csDestroying]*ComponentState=[])
|
|
and (not (csDestroyingHandle in ControlState))
|
|
and (not CompareRect(@NewBounds,@FBoundsRealized))
|
|
then begin
|
|
// the new bounds were not yet sent to the InterfaceObject -> send them
|
|
{$IFDEF CHECK_POSITION}
|
|
//if csDesigning in ComponentState then
|
|
if CheckPosition(Self) then
|
|
DebugLn('[TWinControl.RealizeBounds] A ',DbgSName(Self),
|
|
' OldRelBounds=',dbgs(FBoundsRealized),
|
|
' -> NewBounds=',dbgs(NewBounds));
|
|
{$ENDIF}
|
|
BeginUpdateBounds;
|
|
try
|
|
DoSendBoundsToInterface;
|
|
finally
|
|
EndUpdateBounds;
|
|
end;
|
|
end else begin
|
|
{$IFDEF CHECK_POSITION}
|
|
if CheckPosition(Self) then begin
|
|
DbgOut('[TWinControl.RealizeBounds] NOT REALIZING ',DbgSName(Self),
|
|
' OldRelBounds=',dbgs(FBoundsRealized),
|
|
' -> NewBounds=',dbgs(NewBounds),
|
|
', because ');
|
|
if not HandleAllocated then debugln('not HandleAllocated');
|
|
if (csLoading in ComponentState) then debugln('csLoading');
|
|
if (csDestroying in ComponentState) then debugln('csDestroying');
|
|
if (CompareRect(@NewBounds,@FBoundsRealized)) then debugln('bounds not changed');
|
|
end;
|
|
{$ENDIF}
|
|
if not HandleAllocated then Check;
|
|
end;
|
|
end;
|
|
|
|
procedure TWinControl.RealizeBoundsRecursive;
|
|
var
|
|
i: Integer;
|
|
OldRealizing: boolean;
|
|
AControl: TControl;
|
|
begin
|
|
if not HandleAllocated then exit;
|
|
OldRealizing:=wcfRealizingBounds in FWinControlFlags;
|
|
Include(FWinControlFlags,wcfRealizingBounds);
|
|
try
|
|
if FControls<>nil then begin
|
|
for i:=0 to FControls.Count-1 do begin
|
|
AControl:=TControl(FControls[i]);
|
|
if (AControl is TWinControl) then
|
|
TWinControl(AControl).RealizeBoundsRecursive;
|
|
end;
|
|
end;
|
|
RealizeBounds;
|
|
finally
|
|
if not OldRealizing then
|
|
Exclude(FWinControlFlags,wcfRealizingBounds);
|
|
end;
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.CMShowingChanged
|
|
Params: Message : not used
|
|
Returns: nothing
|
|
|
|
Shows or hides a control
|
|
Called by UpdateShowing
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.CMShowingChanged(var Message: TLMessage);
|
|
begin
|
|
{$IFDEF VerboseShowing}
|
|
DebugLn(['TWinControl.CMShowingChanged ',DbgSName(Self),' HandleAllocated=',HandleAllocated,' ',dbgs(ComponentState)]);
|
|
{$ENDIF}
|
|
if HandleAllocated and ([csDestroying,csLoading]*ComponentState=[]) then
|
|
DoSendShowHideToInterface
|
|
else
|
|
Exclude(FWinControlFlags, wcfHandleVisible);
|
|
end;
|
|
|
|
{------------------------------------------------------------------------------
|
|
Method: TWinControl.ShowControl
|
|
Params: AControl: Control to show
|
|
Returns: nothing
|
|
|
|
Called by a child control (in TControl.Show), before setting Visible=true.
|
|
Asks to show the child control and recursively shows the parents.
|
|
------------------------------------------------------------------------------}
|
|
procedure TWinControl.ShowControl(AControl: TControl);
|
|
begin
|
|
if Parent <> nil then Parent.ShowControl(Self);
|
|
end;
|
|
|
|
{ TWinControlEnumerator }
|
|
|
|
function TWinControlEnumerator.GetCurrent: TControl;
|
|
begin
|
|
if (FIndex>=0) and (FIndex<FParent.ControlCount) then
|
|
Result:=FParent.Controls[FIndex]
|
|
else
|
|
Result:=nil;
|
|
end;
|
|
|
|
constructor TWinControlEnumerator.Create(Parent: TWinControl;
|
|
aLowToHigh: boolean);
|
|
begin
|
|
FParent:=Parent;
|
|
FLowToHigh:=aLowToHigh;
|
|
if FLowToHigh then
|
|
FIndex:=-1
|
|
else
|
|
FIndex:=FParent.ControlCount;
|
|
end;
|
|
|
|
function TWinControlEnumerator.GetEnumerator: TWinControlEnumerator;
|
|
begin
|
|
Result:=Self;
|
|
end;
|
|
|
|
function TWinControlEnumerator.MoveNext: Boolean;
|
|
begin
|
|
if FLowToHigh then
|
|
begin
|
|
inc(FIndex);
|
|
Result:=FIndex<FParent.ControlCount;
|
|
end
|
|
else begin
|
|
dec(FIndex);
|
|
Result:=FIndex>=0
|
|
end;
|
|
end;
|
|
|
|
{ $UNDEF CHECK_POSITION}
|
|
|
|
{$IFDEF ASSERT_IS_ON}
|
|
{$UNDEF ASSERT_IS_ON}
|
|
{$C-}
|
|
{$ENDIF}
|