{
 *****************************************************************************
  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

  Author: Mattias Gaertner
  
  Abstract:
    Provides general classes and methods to access and handle IDE dialogs and
    windows.
}
unit IDEWindowIntf;

{$mode objfpc}{$H+}

interface

uses
  Math, Classes, SysUtils, LCLProc, LazConfigStorage, LazUTF8, Forms, Controls,
  LCLIntf, IDEOptionsIntf;

const
  IDEWndCfgFileVersion = 2;
  // 2: changed default WindowPlacement from iwpRestoreWindowSize to iwpRestoreWindowGeometry

  //----------------------------------------------------------------------------
  // layout settings of modal forms (dialogs) in the IDE
type

  TIDEDialogLayoutList = class;

  { TIDEDialogLayout - for modal forms
    For non modal forms see TIDEWindowCreator below }

  TIDEDialogLayout = class
  private
    FList: TIDEDialogLayoutList;
    FModified: boolean;
    FName: string;
    FWidth: integer;
    FHeight: integer;
    procedure SetList(const AValue: TIDEDialogLayoutList);
    procedure SetModified(const AValue: boolean);
    procedure SetWidth(const AValue: integer);
    procedure SetHeight(const AValue: integer);
  public
    constructor Create(const TheName: string; TheList: TIDEDialogLayoutList);
    procedure Assign(Source: TIDEDialogLayout);
    function SizeValid: boolean;
    property Width: integer read FWidth write SetWidth;
    property Height: integer read FHeight write SetHeight;
    property Name: string read FName;
    procedure LoadFromConfig(Config: TConfigStorage; const Path: string);
    procedure SaveToConfig(Config: TConfigStorage; const Path: string);
    property List: TIDEDialogLayoutList read FList write SetList;
    property Modified: boolean read FModified write SetModified;
  end;
  TIDEDialogLayoutClass = class of TIDEDialogLayout;

  { TIDEDialogLayoutList - for modal forms }

  TIDEDialogLayoutList = class
  private
    FItemClass: TIDEDialogLayoutClass;
    FItems: TList;
    FModified: boolean;
    function GetItems(Index: integer): TIDEDialogLayout;
  protected
    procedure SetModified(const AValue: boolean); virtual;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Assign(Source: TIDEDialogLayoutList);
    procedure ApplyLayout(ADialog: TControl;
                          DefaultWidth, DefaultHeight: integer;
                          UseAsMin: boolean = true);
    procedure ApplyLayout(ADialog: TControl);
    procedure SaveLayout(ADialog: TControl);
    procedure Clear;
    function Count: integer;
    function Find(const DialogName: string;
                  CreateIfNotExists: boolean): TIDEDialogLayout;
    function Find(ADialog: TObject;
                  CreateIfNotExists: boolean): TIDEDialogLayout;
    function IndexOf(const DialogName: string): integer;
    procedure LoadFromConfig(Config: TConfigStorage; const Path: string);
    procedure SaveToConfig(Config: TConfigStorage; const Path: string);
    property Items[Index: integer]: TIDEDialogLayout read GetItems;
    property Modified: boolean read FModified write SetModified;
    property ItemClass: TIDEDialogLayoutClass read FItemClass write FItemClass;
  end;
  
  { TIDEDialogLayoutStorage }

  TIDEDialogLayoutStorage = class(TComponent)
  protected
    procedure OnCreateForm(Sender: TObject);
    procedure OnCloseForm(Sender: TObject; var {%H-}CloseAction: TCloseAction);
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
  end;

var
  IDEDialogLayoutList: TIDEDialogLayoutList = nil;// set by the IDE

type
  { TSimpleWindowLayout stores information about the position, min/maximized state
    and similar things for an IDE window or dialog, like the source editor,
    the object inspector, the main bar or the message view.
  }
  TIDEWindowPlacement = (
    iwpUseWindowManagerSetting, // leave window position, where window manager
                                //   creates the window
    iwpDefault,                 // set window to the default position
    iwpRestoreWindowGeometry,   // save window geometry at end and restore it
                                //   at start
    iwpCustomPosition,          // set window to custom position
    iwpRestoreWindowSize        // save window size at end and restore it
                                //   at start
    );
  TIDEWindowPlacements = set of TIDEWindowPlacement;
  TIDEWindowState = (iwsNormal, iwsMaximized, iwsMinimized, iwsHidden);
  TIDEWindowStates = set of TIDEWindowState;

  TSimpleWindowLayoutDividerPosPlacement = (
    iwpdDefault,      // set column/row/splitter to the default size
    iwpdCustomSize,   // set column/row/splitter to the custom size
    iwpdRestore,      // save column/row/splitter size on exit, and restore
    iwpdUseWindowSetting
  );

  TSimpleWindowLayoutDividerPosSizeGetter =
    function(AForm: TCustomForm; AColId: Integer; var ASize: Integer): Boolean;

  TSimpleWindowLayoutDividerPosSizeSetter =
    procedure(AForm: TCustomForm; AColId: Integer; ASize: Integer);

  { TSimpleWindowLayoutDividerPos }

  TSimpleWindowLayoutDividerPos = class
  private
    FDefaultSize: integer;
    FDisplayName: PString;
    FId: Integer;
    FIdString: String;
    FPlacement: TSimpleWindowLayoutDividerPosPlacement;
    FSize: integer;
    function GetDisplayName: String;
  protected
    procedure SetDisplayName(ADisplayName: PString);
    procedure SetId(AnId: Integer);
  public
    constructor Create(AnIdString: String);
    constructor Create(AnIdString: String; AnId: Integer; ADisplayName: PString);
    procedure Assign(ADividerPos: TSimpleWindowLayoutDividerPos); reintroduce;
    procedure LoadFromConfig(Config: TConfigStorage; const Path: string);
    function  SaveToConfig(Config: TConfigStorage; const Path: string) : Boolean;
    procedure Clear;
    property IdString: String read FIdString;
    property Id: Integer read FId;
    property DisplayName: String read GetDisplayName;
    property Placement: TSimpleWindowLayoutDividerPosPlacement read FPlacement write FPlacement;
    property Size: integer read FSize write FSize;
    property DefaultSize: integer read FDefaultSize write FDefaultSize;
  end;

  { TSimpleWindowLayoutDividerPosList }

  TSimpleWindowLayoutDividerPosList = class
  private
    FList: TList;
    function GetItems(Index: Integer): TSimpleWindowLayoutDividerPos;
    function GetNamedItems(Index: Integer): TSimpleWindowLayoutDividerPos;
  protected
    procedure Merge(AnItem: TSimpleWindowLayoutDividerPos);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Assign(ADividerPosList: TSimpleWindowLayoutDividerPosList); reintroduce;
    procedure LoadFromConfig(Config: TConfigStorage; const Path: string);
    procedure SaveToConfig(Config: TConfigStorage; const Path: string);
    procedure Clear;
    procedure ClearItems;
    function Add(AnIdString: String; AnId: Integer; ADisplayName: PString): TSimpleWindowLayoutDividerPos;
    function Add(AnIdString: String): TSimpleWindowLayoutDividerPos;
    function Count: Integer;
    function NamedCount: Integer;
    function Find(AnId: Integer): TSimpleWindowLayoutDividerPos;
    function Find(AnIdString: String): TSimpleWindowLayoutDividerPos;
    function IndexOf(AnId: Integer): Integer;
    function IndexOf(AnIdString: String): Integer;
    function IndexOf(AnItem: TSimpleWindowLayoutDividerPos): Integer;
    property Items[Index: Integer]: TSimpleWindowLayoutDividerPos read GetItems; default;
    property NamedItems[Index: Integer]: TSimpleWindowLayoutDividerPos read GetNamedItems;
  end;

  { TSimpleWindowLayout }

  TSimpleWindowLayout = class(TComponent)
  private
    FApplied: boolean;
    FFormCaption: string;
    FVisible: boolean;
    FWindowPlacement: TIDEWindowPlacement;
    FLeft: integer;
    FTop: integer;
    FWidth: integer;
    FHeight: integer;
    FDefaultLeft: integer;
    FDefaultTop: integer;
    FDefaultWidth: integer;
    FDefaultHeight: integer;
    FWindowState: TIDEWindowState;
    FForm: TCustomForm;
    FFormID: string;
    FDefaultWindowPlacement: TIDEWindowPlacement;
    FDividers: TSimpleWindowLayoutDividerPosList;
    procedure SetForm(const AForm: TCustomForm);
    function GetFormCaption: string;
    procedure OnFormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
  protected
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AFormID: string); reintroduce;
    destructor Destroy; override;
    procedure Clear;
    procedure GetCurrentPosition;
    function  Apply(const aForce: Boolean = False): Boolean;
    procedure ApplyDivider(AForce: Boolean = False);
    procedure Assign(Layout: TSimpleWindowLayout); reintroduce;
    procedure ReadCurrentDividers(AForce: Boolean = False);
    procedure ReadCurrentCoordinates;
    procedure ReadCurrentState;
    procedure LoadFromConfig(Config: TConfigStorage; const Path: string; FileVersion: integer);
    procedure SaveToConfig(Config: TConfigStorage; const Path: string);
    function CustomCoordinatesAreValid: boolean;
    function DefaultCoordinatesAreValid: boolean;
    procedure CloseForm;
    function ValidateAndSetCoordinates(const aForce: Boolean = False): Boolean;
    procedure SetDefaultPosition(const AForm: TCustomForm);
  public
    property FormID: string read FFormID write FFormID;
    function FormBaseID(out SubIndex: Integer): String; // split FormID into name+number
    property FormCaption: string read GetFormCaption;
    property WindowPlacement: TIDEWindowPlacement read fWindowPlacement write FWindowPlacement;
    property DefaultWindowPlacement: TIDEWindowPlacement
      read FDefaultWindowPlacement write FDefaultWindowPlacement;
    property Left: integer read FLeft write FLeft;
    property Top: integer read FTop write FTop;
    property Width: integer read FWidth write FWidth;
    property Height: integer read FHeight write FHeight;
    property DefaultLeft: integer read FDefaultLeft write FDefaultLeft;
    property DefaultTop: integer read FDefaultTop write FDefaultTop;
    property DefaultWidth: integer read FDefaultWidth write FDefaultWidth;
    property DefaultHeight: integer read FDefaultHeight write FDefaultHeight;
    property WindowState: TIDEWindowState read FWindowState write FWindowState;
    property Form: TCustomForm read FForm write SetForm;
    property Visible: boolean read FVisible write FVisible;
    property Applied: boolean read FApplied write FApplied;
    property Dividers: TSimpleWindowLayoutDividerPosList read FDividers;
  end;

  { TSimpleWindowLayoutList }
  TLayoutMoveToVisbleMode = (
    vmAlwaysMoveToVisible,
    vmNeverMoveToVisible,
    vmOnlyMoveOffScreenToVisible   // Only make visible, if offscreen (with a threshold)
  );

  TSimpleWindowLayoutList = class
  private
    fItems: TFPList;
    fRegisterEventHandlers: Boolean;
    function GetItems(Index: Integer): TSimpleWindowLayout;
    procedure OnScreenRemoveForm(Sender: TObject; AForm: TCustomForm);
  public
    constructor Create(ARegisterEventHandlers: Boolean);
    destructor Destroy; override;
    procedure Clear;
    procedure Delete(Index: Integer);
    procedure ApplyAndShow(Sender: TObject; AForm: TCustomForm;
                           BringToFront: boolean;
                           AMoveToVisbleMode: TLayoutMoveToVisbleMode = vmOnlyMoveOffScreenToVisible);
    procedure StoreWindowPositions;
    procedure SetDefaultPosition(const AForm: TCustomForm);
    procedure CopyItemsFrom(SrcList: TSimpleWindowLayoutList);
    function Add(ALayout: TSimpleWindowLayout): integer;
    function CreateWindowLayout(const TheFormID: string): TSimpleWindowLayout;
    function CreateWindowLayout(const TheForm: TCustomForm): TSimpleWindowLayout;
    function IndexOf(const FormID: string): integer;
    function IndexOf(const AForm: TCustomForm): integer;
    function ItemByForm(AForm: TCustomForm): TSimpleWindowLayout;
    function ItemByFormID(const FormID: string): TSimpleWindowLayout;
    function ItemByFormCaption(const aFormCaption: string): TSimpleWindowLayout;
    procedure CloseForm(AForm: TCustomForm);
    procedure LoadFromConfig(Config: TConfigStorage; const Path: string);
    procedure SaveToConfig(Config: TConfigStorage; const Path: string);
  public
    function Count: integer;
    property Items[Index: Integer]: TSimpleWindowLayout read GetItems; default;
  end;

