lazarus/components/anchordocking/design/registeranchordocking.pas

543 lines
18 KiB
ObjectPascal

{ Installs anchor docking manager in the Lazarus IDE.
Copyright (C) 2018 Mattias Gaertner mattias@freepascal.org
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify
this library, you may extend this exception to your version of the library,
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.
}
unit RegisterAnchorDocking;
{$mode objfpc}{$H+}
{ $DEFINE VerboseAnchorDocking}
interface
uses
Math, Classes, SysUtils,
// LCL
Forms, Controls, Dialogs, StdCtrls,
// LazUtils
LazFileCache, LazFileUtils, LazLoggerBase,
// IdeIntf
LCLIntf, IDEWindowIntf, IDEOptionsIntf, IDEOptEditorIntf, LazIDEIntf,
// AnchorDocking
AnchorDockStr, AnchorDocking, AnchorDesktopOptions, AnchorDockOptionsDlg;
type
{ TIDEAnchorDockMaster }
TIDEAnchorDockMaster = class(TIDEDockMaster)
private
FChangeStamp: int64;
FCmdLineLayoutFile: string;
FSavedChangeStamp: int64;
FSavedDMChangeStamp: int64;
FUserLayoutLoaded: boolean;
procedure DockMasterCreateControl(Sender: TObject; aName: string;
var AControl: TControl; DoDisableAutoSizing: boolean);
procedure GetDefaultBounds(AForm: TCustomForm; out Creator: TIDEWindowCreator;
out NewBounds: TRect; out DockSiblingName: string; out DockAlign: TAlign);
function GetModified: boolean;
procedure SetModified(const AValue: boolean);
procedure SetUserLayoutLoaded(const AValue: boolean);
public
constructor Create;
destructor Destroy; override;
procedure IncreaseChangeStamp; inline;
property ChangeStamp: int64 read FChangeStamp;
property Modified: boolean read GetModified write SetModified;
function DockedDesktopOptClass: TAbstractDesktopDockingOptClass; override;
// layouts
property UserLayoutLoaded: boolean read FUserLayoutLoaded write SetUserLayoutLoaded;
property CmdLineLayoutFile: string read FCmdLineLayoutFile write FCmdLineLayoutFile;
// events
procedure MakeIDEWindowDockSite(AForm: TCustomForm; ASides: TDockSides = [alBottom]); override;
procedure MakeIDEWindowDockable(AControl: TWinControl); override;
function AddableInWindowMenu(AForm: TCustomForm): boolean; override;
procedure ShowForm(AForm: TCustomForm; BringToFront: boolean); override;
procedure AdjustMainIDEWindowHeight(const AIDEWindow: TCustomForm;
const AAdjustHeight: Boolean; const ANewHeight: Integer); override;
procedure CloseAll; override;
procedure ResetSplitters; override;
procedure SetMainDockWindow(AForm: TCustomForm); override;
end;
{ TAnchorDockIDEFrame }
TAnchorDockIDEFrame = class(TAbstractIDEOptionsEditor)
cbEnableAnchorDocking: TCheckBox;
NoteLabel: TLabel;
procedure cbEnableAnchorDockingChange(Sender: TObject);
private
FSettings: TAnchorDockSettings;
FDisabledChanged: boolean;
public
OptionsFrame: TAnchorDockOptionsFrame;
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
function GetTitle: String; override;
procedure Setup(ADialog: TAbstractOptionsEditorDialog); override;
procedure ReadSettings(AOptions: TAbstractIDEOptions); override;
procedure WriteSettings(AOptions: TAbstractIDEOptions); override;
class function SupportedOptionsClass: TAbstractIDEOptionsClass; override;
end;
TAnchorDockIDEDisabledFrame = class(TAnchorDockIDEFrame)
end;
var
IDEAnchorDockMaster: TIDEAnchorDockMaster = nil;
AnchorDockOptionsID: integer = 1000;
procedure Register;
procedure ProvideIDEDockMaster;
implementation
{$R *.lfm}
procedure Register;
begin
AnchorDockGlobalOptions.LoadSafe;
if not AnchorDockGlobalOptions.EnableAnchorDock then begin
AnchorDockOptionsID:=RegisterIDEOptionsEditor(GroupEnvironment,TAnchorDockIDEDisabledFrame,
AnchorDockOptionsID)^.Index;
exit;
end;
// Calling IDEDockMaster will create it, if it hasn't already been created.
if not (IDEDockMaster is TIDEAnchorDockMaster) then
exit;
// add options frame
AnchorDockOptionsID:=RegisterIDEOptionsEditor(GroupEnvironment,TAnchorDockIDEFrame,
AnchorDockOptionsID)^.Index;
end;
{ TIDEAnchorDockMaster }
procedure TIDEAnchorDockMaster.DockMasterCreateControl(Sender: TObject;
aName: string; var AControl: TControl; DoDisableAutoSizing: boolean);
begin
//debugln(['TIDEAnchorDockMaster.DockMasterCreateControl CtrlName="',dbgstr(AName),'"']);
AControl:=IDEWindowCreators.GetForm(aName,true,DoDisableAutoSizing);
//debugln(['TIDEAnchorDockMaster.DockMasterCreateControl Result=',DbgSName(AControl)]);
end;
procedure TIDEAnchorDockMaster.GetDefaultBounds(AForm: TCustomForm; out
Creator: TIDEWindowCreator; out NewBounds: TRect; out
DockSiblingName: string; out DockAlign: TAlign);
var
AControl: TControl;
begin
NewBounds:=Rect(0,0,0,0);
DockSiblingName:='';
DockAlign:=alNone;
Creator := nil;
// get the embedded control
AControl:=DockMaster.GetControl(AForm);
if not (AControl is TCustomForm) then exit;
AForm:=TCustomForm(AControl);
Creator:=IDEWindowCreators.FindWithName(AForm.Name);
if Creator=nil then exit;
if Creator.OnGetLayout<>nil then
Creator.OnGetLayout(Self,AForm.Name,NewBounds,DockSiblingName,DockAlign)
else begin
Creator.GetDefaultBounds(AForm,NewBounds);
DockSiblingName:=Creator.DockSibling;
DockAlign:=Creator.DockAlign;
end;
{$IFDEF darwin}
if (NewBounds.Top=0) and (NewBounds.Left=0) then
NewBounds.Top:=30;
{$ENDIF}
NewBounds.Left:=Min(10000,Max(-10000,NewBounds.Left));
NewBounds.Top:=Min(10000,Max(-10000,NewBounds.Top));
NewBounds.Right:=Max(NewBounds.Left+100,NewBounds.Right);
NewBounds.Bottom:=Max(NewBounds.Top+100,NewBounds.Bottom);
end;
function TIDEAnchorDockMaster.GetModified: boolean;
begin
Result:=true;
if FChangeStamp=FSavedChangeStamp then exit;
if DockMaster.OptionsChangeStamp=FSavedDMChangeStamp then exit;
Result:=false;
end;
procedure TIDEAnchorDockMaster.SetModified(const AValue: boolean);
begin
if AValue then
IncreaseChangeStamp
else begin
FSavedChangeStamp:=FChangeStamp;
FSavedDMChangeStamp:=DockMaster.OptionsChangeStamp;
end;
end;
procedure TIDEAnchorDockMaster.SetUserLayoutLoaded(const AValue: boolean);
begin
if FUserLayoutLoaded=AValue then exit;
FUserLayoutLoaded:=AValue;
end;
constructor TIDEAnchorDockMaster.Create;
begin
inherited Create;
DefaultAnchorDockOptionFlags:=[adofShow_ShowHeader,adofSpinEdits];
IDEAnchorDockMaster:=Self;
DockMaster.OnCreateControl:=@DockMasterCreateControl;
DockMaster.OnShowOptions:=@ShowAnchorDockOptions;
DockMaster.ShowMenuItemShowHeader:=true;
fCmdLineLayoutFile:=TrimAndExpandFilename(Application.GetOptionValue('anchordocklayout'));
if CmdLineLayoutFile<>'' then
debugln(['Hint: anchordocking layout file: "',CmdLineLayoutFile,'"']);
end;
destructor TIDEAnchorDockMaster.Destroy;
begin
IDEAnchorDockMaster:=nil;
if IDEDockMaster=Self then
IDEDockMaster:=nil;
inherited Destroy;
end;
function TIDEAnchorDockMaster.DockedDesktopOptClass: TAbstractDesktopDockingOptClass;
begin
Result := TAnchorDesktopOpt;
end;
procedure TIDEAnchorDockMaster.MakeIDEWindowDockSite(AForm: TCustomForm;
ASides: TDockSides);
var
aManager: TAnchorDockManager;
begin
{$IFDEF VerboseAnchorDocking}
debugln(['TIDEAnchorDockMaster.MakeIDEWindowDockSite ',DbgSName(AForm)]);
{$ENDIF}
if ASides=[] then ;
DockMaster.MakeDockSite(AForm,[akBottom],admrpChild);
if AForm.DockManager is TAnchorDockManager then begin
aManager:=TAnchorDockManager(AForm.DockManager);
aManager.PreferredSiteSizeAsSiteMinimum:=false;
end;
end;
procedure TIDEAnchorDockMaster.ResetSplitters;
begin
DockMaster.ResetSplitters;
end;
procedure TIDEAnchorDockMaster.SetMainDockWindow(AForm: TCustomForm);
begin
{$IFDEF VerboseAnchorDocking}
debugln(['TIDEAnchorDockMaster.SetMainDockWindow ',DbgSName(AForm)]);
{$ENDIF}
DockMaster.MainDockForm := AForm;
end;
procedure TIDEAnchorDockMaster.MakeIDEWindowDockable(AControl: TWinControl);
begin
{$IFDEF VerboseAnchorDocking}
debugln(['TIDEAnchorDockMaster.MakeIDEWindowDockable ',DbgSName(AControl)]);
{$ENDIF}
DockMaster.MakeDockable(AControl,false);
end;
function TIDEAnchorDockMaster.AddableInWindowMenu(AForm: TCustomForm): boolean;
begin
Result:=false;
if AForm is TAnchorDockHostSite then exit;
if (DockMaster.FindControl(AForm.Name)=nil) and (AForm.Parent<>nil) then exit;
Result := AForm.IsVisible
or (
Assigned(AForm.Parent)
and Assigned(AForm.Parent.Parent)
and (AForm.Parent.Parent is TAnchorDockPage)
);
end;
procedure TIDEAnchorDockMaster.AdjustMainIDEWindowHeight(
const AIDEWindow: TCustomForm; const AAdjustHeight: Boolean;
const ANewHeight: Integer);
var
Site: TAnchorDockHostSite;
I: Integer;
SiteNewHeight: Integer;
begin
inherited AdjustMainIDEWindowHeight(AIDEWindow, AAdjustHeight, ANewHeight);
Site := nil;
for I := 0 to AIDEWindow.ControlCount-1 do
if AIDEWindow.Controls[I] is TAnchorDockHostSite then
begin
Site := TAnchorDockHostSite(AIDEWindow.Controls[I]);
if (Site.Parent<>nil) and (Site.Parent=LazarusIDE.GetMainBar) then
Break // found
else
Site := nil;
end;
if (Site=nil) or (Site.BoundSplitter=nil) then
Exit;
Site.BoundSplitter.Enabled := not AAdjustHeight;
Site.BoundSplitter.CustomWidth := not Site.BoundSplitter.Enabled;
if Site.BoundSplitter.Enabled then
Site.BoundSplitter.Height := DockMaster.SplitterWidth
else
begin
Site.BoundSplitter.Constraints.MinHeight := 2;
Site.BoundSplitter.Height := Site.BoundSplitter.Constraints.MinHeight;
end;
SiteNewHeight := Site.Parent.ClientHeight - ANewHeight - Site.BoundSplitter.Height;
if AAdjustHeight and (Site.Height <> SiteNewHeight) then
Site.Height := SiteNewHeight;
end;
procedure TIDEAnchorDockMaster.ShowForm(AForm: TCustomForm;
BringToFront: boolean);
var
Parent: TCustomForm;
Creator: TIDEWindowCreator;
NewBounds: TRect;
DockSiblingName: string;
DockAlign: TAlign;
DockSibling: TCustomForm;
NewDockSite: TCustomForm;
Site: TAnchorDockHostSite;
AControl: TControl;
NeedPlacing: Boolean;
SiteForm: TCustomForm;
OldActiveControl: TWinControl;
begin
//debugln(['TIDEAnchorDockMaster.ShowForm START ',DbgSName(AForm),' BringToFront=',BringToFront,' IsSite=',DockMaster.IsSite(AForm),' IsCustomSite=',DockMaster.IsCustomSite(AForm)]);
try
AForm.DisableAlign;
NeedPlacing:=not AForm.IsVisible;
if DockMaster.GetSite(AForm)=nil then begin
// this form was not yet docked
// => make it dockable
DockMaster.MakeDockable(AForm,false);
NeedPlacing:=true;
end;
AControl:=DockMaster.GetControl(AForm);
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
if not AForm.IsVisible then
debugln(['TIDEAnchorDockMaster.ShowForm AControl=',DbgSName(AControl),' NeedPlacing=',NeedPlacing,' Floating=',DockMaster.IsFloating(AForm)]);
{$ENDIF}
if (AControl<>nil) and NeedPlacing and DockMaster.IsFloating(AForm) then begin
// this form is not yet on the screen and is not yet docked
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
debugln(['TIDEAnchorDockMaster.ShowForm placing ',DbgSName(AControl),' ...']);
{$ENDIF}
// ToDo: use the restore layout
// place it at a default position and/or dock it
GetDefaultBounds(AForm,Creator,NewBounds,DockSiblingName,DockAlign);
if Creator<>nil then begin
SiteForm:=GetParentForm(AForm);
SiteForm.BoundsRect:=NewBounds;
SiteForm.UndockWidth:=NewBounds.Right-NewBounds.Left;
SiteForm.UndockHeight:=NewBounds.Bottom-NewBounds.Top;
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
debugln(['TIDEAnchorDockMaster.ShowForm creator for ',DbgSName(AControl),' found: Left=',Creator.Left,' Top=',Creator.Top,' Right=',Creator.Right,' Bottom=',Creator.Bottom,' DockSiblingName=',DockSiblingName,' DockAlign=',dbgs(DockAlign),' ',dbgs(SiteForm.BoundsRect)]);
{$ENDIF}
Site:=DockMaster.GetAnchorSite(SiteForm);
if (Site<>nil) and (DockSiblingName<>'') then begin
DockSibling:=Screen.FindForm(DockSiblingName);
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
debugln(['TIDEAnchorDockMaster.ShowForm DockSiblingName="',DockSiblingName,'" DockSibling=',DbgSName(DockSibling)]);
{$ENDIF}
if DockSibling<>nil then begin
NewDockSite:=DockMaster.GetSite(DockSibling);
if NewDockSite<>nil then begin
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
debugln(['TIDEAnchorDockMaster.ShowForm NewDockSite=',DbgSName(NewDockSite),'="',NewDockSite.Caption,'"']);
{$ENDIF}
DockMaster.ManualDock(Site,NewDockSite,DockAlign,NewDockSite);
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
debugln(['TIDEAnchorDockMaster.ShowForm after docking: ',DbgSName(AControl),' Floating=',DockMaster.IsFloating(AControl)]);
{$ENDIF}
end;
end;
end;
end;
end;
finally
OldActiveControl:=AForm.LastActiveControl;
{$IF defined(VerboseAnchorDocking) or defined(VerboseAnchorDockRestore)}
if not AForm.IsVisible then
debugln(['TIDEAnchorDockMaster.ShowForm MakeVisible ',DbgSName(AForm),' ',dbgs(AForm.BoundsRect),' Floating=',DockMaster.IsFloating(AForm)]);
{$ENDIF}
DockMaster.MakeVisible(AForm,BringToFront);
AForm.EnableAlign;
if BringToFront then begin
if (OldActiveControl=nil)
or (not OldActiveControl.HandleAllocated)
or (FindControl(GetFocus)<>OldActiveControl) then begin
Parent:=GetParentForm(AForm);
Parent.ShowOnTop;
if (OldActiveControl<>nil) and OldActiveControl.CanFocus then
begin
Parent.ActiveControl:=OldActiveControl;
Parent.SetFocus;
end;
//debugln(['TIDEAnchorDockMaster.ShowForm AForm.ActiveControl=',dbgsname(AForm.ActiveControl),' ',DbgSName(Parent.ActiveControl),' ',DbgSName(FindControl(GetFocus))]);
end;
end;
end;
//debugln(['TIDEAnchorDockMaster.ShowForm END ',DbgSName(AForm),' ',dbgs(AForm.BoundsRect),' ',DbgSName(FindControl(GetFocus))]);
end;
procedure TIDEAnchorDockMaster.CloseAll;
begin
DockMaster.CloseAll;
end;
procedure TIDEAnchorDockMaster.IncreaseChangeStamp;
begin
LUIncreaseChangeStamp64(FChangeStamp);
end;
{ TAnchorDockIDEFrame }
procedure TAnchorDockIDEFrame.cbEnableAnchorDockingChange(Sender: TObject);
begin
FDisabledChanged := True;
end;
constructor TAnchorDockIDEFrame.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
FSettings:=TAnchorDockSettings.Create;
OptionsFrame:=TAnchorDockOptionsFrame.Create(Self);
with OptionsFrame do begin
Name:='OptionsFrame';
end;
end;
destructor TAnchorDockIDEFrame.Destroy;
begin
FreeAndNil(FSettings);
inherited Destroy;
end;
function TAnchorDockIDEFrame.GetTitle: String;
begin
Result:=adrsDockingAnchordocking;
end;
procedure TAnchorDockIDEFrame.Setup(ADialog: TAbstractOptionsEditorDialog);
begin
cbEnableAnchorDocking.Caption := adrsEnableAnchorDock;
cbEnableAnchorDocking.Hint := adrsEnableAnchorDockHint;
if self is TAnchorDockIDEDisabledFrame then begin
NoteLabel.Visible := False;
exit;
end;
if ADialog=nil then ;
if IDEDockMaster=IDEAnchorDockMaster then begin
NoteLabel.Visible:=false;
OptionsFrame.Align:=alClient;
OptionsFrame.Parent:=Self;
end else begin
NoteLabel.Visible:=true;
NoteLabel.Caption:=Format(adrsToUseAnchordockingYouMustFirstUninstall, [
DbgSName(IDEDockMaster)]);
NoteLabel.Hint:=Format(
adrsThereIsAnotherDockMasterInstalledOnlyOneDockingPac, [DbgSName(
IDEDockMaster)]);
OptionsFrame.Parent:=nil;
end;
end;
procedure TAnchorDockIDEFrame.ReadSettings(AOptions: TAbstractIDEOptions);
begin
cbEnableAnchorDocking.Checked := AnchorDockGlobalOptions.EnableAnchorDock;
FDisabledChanged := False;
if self is TAnchorDockIDEDisabledFrame then exit;
if not (AOptions is SupportedOptionsClass) then exit;
DockMaster.SaveSettings(FSettings);
OptionsFrame.LoadFromSettings(FSettings);
end;
procedure TAnchorDockIDEFrame.WriteSettings(AOptions: TAbstractIDEOptions);
begin
AnchorDockGlobalOptions.EnableAnchorDock := cbEnableAnchorDocking.Checked;
if FDisabledChanged then
AnchorDockGlobalOptions.DoneAskUserEnableAnchorDock := True;
AnchorDockGlobalOptions.SaveSafe;
if self is TAnchorDockIDEDisabledFrame then exit;
if not (AOptions is SupportedOptionsClass) then exit;
OptionsFrame.SaveToSettings(FSettings);
if (not DockMaster.SettingsAreEqual(FSettings))
then begin
DockMaster.LoadSettings(FSettings);
end;
end;
class function TAnchorDockIDEFrame.
SupportedOptionsClass: TAbstractIDEOptionsClass;
begin
Result:=IDEEditorGroups.GetByIndex(GroupEnvironment)^.GroupClass;
end;
procedure ProvideIDEDockMaster;
begin
OnIDEDockMasterNeeded := nil;
AnchorDockGlobalOptions.LoadSafe;
if AnchorDockGlobalOptions.EnableAnchorDock then
IDEDockMaster:=TIDEAnchorDockMaster.Create;
end;
initialization
AnchorDockGlobalOptions := TAnchorDockGlobalOptions.Create;
if OnIDEDockMasterNeeded = nil then OnIDEDockMasterNeeded := @ProvideIDEDockMaster;
finalization
FreeAndNil(IDEAnchorDockMaster);
FreeAndNil(AnchorDockGlobalOptions);
end.