lazarus/designer/anchoreditor.pas
2021-02-11 21:35:54 +00:00

1308 lines
40 KiB
ObjectPascal

{ /***************************************************************************
AnchorEditor.pas - Lazarus IDE unit
-----------------------------------
***************************************************************************/
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
Editor for editing Anchors, AnchorSide properties.
}
unit AnchorEditor;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils,
// LCL
Forms, Controls, Dialogs, StdCtrls, Buttons, Spin, ExtCtrls, Graphics,
// LazUtils
LazUtilities, LazUTF8,
// IdeIntf
IDECommands, PropEdits, IDEDialogs, IDEImagesIntf,
// IDE
LazarusIDEStrConsts, IDEOptionDefs, EnvironmentOpts;
type
TAnchorSideRefSet = set of TAnchorSideReference;
{ TAnchorDesignerSideValues }
TAnchorDesignerSideValues = class
strict private
FBorderSpace_IsAmbiguous: boolean;
FEnabled_IsAmbiguous: boolean;
FSibling_IsAmbiguous: boolean;
FAnchorKind: TAnchorKind;
FBorderSpace: Integer;
FCount: Integer;
FEnabled: Boolean;
FSibling: string;
FSideRefs: TAnchorSideRefSet;
function GetSideRef_IsAmbiguous: Boolean;
public
constructor Create(TheKind: TAnchorKind);
procedure SetValues(AControl: TControl);
procedure MergeValues(AControl: TControl);
public
property AnchorKind: TAnchorKind read FAnchorKind;
property Enabled: boolean read FEnabled write FEnabled;
property Enabled_IsAmbiguous: boolean read FEnabled_IsAmbiguous write FEnabled_IsAmbiguous;
property Sibling: string read FSibling write FSibling;
property Sibling_IsAmbiguous: boolean read FSibling_IsAmbiguous write FSibling_IsAmbiguous;
property SideRef_IsAmbiguous: Boolean read GetSideRef_IsAmbiguous;
property BorderSpace: integer read FBorderSpace write FBorderSpace;
property BorderSpace_IsAmbiguous: boolean read FBorderSpace_IsAmbiguous write FBorderSpace_IsAmbiguous;
property SideRefs: TAnchorSideRefSet read FSideRefs;
property Count: Integer read FCount;
end;
{ TAnchorDesignerValues }
TAnchorDesignerValues = class
strict private
FAmbiguousBorderspaceAround: boolean;
FBorderspaceAround: integer;
FSides: array[TAnchorKind] of TAnchorDesignerSideValues;
function GetSides(Kind: TAnchorKind): TAnchorDesignerSideValues;
procedure SetAmbiguousBorderspaceAround(const AValue: boolean);
procedure SetBorderspaceAround(const AValue: integer);
public
constructor Create;
destructor Destroy; override;
procedure SetValues(AControl: TControl);
procedure MergeValues(AControl: TControl);
public
property Sides[Kind: TAnchorKind]: TAnchorDesignerSideValues read GetSides;
property BorderspaceAround: integer read FBorderspaceAround write SetBorderspaceAround;
property AmbiguousBorderspaceAround: boolean read FAmbiguousBorderspaceAround write SetAmbiguousBorderspaceAround;
end;
{ TAnchorDesignerSideControls }
TAnchorDesigner = class;
TAnchorDesignerSideControls = class
strict private
FForm: TAnchorDesigner;
{Controls}
FEnabled_CheckBox: TCheckBox;
FSibling_ComboBox: TComboBox;
FSideRef_SButtons: array [TAnchorSideReference] of TSpeedButton;
FBorderSpace_SpinEdit: TSpinEdit;
{Frames}
FEnabled_Frame: TShape;
FSibling_Frame: TShape;
FSideRef_Frames: array [TAnchorSideReference] of TShape;
FBorderSpace_Frame: TShape;
public
constructor Create(
AEnabled: TCheckBox;
ASibling: TComboBox;
ABorderSpace_SpinEdit: TSpinEdit;
ASideRefTop{Left}, ASideRefCenter, ASideRefBottom{Right}: TSpeedButton;
AForm: TAnchorDesigner);
procedure Refresh(ASideValues: TAnchorDesignerSideValues);
end;
{ TAnchorDesigner }
TAnchorDesigner = class(TForm)
AroundBorderSpaceSpinEdit: TSpinEdit;
BorderSpaceGroupBox: TGroupBox;
BottomAnchoredCheckBox: TCheckBox;
BottomBorderSpaceSpinEdit: TSpinEdit;
BottomGroupBox: TGroupBox;
BottomRefBottomSpeedButton: TSpeedButton;
BottomRefCenterSpeedButton: TSpeedButton;
BottomRefTopSpeedButton: TSpeedButton;
BottomSiblingComboBox: TComboBox;
BottomSiblingLabel: TLabel;
LeftAnchoredCheckBox: TCheckBox;
LeftBorderSpaceSpinEdit: TSpinEdit;
LeftGroupBox: TGroupBox;
LeftRefCenterSpeedButton: TSpeedButton;
LeftRefLeftSpeedButton: TSpeedButton;
LeftRefRightSpeedButton: TSpeedButton;
LeftSiblingComboBox: TComboBox;
LeftSiblingLabel: TLabel;
RightAnchoredCheckBox: TCheckBox;
RightBorderSpaceSpinEdit: TSpinEdit;
RightGroupBox: TGroupBox;
RightRefCenterSpeedButton: TSpeedButton;
RightRefLeftSpeedButton: TSpeedButton;
RightRefRightSpeedButton: TSpeedButton;
RightSiblingComboBox: TComboBox;
RightSiblingLabel: TLabel;
TopAnchoredCheckBox: TCheckBox;
TopBorderSpaceSpinEdit: TSpinEdit;
TopGroupBox: TGroupBox;
TopRefBottomSpeedButton: TSpeedButton;
TopRefCenterSpeedButton: TSpeedButton;
TopRefTopSpeedButton: TSpeedButton;
TopSiblingComboBox: TComboBox;
TopSiblingLabel: TLabel;
procedure FormActivate(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure AnchorEnabledCheckBoxChange(Sender: TObject);
procedure BorderSpaceSpinEditChange(Sender: TObject);
procedure FormDeactivate(Sender: TObject);
procedure SiblingComboBoxChange(Sender: TObject);
procedure ReferenceSideButtonClicked(Sender: TObject);
private
Values: TAnchorDesignerValues;
FSelection: TPersistentSelectionList;
FSelectedControlsList: TList;
FUpdating: Boolean;
FNeedUpdate: boolean;
FSideControls: array[TAnchorKind] of TAnchorDesignerSideControls;
procedure OnModifiedWithName(Sender: TObject; PropName: ShortString);
procedure Refresh;
procedure OnRefreshPropertyValues;
procedure OnSetSelection(const ASelection: TPersistentSelectionList);
function GetSelectedControls: TList;
function FindSibling(const Sibling: string): TControl;
procedure FillComboBoxWithSiblings(AComboBox: TComboBox);
function AnchorDesignerNoSiblingText: string;
function AnchorDesignerNeighbourText(direction: TAnchorKind): string;
procedure CollectValues(const ASelection: TList;
out TheValues: TAnchorDesignerValues;
out SelectedControlCount: integer);
procedure SetCaptionsAndHints;
procedure LoadGlyphs;
procedure CreateSideControls;
protected
procedure KeyUp(var Key: Word; Shift: TShiftState); override;
procedure UpdateShowing; override;
public
destructor Destroy; override;
class function ControlToStr(AControl: TControl): string;
property Selection: TPersistentSelectionList read FSelection;
end;
{ TAnchorPropertyEditor }
TAnchorPropertyEditor = class(TSetPropertyEditor)
public
function GetAttributes: TPropertyAttributes; override;
procedure Edit; override;
end;
var
AnchorDesigner: TAnchorDesigner = nil;
ShowAnchorDesigner: TNotifyEvent = nil;
implementation
{$R *.lfm}
type
TFrameStyle=(fsNormal, fsAmbigous, fsIgnored, fsNone);
const
FrameMargin = 1;
clNormal=clRed; // color for fsNormal
clAmbigous=clRed; // color for fsAmbigous
clIgnored=clBtnShadow; // color for fsIgnored
psNormal=psSolid; // pen style for fsNormal
psAmbigous=psDot; // pen style for fsAmbigous
psIgnored=psSolid; // pen style for fsIgnored
function CreateFrameForControl(AControl: TControl; AFrameMargin: Integer=FrameMargin): TShape;
begin
Result:=TShape.Create(AControl.Owner);
with Result do
begin
Brush.Style:=bsClear;
Brush.Color:=clNone;
Shape:=stRectangle;
Pen.Cosmetic:=True;
Height:=AControl.Height+AFrameMargin*2;
Width:=AControl.Width+AFrameMargin*2;
Parent:=AControl.Parent;
AnchorVerticalCenterTo(AControl);
AnchorHorizontalCenterTo(AControl);
SendToBack;
Visible:=False;
end;
end;
procedure SetEnabledControls(AEnabled: Boolean; const AControls: array of TControl);
var
i: Integer;
begin
for i:=Low(AControls) to High(AControls) do
AControls[i].Enabled:=AEnabled;
end;
procedure SetFrameStyle(AFrame: TShape; AStyle: TFrameStyle);
begin
case AStyle of
fsNormal:
begin
AFrame.Pen.Color:=clNormal;
AFrame.Pen.Style:=psNormal;
AFrame.Visible:=True;
end;
fsAmbigous:
begin
AFrame.Pen.Color:=clAmbigous;
AFrame.Pen.Style:=psAmbigous;
AFrame.Visible:=True;
end;
fsIgnored:
begin
AFrame.Pen.Color:=clIgnored;
AFrame.Pen.Style:=psIgnored;
AFrame.Visible:=True;
end;
fsNone:
begin
AFrame.Visible:=False;
end;
end;
end;
{ TAnchorDesignerSideControls }
constructor TAnchorDesignerSideControls.Create(
AEnabled: TCheckBox;
ASibling: TComboBox;
ABorderSpace_SpinEdit: TSpinEdit;
ASideRefTop{Left}, ASideRefCenter, ASideRefBottom{Right}: TSpeedButton;
AForm: TAnchorDesigner);
var
SideRefIndex: TAnchorSideReference;
begin
inherited Create;
FForm:=AForm;
// Assign controls
FEnabled_CheckBox:=AEnabled;
FSibling_ComboBox:=ASibling;
FBorderSpace_SpinEdit:=ABorderSpace_SpinEdit;
FSideRef_SButtons[asrTop]:=ASideRefTop;
FSideRef_SButtons[asrCenter]:=ASideRefCenter;
FSideRef_SButtons[asrBottom]:=ASideRefBottom;
// Create and assign frames
FEnabled_Frame:=CreateFrameForControl(FEnabled_CheckBox);
FSibling_Frame:=CreateFrameForControl(FSibling_ComboBox);
FBorderSpace_Frame:=CreateFrameForControl(FBorderSpace_SpinEdit);
for SideRefIndex:=Low(SideRefIndex) to High(SideRefIndex) do
FSideRef_Frames[SideRefIndex]:=CreateFrameForControl(FSideRef_SButtons[SideRefIndex]);
end;
procedure TAnchorDesignerSideControls.Refresh(ASideValues: TAnchorDesignerSideValues);
var
SiblingText: String;
SideRefIndex: TAnchorSideReference;
begin
// Enabled
FEnabled_CheckBox.AllowGrayed:=ASideValues.Enabled_IsAmbiguous;
if ASideValues.Enabled_IsAmbiguous then
FEnabled_CheckBox.State:=cbGrayed
else
FEnabled_CheckBox.Checked:=ASideValues.Enabled;
// BorderSpace
if ASideValues.BorderSpace_IsAmbiguous then
FBorderSpace_SpinEdit.Value:=-1
else
FBorderSpace_SpinEdit.Value:=ASideValues.BorderSpace;
FBorderSpace_SpinEdit.ValueEmpty:=ASideValues.BorderSpace_IsAmbiguous;
// Sibling
SiblingText:=ASideValues.Sibling;
FSibling_ComboBox.Text:=SiblingText;
FForm.FillComboBoxWithSiblings(FSibling_ComboBox);
// SideRefs (after Enabled & Sibling)
for SideRefIndex:=Low(SideRefIndex) to High(SideRefIndex) do
begin
// Set Down
FSideRef_SButtons[SideRefIndex].Down:=(SideRefIndex in ASideValues.SideRefs);
// Not Down => fsNone
if not FSideRef_SButtons[SideRefIndex].Down then
begin
SetFrameStyle(FSideRef_Frames[SideRefIndex], fsNone);
Continue;
end;
// Single select
if ASideValues.Count=1 then
if FEnabled_CheckBox.Checked and (FSibling_ComboBox.Text<>'') then
begin
SetFrameStyle(FSideRef_Frames[SideRefIndex], fsNormal);
Continue;
end
else
begin
SetFrameStyle(FSideRef_Frames[SideRefIndex], fsIgnored);
Continue;
end;
// Multiselect
if ASideValues.SideRef_IsAmbiguous then
begin
SetFrameStyle(FSideRef_Frames[SideRefIndex], fsAmbigous);
Continue;
end
else
if (FEnabled_CheckBox.State<>cbUnchecked) then
begin
SetFrameStyle(FSideRef_Frames[SideRefIndex], fsNormal);
Continue;
end
else
begin
SetFrameStyle(FSideRef_Frames[SideRefIndex], fsIgnored);
Continue;
end;
end;
end;
{ TAnchorDesigner }
procedure TAnchorDesigner.FormCreate(Sender: TObject);
begin
Name:=NonModalIDEWindowNames[nmiwAnchorEditor];
KeyPreview:=true;
FSelection:=TPersistentSelectionList.Create;
FSelectedControlsList := TList.Create;
SetCaptionsAndHints;
LoadGlyphs;
CreateSideControls;
GlobalDesignHook.AddHandlerModifiedWithName(@OnModifiedWithName);
GlobalDesignHook.AddHandlerRefreshPropertyValues(@OnRefreshPropertyValues);
GlobalDesignHook.AddHandlerSetSelection(@OnSetSelection);
end;
procedure TAnchorDesigner.FormDestroy(Sender: TObject);
var
i: TAnchorKind;
begin
FreeAndNil(Values);
GlobalDesignHook.RemoveAllHandlersForObject(Self);
FreeAndNil(FSelection);
FreeAndNil(FSelectedControlsList);
for i:=Low(i) to High(i) do
FSideControls[i].Free;
end;
procedure TAnchorDesigner.SetCaptionsAndHints;
var
AnchorEnabledHint: String;
begin
AnchorEnabledHint:=lisAnchorEnabledHint;
AroundBorderSpaceSpinEdit.Hint:=lisAroundBorderSpaceHint;
BorderSpaceGroupBox.Caption:=lisBorderSpace;
BottomAnchoredCheckBox.Caption:=lisEnabled;
BottomAnchoredCheckBox.Hint:=Format(AnchorEnabledHint,['akBottom']);
BottomBorderSpaceSpinEdit.Hint:=lisBottomBorderSpaceSpinEditHint;
BottomGroupBox.Caption:=lisBottomGroupBoxCaption;
BottomRefBottomSpeedButton.Hint:=lisAnchorBottomToBottomSide;
BottomRefCenterSpeedButton.Hint:=lisCenterControlVerticallyRelativeToSibling;
BottomRefTopSpeedButton.Hint:=lisAnchorBottomToTopSide;
BottomSiblingComboBox.Hint:=lisBottomSiblingComboBoxHint;
BottomSiblingLabel.Caption:=lisSibling;
LeftAnchoredCheckBox.Caption:=lisEnabled;
LeftAnchoredCheckBox.Hint:=Format(AnchorEnabledHint,['akLeft']);
LeftBorderSpaceSpinEdit.Hint:=lisLeftBorderSpaceSpinEditHint;
LeftGroupBox.Caption:=lisLeftGroupBoxCaption;
LeftRefCenterSpeedButton.Hint:=lisCenterControlHorizontallyRelativeToSibling;
LeftRefLeftSpeedButton.Hint:=lisAnchorLeftToLeftSide;
LeftRefRightSpeedButton.Hint:=lisAnchorLeftToRightSide;
LeftSiblingComboBox.Hint:=lisLeftSiblingComboBoxHint;
LeftSiblingLabel.Caption:=lisSibling;
RightAnchoredCheckBox.Caption:=lisEnabled;
RightAnchoredCheckBox.Hint:=Format(AnchorEnabledHint,['akRight']);
RightBorderSpaceSpinEdit.Hint:=lisRightBorderSpaceSpinEditHint;
RightGroupBox.Caption:=lisRightAnchoring;
RightRefCenterSpeedButton.Hint:=lisCenterControlHorizontallyRelativeToSibling;
RightRefLeftSpeedButton.Hint:=lisAnchorRightToLeftSide;
RightRefRightSpeedButton.Hint:=lisAnchorRightToRightSide;
RightSiblingComboBox.Hint:=lisRightSiblingComboBoxHint;
RightSiblingLabel.Caption:=lisSibling;
TopAnchoredCheckBox.Caption:=lisEnabled;
TopAnchoredCheckBox.Hint:=Format(AnchorEnabledHint,['akTop']);
TopBorderSpaceSpinEdit.Hint:=lisTopBorderSpaceSpinEditHint;
TopGroupBox.Caption:=lisTopAnchoring;
TopRefBottomSpeedButton.Hint:=lisAnchorTopToBottomSide;
TopRefCenterSpeedButton.Hint:=lisCenterControlVerticallyRelativeToSibling;
TopRefTopSpeedButton.Hint:= lisAnchorTopToTopSide;
TopSiblingComboBox.Hint:=lisTopSiblingComboBoxHint;
TopSiblingLabel.Caption:=lisSibling;
end;
procedure TAnchorDesigner.LoadGlyphs;
begin
IDEImages.AssignImage(LeftRefLeftSpeedButton, 'anchor_left_left');
IDEImages.AssignImage(LeftRefCenterSpeedButton, 'anchor_left_center');
IDEImages.AssignImage(LeftRefRightSpeedButton, 'anchor_left_right');
IDEImages.AssignImage(RightRefLeftSpeedButton, 'anchor_right_left');
IDEImages.AssignImage(RightRefCenterSpeedButton, 'anchor_right_center');
IDEImages.AssignImage(RightRefRightSpeedButton, 'anchor_right_right');
IDEImages.AssignImage(TopRefTopSpeedButton, 'anchor_top_top');
IDEImages.AssignImage(TopRefCenterSpeedButton, 'anchor_top_center');
IDEImages.AssignImage(TopRefBottomSpeedButton, 'anchor_top_bottom');
IDEImages.AssignImage(BottomRefTopSpeedButton, 'anchor_bottom_top');
IDEImages.AssignImage(BottomRefCenterSpeedButton, 'anchor_bottom_center');
IDEImages.AssignImage(BottomRefBottomSpeedButton, 'anchor_bottom_bottom');
end;
procedure TAnchorDesigner.CreateSideControls;
begin
FSideControls[akTop]:=TAnchorDesignerSideControls.Create(
TopAnchoredCheckBox,
TopSiblingComboBox,
TopBorderSpaceSpinEdit,
TopRefTopSpeedButton,
TopRefCenterSpeedButton,
TopRefBottomSpeedButton,
Self);
FSideControls[akBottom]:=TAnchorDesignerSideControls.Create(
BottomAnchoredCheckBox,
BottomSiblingComboBox,
BottomBorderSpaceSpinEdit,
BottomRefTopSpeedButton,
BottomRefCenterSpeedButton,
BottomRefBottomSpeedButton,
Self);
FSideControls[akLeft]:=TAnchorDesignerSideControls.Create(
LeftAnchoredCheckBox,
LeftSiblingComboBox,
LeftBorderSpaceSpinEdit,
LeftRefLeftSpeedButton, // Left <-> Top
LeftRefCenterSpeedButton,
LeftRefRightSpeedButton, // Right <-> Bottom
Self);
FSideControls[akRight]:=TAnchorDesignerSideControls.Create(
RightAnchoredCheckBox,
RightSiblingComboBox,
RightBorderSpaceSpinEdit,
RightRefLeftSpeedButton, // Left <-> Top
RightRefCenterSpeedButton,
RightRefRightSpeedButton, // Right <-> Bottom
Self);
end;
procedure TAnchorDesigner.FormShow(Sender: TObject);
begin
Refresh;
end;
procedure TAnchorDesigner.AnchorEnabledCheckBoxChange(Sender: TObject);
var
Kind: TAnchorKind;
CurSide: TAnchorDesignerSideValues;
NewValue: Boolean;
SelectedControls: TList;
i: Integer;
CurControl: TControl;
ReferenceControl: TControl;
ReferenceSide: TAnchorSideReference;
CheckPosition: Integer;
begin
if FUpdating or (Values=nil) then exit;
if Sender=LeftAnchoredCheckBox then
Kind:=akLeft
else if Sender=RightAnchoredCheckBox then
Kind:=akRight
else if Sender=TopAnchoredCheckBox then
Kind:=akTop
else if Sender=BottomAnchoredCheckBox then
Kind:=akBottom
else
exit;
NewValue:=TCheckBox(Sender).Checked;
CurSide:=Values.Sides[Kind];
if CurSide.Enabled_IsAmbiguous or (CurSide.Enabled<>NewValue) then begin
// user changed an anchor
SelectedControls:=GetSelectedControls;
if SelectedControls=nil then exit;
// check
for i:=0 to SelectedControls.Count-1 do begin
CurControl:=TControl(SelectedControls[i]);
if NewValue and (not CurControl.AnchorSide[Kind].CheckSidePosition(
CurControl.AnchorSide[Kind].Control,
CurControl.AnchorSide[Kind].Side,
ReferenceControl,ReferenceSide,CheckPosition))
then begin
if IDEMessageDialog(lisCCOWarningCaption, lisThisWillCreateACircularDependency,
mtWarning, [mbIgnore, mbCancel]) <> mrIgnore
then begin
Refresh;
exit;
end;
break;
end;
end;
// commit
for i:=0 to SelectedControls.Count-1 do begin
CurControl:=TControl(SelectedControls[i]);
if NewValue then
CurControl.Anchors:=CurControl.Anchors+[Kind]
else
CurControl.Anchors:=CurControl.Anchors-[Kind];
end;
GlobalDesignHook.Modified(Self, 'Anchors');
GlobalDesignHook.RefreshPropertyValues;
end;
end;
procedure TAnchorDesigner.BorderSpaceSpinEditChange(Sender: TObject);
var
Around: Boolean;
NewValue: LongInt;
CurSide: TAnchorDesignerSideValues;
SelectedControls: TList;
i: Integer;
CurControl: TControl;
Kind: TAnchorKind;
begin
if FUpdating or (Values=nil) then exit;
Around:=false;
if Sender=LeftBorderSpaceSpinEdit then
Kind:=akLeft
else if Sender=RightBorderSpaceSpinEdit then
Kind:=akRight
else if Sender=TopBorderSpaceSpinEdit then
Kind:=akTop
else if Sender=BottomBorderSpaceSpinEdit then
Kind:=akBottom
else if Sender=AroundBorderSpaceSpinEdit then begin
Kind:=akLeft;
Around:=true;
end else
exit;
NewValue:=RoundToInt(TSpinEdit(Sender).Value);
CurSide:=Values.Sides[Kind];
if (Around and (Values.AmbiguousBorderspaceAround
or (Values.BorderspaceAround<>NewValue)))
or ((not Around) and (CurSide.BorderSpace_IsAmbiguous
or (CurSide.BorderSpace<>NewValue)))
then begin
// user changed a BorderSpace
SelectedControls:=GetSelectedControls;
if SelectedControls=nil then exit;
for i:=0 to SelectedControls.Count-1 do begin
CurControl:=TControl(SelectedControls[i]);
if Around then
CurControl.BorderSpacing.Around:=NewValue
else
CurControl.BorderSpacing.Space[Kind]:=NewValue;
end;
GlobalDesignHook.Modified(Self, 'Anchors');
GlobalDesignHook.RefreshPropertyValues;
end;
end;
procedure TAnchorDesigner.FormDeactivate(Sender: TObject);
begin
// commit changes made by keyboard
if (ActiveControl is TComboBox) then
SiblingComboBoxChange(ActiveControl);
end;
function compareControlTop(Item1, Item2: pointer): Integer;
begin
if TControl(Item1).Top<TControl(Item2).Top then Result:=-1
else if TControl(Item1).Top>TControl(Item2).Top then Result:=1
else Result:=0;
end;
function compareControlLeft(Item1, Item2: pointer): Integer;
begin
if TControl(Item1).Left<TControl(Item2).Left then Result:=-1
else if TControl(Item1).Left>TControl(Item2).Left then Result:=1
else result:=0;
end;
function compareControlRight(Item1, Item2: pointer): Integer;
begin
if TControl(item1).left+TControl(item1).width>TControl(item2).left+TControl(item2).width then result:=-1
else if TControl(item1).left+TControl(item1).width<TControl(item2).left+TControl(item2).width then result:=1
else result:=0;
end;
function compareControlBottom(Item1, Item2: pointer): Integer;
begin
if TControl(item1).top+TControl(item1).Height>TControl(item2).top+TControl(item2).Height then result:=-1
else if TControl(item1).top+TControl(item1).Height<TControl(item2).top+TControl(item2).Height then result:=1
else result:=0;
end;
procedure TAnchorDesigner.SiblingComboBoxChange(Sender: TObject);
var
Kind,CurNeighbour: TAnchorKind;
NewSibling: TControl;
CurSide: TAnchorDesignerSideValues;
SelectedControls: TList;
i: Integer;
CurControl: TControl;
NewValue: String;
UseNeighbours: boolean;
OldPositions,OldPositions2: array of Integer;
function NeighbourPosition(c: TControl): Integer;
begin
result:=0;
case CurNeighbour of
akTop: result:=c.top;
akLeft: result:=c.Left;
akRight: result:=c.left+c.Width;
akBottom: result:=c.Top+c.Height;
end;
end;
function FindNeighbour(i:longint):TControl;
var firstNeighbour,lastNeighbour,cur,resultId: Integer;
begin
if i=0 then exit(nil);
firstNeighbour:=i-1;
while (firstNeighbour>=0) and (OldPositions[firstNeighbour] = OldPositions[i]) do
dec(firstNeighbour);
if firstNeighbour=-1 then exit(nil); //there is no real neighbour at this side
lastNeighbour:=firstNeighbour;
while (lastNeighbour>=0) and (OldPositions[lastNeighbour] = OldPositions[firstNeighbour]) do
dec(lastNeighbour);
inc(lastNeighbour);
//take nearest
resultId:=lastNeighbour;
for cur:=lastNeighbour+1 to firstNeighbour do
if abs(OldPositions2[cur]-OldPositions2[i]) < abs(OldPositions2[resultId]-OldPositions2[i]) then
resultid:=cur;
result:=TControl(SelectedControls[resultId]);
end;
var
ReferenceControl: TControl;
ReferenceSide: TAnchorSideReference;
CheckPosition: Integer;
begin
if FUpdating or (Values=nil) then exit;
if Sender=LeftSiblingComboBox then
Kind:=akLeft
else if Sender=RightSiblingComboBox then
Kind:=akRight
else if Sender=TopSiblingComboBox then
Kind:=akTop
else if Sender=BottomSiblingComboBox then
Kind:=akBottom
else
exit;
NewValue:=TComboBox(Sender).Caption;
CurSide:=Values.Sides[Kind];
if CurSide.Sibling_IsAmbiguous or (CompareText(CurSide.Sibling,NewValue)<>0) then
begin
// user changed a sibling
SelectedControls:=GetSelectedControls;
if SelectedControls=nil then exit;
UseNeighbours:=false;
NewSibling:=nil;
if (NewValue<>AnchorDesignerNoSiblingText) then
begin
CurNeighbour:=low(TAnchorKind);
// Check if relative Anchor to be used, such as '(selected left neighbour)'
while true do begin
if NewValue=AnchorDesignerNeighbourText(CurNeighbour) then begin
UseNeighbours:=true;
break;
end;
if (CurNeighbour = high(TAnchorKind)) then
break;
inc(CurNeighbour);
end;
if UseNeighbours then
begin
case CurNeighbour of //todo: use just one sorting function
akTop: SelectedControls.Sort(@compareControlTop);
akLeft: SelectedControls.Sort(@compareControlLeft);
akRight: SelectedControls.Sort(@compareControlRight);
akBottom: SelectedControls.Sort(@compareControlBottom);
end;
setlength(OldPositions{%H-},SelectedControls.Count);
setlength(OldPositions2{%H-},SelectedControls.Count);
for i:=0 to SelectedControls.Count-1 do begin
OldPositions[i]:=NeighbourPosition(TControl(SelectedControls[i]));
case CurNeighbour of
akLeft,akRight: OldPositions2[i]:=TControl(SelectedControls[i]).top;
akTop,akBottom: OldPositions2[i]:=TControl(SelectedControls[i]).Left;
end;
end;
end
else begin
NewSibling:=FindSibling(NewValue);
if NewSibling=nil then exit;
end;
end;
// check
for i:=0 to SelectedControls.Count-1 do begin
CurControl:=TControl(SelectedControls[i]);
if UseNeighbours then begin
NewSibling:=findNeighbour(i);
if (NewSibling=nil) and (i<>0) then continue;
end;
if (Kind in CurControl.Anchors)
and (not CurControl.AnchorSide[Kind].CheckSidePosition(NewSibling,
CurControl.AnchorSide[Kind].Side,
ReferenceControl,ReferenceSide,CheckPosition))
then begin
if IDEMessageDialog(lisCCOWarningCaption,
lisThisWillCreateACircularDependency, mtWarning, [mbIgnore, mbCancel])<>
mrIgnore
then begin
Refresh;
exit;
end;
break;
end;
end;
// commit
for i:=0 to SelectedControls.Count-1 do begin
CurControl:=TControl(SelectedControls[i]);
if UseNeighbours then begin
NewSibling:=findNeighbour(i);
if (NewSibling=nil) and (i<>0) then continue;
end;
try
CurControl.AnchorSide[Kind].Control:=NewSibling;
except
on E: Exception do begin
IDEMessageDialog(lisCCOErrorCaption, lisUnableToSetAnchorSideControl+
LineEnding+E.Message,
mtError,[mbCancel]);
end;
end;
end;
GlobalDesignHook.Modified(Self, 'Anchors');
GlobalDesignHook.RefreshPropertyValues;
if UseNeighbours then
TComboBox(Sender).Caption:=NewValue;
end;
end;
procedure TAnchorDesigner.ReferenceSideButtonClicked(Sender: TObject);
var
Kind: TAnchorKind;
SideRef: TAnchorSideReference;
SelectedControls: TList;
i: Integer;
CurControl: TControl;
ReferenceControl: TControl;
ReferenceSide: TAnchorSideReference;
CheckPosition: Integer;
begin
if FUpdating or (Values=nil) then Exit;
// Get Kind and SideRef
if Sender=LeftRefCenterSpeedButton then begin
Kind:=akLeft;
SideRef:=asrCenter;
end
else if Sender=LeftRefLeftSpeedButton then begin
Kind:=akLeft;
SideRef:=asrLeft;
end
else if Sender=LeftRefRightSpeedButton then begin
Kind:=akLeft;
SideRef:=asrRight;
end
else if Sender=RightRefCenterSpeedButton then begin
Kind:=akRight;
SideRef:=asrCenter;
end
else if Sender=RightRefLeftSpeedButton then begin
Kind:=akRight;
SideRef:=asrLeft;
end
else if Sender=RightRefRightSpeedButton then begin
Kind:=akRight;
SideRef:=asrRight;
end
else if Sender=TopRefCenterSpeedButton then begin
Kind:=akTop;
SideRef:=asrCenter;
end
else if Sender=TopRefTopSpeedButton then begin
Kind:=akTop;
SideRef:=asrTop;
end
else if Sender=TopRefBottomSpeedButton then begin
Kind:=akTop;
SideRef:=asrBottom;
end
else if Sender=BottomRefCenterSpeedButton then begin
Kind:=akBottom;
SideRef:=asrCenter;
end
else if Sender=BottomRefTopSpeedButton then begin
Kind:=akBottom;
SideRef:=asrTop;
end
else if Sender=BottomRefBottomSpeedButton then begin
Kind:=akBottom;
SideRef:=asrBottom;
end else
Exit;
// User changed a sibling
SelectedControls:=GetSelectedControls;
if SelectedControls=nil then Exit;
// Check
for i:=0 to SelectedControls.Count-1 do
begin
CurControl:=TControl(SelectedControls[i]);
if (Kind in CurControl.Anchors)
and (not CurControl.AnchorSide[Kind].CheckSidePosition(
CurControl.AnchorSide[Kind].Control, SideRef,
ReferenceControl, ReferenceSide, CheckPosition)) then
begin
if IDEMessageDialog(lisCCOWarningCaption,
lisThisWillCreateACircularDependency,
mtWarning, [mbIgnore, mbCancel]) <> mrIgnore then
begin
Refresh;
Exit;
end;
Break;
end;
end;
// Commit
for i:=0 to SelectedControls.Count-1 do
begin
CurControl:=TControl(SelectedControls[i]);
CurControl.AnchorSide[Kind].Side:=SideRef;
end;
GlobalDesignHook.Modified(Self, 'Anchors');
GlobalDesignHook.RefreshPropertyValues;
end;
procedure TAnchorDesigner.KeyUp(var Key: Word; Shift: TShiftState);
begin
inherited KeyUp(Key, Shift);
ExecuteIDEShortCut(Self,Key,Shift,nil);
end;
procedure TAnchorDesigner.UpdateShowing;
begin
inherited UpdateShowing;
if IsVisible and FNeedUpdate then
Refresh;
end;
procedure TAnchorDesigner.FillComboBoxWithSiblings(AComboBox: TComboBox);
var
sl: TStringListUTF8Fast;
i: Integer;
CurControl: TControl;
j: Integer;
Sibling: TControl;
SelectedControls: TList;
OldText: String;
Kind: TAnchorKind;
HasSelectedSiblings: Boolean;
function AddSibling(AControl: TControl): boolean;
var
NewControlStr: String;
begin
Result:=false;
if AControl.Name='' then exit;
if SelectedControls.IndexOf(AControl)>=0 then exit;
NewControlStr:=ControlToStr(AControl);
if sl.IndexOf(NewControlStr)>=0 then exit;
sl.Add(NewControlStr);
Result:=true;
end;
begin
sl:=TStringListUTF8Fast.Create;
sl.Add(AnchorDesignerNoSiblingText);
HasSelectedSiblings:=false;
SelectedControls:=GetSelectedControls;
if SelectedControls<>nil then begin
for i:=0 to SelectedControls.Count-1 do begin
if TObject(SelectedControls[i]) is TControl then begin
CurControl:=TControl(SelectedControls[i]);
if (CurControl.Parent<>nil)
and not (csDesignInstance in CurControl.ComponentState)
then begin
AddSibling(CurControl.Parent);
for j:=0 to CurControl.Parent.ControlCount-1 do begin
Sibling:=CurControl.Parent.Controls[j];
if (Sibling<>CurControl) then begin
AddSibling(Sibling);
if SelectedControls.IndexOf(Sibling)>=0 then
HasSelectedSiblings:=true;
end;
end;
end;
break;
end;
end;
end;
if HasSelectedSiblings then
for Kind:=akTop to akBottom do
sl.add(AnchorDesignerNeighbourText(Kind));
OldText:=AComboBox.Text;
AComboBox.Items.Assign(sl);
AComboBox.Text:=OldText;
sl.Free;
end;
function TAnchorDesigner.AnchorDesignerNoSiblingText: string;
begin
Result:='(nil)';
end;
function TAnchorDesigner.AnchorDesignerNeighbourText(direction: TAnchorKind): string;
begin
result:='';
case direction of
akLeft: result:=lisSelectedLeftNeighbour;
akRight: result:=lisSelectedRightNeighbour;
akTop: result:=lisSelectedTopNeighbour;
akBottom: result:=lisSelectedBottomNeighbour;
end;
end;
destructor TAnchorDesigner.Destroy;
begin
inherited Destroy;
if AnchorDesigner=Self then AnchorDesigner:=nil;
end;
procedure TAnchorDesigner.Refresh;
var
SelectedControlCount: Integer;
CurSelection: TList;
AnchorKindIndex: TAnchorKind;
begin
// Check if update is needed
if not IsVisible then
begin
FNeedUpdate:=True;
Exit;
end;
if FUpdating then Exit;
FUpdating:=True;
FNeedUpdate:=False;
try
FreeAndNil(Values);
CurSelection:=GetSelectedControls;
CollectValues(CurSelection,Values,SelectedControlCount); // out TheValues
SetEnabledControls(Assigned(Values), [BorderSpaceGroupBox, TopGroupBox,
LeftGroupBox, RightGroupBox, BottomGroupBox]);
if (Values=nil) then
begin
Caption:=lisAnchorEditorNoControlSelected;
end
else
begin
if CurSelection.Count=1 then
Caption:=Format(lisAnchorsOf,[TControl(CurSelection[0]).Name])
else
Caption:=lisAnchorsOfSelectedControls;
// All
BorderSpaceGroupBox.Enabled:=true;
if Values.AmbiguousBorderspaceAround then
AroundBorderSpaceSpinEdit.Value:=-1
else
AroundBorderSpaceSpinEdit.Value:=Values.BorderspaceAround;
// Sides
for AnchorKindIndex:=Low(AnchorKindIndex) to High(AnchorKindIndex) do
FSideControls[AnchorKindIndex].Refresh(Values.Sides[AnchorKindIndex]);
end;
finally
FUpdating:=False;
end;
end;
procedure TAnchorDesigner.OnModifiedWithName(Sender: TObject; PropName: ShortString);
begin
if Sender = Self then Exit;
if PropName = 'Anchors' then
Refresh;
end;
procedure TAnchorDesigner.OnRefreshPropertyValues;
begin
Refresh;
end;
function TAnchorDesigner.GetSelectedControls: TList;
var
CurPersistent: TPersistent;
AControl: TControl;
i: Integer;
begin
Result:=nil;
GlobalDesignHook.GetSelection(FSelection);
if FSelection=nil then exit;
// Collect values of selected controls
for i:=0 to FSelection.Count-1 do begin
CurPersistent:=FSelection[i];
if CurPersistent is TControl then begin
AControl:=TControl(CurPersistent);
if Result = nil then
begin
Result := FSelectedControlsList;
Result.Clear;
end;
Result.Add(AControl);
end;
end;
end;
function TAnchorDesigner.FindSibling(const Sibling: string): TControl;
var
Root: TPersistent;
RootComponent: TComponent;
i: Integer;
CurComponent: TComponent;
CurControl: TControl;
begin
Result:=nil;
Root:=GlobalDesignHook.LookupRoot;
if not (Root is TComponent) then exit;
RootComponent:=TComponent(Root);
if (RootComponent is TControl)
and (CompareText(Sibling,ControlToStr(TControl(RootComponent)))=0) then begin
Result:=TControl(RootComponent);
exit;
end;
for i:=0 to RootComponent.ComponentCount-1 do begin
CurComponent:=TComponent(RootComponent.Components[i]);
if CurComponent is TControl then begin
CurControl:=TControl(CurComponent);
if CompareText(Sibling,ControlToStr(CurControl))=0 then begin
Result:=CurControl;
exit;
end;
end;
end;
end;
procedure TAnchorDesigner.FormActivate(Sender: TObject);
begin
LeftSiblingComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
RightSiblingComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
TopSiblingComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
BottomSiblingComboBox.DropDownCount:=EnvironmentOptions.DropDownCount;
end;
class function TAnchorDesigner.ControlToStr(AControl: TControl): string;
begin
if AControl=nil then
Result:=''
else
Result:=AControl.Name+':'+AControl.ClassName;
end;
procedure TAnchorDesigner.CollectValues(const ASelection: TList; out
TheValues: TAnchorDesignerValues; out SelectedControlCount: integer);
var
i: Integer;
AControl: TControl;
CurObject: TObject;
begin
TheValues:=nil;
SelectedControlCount:=0;
if ASelection=nil then exit;
// Collect values of selected controls
for i:=0 to ASelection.Count-1 do begin
CurObject:=TObject(ASelection[i]);
if CurObject is TControl then begin
AControl:=TControl(CurObject);
if SelectedControlCount=0 then begin
TheValues:=TAnchorDesignerValues.Create;
TheValues.SetValues(AControl);
end else begin
TheValues.MergeValues(AControl);
end;
inc(SelectedControlCount);
end;
end;
end;
procedure TAnchorDesigner.OnSetSelection(const ASelection: TPersistentSelectionList);
begin
if FSelection.IsEqual(ASelection) then exit;
Refresh;
end;
{ TAnchorDesignerValues }
function TAnchorDesignerValues.GetSides(Kind: TAnchorKind): TAnchorDesignerSideValues;
begin
Result:=FSides[Kind];
end;
procedure TAnchorDesignerValues.SetAmbiguousBorderspaceAround(
const AValue: boolean);
begin
if FAmbiguousBorderspaceAround=AValue then exit;
FAmbiguousBorderspaceAround:=AValue;
end;
procedure TAnchorDesignerValues.SetBorderspaceAround(const AValue: integer);
begin
if FBorderspaceAround=AValue then exit;
FBorderspaceAround:=AValue;
end;
constructor TAnchorDesignerValues.Create;
var
a: TAnchorKind;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do
FSides[a]:=TAnchorDesignerSideValues.Create(a);
end;
destructor TAnchorDesignerValues.Destroy;
var
a: TAnchorKind;
begin
for a:=Low(TAnchorKind) to High(TAnchorKind) do FSides[a].Free;
inherited Destroy;
end;
procedure TAnchorDesignerValues.SetValues(AControl: TControl);
var
a: TAnchorKind;
begin
BorderspaceAround:=AControl.BorderSpacing.Around;
AmbiguousBorderspaceAround:=false;
for a:=Low(TAnchorKind) to High(TAnchorKind) do
FSides[a].SetValues(AControl);
end;
procedure TAnchorDesignerValues.MergeValues(AControl: TControl);
var
a: TAnchorKind;
begin
FAmbiguousBorderspaceAround:=FAmbiguousBorderspaceAround
or (FBorderspaceAround<>AControl.BorderSpacing.Around);
for a:=Low(TAnchorKind) to High(TAnchorKind) do
FSides[a].MergeValues(AControl);
end;
{ TAnchorDesignerSideValues }
function TAnchorDesignerSideValues.GetSideRef_IsAmbiguous: Boolean;
var
SideRefIndex: TAnchorSideReference;
n: Integer;
begin
n:=0;
for SideRefIndex:=Low(SideRefIndex) to High(SideRefIndex) do
if (SideRefIndex in FSideRefs)
then Inc(n);
Result:=(n>1);
end;
constructor TAnchorDesignerSideValues.Create(TheKind: TAnchorKind);
begin
FAnchorKind:=TheKind;
end;
procedure TAnchorDesignerSideValues.SetValues(AControl: TControl);
var
CurSide: TAnchorSide;
begin
FCount:=1;
FBorderSpace_IsAmbiguous:=false;
FBorderSpace:=AControl.BorderSpacing.GetSpace(FAnchorKind);
FEnabled_IsAmbiguous:=false;
FEnabled:=(FAnchorKind in AControl.Anchors);
CurSide:=AControl.AnchorSide[FAnchorKind];
FSideRefs:=[CurSide.Side];
FSibling_IsAmbiguous:=false;
FSibling:=TAnchorDesigner.ControlToStr(CurSide.Control);
end;
procedure TAnchorDesignerSideValues.MergeValues(AControl: TControl);
var
CurSide: TAnchorSide;
begin
Inc(FCount);
FBorderSpace_IsAmbiguous:=FBorderSpace_IsAmbiguous
or (FBorderSpace<>AControl.BorderSpacing.GetSpace(FAnchorKind));
FEnabled_IsAmbiguous:=FEnabled_IsAmbiguous
or (FEnabled<>(FAnchorKind in AControl.Anchors));
CurSide:=AControl.AnchorSide[FAnchorKind];
FSibling_IsAmbiguous:=FSibling_IsAmbiguous
or (TAnchorDesigner.ControlToStr(CurSide.Control)<>FSibling);
FSideRefs:=FSideRefs+[CurSide.Side];
end;
{ TAnchorPropertyEditor }
function TAnchorPropertyEditor.GetAttributes: TPropertyAttributes;
begin
Result:=(inherited GetAttributes)+[paDialog];
end;
procedure TAnchorPropertyEditor.Edit;
begin
ShowAnchorDesigner(Self);
end;
initialization
RegisterPropertyEditor(TypeInfo(TAnchors), TControl, 'Anchors',
TAnchorPropertyEditor);
end.