const
  IDEWindowPlacementNames: array[TIDEWindowPlacement] of string = (
      'UseWindowManagerSetting',
      'Default',
      'RestoreWindowGeometry',
      'CustomPosition',
      'RestoreWindowSize'
    );
  IDEWindowStateNames: array[TIDEWindowState] of string = (
      'Normal', 'Maximized', 'Minimized', 'Hidden'
    );

function StrToIDEWindowPlacement(const s: string): TIDEWindowPlacement;
function StrToIDEWindowState(const s: string): TIDEWindowState;

type
  TIWCState = (
    iwcsHidden,
    iwcsIconified,
    iwcsNormal,
    iwcsDocked
    );
  TIWGetFormState = (
    iwgfDisabled, // create if not exist with disabled autosizing
    iwgfEnabled, // create if not exists
    iwgfShow, // create and show
    iwgfShowOnTop // create, show and bring to front
    );
  TIWGetFormStates = set of TIWGetFormState;

  TCreateIDEWindowMethod = procedure(Sender: TObject; aFormName: string;
                var AForm: TCustomForm; DoDisableAutoSizing: boolean) of object;
  TCreateIDEWindowProc = procedure(Sender: TObject; aFormName: string;
                var AForm: TCustomForm; DoDisableAutoSizing: boolean);
  TGetDefaultIDEWindowLayoutEvent = procedure(Sender: TObject; aFormName: string;
   out aBounds: TRect; out DockSibling: string; out DockAlign: TAlign) of object;
  TShowIDEWindowEvent = procedure(Sender: TObject; AForm: TCustomForm;
                                  BringToFront: boolean) of object;

  { TIDEWindowCreator
    Every dockable window of the IDE needs a TIDEWindowCreator. }

  TIDEWindowCreator = class
  private
    FCreateFormMethod: TCreateIDEWindowMethod;
    FCreateFormProc: TCreateIDEWindowProc;
    FDockAlign: TAlign;
    FDockSibling: string;
    FFormName: string;
    FBottom: string;
    FLeft: string;
    FMulti: boolean;
    FOnGetDividerSize: TSimpleWindowLayoutDividerPosSizeGetter;
    FOnGetLayout: TGetDefaultIDEWindowLayoutEvent;
    FOnSetDividerSize: TSimpleWindowLayoutDividerPosSizeSetter;
    FState: TIWCState;
    FTop: string;
    FRight: string;
    FDividerTemplate: TSimpleWindowLayoutDividerPosList;
    function GetDividerTemplate: TSimpleWindowLayoutDividerPosList;
    procedure SetBottom(const AValue: string);
    procedure SetLeft(const AValue: string);
    procedure SetTop(const AValue: string);
    procedure SetRight(const AValue: string);
    procedure InitSimpleLayout(ALayout: TSimpleWindowLayout);
  public
    constructor Create(aFormName: string); overload;
    constructor Create(aFormName: string;
                       CreateFormProc: TCreateIDEWindowProc;
                       CreateFormMethod: TCreateIDEWindowMethod;
                       aLeft, aTop, aRight, aBottom: string;
                       aDockSibling : string = '';
                       aDockAlign: TAlign = alNone;
                       aMulti: boolean = false;
               GetLayoutEvent: TGetDefaultIDEWindowLayoutEvent = nil); overload;
    destructor Destroy; override;
    property FormName: string read FFormName; // prefix for all forms
    property Multi: boolean read FMulti; // there can be more than one of this form, e.g. the source editors and the package editors
    property OnCreateFormMethod: TCreateIDEWindowMethod read FCreateFormMethod write FCreateFormMethod;
    property OnCreateFormProc: TCreateIDEWindowProc read FCreateFormProc write FCreateFormProc;
    function NameFits(const AName: string): boolean;

    // hints for the layout system / dock master. It may ignore them completely.
    property State: TIWCState read FState write FState;
    property Left: string read FLeft write SetLeft; // '12' for 12 pixel, '10%' for 10 percent of screen.width
    property Top: string read FTop write SetTop; // '12' for 12 pixel, '10%' for 10 percent of screen.height
    property Right: string read FRight write SetRight; // '12' for 12 pixel, '+12' for a Width of 12 pixel, '-12' for Screen.Width-12, '10%' for 10 percent of screen.width
    property Bottom: string read FBottom write SetBottom; // '12' for 12 pixel, '+12' for a Height of 12 pixel, '10%' for 10 percent of screen.height
    property DockSibling: string read FDockSibling write FDockSibling; // another form name
    property DockAlign: TAlign read FDockAlign write FDockAlign;
    property OnGetLayout: TGetDefaultIDEWindowLayoutEvent read FOnGetLayout
                                                          write FOnGetLayout;
    procedure CheckBoundValue(s: string);
    procedure GetDefaultBounds(AForm: TCustomForm; out DefBounds: TRect);

    function CreateSimpleLayout: TSimpleWindowLayout;
    // TODO: Need a WindowCreator factory, by class of TForm
    // then this data can be stored per window class
    property  DividerTemplate: TSimpleWindowLayoutDividerPosList read GetDividerTemplate;
    property  OnGetDividerSize: TSimpleWindowLayoutDividerPosSizeGetter read FOnGetDividerSize write FOnGetDividerSize;
    property  OnSetDividerSize: TSimpleWindowLayoutDividerPosSizeSetter read FOnSetDividerSize write FOnSetDividerSize;
  end;

  { TIDEWindowCreatorList }

  TIDEWindowCreatorList = class
  private
    fItems: TFPList; // list of TIDEWindowCreator
    FScreenMaxSizeForDefaults: TPoint;
    FSimpleLayoutStorage: TSimpleWindowLayoutList;
    FOnLayoutChanged: TMethodList;
    procedure LayoutChanged;
    function GetItems(Index: integer): TIDEWindowCreator;
    procedure ErrorIfFormExists(FormName: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    function Count: integer;
    property Items[Index: integer]: TIDEWindowCreator read GetItems; default;
    function Add(aLayout: TIDEWindowCreator): integer; overload;
    function Add(aFormName: string): TIDEWindowCreator; overload;
    function Add(aFormName: string;
                 CreateFormProc: TCreateIDEWindowProc;
                 CreateFormMethod: TCreateIDEWindowMethod;
                 // you can pass some hints to the layout system how to place the form by default
                 // Note: the IDE dockmaster may completely ignore these values
                 aLeft, aTop, aRight, aBottom: string;
                 aDockSibling : string = '';
                 aDockAlign: TAlign = alNone;
                 aMulti: boolean = false; // if true, then there can be more than one instance, the aFormName is the shared prefix
                 GetLayoutEvent: TGetDefaultIDEWindowLayoutEvent = nil
                 ): TIDEWindowCreator; overload;
    procedure Delete(Index: integer);
    function IndexOfName(FormName: string): integer;
    function FindWithName(FormName: string): TIDEWindowCreator;
    function GetForm(aFormName: string; AutoCreate: boolean;
                               DisableAutoSizing: boolean = false): TCustomForm;
    procedure ShowForm(AForm: TCustomForm; BringToFront: boolean;
                       AMoveToVisbleMode: TLayoutMoveToVisbleMode = vmOnlyMoveOffScreenToVisible); overload;
    function ShowForm(AFormName: string; BringToFront: boolean): TCustomForm; overload;
    procedure CreateForm(var AForm; AFormClass: TCustomFormClass;
                         DoDisableAutoSizing: boolean; TheOwner: TComponent); // utility function to create a form with delayed autosizing

    property SimpleLayoutStorage: TSimpleWindowLayoutList read FSimpleLayoutStorage;
    procedure RestoreSimpleLayout;
    procedure AddLayoutChangedHandler(const aEvent: TNotifyEvent);
    procedure RemoveLayoutChangedHandler(const aEvent: TNotifyEvent);

    property ScreenMaxSizeForDefaults: TPoint read FScreenMaxSizeForDefaults
                                              write FScreenMaxSizeForDefaults; // on big screens: do not span the whole screen
    function GetScreenrectForDefaults: TRect;
  end;

var
  IDEWindowCreators: TIDEWindowCreatorList = nil; // set by the IDE

type

  TDockSides = set of TAlign;

  { TIDEDockMaster }

  TIDEDockMaster = class
  protected
    FHideSimpleLayoutOptions: boolean;
  public
    procedure MakeIDEWindowDockable(AControl: TWinControl); virtual; abstract; // make AControl dockable, it can be docked and other dockable windows can be docked to it, this does not make it visible
    procedure MakeIDEWindowDockSite(AForm: TCustomForm; ASides: TDockSides = [alBottom]); virtual; abstract; // make AForm a dock site, AForm can not be docked, its Parent must be kept nil, this does not make it visible
    procedure ShowForm(AForm: TCustomForm; BringToFront: boolean); virtual; abstract; // make a form visible, set BringToFront=true if form should be shown on active screen and on front of other windows, normally this focus the form
    function AddableInWindowMenu({%H-}AForm: TCustomForm): boolean; virtual;
    procedure AdjustMainIDEWindowHeight(const {%H-}AIDEWindow: TCustomForm;
      const {%H-}AAdjustHeight: Boolean; const {%H-}ANewHeight: Integer); virtual;
    procedure CloseAll; virtual; // close all forms, called after IDE has saved all and shuts down
    procedure ResetSplitters; virtual; abstract; // if the dock site has been resized after loading, you have to reset (percentual) splitters
    function DockedDesktopOptClass: TAbstractDesktopDockingOptClass; virtual; abstract;
    property HideSimpleLayoutOptions: boolean read FHideSimpleLayoutOptions;
  end;

  TIDEWindowGlobalOption = class
  public
    CanSetVisibility: Boolean;
  end;

  TIDEWindowsGlobalOptions = class
  private
    FList: TStringList;
  public
    constructor Create;
    destructor Destroy; override;
  public
    procedure Add(const aFormIDPrefix: string; CanSetVisibility: Boolean);
    function CanSetVisibility(const aFormID: string): Boolean;
  end;

var
  IDEDockMaster: TIDEDockMaster = nil; // can be set by a package

procedure MakeIDEWindowDockable(AControl: TWinControl);
procedure MakeIDEWindowDockSite(AForm: TCustomForm);
procedure CloseAllForms;
function IDEWindowsGlobalOptions: TIDEWindowsGlobalOptions;

procedure SetPopupModeParentForPropertyEditor(const AEditorDlg: TCustomForm);

procedure Register;

implementation

uses
  LazIDEIntf;

var
  FIDEWindowsGlobalOptions: TIDEWindowsGlobalOptions = nil;

procedure SetPopupModeParentForPropertyEditor(const AEditorDlg: TCustomForm);
begin
  if IDEDockMaster<>nil then
    AEditorDlg.PopupParent:=LazarusIDE.GetMainBar// sets PopupMode:=pmExplicit automatically
  else
    AEditorDlg.PopupMode:=pmNone;
end;

function StrToIDEWindowPlacement(const s: string): TIDEWindowPlacement;
begin
  for Result:=Low(TIDEWindowPlacement) to High(TIDEWindowPlacement) do
    if UTF8CompareText(s,IDEWindowPlacementNames[Result])=0 then exit;
  Result:=iwpDefault;
end;

function StrToIDEWindowState(const s: string): TIDEWindowState;
begin
  for Result:=Low(TIDEWindowState) to High(TIDEWindowState) do
    if UTF8CompareText(s,IDEWindowStateNames[Result])=0 then exit;
  Result:=iwsNormal;
end;

procedure MakeIDEWindowDockable(AControl: TWinControl);
begin
  if Assigned(IDEDockMaster) then
    IDEDockMaster.MakeIDEWindowDockable(AControl);
end;

procedure MakeIDEWindowDockSite(AForm: TCustomForm);
begin
  if Assigned(IDEDockMaster) then
    IDEDockMaster.MakeIDEWindowDockSite(AForm);
end;

procedure CloseAllForms;
var
  i: Integer;
  AForm: TCustomForm;
  AControl: TWinControl;
begin
  // hide all forms
  i:=Screen.CustomFormCount-1;
  while i>=0 do begin
    AForm:=GetParentForm(Screen.CustomForms[i]);
    AForm.Hide;
    dec(i);
    if i>=Screen.CustomFormCount then i:=Screen.CustomFormCount-1;
  end;

  // close all forms except the MainForm
  i:=Screen.CustomFormCount-1;
  while i>=0 do begin
    AForm:=Screen.CustomForms[i];
    if (AForm<>Application.MainForm) and not AForm.IsParentOf(Application.MainForm)
    then begin
      AControl:=AForm;
      while (AControl.Parent<>nil)
      and (AControl.Parent<>Application.MainForm) do begin
        AControl:=AControl.Parent;
        if AControl is TCustomForm then AForm:=TCustomForm(AControl);
      end;
      AForm.Close;
    end;
    dec(i);
    if i>=Screen.CustomFormCount then i:=Screen.CustomFormCount-1;
  end;
end;

function IDEWindowsGlobalOptions: TIDEWindowsGlobalOptions;
begin
  if not Assigned(FIDEWindowsGlobalOptions) then
    FIDEWindowsGlobalOptions := TIDEWindowsGlobalOptions.Create;

  Result := FIDEWindowsGlobalOptions;
end;

procedure Register;
begin
  RegisterComponents('Misc',[TIDEDialogLayoutStorage]);
end;

{ TIDEWindowsGlobalOptions }

procedure TIDEWindowsGlobalOptions.Add(const aFormIDPrefix: string;
  CanSetVisibility: Boolean);
var
  xIndex: Integer;
begin
  xIndex := FList.Add(aFormIDPrefix);
  if FList.Objects[xIndex] = nil then
    FList.Objects[xIndex] := TIDEWindowGlobalOption.Create;

  TIDEWindowGlobalOption(FList.Objects[xIndex]).CanSetVisibility := CanSetVisibility;
end;

function TIDEWindowsGlobalOptions.CanSetVisibility(const aFormID: string
  ): Boolean;
var
  I: Integer;
begin
  for I := 0 to FList.Count-1 do
  if Copy(aFormID, 1, Length(FList[I])) = FList[I] then
  begin
    Result := TIDEWindowGlobalOption(FList.Objects[I]).CanSetVisibility;
    Exit;
  end;
  Result := True;//default is true
end;

constructor TIDEWindowsGlobalOptions.Create;
begin
  inherited Create;

  FList := TStringList.Create;
  FList.Sorted := True;
  FList.OwnsObjects := True;
end;

destructor TIDEWindowsGlobalOptions.Destroy;
begin
  FList.Free;
  inherited Destroy;
end;

{ TSimpleWindowLayoutDividerPosList }

function TSimpleWindowLayoutDividerPosList.GetItems(Index: Integer): TSimpleWindowLayoutDividerPos;
begin
  Result := TSimpleWindowLayoutDividerPos(FList[Index]);
end;

function TSimpleWindowLayoutDividerPosList.GetNamedItems(Index: Integer): TSimpleWindowLayoutDividerPos;
var
  i: Integer;
begin
  Result := nil;
  i := 0;
  while i < Count do begin
    if Items[i].DisplayName <> '' then begin
      if Index = 0 then begin
        Result := Items[i];
        exit;
      end;
      dec(Index);
    end;
    inc(i);
  end;
end;

procedure TSimpleWindowLayoutDividerPosList.Merge(AnItem: TSimpleWindowLayoutDividerPos);
var
  i: Integer;
  old: TSimpleWindowLayoutDividerPos;
begin
  i := IndexOf(AnItem.IdString);
  FList.Add(AnItem);
  if i < 0 then
    exit;

  old := Items[i];
  if AnItem.Id < 0 then
    AnItem.SetId(old.Id);
  if AnItem.DisplayName = '' then
    AnItem.SetDisplayName(old.FDisplayName);
  if AnItem.DefaultSize < 0 then
    AnItem.DefaultSize := old.DefaultSize;

  FList.Remove(old);
  old.Free;
end;

constructor TSimpleWindowLayoutDividerPosList.Create;
begin
  FList := TList.Create;
end;

destructor TSimpleWindowLayoutDividerPosList.Destroy;
begin
  Clear;
  inherited Destroy;
  FreeAndNil(FList);
end;

procedure TSimpleWindowLayoutDividerPosList.Assign(ADividerPosList: TSimpleWindowLayoutDividerPosList);
var
  i: Integer;
  tmp: TSimpleWindowLayoutDividerPos;
begin
  Clear;
  for i := 0 to ADividerPosList.Count - 1 do begin
    tmp := Add('');
    tmp.Assign(ADividerPosList[i]);
  end;
end;

procedure TSimpleWindowLayoutDividerPosList.LoadFromConfig(Config: TConfigStorage;
  const Path: string);
var
  tmp: TSimpleWindowLayoutDividerPos;
  c, i: Integer;
begin
  ClearItems;
  c := Config.GetValue(Path+'Count', 0);
  for i := 0 to c - 1 do begin
    tmp := TSimpleWindowLayoutDividerPos.Create('');
    tmp.LoadFromConfig(Config, Path + 'Item' + IntToStr(i) + '/');
    Merge(tmp);
  end;
end;

procedure TSimpleWindowLayoutDividerPosList.SaveToConfig(Config: TConfigStorage;
  const Path: string);
var
  i, c: Integer;
begin
  c := 0;
  for i := 0 to Count - 1 do
    if Items[i].SaveToConfig(Config, Path + 'Item' + IntToStr(c) + '/') then
      inc(c);
  for i := c to Count - 1 do
    Config.DeletePath(Path + 'Item' + IntToStr(i) + '/');
  Config.SetDeleteValue(Path+'Count', c, 0);
end;

procedure TSimpleWindowLayoutDividerPosList.Clear;
begin
  while FList.Count > 0 do begin
    TObject(FList[0]).Free;
    FList.Delete(0);
  end;
end;

procedure TSimpleWindowLayoutDividerPosList.ClearItems;
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
    Items[i].Clear;
end;

function TSimpleWindowLayoutDividerPosList.Add(AnIdString: String;
  AnId: Integer; ADisplayName: PString): TSimpleWindowLayoutDividerPos;
var
  i: Integer;
begin
  i := IndexOf(AnId);
  if i < 0
  then begin
    Result := TSimpleWindowLayoutDividerPos.Create(AnIdString, AnId, ADisplayName);
    FList.Add(Result);
  end
  else begin
    Result := Items[i];
    if ADisplayName = nil then
      Result.SetDisplayName(ADisplayName);
  end
end;

function TSimpleWindowLayoutDividerPosList.Add(AnIdString: String): TSimpleWindowLayoutDividerPos;
begin
  Result := Add(AnIdString, -1, nil);
end;

function TSimpleWindowLayoutDividerPosList.Count: Integer;
begin
  Result := FList.Count;
end;

function TSimpleWindowLayoutDividerPosList.NamedCount: Integer;
var
  i: Integer;
begin
  Result := 0;
  i := Count - 1;
  while i >= 0 do begin
    if Items[i].DisplayName <> '' then inc(Result);
    dec(i);
  end;
end;

function TSimpleWindowLayoutDividerPosList.Find(AnId: Integer): TSimpleWindowLayoutDividerPos;
var
  i: Integer;
begin
  Result := nil;
  i := IndexOf(AnId);
  if i >= 0 then
    Result := Items[i];
end;

function TSimpleWindowLayoutDividerPosList.Find(AnIdString: String): TSimpleWindowLayoutDividerPos;
var
  i: Integer;
begin
  Result := nil;
  i := IndexOf(AnIdString);
  if i >= 0 then
    Result := Items[i];
end;

function TSimpleWindowLayoutDividerPosList.IndexOf(AnId: Integer): Integer;
begin
  Result := Flist.Count-1;
  while Result >= 0 do begin
    if Items[Result].Id = AnId then
      exit;
    dec(Result);
  end;
end;

function TSimpleWindowLayoutDividerPosList.IndexOf(AnIdString: String): Integer;
begin
  Result := Flist.Count-1;
  while Result >= 0 do begin
    if Items[Result].IdString = AnIdString then
      exit;
    dec(Result);
  end;
end;

function TSimpleWindowLayoutDividerPosList.IndexOf(AnItem: TSimpleWindowLayoutDividerPos): Integer;
begin
  Result := FList.IndexOf(AnItem);
end;

{ TSimpleWindowLayoutDividerPos }

function TSimpleWindowLayoutDividerPos.GetDisplayName: String;
begin
  if FDisplayName = nil then
    Result := ''
  else
    Result := FDisplayName^;
end;

procedure TSimpleWindowLayoutDividerPos.SetDisplayName(ADisplayName: PString);
begin
  FDisplayName := ADisplayName;
end;

procedure TSimpleWindowLayoutDividerPos.SetId(AnId: Integer);
begin
  FId := AnId;
end;

constructor TSimpleWindowLayoutDividerPos.Create(AnIdString: String);
begin
  Create(AnIdString, -1, nil);
end;

constructor TSimpleWindowLayoutDividerPos.Create(AnIdString: String; AnId: Integer;
  ADisplayName: PString);
begin
  FDefaultSize := -1;
  Clear;
  FId := AnId;
  FIdString := AnIdString;
  FDisplayName := ADisplayName;
end;

procedure TSimpleWindowLayoutDividerPos.Assign(ADividerPos: TSimpleWindowLayoutDividerPos);
begin
  FDefaultSize := ADividerPos.FDefaultSize;
  FDisplayName := ADividerPos.FDisplayName;
  FId          := ADividerPos.FId;
  FIdString    := ADividerPos.FIdString;
  FPlacement   := ADividerPos.FPlacement;
  FSize        := ADividerPos.FSize;
end;

procedure TSimpleWindowLayoutDividerPos.LoadFromConfig(Config: TConfigStorage;
  const Path: string);
var
  s: String;
begin
  Clear;
  FIdString := Config.GetValue(Path+'ID', '');
  FSize := Config.GetValue(Path+'Size', -1);
  s := Config.GetValue(Path+'Placement', 'iwpdUseWindowSetting');
  try
    ReadStr(s, FPlacement);
  except
    FPlacement := iwpdUseWindowSetting;
  end;
end;

function TSimpleWindowLayoutDividerPos.SaveToConfig(Config: TConfigStorage;
  const Path: string): Boolean;
var
  s: String;
begin
  Result := (FSize <> -1) or (FPlacement <> iwpdUseWindowSetting);
  if not Result then
    exit;
  WriteStr(s, FPlacement);
  Config.SetDeleteValue(Path+'ID', FIdString, '');
  Config.SetDeleteValue(Path+'Size', FSize, -1);
  Config.SetDeleteValue(Path+'Placement', s, 'iwpdUseWindowSetting');
end;

procedure TSimpleWindowLayoutDividerPos.Clear;
begin
  FSize := FDefaultSize;
  FPlacement := iwpdUseWindowSetting;
end;

{ TIDEDialogLayout }

procedure TIDEDialogLayout.SetList(const AValue: TIDEDialogLayoutList);
begin
  if FList=AValue then exit;
  FList:=AValue;
  if (List<>nil) and Modified then List.Modified:=true;
end;

procedure TIDEDialogLayout.SetModified(const AValue: boolean);
begin
  FModified:=AValue;
  if FModified and (FList<>nil) then FList.Modified:=true;
end;

procedure TIDEDialogLayout.SetWidth(const AValue: integer);
begin
  if FWidth=AValue then exit;
  FWidth:=AValue;
  Modified:=true;
end;

procedure TIDEDialogLayout.SetHeight(const AValue: integer);
begin
  if FHeight=AValue then exit;
  FHeight:=AValue;
  Modified:=true;
end;

constructor TIDEDialogLayout.Create(const TheName: string;
  TheList: TIDEDialogLayoutList);
begin
  FName:=TheName;
  FList:=TheList;
end;

procedure TIDEDialogLayout.Assign(Source: TIDEDialogLayout);
begin
  FName := Source.FName;
  FWidth := Source.FWidth;
  FHeight := Source.FHeight;
  FModified := True;
end;

function TIDEDialogLayout.SizeValid: boolean;
begin
  Result:=(Width>10) and (Height>10);
end;

procedure TIDEDialogLayout.LoadFromConfig(Config: TConfigStorage; const Path: string);
begin
  FName:=Config.GetValue(Path+'Name/Value','');
  FWidth:=Config.GetValue(Path+'Size/Width',0);
  FHeight:=Config.GetValue(Path+'Size/Height',0);
  Modified:=false;
end;

procedure TIDEDialogLayout.SaveToConfig(Config: TConfigStorage; const Path: string);
begin
  Config.SetValue(Path+'Name/Value',Name);
  Config.SetValue(Path+'Size/Width',Width);
  Config.SetValue(Path+'Size/Height',Height);
  Modified:=false;
end;

{ TIDEDialogLayoutList }

function TIDEDialogLayoutList.GetItems(Index: integer): TIDEDialogLayout;
begin
  Result:=TIDEDialogLayout(FItems[Index]);
end;

procedure TIDEDialogLayoutList.SetModified(const AValue: boolean);
begin
  if FModified=AValue then exit;
  FModified:=AValue;
end;

constructor TIDEDialogLayoutList.Create;
begin
  inherited Create;
  FItems:=TList.Create;
  FItemClass:=TIDEDialogLayout;
end;

destructor TIDEDialogLayoutList.Destroy;
begin
  Clear;
  FreeAndNil(FItems);
  inherited Destroy;
end;

procedure TIDEDialogLayoutList.Assign(Source: TIDEDialogLayoutList);
var
  i: Integer;
  Layout: TIDEDialogLayout;
begin
  FItemClass := Source.FItemClass;
  Clear;
  for i:=0 to Source.FItems.Count-1 do begin
    Layout := TIDEDialogLayout.Create(Source.Items[i].Name, Self);
    Layout.Assign(Source.Items[i]);
    FItems.Add(Layout);
  end;
  FModified := True;
end;

procedure TIDEDialogLayoutList.ApplyLayout(ADialog: TControl;
  DefaultWidth, DefaultHeight: integer; UseAsMin: boolean);
var
  ALayout: TIDEDialogLayout;
  NewWidth, NewHeight: integer;
begin
  if (ADialog=nil) or (Self=nil) then exit;
  ALayout:=Find(ADialog,true);
  //debugln(['TIDEDialogLayoutList.ApplyLayout ',ALayout.Name,' ',ALayout.SizeValid,' ',ALayout.Width,',',ALayout.Height]);
  if ALayout.SizeValid then begin
    NewWidth:=ALayout.Width;
    NewHeight:=ALayout.Height;
  end else begin
    NewWidth:=DefaultWidth;
    NewHeight:=DefaultHeight;
  end;
  if UseAsMin then begin
    if NewWidth<DefaultWidth then NewWidth:=DefaultWidth;
    if NewHeight<DefaultHeight then NewHeight:=DefaultHeight;
  end;
  ADialog.SetBounds(ADialog.Left,ADialog.Top,NewWidth,NewHeight);
end;

procedure TIDEDialogLayoutList.ApplyLayout(ADialog: TControl);
begin
  ApplyLayout(ADialog,ADialog.Width,ADialog.Height);
end;

procedure TIDEDialogLayoutList.SaveLayout(ADialog: TControl);
var
  ALayout: TIDEDialogLayout;
begin
  if (ADialog=nil) or (Self=nil) then exit;
  ALayout:=Find(ADialog,true);
  ALayout.Width:=ADialog.Width;
  ALayout.Height:=ADialog.Height;
end;

procedure TIDEDialogLayoutList.Clear;
var i: integer;
begin
  for i:=0 to FItems.Count-1 do
    Items[i].Free;
  FItems.Clear;
end;

function TIDEDialogLayoutList.Count: integer;
begin
  Result:=FItems.Count;
end;

function TIDEDialogLayoutList.Find(const DialogName: string;
  CreateIfNotExists: boolean): TIDEDialogLayout;
var i: integer;
begin
  i:=IndexOf(DialogName);
  if (i<0) then begin
    if CreateIfNotExists then begin
      Result:=FItemClass.Create(DialogName,Self);
      FItems.Add(Result);
    end else begin
      Result:=nil;
    end;
  end else begin
    Result:=Items[i];
  end;
end;

function TIDEDialogLayoutList.Find(ADialog: TObject; CreateIfNotExists: boolean
  ): TIDEDialogLayout;
begin
  if ADialog<>nil then begin
    Result:=Find(ADialog.ClassName,CreateIfNotExists);
  end else begin
    Result:=nil;
  end;
end;

function TIDEDialogLayoutList.IndexOf(const DialogName: string): integer;
begin
  Result:=Count-1;
  while (Result>=0) and (CompareText(DialogName,Items[Result].Name)<>0) do
    dec(Result);
end;

procedure TIDEDialogLayoutList.LoadFromConfig(Config: TConfigStorage; const Path: string);
var
  NewCount, i: integer;
  NewDialogLayout: TIDEDialogLayout;
begin
  Clear;
  NewCount:=Config.GetValue(Path+'Count',0);
  for i:=0 to NewCount-1 do begin
    NewDialogLayout:=FItemClass.Create('',Self);
    FItems.Add(NewDialogLayout);
    NewDialogLayout.LoadFromConfig(Config,Path+'Dialog'+IntToStr(i+1)+'/');
  end;
  Modified:=false;
end;

procedure TIDEDialogLayoutList.SaveToConfig(Config: TConfigStorage; const Path: string);
var i: integer;
begin
  Config.SetDeleteValue(Path+'Count',Count,0);
  for i:=0 to Count-1 do
    Items[i].SaveToConfig(Config,Path+'Dialog'+IntToStr(i+1)+'/');
  Modified:=false;
end;

{ TIDEDialogLayoutStorage }

procedure TIDEDialogLayoutStorage.OnCreateForm(Sender: TObject);
begin
  IDEDialogLayoutList.ApplyLayout(Sender as TControl);
end;

procedure TIDEDialogLayoutStorage.OnCloseForm(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  IDEDialogLayoutList.SaveLayout(Sender as TControl);
end;

constructor TIDEDialogLayoutStorage.Create(TheOwner: TComponent);
var
  Form: TCustomForm;
begin
  inherited Create(TheOwner);
  if Owner is TCustomForm then
  begin
    Form:=TCustomForm(Owner);
    Form.AddHandlerCreate(@OnCreateForm);
    Form.AddHandlerClose(@OnCloseForm);
  end;
end;

destructor TIDEDialogLayoutStorage.Destroy;
var
  Form: TCustomForm;
begin
  if Owner is TCustomForm then
  begin
    Form:=TCustomForm(Owner);
    Form.RemoveAllHandlersOfObject(Self);
  end;
  inherited Destroy;
end;

{ TSimpleWindowLayout }

constructor TSimpleWindowLayout.Create(AFormID: string);
var
  Creator: TIDEWindowCreator;
begin
  inherited Create(nil);
  FDividers := TSimpleWindowLayoutDividerPosList.Create;
  FormID := AFormID;
  fDefaultWindowPlacement := iwpRestoreWindowGeometry;
  Clear;
  Creator := IDEWindowCreators.FindWithName(AFormID);
  if Creator <> nil then
    Creator.InitSimpleLayout(Self);
end;

destructor TSimpleWindowLayout.Destroy;
begin
  Form:=nil;
  inherited Destroy;
  FDividers.Free;
end;

procedure TSimpleWindowLayout.LoadFromConfig(Config: TConfigStorage;
  const Path: string; FileVersion: integer);
var
  P: string;
  DefaultValue: TIDEWindowPlacement;
begin
  // set all values to default
  Clear;
  // read settings
  // build path
  P:=FormID;
  if P='' then exit;
  P:=Path+P+'/';
  FFormCaption := Config.GetValue(P+'Caption/Value', fFormID);
  // placement
  if FileVersion=1 then
    DefaultValue:=iwpRestoreWindowSize
  else
    DefaultValue:=iwpRestoreWindowGeometry;
  fWindowPlacement:=StrToIDEWindowPlacement(Config.GetValue(
    P+'WindowPlacement/Value',IDEWindowPlacementNames[DefaultValue]));
  // custom position
  Left := Config.GetValue(P+'CustomPosition/Left', Left);
  Top := Config.GetValue(P+'CustomPosition/Top', Top);
  Width := Config.GetValue(P+'CustomPosition/Width', Width);
  Height := Config.GetValue(P+'CustomPosition/Height', Height);
  // state
  fWindowState:=StrToIDEWindowState(Config.GetValue(
    P+'WindowState/Value',IDEWindowStateNames[iwsNormal]));
  FVisible:=Config.GetValue(P+'Visible/Value',false);
  //debugln(['TSimpleWindowLayout.LoadFromConfig ',FormID,' ',Left,',',Top,',',Width,',',Height]);
  FDividers.LoadFromConfig(Config, P + 'Divider/');
end;

procedure TSimpleWindowLayout.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if Operation=opRemove then begin
    if fForm=AComponent then begin
      fForm:=nil;
      Applied:=false;
    end;
  end;
end;

procedure TSimpleWindowLayout.SaveToConfig(Config: TConfigStorage;
  const Path: string);
var
  P: string;
begin
  // build path
  P:=FormID;
  if P='' then exit;
  P:=Path+P+'/';
  Config.SetDeleteValue(P+'Caption/Value',FFormCaption,'');
  // placement
  Config.SetDeleteValue(P+'WindowPlacement/Value',
    IDEWindowPlacementNames[fWindowPlacement],
    IDEWindowPlacementNames[iwpRestoreWindowGeometry]);
  // custom position
  Config.SetDeleteValue(P+'CustomPosition/Left', Left, 0);
  Config.SetDeleteValue(P+'CustomPosition/Top', Top, 0);
  Config.SetDeleteValue(P+'CustomPosition/Width', Width, 0);
  Config.SetDeleteValue(P+'CustomPosition/Height', Height, 0);
  // state
  Config.SetDeleteValue(P+'WindowState/Value',IDEWindowStateNames[fWindowState],IDEWindowStateNames[iwsNormal]);
  Config.SetDeleteValue(P+'Visible/Value',FVisible,false);
  FDividers.SaveToConfig(Config, P + 'Divider/');
end;

procedure TSimpleWindowLayout.SetDefaultPosition(const AForm: TCustomForm);
begin
  FDefaultLeft := AForm.Left;
  FDefaultTop := AForm.Top;
  FDefaultWidth := AForm.Width;
  FDefaultHeight := AForm.Height;
end;

procedure TSimpleWindowLayout.OnFormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  GetCurrentPosition;
end;

function TSimpleWindowLayout.CustomCoordinatesAreValid: boolean;
begin
  Result:=(Width>0) and (Height>0); // and (Left>10-Width) and (Top>10-Height);
end;

function TSimpleWindowLayout.DefaultCoordinatesAreValid: boolean;
begin
  Result:=(DefaultWidth>0) and (DefaultHeight>0);
end;

procedure TSimpleWindowLayout.CloseForm;
begin
  GetCurrentPosition;
end;

function TSimpleWindowLayout.ValidateAndSetCoordinates(const aForce: Boolean
  ): Boolean;
var
  i: Integer;
  NewBounds: TRect;
  xForm: TCustomForm;
begin
  Result := False;
  xForm := Form;
  if Assigned(xForm) and
     (aForce or CustomCoordinatesAreValid) then
  begin
    if not CustomCoordinatesAreValid then//default position
    begin
      if not DefaultCoordinatesAreValid then//don't change the coordinates if default position is invalid
        Exit;
      NewBounds := Bounds(DefaultLeft, DefaultTop, DefaultWidth, DefaultHeight)
    end else// explicit position
      NewBounds := Bounds(Left, Top, Width, Height);

    // set minimum size
    if NewBounds.Right - NewBounds.Left < 60 then
      NewBounds.Right := NewBounds.Left + 60;
    if NewBounds.Bottom - NewBounds.Top < 60 then
      NewBounds.Bottom := NewBounds.Top + 60;

    // Move to visible area :
    // window is out at left side of screen
    if NewBounds.Right < Screen.DesktopLeft + 60 then
      OffsetRect(NewBounds, Screen.DesktopLeft + 60 - NewBounds.Right, 0);

    // window is out above the screen
    if NewBounds.Bottom < Screen.DesktopTop+60 then
      OffsetRect(NewBounds, 0, Screen.DesktopTop + 60 - NewBounds.Bottom);

    // window is out at right side of screen, i = right edge of screen - 60
    i := Screen.DesktopWidth + Screen.DesktopLeft - 60;
    if NewBounds.Left > i then begin
      NewBounds.Left := i;
      NewBounds.Right := NewBounds.Right + i - NewBounds.Left;
    end;

    // window is out below the screen, i = bottom edge of screen - 60
    i := Screen.DesktopHeight + Screen.DesktopTop - 60;
    if NewBounds.Top > i then begin
      NewBounds.Top := i;
      NewBounds.Bottom := NewBounds.Bottom + i - NewBounds.Top;
    end;

    if xForm.WindowState = wsNormal then
      xForm.SetBounds(NewBounds.Left, NewBounds.Top,
                      NewBounds.Right - NewBounds.Left,
                      NewBounds.Bottom - NewBounds.Top)
    else
      xForm.SetRestoredBounds(NewBounds.Left, NewBounds.Top,
                      NewBounds.Right - NewBounds.Left,
                      NewBounds.Bottom - NewBounds.Top);
    Result := True;
  end;
end;

function TSimpleWindowLayout.FormBaseID(out SubIndex: Integer): String;
var
  i: Integer;
begin
  Result := FormID;
  SubIndex := -1;
  i := length(Result);
  while (i > 0) and (Result[i] in ['0'..'9']) do
    dec(i);
  if (i < 1) or (i = length(Result)) then
    exit;
  SubIndex := StrToInt(copy(Result, i+1, length(Result)));
  Result := copy(Result, 1, i);
end;

procedure TSimpleWindowLayout.SetForm(const AForm: TCustomForm);
begin
  if fForm=AForm then exit;
  if Assigned(Form) then
  begin
    RemoveFreeNotification(Form);
    Form.RemoveHandlerClose(@OnFormClose);
  end;
  fForm:=AForm;
  if Assigned(Form) then
  begin
    Assert(Form.Name <> '');
    fFormID := Form.Name;
    FFormCaption := Form.Caption;
    FreeNotification(Form);
    Form.AddHandlerClose(@OnFormClose);
    Applied:=false;
  end;
end;

function TSimpleWindowLayout.GetFormCaption: string;
begin
  if Form<>nil then
    FFormCaption:=Form.Caption
  else if FFormCaption='' then
    FFormCaption:=FormID;
  Result:=FFormCaption;
end;

procedure TSimpleWindowLayout.Clear;
begin
  //debugln(['TSimpleWindowLayout.Clear ',FormID]);
  fApplied := False;
  fVisible := False;
  fWindowPlacement:=fDefaultWindowPlacement;
  fLeft:=0;
  fTop:=0;
  fWidth:=0;
  fHeight:=0;
  fWindowState:=iwsNormal;
  FDividers.ClearItems;
end;

procedure TSimpleWindowLayout.ReadCurrentCoordinates;
var
  p: TPoint;
  xForm: TCustomForm;
begin
  xForm := Form;
  if (xForm<>nil) then
  begin
    if xForm.Parent<>nil then
      p:=xForm.ClientOrigin
    else
      p:=Point(0,0);
    if (xForm.WindowState=wsNormal) then
    begin
      Left:=xForm.Left+p.X;
      Top:=xForm.Top+p.Y;
      Width:=xForm.Width;
      Height:=xForm.Height;
    end else
    begin
      Left:=xForm.RestoredLeft+p.X;
      Top:=xForm.RestoredTop+p.Y;
      Width:=xForm.RestoredWidth;
      Height:=xForm.RestoredHeight;
    end;
  end;
end;

procedure TSimpleWindowLayout.ReadCurrentState;
begin
  Visible:=(Form<>nil) and Form.IsVisible;
  if Form<>nil then begin
    case Form.WindowState of
    wsMinimized: fWindowState:=iwsMinimized;
    wsMaximized: fWindowState:=iwsMaximized;
    else
      fWindowState:=iwsNormal;
    end;
  end;
end;

procedure TSimpleWindowLayout.Assign(Layout: TSimpleWindowLayout);
begin
  Clear;
  Assert(FFormID = Layout.FFormID);
  //IMPORTANT: do not assign FForm and FFormID!
  FVisible:=Layout.FVisible;
  FWindowPlacement:=Layout.FWindowPlacement;
  FLeft:=Layout.FLeft;
  FTop:=Layout.FTop;
  FWidth:=Layout.FWidth;
  FHeight:=Layout.FHeight;
  FWindowState:=Layout.FWindowState;
  FDefaultWindowPlacement:=Layout.FDefaultWindowPlacement;
  FDividers.Assign(Layout.FDividers);
end;

procedure TSimpleWindowLayout.ReadCurrentDividers(AForce: Boolean = False);
var
  i, j: Integer;
  f: Boolean;
  Creator: TIDEWindowCreator;
  xForm: TCustomForm;
begin
  Creator:=IDEWindowCreators.FindWithName(FormID);
  if (Creator = nil) or (Creator.OnGetDividerSize = nil) then exit;
  xForm := Form;
  if xForm = nil then exit;
  for i := 0 to FDividers.Count - 1 do begin
    if FDividers[i].FId < 0 then continue;
    f := AForce;
    case FDividers[i].Placement of
      iwpdRestore:
        f := true;
      iwpdUseWindowSetting:
        f := WindowPlacement in [iwpRestoreWindowGeometry, iwpRestoreWindowSize];
    end;
    if f then begin
      j:=-1;
      if Creator.OnGetDividerSize(xForm, FDividers[i].Id, j) then
        FDividers[i].Size := j
      else
        FDividers[i].Size := -1; // Default / Not Changed / Unavailable
    end;
  end;
end;

procedure TSimpleWindowLayout.GetCurrentPosition;
begin
  //debugln('TSimpleWindowLayout.GetCurrentPosition ',DbgSName(Self),' ',FormID,' ',IDEWindowPlacementNames[WindowPlacement]);
  case WindowPlacement of
  iwpRestoreWindowGeometry, iwpRestoreWindowSize:
    ReadCurrentCoordinates;
  end;
  ReadCurrentDividers;
  ReadCurrentState;
  //debugln('TSimpleWindowLayout.GetCurrentPosition ',DbgSName(Self),' ',FormID,' Width=',dbgs(Width));
end;

function TSimpleWindowLayout.Apply(const aForce: Boolean): Boolean;
var
  xForm: TCustomForm;
begin
  Result := False;
  xForm := Form;
  if xForm = nil then exit;
  Applied:=true;
  {$IFDEF VerboseIDEDocking}
  debugln(['TSimpleWindowLayoutList.ApplyAndShow restore ',
          FormID,' ',IDEWindowPlacementNames[WindowPlacement],
          ' ',Left,',',Top,',',Width,',',Height]);
  {$ENDIF}

  case WindowPlacement of
  iwpCustomPosition,iwpRestoreWindowGeometry:
    begin
      //DebugLn(['TMainIDE.OnApplyWindowLayout ',IDEWindowStateNames[WindowState]]);
      case WindowState of
        iwsMinimized: xForm.WindowState:=wsMinimized;
        iwsMaximized: xForm.WindowState:=wsMaximized;
      end;
      Result := ValidateAndSetCoordinates(aForce);     // Adjust bounds to screen area and apply them.
      if WindowState in [iwsMinimized, iwsMaximized] then
        Result := True;
    end;
  iwpUseWindowManagerSetting:
    Result := True;
  end;

  ApplyDivider(aForce);
end;

procedure TSimpleWindowLayout.ApplyDivider(AForce: Boolean = False);
var
  i: Integer;
  f: Boolean;
  Creator: TIDEWindowCreator;
begin
  Creator:=IDEWindowCreators.FindWithName(FormID);
  if (Creator <> nil) and (Creator.OnSetDividerSize <> nil) then begin
    for i := 0 to FDividers.Count - 1 do begin
      if (FDividers[i].FId < 0) or (FDividers[i].Size < 0) then continue;
      f := AForce;
      case FDividers[i].Placement of
        iwpdRestore, iwpdCustomSize:
          f := true;
        iwpdUseWindowSetting:
          f := WindowPlacement in [iwpRestoreWindowGeometry, iwpRestoreWindowSize];
      end;
      if f then
        Creator.OnSetDividerSize(Form, FDividers[i].Id, FDividers[i].Size);
    end;
  end;
end;

{ TSimpleWindowLayoutList }

procedure TSimpleWindowLayoutList.Clear;
var
  i: integer;
begin
  for i:=0 to Count-1 do Items[i].Free;
  fItems.Clear;
end;

procedure TSimpleWindowLayoutList.Delete(Index: Integer);
begin
  Items[Index].Free;
  fItems.Delete(Index);
end;

function TSimpleWindowLayoutList.GetItems(Index: Integer): TSimpleWindowLayout;
begin
  Result:=TSimpleWindowLayout(fItems[Index]);
end;

procedure TSimpleWindowLayoutList.OnScreenRemoveForm(Sender: TObject;
  AForm: TCustomForm);
begin
  CloseForm(AForm);
end;

constructor TSimpleWindowLayoutList.Create(ARegisterEventHandlers: Boolean);
begin
  fItems:=TFPList.Create;
  fRegisterEventHandlers := ARegisterEventHandlers;
  if ARegisterEventHandlers then
    Screen.AddHandlerRemoveForm(@OnScreenRemoveForm);
end;

destructor TSimpleWindowLayoutList.Destroy;
begin
  Screen.RemoveHandlerRemoveForm(@OnScreenRemoveForm);
  Clear;
  FreeAndNil(fItems);
  inherited Destroy;
end;

function TSimpleWindowLayoutList.IndexOf(const FormID: string): integer;
begin
  Result:=Count-1;
  while (Result>=0) and (FormID<>Items[Result].FormID) do dec(Result);
end;

function TSimpleWindowLayoutList.IndexOf(const AForm: TCustomForm): integer;
begin
  Result:=Count-1;
  while (Result>=0) and (AForm<>Items[Result].Form) do dec(Result);
end;

procedure TSimpleWindowLayoutList.LoadFromConfig(Config: TConfigStorage; const Path: string);
// do not clear, just add/replace the values from the config
var
  i: integer;
  ID: String;
  xLayoutIndex, FileVersion: Integer;
begin
  FileVersion:=Config.GetValue(Path+'Desktop/Version', IDEWndCfgFileVersion);
  // create new windows
  i := Config.GetValue(Path+'Desktop/FormIdCount', 0);
  //debugln(['TSimpleWindowLayoutList.LoadFromConfig ',i]);
  while i > 0 do begin
    ID := Config.GetValue(Path+'Desktop/FormIdList/a'+IntToStr(i), '');
    //debugln(['TSimpleWindowLayoutList.LoadFromConfig ',i,' ',ID]);
    if (ID <> '') and IsValidIdent(ID) then
    begin
      xLayoutIndex := IndexOf(ID);
      if (xLayoutIndex = -1) then
      begin
        CreateWindowLayout(ID);
        xLayoutIndex := Count-1;
      end;
      fItems.Move(xLayoutIndex, 0);
    end;
    dec(i);
  end;

  for i:=0 to Count-1 do
    Items[i].LoadFromConfig(Config,Path,FileVersion);
end;

procedure TSimpleWindowLayoutList.SaveToConfig(Config: TConfigStorage; const Path: string);
var
  i: integer;
begin
  Config.SetValue(Path+'Desktop/Version', IDEWndCfgFileVersion);
  Config.SetDeleteValue(Path+'Desktop/FormIdCount',Count,0);
  //debugln(['TSimpleWindowLayoutList.SaveToConfig ',Count]);
  for i:=0 to Count-1 do begin
    Config.SetDeleteValue(Path+'Desktop/FormIdList/a'+IntToStr(i+1),Items[i].FormID,'');
    Items[i].SaveToConfig(Config,Path);
  end;
end;

procedure TSimpleWindowLayoutList.SetDefaultPosition(const AForm: TCustomForm);
var
  xLayout: TSimpleWindowLayout;
begin
  if AForm.Name <> '' then
  begin
    xLayout:=ItemByFormID(AForm.Name);
    if xLayout<>nil then
      xLayout.SetDefaultPosition(AForm);
  end;
end;

function TSimpleWindowLayoutList.Count: integer;
begin
  Result:=fItems.Count;
end;

function TSimpleWindowLayoutList.ItemByForm(AForm: TCustomForm): TSimpleWindowLayout;
var
  i: integer;
begin
  i:=IndexOf(AForm);
  if i>=0 then
    Result:=Items[i]
  else
    Result:=nil;
end;

function TSimpleWindowLayoutList.ItemByFormID(const FormID: string): TSimpleWindowLayout;
var
  i: integer;
begin
  i:=IndexOf(FormID);
  if i>=0 then
    Result:=Items[i]
  else
    Result:=nil;
end;

function TSimpleWindowLayoutList.ItemByFormCaption(const aFormCaption: string): TSimpleWindowLayout;
var
  i: integer;
begin
  i := Count - 1;
  while i >= 0 do begin
    Result := Items[i];
    if Result.FormCaption = aFormCaption then
      exit;
    dec(i);
  end;
  Result:=nil;
end;

procedure TSimpleWindowLayoutList.CloseForm(AForm: TCustomForm);
var
  ALayout: TSimpleWindowLayout;
begin
  ALayout:=ItemByForm(AForm);
  if ALayout<>nil then
    ALayout.CloseForm;
end;

procedure TSimpleWindowLayoutList.ApplyAndShow(Sender: TObject; AForm: TCustomForm;
  BringToFront: boolean; AMoveToVisbleMode: TLayoutMoveToVisbleMode);

  function IsFormMovable(AForm: TCustomForm; BorderX: Integer = 100;
    BorderTop: Integer = 0; BorderBottom: Integer = 50): Boolean;
  var
    I: Integer;
    xFormRect, xWA, xRectLT, xRectRT, xRectCT: TRect;
  begin
    xFormRect := AForm.BoundsRect;
    Result := False;
    for I := 0 to Screen.MonitorCount-1 do
    begin
      xWA := Screen.Monitors[I].WorkareaRect;
      //the user must be able to move the window
      // - that means we check the topleft, topcenter and topright coordinates of the form
      //   if they are movable
      xRectLT := Rect(xWA.Left-BorderX, xWA.Top-BorderTop, xWA.Right-BorderX, xWA.Bottom-BorderBottom);
      xRectCT := Rect(xWA.Left, xWA.Top-BorderTop, xWA.Right, xWA.Bottom-BorderBottom);
      xRectRT := Rect(xWA.Left+BorderX, xWA.Top-BorderTop, BorderX, xWA.Bottom-BorderBottom);
      if PtInRect(xRectLT, xFormRect.TopLeft)
      or PtInRect(xRectCT, Point((xFormRect.Left+xFormRect.Right) div 2, AForm.Top))
      or PtInRect(xRectRT, Point(xFormRect.Right, AForm.Top))
      then
        Exit(True);
    end;
  end;

var
  ALayout: TSimpleWindowLayout;
  NewBounds: TRect;
  Creator: TIDEWindowCreator;
  DockSiblingName: string;
  DockAlign: TAlign;
  DockSibling: TCustomForm;
  DockSiblingBounds: TRect;
  Offset: TPoint;
  ARestoreVisible: Boolean;
begin
  {$IFDEF VerboseIDEDocking}
  debugln(['TSimpleWindowLayoutList.ApplyAndShow Form=',DbgSName(AForm),' ',BringToFront,
           '  DesktopWidth=', Screen.DesktopWidth, '  DesktopHeight=', Screen.DesktopHeight]);
  {$ENDIF}
  try
    ALayout:=ItemByFormID(AForm.Name);
    if ALayout<>nil then
    begin
      ALayout.Form:=AForm;
      if ALayout.Applied then exit;
      if ALayout.Apply then exit;
    end;

    {$IFDEF VerboseIDEDocking}
    debugln(['TSimpleWindowLayoutList.ApplyAndShow no stored layout found, layout registered=',
            ALayout<>nil,' AForm=',DbgSName(AForm)]);
    {$ENDIF}

    // no layout found => use default
    Creator:=IDEWindowCreators.FindWithName(AForm.Name);
    if Creator<>nil then
    begin
      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 VerboseIDEDocking}
      debugln(['TSimpleWindowLayoutList.ApplyAndShow creator found for ',DbgSName(AForm),': Left=',Creator.Left,' Top=',Creator.Top,' Right=',Creator.Right,' Bottom=',Creator.Bottom,' Creator.DockSibling=',Creator.DockSibling,' Creator.DockAlign=',dbgs(Creator.DockAlign),' NewBounds=',dbgs(NewBounds),' DockSibling=',DockSiblingName,' DockAlign=',dbgs(DockAlign)]);
      {$ENDIF}
      if DockSiblingName<>'' then
      begin
        DockSibling:=Screen.FindForm(DockSiblingName);
        if DockSibling<>nil then
        begin
          DockSiblingBounds:=DockSibling.BoundsRect;
          if DockSibling.Parent<>nil then
          begin
            Offset:=DockSibling.ClientToScreen(Point(0,0));
            OffsetRect(DockSiblingBounds,Offset.X,Offset.Y);
          end;
          case DockAlign of
          alLeft:
            begin
              NewBounds.Top:=DockSiblingBounds.Top;
              NewBounds.Bottom:=DockSiblingBounds.Bottom;
              OffsetRect(NewBounds,DockSiblingBounds.Left-6-NewBounds.Right,0);
            end;
          alRight:
            begin
              NewBounds.Top:=DockSiblingBounds.Top;
              NewBounds.Bottom:=DockSiblingBounds.Bottom;
              OffsetRect(NewBounds,DockSiblingBounds.Right+6-NewBounds.Left,0);
            end;
          alTop:
            begin
              NewBounds.Left:=DockSiblingBounds.Left;
              NewBounds.Right:=DockSiblingBounds.Right;
              OffsetRect(NewBounds,0,DockSiblingBounds.Top-25-NewBounds.Bottom);
            end;
          alBottom:
            begin
              NewBounds.Left:=DockSiblingBounds.Left;
              NewBounds.Right:=DockSiblingBounds.Right;
              OffsetRect(NewBounds,0,DockSiblingBounds.Bottom+25-NewBounds.Top);
            end;
          alClient:
            NewBounds:=DockSibling.BoundsRect;
          end;
        end;
      end;
      {$IFDEF VerboseIDEDocking}
      debugln(['TSimpleWindowLayoutList.ApplyAndShow ',DbgSName(AForm),' NewBounds=',dbgs(NewBounds)]);
      {$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);
      AForm.BoundsRect:=NewBounds;
      AMoveToVisbleMode := vmOnlyMoveOffScreenToVisible;
    end;
  finally
    if (AForm.WindowState in [wsNormal,wsMaximized]) and BringToFront then
    begin
      // do not change Visible property while designing form. issue #20602
      // we save it into ARestoreVisible before any changes from here.
      if IsFormDesign(AForm) then
        ARestoreVisible := AForm.Visible;

      if (AMoveToVisbleMode = vmAlwaysMoveToVisible) or
         ( (AMoveToVisbleMode = vmOnlyMoveOffScreenToVisible) and
           (not IsFormMovable(AForm)) )
      then
        AForm.EnsureVisible(true)
      else
        AForm.ShowOnTop;

      if IsFormDesign(AForm) then
        AForm.Visible := ARestoreVisible;
    end else
    begin
      if IsFormDesign(AForm) then
        ARestoreVisible := AForm.Visible;

      AForm.Visible := True;
      if (AForm.WindowState in [wsMinimized]) then
      begin
        if BringToFront then
        begin
          // issue #19769
          if AForm.Visible and not (fsModal in AForm.FormState) then
            AForm.Visible := False;
          if (AMoveToVisbleMode = vmAlwaysMoveToVisible) or
             ( (AMoveToVisbleMode = vmOnlyMoveOffScreenToVisible) and
               (not IsFormMovable(AForm)) )
          then
            AForm.EnsureVisible(true)
          else
            AForm.ShowOnTop;
        end;
      end;

      if IsFormDesign(AForm) then
        AForm.Visible := ARestoreVisible;
    end;
  end;
end;

procedure TSimpleWindowLayoutList.StoreWindowPositions;
var
  i: integer;
  xOldLayoutIndex: Integer;
begin
  //store positions
  for i:=0 to Count-1 do
    Items[i].GetCurrentPosition;

  //store z-order
  for i:=Screen.CustomFormCount-1 downto 0 do
  begin
    xOldLayoutIndex := IndexOf(Screen.CustomForms[i]);
    if xOldLayoutIndex > 0 then
      fItems.Move(xOldLayoutIndex, 0);
  end;
end;

procedure TSimpleWindowLayoutList.CopyItemsFrom(SrcList: TSimpleWindowLayoutList);
var
  SrcI: Integer;//index in SrcList
  SelfI: Integer;//index in Self
begin
  if SrcList=nil then exit;

  //do not clear self, always copy items
  for SrcI:=0 to SrcList.Count-1 do
  begin
    SelfI := IndexOf(SrcList[SrcI].FormID);
    if SelfI < 0 then
      SelfI := Self.Add(TSimpleWindowLayout.Create(SrcList[SrcI].FormID));//not found, create
    Self.fItems.Move(SelfI, Min(SrcI, Self.Count-1));//move it to right position
    SelfI := SrcI;
    Self.Items[SelfI].Assign(SrcList[SrcI])
  end;
end;

function TSimpleWindowLayoutList.Add(ALayout: TSimpleWindowLayout): integer;
begin
  Result:=fItems.Add(ALayout);
end;

function TSimpleWindowLayoutList.CreateWindowLayout(const TheFormID: string
  ): TSimpleWindowLayout;
begin
  if TheFormID='' then
    raise Exception.Create('TEnvironmentOptions.CreateWindowLayout TheFormID empty');
  if not IsValidIdent(TheFormID) then
    raise Exception.Create('TEnvironmentOptions.CreateWindowLayout TheFormID "'+TheFormID+'" invalid identifier');
  if ItemByFormID(TheFormID)<>nil then
    raise Exception.Create('TEnvironmentOptions.CreateWindowLayout TheFormID "'+TheFormID+'" already exists');
  Result:=TSimpleWindowLayout.Create(TheFormID);
  Add(Result);
end;

function TSimpleWindowLayoutList.CreateWindowLayout(const TheForm: TCustomForm
  ): TSimpleWindowLayout;
begin
  Result:=CreateWindowLayout(TheForm.Name);
  Result.Form:=TheForm;
end;

{ TIDEWindowCreator }

procedure TIDEWindowCreator.SetBottom(const AValue: string);
begin
  CheckBoundValue(AValue);
  if FBottom=AValue then exit;
  FBottom:=AValue;
end;

function TIDEWindowCreator.GetDividerTemplate: TSimpleWindowLayoutDividerPosList;
begin
  If FDividerTemplate = nil then
    FDividerTemplate := TSimpleWindowLayoutDividerPosList.Create;
  Result := FDividerTemplate;
end;

procedure TIDEWindowCreator.SetLeft(const AValue: string);
begin
  CheckBoundValue(AValue);
  if FLeft=AValue then exit;
  FLeft:=AValue;
end;

procedure TIDEWindowCreator.SetTop(const AValue: string);
begin
  CheckBoundValue(AValue);
  if FTop=AValue then exit;
  FTop:=AValue;
end;

procedure TIDEWindowCreator.SetRight(const AValue: string);
begin
  CheckBoundValue(AValue);
  if FRight=AValue then exit;
  FRight:=AValue;
end;

procedure TIDEWindowCreator.CheckBoundValue(s: string);
var
  p: Integer;
begin
  if s='' then exit;
  p:=1;
  if (p<=length(s)) and (s[p] in ['+','-']) then inc(p);
  while (p<=length(s)) and (s[p] in ['0'..'9']) do inc(p);
  if p<=1 then
    raise Exception.Create('TIDEWindowDefaultLayout.CheckBoundValue: expected number, but '+s+' found');
  // check for percent
  if (p<=length(s)) and (s[p]='%') then inc(p);
  if p<=length(s) then
    raise Exception.Create('TIDEWindowDefaultLayout.CheckBoundValue: expected number, but '+s+' found');
end;

procedure TIDEWindowCreator.GetDefaultBounds(AForm: TCustomForm; out
  DefBounds: TRect);
var
  aRight: LongInt;
  aBottom: LongInt;
  ScreenR: TRect;
  ScreenW: Integer;
  ScreenH: Integer;
begin
  ScreenR:=IDEWindowCreators.GetScreenrectForDefaults;
  ScreenW:=ScreenR.Right-ScreenR.Left;
  ScreenH:=ScreenR.Bottom-ScreenR.Top;

  // left
  if Left='' then
    DefBounds.Left:=AForm.Left
  else if Left[length(Left)]='%' then
    DefBounds.Left:=ScreenR.Left+ScreenW*StrToIntDef(copy(Left,1,length(Left)-1),0) div 100
  else
    DefBounds.Left:=ScreenR.Left+StrToIntDef(Left,0);
  // top
  if Top='' then
    DefBounds.Top:=AForm.Top
  else if Top[length(Top)]='%' then
    DefBounds.Top:=ScreenR.Top+ScreenH*StrToIntDef(copy(Top,1,length(Top)-1),0) div 100
  else
    DefBounds.Top:=ScreenR.Top+StrToIntDef(Top,0);
  // right
  if Right='' then
    aRight:=DefBounds.Left+AForm.Width
  else begin
    // 300 = fixed at 300,
    // +300 = Left+300
    // 30% = fixed at 30% on screen
    // +30% = Left+30% of screen
    // -300 = fixed 300 from right border of screen
    // -30% = fixed 30% from right border of screen
    if Right[length(Right)]='%' then
      aRight:=ScreenW*StrToIntDef(copy(Right,1,length(Right)-1),0) div 100
    else
      aRight:=StrToIntDef(Right,0);
    if aRight<0 then
      aRight:=ScreenR.Right+aRight // relative to right of screen
    else if (Right<>'') and (Right[1]='+') then
      inc(aRight,DefBounds.Left) // relative to Left
    else
      inc(aRight,ScreenR.Left); // relative to left of screen
  end;
  DefBounds.Right:=aRight;
  // bottom
  if Bottom='' then
    aBottom:=DefBounds.Top+AForm.Height
  else begin
    // 300 = fixed at 300,
    // +300 = Top+300
    // 30% = fixed at 30% on screen
    // +30% = Top+30% of screen
    // -300 = fixed 300 from bottom border of screen
    // -30% = fixed 30% from bottom border of screen
    if Bottom[length(Bottom)]='%' then
      aBottom:=ScreenH*StrToIntDef(copy(Bottom,1,length(Bottom)-1),0) div 100
    else
      aBottom:=StrToIntDef(Bottom,0);
    if aBottom<0 then
      aBottom:=ScreenR.Bottom+aBottom // relative to bottom of screen
    else if (Bottom<>'') and (Bottom[1]='+') then
      inc(aBottom,DefBounds.Top) // relative to Top
    else
      inc(aBottom,ScreenR.Top); // relative to top of screen
  end;
  DefBounds.Bottom:=aBottom;
end;

function TIDEWindowCreator.CreateSimpleLayout: TSimpleWindowLayout;
begin
  Result:=IDEWindowCreators.SimpleLayoutStorage.ItemByFormID(FormName);
  if not Assigned(Result) then
  begin
    Result := IDEWindowCreators.SimpleLayoutStorage.CreateWindowLayout(FormName);
    InitSimpleLayout(Result);
  end;
end;

procedure TIDEWindowCreator.InitSimpleLayout(ALayout: TSimpleWindowLayout);
begin
  if FDividerTemplate <> nil then
    ALayout.Dividers.Assign(FDividerTemplate);
end;

constructor TIDEWindowCreator.Create(aFormName: string);
begin
  FFormName:=aFormName;
  FDividerTemplate := nil;
end;

constructor TIDEWindowCreator.Create(aFormName: string;
  CreateFormProc: TCreateIDEWindowProc;
  CreateFormMethod: TCreateIDEWindowMethod;
  aLeft, aTop, aRight, aBottom: string; aDockSibling: string; aDockAlign: TAlign; aMulti: boolean;
  GetLayoutEvent: TGetDefaultIDEWindowLayoutEvent);
begin
  Create(aFormName);
  FMulti:=aMulti;
  Left:=aLeft;
  Top:=aTop;
  Right:=aRight;
  Bottom:=aBottom;
  DockSibling:=aDockSibling;
  DockAlign:=aDockAlign;
  OnCreateFormMethod:=CreateFormMethod;
  OnCreateFormProc:=CreateFormProc;
  OnGetLayout:=GetLayoutEvent;
end;

destructor TIDEWindowCreator.Destroy;
begin
  inherited Destroy;
  FreeAndNil(FDividerTemplate);
end;

function TIDEWindowCreator.NameFits(const AName: string): boolean;
begin
  Result:=CompareText(copy(AName,1,Length(FormName)),FormName)=0;
end;

{ TIDEWindowCreatorList }

function TIDEWindowCreatorList.GetItems(Index: integer
  ): TIDEWindowCreator;
begin
  Result:=TIDEWindowCreator(fItems[Index]);
end;

procedure TIDEWindowCreatorList.ErrorIfFormExists(FormName: string);
begin
  if IndexOfName(FormName)>=0 then
    raise Exception.Create('TIDEWindowDefaultLayoutList.Add: form name '+FormName+' already exists');
end;

constructor TIDEWindowCreatorList.Create;
begin
  fItems:=TFPList.Create;
  FSimpleLayoutStorage:=TSimpleWindowLayoutList.Create(True);
  FScreenMaxSizeForDefaults:=Point(1200,900);
  FOnLayoutChanged:=TMethodList.Create;
end;

destructor TIDEWindowCreatorList.Destroy;
begin
  Clear;
  FreeAndNil(fItems);
  FreeAndNil(FSimpleLayoutStorage);
  FOnLayoutChanged.Free;
  inherited Destroy;
end;

procedure TIDEWindowCreatorList.Clear;
var
  i: Integer;
begin
  for i:=0 to fItems.Count-1 do
    TObject(fItems[i]).Free;
end;

function TIDEWindowCreatorList.Count: integer;
begin
  Result:=fItems.Count;
end;

function TIDEWindowCreatorList.Add(aLayout: TIDEWindowCreator
  ): integer;
begin
  ErrorIfFormExists(aLayout.FormName);
  Result:=fItems.Add(aLayout);
end;

procedure TIDEWindowCreatorList.AddLayoutChangedHandler(
  const aEvent: TNotifyEvent);
begin
  FOnLayoutChanged.Add(TMethod(aEvent));
end;

function TIDEWindowCreatorList.Add(aFormName: string
  ): TIDEWindowCreator;
begin
  ErrorIfFormExists(aFormName);
  Result:=TIDEWindowCreator.Create(aFormName);
  Add(Result);
end;

function TIDEWindowCreatorList.Add(aFormName: string;
  CreateFormProc: TCreateIDEWindowProc;
  CreateFormMethod: TCreateIDEWindowMethod;
  aLeft, aTop, aRight, aBottom: string;
  aDockSibling: string; aDockAlign: TAlign; aMulti: boolean;
  GetLayoutEvent: TGetDefaultIDEWindowLayoutEvent): TIDEWindowCreator;
begin
  ErrorIfFormExists(aFormName);
  Result:=TIDEWindowCreator.Create(aFormName,CreateFormProc,CreateFormMethod,
       aLeft,aTop,aRight,aBottom,aDockSibling,aDockAlign,aMulti,GetLayoutEvent);
  Add(Result);
end;

procedure TIDEWindowCreatorList.Delete(Index: integer);
begin
  TObject(fItems[Index]).Free;
  fItems.Delete(Index);
end;

function TIDEWindowCreatorList.IndexOfName(FormName: string): integer;
begin
  Result:=Count-1;
  while (Result>=0) and not Items[Result].NameFits(FormName) do
    dec(Result);
end;

procedure TIDEWindowCreatorList.LayoutChanged;
var
  I: Integer;
  xEvent: TNotifyEvent;
begin
  for I := 0 to FOnLayoutChanged.Count-1 do
  begin
    xEvent := TNotifyEvent(FOnLayoutChanged[I]);
    xEvent(Self);
  end;
end;

procedure TIDEWindowCreatorList.RemoveLayoutChangedHandler(
  const aEvent: TNotifyEvent);
begin
  FOnLayoutChanged.Remove(TMethod(aEvent));
end;

function TIDEWindowCreatorList.FindWithName(FormName: string): TIDEWindowCreator;
var
  i: LongInt;
begin
  i:=IndexOfName(FormName);
  if i>=0 then
    Result:=Items[i]
  else
    Result:=nil;
end;

function TIDEWindowCreatorList.GetForm(aFormName: string; AutoCreate: boolean;
  DisableAutoSizing: boolean): TCustomForm;
var
  Item: TIDEWindowCreator;
begin
  Result:=Screen.FindForm(aFormName);
  if Result<>nil then begin
    if DisableAutoSizing then
      Result.DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TAnchorDockMaster Delayed'){$ENDIF};
    exit;
  end;
  if AutoCreate then begin
    Item:=FindWithName(aFormName);
    if Item=nil then begin
      debugln(['TIDEWindowCreatorList.GetForm no creator for ',aFormName]);
      exit;
    end;
    if Assigned(Item.OnCreateFormProc) then begin
      Item.OnCreateFormProc(Self,aFormName,Result,DisableAutoSizing);
    end else if Assigned(Item.OnCreateFormMethod) then begin
      Item.OnCreateFormMethod(Self,aFormName,Result,DisableAutoSizing);
    end else begin
      debugln(['TIDEWindowCreatorList.GetForm no OnCreateForm for ',aFormName]);
      exit;
    end;
    if Result=nil then begin
      debugln(['TIDEWindowCreatorList.GetForm create failed for ',aFormName]);
      exit;
    end;
    SimpleLayoutStorage.SetDefaultPosition(Result);
  end;
end;

procedure TIDEWindowCreatorList.ShowForm(AForm: TCustomForm; BringToFront: boolean;
  AMoveToVisbleMode: TLayoutMoveToVisbleMode);
var
  Layout: TSimpleWindowLayout;
begin
  if (AForm.Name='') or (not IsValidIdent(AForm.Name)) then
    raise Exception.Create('TIDEWindowCreatorList.ShowForm invalid form name '+AForm.Name);

  // auto create a layout storage for every shown form
  Layout:=SimpleLayoutStorage.ItemByFormID(AForm.Name);
  if Layout=nil then begin
    if not IsFormDesign(AForm) then
      SimpleLayoutStorage.CreateWindowLayout(AForm);
  end
  else
    Layout.Form:=AForm;

  if (IDEDockMaster<>nil) and (not IsFormDesign(AForm))
  and (FindWithName(AForm.Name)<>nil) then
    // show dockable if it has a creator and is not a designer form
    IDEDockMaster.ShowForm(AForm,BringToFront)
  else
    if (IDETabMaster <> nil) and IsFormDesign(AForm) then
      IDETabMaster.ShowForm(AForm)
    else
      SimpleLayoutStorage.ApplyAndShow(Self,AForm,BringToFront,AMoveToVisbleMode);
end;

function TIDEWindowCreatorList.ShowForm(AFormName: string; BringToFront: boolean): TCustomForm;
begin
  Result:=GetForm(AFormName,true,false);
  if Result<>nil then
    ShowForm(Result,BringToFront);
end;

procedure TIDEWindowCreatorList.CreateForm(var AForm;
  AFormClass: TCustomFormClass; DoDisableAutoSizing: boolean;
  TheOwner: TComponent);
begin
  if TCustomForm(AForm)=nil then begin
    TCustomForm(AForm):=TCustomForm(AFormClass.NewInstance);
    {$IFDEF DebugDisableAutoSizing}
    if DoDisableAutoSizing then
      TCustomForm(AForm).DisableAutoSizing('TAnchorDockMaster Delayed')
    else
      TCustomForm(AForm).DisableAutoSizing('TIDEWindowCreatorList.CreateForm');
    {$ELSE}
    TCustomForm(AForm).DisableAutoSizing;
    {$ENDIF};
    try
      TCustomForm(AForm).Create(TheOwner);
    finally
      if not DoDisableAutoSizing then
        TCustomForm(AForm).EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TIDEWindowCreatorList.CreateForm'){$ENDIF};
    end;
    SimpleLayoutStorage.SetDefaultPosition(TCustomForm(AForm));
  end else if DoDisableAutoSizing then
    TCustomForm(AForm).DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TAnchorDockMaster Delayed'){$ENDIF};
end;

procedure TIDEWindowCreatorList.RestoreSimpleLayout;
var
  i: Integer;
  ALayout: TSimpleWindowLayout;
  AForm: TCustomForm;
  HasChanged: Boolean;
  AChangeVisibility: Boolean;
begin
  if IDEDockMaster=nil then
  begin
    HasChanged:=false;
    for i:=SimpleLayoutStorage.Count-1 downto 0 do//loop down in order to keep z-order of the forms
    begin
      ALayout:=SimpleLayoutStorage[i];
      AChangeVisibility := IDEWindowsGlobalOptions.CanSetVisibility(ALayout.FormID);
      AForm:=GetForm(ALayout.FormID,AChangeVisibility and ALayout.Visible);
      if AForm=nil then Continue;
      HasChanged:=true;
      ALayout.Apply(AChangeVisibility);
      if AChangeVisibility then
      begin
        if ALayout.Visible or (AForm=Application.MainForm) then
          ShowForm(AForm,true,vmOnlyMoveOffScreenToVisible)
        else if AForm.Visible then
          AForm.Close;
      end else
      begin//do not change visibility
        if AForm.Visible then//Only make sure their z-index is OK if they are already visible
          ShowForm(AForm,true)
      end;
    end;
    if HasChanged then
      LayoutChanged;
  end;
end;

function TIDEWindowCreatorList.GetScreenrectForDefaults: TRect;
begin
  Result:=Screen.WorkAreaRect;
  if (Result.Right-Result.Left<10)
  or (Result.Bottom-Result.Top<10) then begin
    // screen not recognized
    Result:=Rect(0,0,Max(Screen.Width,600),Max(Screen.Height,400));
  end;
  Result.Right:=Min(Result.Right,Result.Left+IDEWindowCreators.ScreenMaxSizeForDefaults.X);
  Result.Bottom:=Min(Result.Bottom,Result.Top+IDEWindowCreators.ScreenMaxSizeForDefaults.Y);
end;

{ TIDEDockMaster }

function TIDEDockMaster.AddableInWindowMenu(AForm: TCustomForm): boolean;
begin
  Result:=true;
end;

procedure TIDEDockMaster.AdjustMainIDEWindowHeight(
  const AIDEWindow: TCustomForm; const AAdjustHeight: Boolean;
  const ANewHeight: Integer);
begin

end;

procedure TIDEDockMaster.CloseAll;
begin
  CloseAllForms;
end;

initialization
  IDEWindowCreators:=TIDEWindowCreatorList.Create;
  IDEDialogLayoutList:=TIDEDialogLayoutList.Create;
finalization
  FreeAndNil(IDEWindowCreators);
  FreeAndNil(IDEDialogLayoutList);
  FreeAndNil(FIDEWindowsGlobalOptions);

end.

