{ $Id: cocoawsforms.pp 12783 2007-11-08 11:45:39Z tombo $}
{
 *****************************************************************************
 *                             CocoaWSForms.pp                               *
 *                               ------------                                *
 *                                                                           *
 *                                                                           *
 *****************************************************************************

 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************
}
unit CocoaWSForms;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$include cocoadefines.inc}

interface

uses
  // RTL,FCL
  Classes, SysUtils,
  // LCL
  Controls, Forms, Graphics, LCLType, Messages, LMessages, LCLProc, GraphMath,
  // Widgetset
  WSForms, WSLCLClasses, LCLMessageGlue,
  // LCL Cocoa
  MacOSAll, CocoaAll,
  CocoaWSService, CocoaWSModalService, CocoaConfig, CocoaPrivate,
  CocoaCommonCallback, CocoaWSPrivate,
  CocoaGDIObjects, CocoaWindows, CocoaToolBar, CocoaCustomControl, CocoaScrollers,
  CocoaWSScrollers, CocoaUtils, CocoaMenus, Cocoa_Extra;

type
  { TLCLWindowCallback }

  TLCLWindowCallback = class(TLCLCommonCallBack, IWindowCallback)
  private
    IsActivating: boolean;
  public
    window : CocoaAll.NSWindow;
    constructor Create(AOwner: NSObject; ATarget: TWinControl; AHandleView: NSView); override;
    destructor Destroy; override;

    function CanActivate: Boolean; virtual;
    procedure Activate; virtual;
    procedure Deactivate; virtual;
    procedure CloseQuery(var CanClose: Boolean); virtual;
    procedure Close; virtual;
    procedure Resize; virtual;
    procedure Move; virtual;
    procedure WindowStateChanged; virtual;

    function GetEnabled: Boolean; virtual;
    procedure SetEnabled(AValue: Boolean); virtual;

    function AcceptFilesDrag: Boolean;
    procedure DropFiles(const FileNames: array of string);

    function HasCancelControl: Boolean;
    function HasDefaultControl: Boolean;

    property Enabled: Boolean read GetEnabled write SetEnabled;
  end;

  { TCocoaWSScrollingWinControl }

  TCocoaWSScrollingWinControl = class(TWSScrollingWinControl)
  published
    class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLHandle; override;
    class procedure SetBorderStyle(const AWinControl: TWinControl; const ABorderStyle: TBorderStyle); override;
  end;

  { TCocoaWSCustomForm }
  TCocoaWSCustomFormClass = class of TCocoaWSCustomForm;
  TCocoaWSCustomForm = class(TWSCustomForm)
  private
    class function GetStyleMaskFor(ABorderStyle: TFormBorderStyle; ABorderIcons: TBorderIcons): NSUInteger;
    class procedure UpdateWindowIcons(AWindow: NSWindow; ABorderStyle: TFormBorderStyle; ABorderIcons: TBorderIcons);
  public
    class procedure UpdateWindowMask(AWindow: NSWindow; ABorderStyle: TFormBorderStyle; ABorderIcons: TBorderIcons);
    class function GetWindowFromHandle(const ACustomForm: TCustomForm): TCocoaWindow;
    class function GetWindowContentFromHandle(const ACustomForm: TCustomForm): TCocoaWindowContent;
  published
    class function AllocWindowHandle: TCocoaWindow; virtual;
    class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLHandle; override;
    class procedure DestroyHandle(const AWinControl: TWinControl); override;

    class function GetText(const AWinControl: TWinControl; var AText: String): Boolean; override;
    class function GetTextLen(const AWinControl: TWinControl; var ALength: Integer): Boolean; override;
    class procedure SetText(const AWinControl: TWinControl; const AText: String); override;

    class procedure CloseModal(const ACustomForm: TCustomForm); override;
    class procedure PaintTo(const AWinControl: TWinControl; ADC: HDC; X, Y: Integer); override;
    class procedure ShowModal(const ACustomForm: TCustomForm); override;
    class procedure SetModalResult(const ACustomForm: TCustomForm; ANewValue: TModalResult); override;

    class procedure SetAllowDropFiles(const AForm: TCustomForm; AValue: Boolean); override;
    class procedure SetAlphaBlend(const ACustomForm: TCustomForm; const AlphaBlend: Boolean; const Alpha: Byte); override;
    class procedure SetBorderIcons(const AForm: TCustomForm; const ABorderIcons: TBorderIcons); override;
    class procedure SetFormBorderStyle(const AForm: TCustomForm; const AFormBorderStyle: TFormBorderStyle); override;
    class procedure SetFormStyle(const AForm: TCustomform; const AFormStyle, AOldFormStyle: TFormStyle); override;
    class procedure SetIcon(const AForm: TCustomForm; const Small, Big: HICON); override;
    class procedure SetRealPopupParent(const ACustomForm: TCustomForm;
      const APopupParent: TCustomForm); override;
    class procedure ShowHide(const AWinControl: TWinControl); override;

    {need to override these }
    class function GetClientBounds(const AWincontrol: TWinControl; var ARect: TRect): Boolean; override;
    class function GetClientRect(const AWincontrol: TWinControl; var ARect: TRect): Boolean; override;
    class procedure SetBounds(const AWinControl: TWinControl; const ALeft, ATop, AWidth, AHeight: Integer); override;
  end;

  { TCocoaWSCustomFormHelper }

  TCocoaWSCustomFormHelper=objcclass( NSObject )
  private
    _window: TCocoaWindow;
  private
    procedure doSetCollectionBehavior; message 'doSetCollectionBehavior';
  public
    class procedure delaySetCollectionBehavior(
      const form: TCustomForm; const window: TCocoaWindow );
      message 'delaySetCollectionBehavior:form:';
  end;

  { TCocoaWSHintWindow }

  TCocoaWSHintWindow = class(TWSHintWindow)
  published
    class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLHandle; override;
    class procedure SetText(const AWinControl: TWinControl; const AText: String); override;
  end;

implementation

type
  TControlScrollBarAccess = class(TControlScrollBar);

const
  // The documentation is using constants like "NSNormalWindowLevel=4" for normal forms,
  // however, these are macros of a function call to CGWindowLevelKey()
  // where "Key" values of kCGNormalWindowLevelKey=4.

  FormStyleToWindowLevelKey: array[TFormStyle] of NSInteger = (
 { fsNormal          } kCGNormalWindowLevelKey,
 { fsMDIChild        } kCGNormalWindowLevelKey,
 { fsMDIForm         } kCGNormalWindowLevelKey,
 { fsStayOnTop       } kCGFloatingWindowLevelKey,
 { fsSplash          } kCGFloatingWindowLevelKey,
 { fsSystemStayOnTop } kCGFloatingWindowLevelKey  // NSModalPanelWindowLevel
  );

  HintWindowLevel = 11;  // NSPopUpMenuWindowLevel

function GetDesigningBorderStyle(const AForm: TCustomForm): TFormBorderStyle;
begin
  if csDesigning in AForm.ComponentState then
    Result := bsSizeable
  else
    Result := AForm.BorderStyle;
end;

procedure WindowSetFormStyle(win: NSWindow; AFormStyle: TFormStyle);
var
  lvl : NSInteger;
begin
  lvl := CGWindowLevelForKey(FormStyleToWindowLevelKey[AFormStyle]);
  win.setLevel(lvl);
  if win.isKindOfClass(TCocoaWindow) then
    TCocoaWindow(win).keepWinLevel := lvl;
end;

type
  PCocoaConfigForm = ^TCocoaConfigForm;

  { TCocoaFormUtils }

  TCocoaFormUtils = class
    class function getConfigByName( const name: String ): PCocoaConfigForm;
    class function getConfigByClassName( const aClassName: String ): PCocoaConfigForm;
    class function getConfigForMainForm: PCocoaConfigForm;
  end;

class function TCocoaFormUtils.getConfigByName( const name: String ):
  PCocoaConfigForm;
var
  i: Integer;
  count: Integer;
  findStr: String;
  currentStr: String;
begin
  Result:= nil;
  if name.IsEmpty then
    Exit;

  count:= length( CocoaConfigForms );

  findStr:= ';' + name + ';';
  for i:=0 to count-1 do begin
    currentStr:= ';' + CocoaConfigForms[i].name + ';';
    if currentStr.IndexOf(findStr) >= 0 then begin
      Result:= @CocoaConfigForms[i];
      Exit;
    end;
  end;
end;

class function TCocoaFormUtils.getConfigByClassName( const aClassName: String ):
  PCocoaConfigForm;
var
  i: Integer;
  count: Integer;
  findStr: String;
  currentStr: String;
begin
  Result:= nil;
  count:= length( CocoaConfigForms );

  findStr:= ';' + aClassName + ';';
  for i:=0 to count-1 do begin
    currentStr:= ';' + CocoaConfigForms[i].className + ';';
    if currentStr.IndexOf(findStr) >= 0 then begin
      Result:= @CocoaConfigForms[i];
      Exit;
    end;
  end;
end;

class function TCocoaFormUtils.getConfigForMainForm: PCocoaConfigForm;
var
  i: Integer;
  count: Integer;
begin
  Result:= nil;
  count:= length( CocoaConfigForms );
  for i:=0 to count-1 do begin
    if CocoaConfigForms[i].isMainForm then begin
      Result:= @CocoaConfigForms[i];
      Exit;
    end;
  end;
end;

{ TCocoaWSHintWindow }

class function TCocoaWSHintWindow.CreateHandle(const AWinControl: TWinControl;
  const AParams: TCreateParams): TLCLHandle;
var
  win: TCocoaPanel;
  cnt: TCocoaWindowContent;
  R: NSRect;
  cb: TLCLWindowCallback;
  doc: TCocoaWindowContentDocument;
const
  WinMask = NSBorderlessWindowMask or NSUtilityWindowMask;
begin
  win := TCocoaPanel(TCocoaPanel.alloc);

  if not Assigned(win) then
  begin
    Result := 0;
    Exit;
  end;

  R := TCocoaTypeUtil.toRect(AParams);
  {$ifdef BOOLFIX}
  win := TCocoaPanel(win.initWithContentRect_styleMask_backing_defer_(R, WinMask, NSBackingStoreBuffered, Ord(False)));
  {$else}
  win := TCocoaPanel(win.initWithContentRect_styleMask_backing_defer(R, WinMask, NSBackingStoreBuffered, False));
  {$endif}
  win.setLevel(HintWindowLevel);
  win.setDelegate(win);
  {$ifdef BOOLFIX}
  win.setHasShadow_(Ord(true));
  {$else}
  win.setHasShadow(true);
  {$endif}
  if AWinControl.Perform(WM_NCHITTEST, 0, 0)=HTTRANSPARENT then
    {$ifdef BOOLFIX}
    win.setIgnoresMouseEvents_(Ord(True))
    {$else}
    win.setIgnoresMouseEvents(True)
    {$endif}
  else
    {$ifdef BOOLFIX}
    win.setAcceptsMouseMovedEvents_(Ord(True));
    {$else}
    win.setAcceptsMouseMovedEvents(True);
    {$endif}


  R.origin.x := 0;
  R.origin.y := 0;
  cnt := TCocoaWindowContent.alloc.initWithFrame(R);
  doc := TCocoaWindowContentDocument.alloc.initWithFrame(R);
  doc.setHidden(false);
  doc.setAutoresizesSubviews(true);
  doc.setAutoresizingMask(NSViewMaxXMargin or NSViewMinYMargin or NSViewHeightSizable or NSViewWidthSizable);
  cb := TLCLWindowCallback.Create(doc, AWinControl, cnt);
  doc.callback := cb;
  doc.wincallback := cb;
  cb.window := win;
  cnt.callback := cb;
  cnt.wincallback := cb;
  cnt.preventKeyOnShow := true;
  cnt.isCustomRange := true;
  cnt.setDocumentView(doc);
  cnt.setDrawsBackground(false); // everything is covered anyway
  TCocoaPanel(win).callback := cb;

  win.setContentView(cnt);
  doc.release;

  Result := TLCLHandle(cnt);
end;

class procedure TCocoaWSHintWindow.SetText(const AWinControl: TWinControl;
  const AText: String);
begin
  TCocoaWSCustomForm.SetText(AWinControl, AText);
  AWinControl.Invalidate;
end;

{ TLCLWindowCallback }

type
  TWinControlAccess = class(TWinControl)
  end;

function TLCLWindowCallback.CanActivate: Boolean;
begin
  Result := Enabled;
  // it's possible that a Modal window requests this (target) window
  // to become visible (i.e. when modal is closing)
  // All other Windows are disabled while modal is active.
  // Thus must check wcfUpdateShowing flag (which set when changing window visibility)
  // And if it's used, then we allow the window to become Key window
  if not Result and (Target is TWinControl) then
    Result := wcfUpdateShowing in TWinControlAccess(Target).FWinControlFlags;
end;

constructor TLCLWindowCallback.Create(AOwner: NSObject; ATarget: TWinControl; AHandleView: NSView);
begin
  inherited;
  IsActivating:=false;
end;

destructor TLCLWindowCallback.Destroy;
begin
  if Assigned(window) then window.lclClearCallback;
  inherited Destroy;
end;

procedure TLCLWindowCallback.Activate;
var
  ACustForm: TCustomForm;
  isDesign: Boolean;
  focusedCb: ICommonCallback;
begin
  CocoaWidgetSetState.currentKeyWindow:= window;

  if not IsActivating then
  begin
    IsActivating:=True;
    ACustForm := Target as TCustomForm;

    isDesign :=
      (csDesigning in ACustForm.ComponentState)
      or (
        Assigned(ACustForm.Menu)
        and (csDesigning in ACustForm.Menu.ComponentState)
      );

    // only adjust main menu, if the form is not being designed
    if not isDesign then
    begin
      if (ACustForm.Menu <> nil) and
         (ACustForm.Menu.HandleAllocated) then
      begin
        if NSObject(ACustForm.Menu.Handle).isKindOfClass_(TCocoaMenu) then
        begin
          CocoaWidgetSetMenuService.SetMainMenu(ACustForm.Menu.Handle, ACustForm.Menu);
        end
        else
          debugln('Warning: Menu does not have a valid handle.');
      end
      else
        CocoaWidgetSetMenuService.SetMainMenu(0, nil);
    end;

    LCLSendActivateMsg(Target, WA_ACTIVE, false);
    focusedCb := window.firstResponder.lclGetCallback;
    if Assigned(focusedCb) then
      TCocoaLCLMessageUtil.BecomeFirstResponder(focusedCb);
    // The only way to update Forms.ActiveCustomForm for the main form
    // is calling TCustomForm.SetFocusedControl, see bug 31056
    ACustForm.SetFocusedControl(ACustForm.ActiveControl);

    IsActivating:=False;

    if CocoaWidgetSetModalService.isModalSession then
      NSView(ACustForm.Handle).window.orderFront(nil);
  end;
end;

procedure TLCLWindowCallback.Deactivate;
var
  focusedCb: ICommonCallback;
begin
  CocoaWidgetSetState.currentKeyWindow:= nil;

  focusedCb:= window.firstResponder.lclGetCallback;
  if Assigned(focusedCb) then begin
    if not (csDestroying in TComponent(focusedCb.GetTarget).ComponentState) then
      TCocoaLCLMessageUtil.ResignFirstResponder(focusedCb);
  end;
  LCLSendActivateMsg(Target, WA_INACTIVE, false);
end;

procedure TLCLWindowCallback.CloseQuery(var CanClose: Boolean);
var
  i: Integer;
begin
  // Message results : 0 - do nothing, 1 - destroy window
  CanClose := LCLSendCloseQueryMsg(Target) > 0;

  // Special code for modal forms, which otherwise would get 0 here and not call Close
  if (CocoaWidgetSetModalService.currentModal = window) and
    (TCustomForm(Target).ModalResult <> mrNone) then
  begin
    {$IFDEF COCOA_USE_NATIVE_MODAL}
    NSApp.stopModal();
    {$ENDIF}
    {// Felipe: This code forces focusing another form, its a work around
    // for a gdb issue, gdb doesn't start the app properly
    //
    // At this point the modal form is closed, but the previously open form isn't focused
    // Focus the main window if it is visible
    if Application.MainForm.Visible then Application.MainForm.SetFocus()
    else
    begin
      // if the mainform is hidden, just choose any visible form
      // ToDo: Figure out a better solution
      for i := 0 to Screen.FormCount-1 do
        if Screen.Forms[i].Visible then
        begin
          Screen.Forms[i].SetFocus();
          Break;
        end;
    end;}
  end;
end;

procedure TLCLWindowCallback.Close;
begin
  LCLSendCloseUpMsg(Target);
end;

procedure TLCLWindowCallback.Resize;
begin
  boundsDidChange(Owner);
end;

procedure TLCLWindowCallback.Move;
begin
  boundsDidChange(Owner);
end;

procedure TLCLWindowCallback.WindowStateChanged;
var
  Bounds: TRect;
begin
  Bounds := HandleFrame.lclFrame;
  LCLSendSizeMsg(Target, Bounds.Right - Bounds.Left, Bounds.Bottom - Bounds.Top,
    Owner.lclWindowState, True);
end;

function TLCLWindowCallback.GetEnabled: Boolean;
begin
  Result := Owner.lclIsEnabled;
end;

procedure TLCLWindowCallback.SetEnabled(AValue: Boolean);
begin
  Owner.lclSetEnabled(AValue);
end;

function TLCLWindowCallback.AcceptFilesDrag: Boolean;
begin
  Result := Assigned(Target)
    and TCustomForm(Target).AllowDropFiles
    and Assigned(TCustomForm(Target).OnDropFiles);
end;

procedure TLCLWindowCallback.DropFiles(const FileNames: array of string);
begin
  if Assigned(Target) then
    TCustomForm(Target).IntfDropFiles(FileNames);
end;

function TLCLWindowCallback.HasCancelControl: Boolean;
{ TODO: Should this be solved differently?  TForm/TApplication could expose a
  property to avoid duplicating them here and in TApplication.DoEscapeKey }
var
  lControl: TControl;
begin
  if Assigned(Target) and
     (anoEscapeForCancelControl in Application.Navigation) then
  begin
    lControl := TCustomForm(Target).CancelControl;
    Result := Assigned(lControl) and lControl.Enabled and lControl.Visible;
  end
  else
    Result := False;
end;

function TLCLWindowCallback.HasDefaultControl: Boolean;
{ TODO: Should this be solved differently?  TForm/TApplication could expose a
  property to avoid duplicating them here and in TApplication.DoReturnKey }
var
  lControl: TControl;
begin
  if Assigned(Target) and
     (anoReturnForDefaultControl in Application.Navigation) then
  begin
    lControl := TCustomForm(Target).ActiveDefaultControl;
    if lControl = nil then
      lControl := TCustomForm(Target).DefaultControl;
    Result := Assigned(lControl) and
      ((lControl.Parent = nil) or lControl.Parent.CanFocus) and
      lControl.Enabled and lControl.Visible;
  end
  else
    Result := False;
end;

{ TCocoaWSScrollingWinControl}

class function  TCocoaWSScrollingWinControl.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLHandle;
var
  scrollcon: TCocoaScrollView;
  docview: TCocoaCustomControl;
  lcl : TLCLCommonCallback;
begin
  scrollcon:= TCocoaScrollView.alloc.lclInitWithCreateParams(AParams);
  TCocoaScrollUtil.setBorderStyle(scrollcon, TScrollingWinControl(AWincontrol).BorderStyle);
  scrollcon.setDrawsBackground(false); // everything is covered anyway
  scrollcon.setBackgroundColor(NSColor.windowBackgroundColor);
  scrollcon.setAutohidesScrollers(True);
  scrollcon.isCustomRange := true;

  docview:= TCocoaCustomControl.alloc.init;
  docview.setFrameSize( scrollcon.contentSize );
  scrollcon.setDocumentView(docview);

  lcl := TLCLCommonCallback.Create(docview, AWinControl, scrollcon);
  lcl.BlockCocoaUpDown := true;
  scrollcon.callback := lcl;
  docview.callback := lcl;

  Result := TLCLHandle(scrollcon);
end;

class procedure TCocoaWSScrollingWinControl.SetBorderStyle(
  const AWinControl: TWinControl; const ABorderStyle: TBorderStyle);
begin
  if not Assigned(AWinControl) or not AWincontrol.HandleAllocated then Exit;
  TCocoaScrollUtil.setBorderStyle( NSScrollView(AWinControl.Handle), ABorderStyle);
end;


{ TCocoaWSCustomForm }

procedure ArrangeTabOrder(const AWinControl: TWinControl);
var
  lList: TFPList;
  prevControl, curControl: TWinControl;
  lPrevView, lCurView: NSView;
  i: Integer;
begin
  lList := TFPList.Create;
  try
    AWinControl.GetTabOrderList(lList);
    if lList.Count>0 then
      begin
      prevControl := TWinControl(lList.Items[lList.Count-1]);
      lPrevView := NSObject(prevControl.Handle).lclContentView;
      for i := 0 to lList.Count-1 do
      begin
        curControl := TWinControl(lList.Items[i]);
        lCurView := NSObject(curControl.Handle).lclContentView;

        if (lCurView <> nil) and (lPrevView <> nil) then
          lPrevView.setNextKeyView(lCurView);

        lPrevView := lCurView;
      end;
    end;
  finally
    lList.Free;
  end;
end;


class function TCocoaWSCustomForm.GetStyleMaskFor(
  ABorderStyle: TFormBorderStyle; ABorderIcons: TBorderIcons): NSUInteger;
begin
  case ABorderStyle of
    bsSizeable, bsSizeToolWin:
      Result := NSTitledWindowMask or NSResizableWindowMask;
    bsSingle, bsDialog, bsToolWindow:
      Result := NSTitledWindowMask;
  else
    Result := NSBorderlessWindowMask;
  end;
  if biSystemMenu in ABorderIcons then
  begin
    Result := Result or NSClosableWindowMask;
    if biMinimize in ABorderIcons then
      Result := Result or NSMiniaturizableWindowMask;
  end;
end;

class procedure TCocoaWSCustomForm.UpdateWindowIcons(AWindow: NSWindow;
  ABorderStyle: TFormBorderStyle; ABorderIcons: TBorderIcons);

  procedure SetWindowButtonState(AButton: NSWindowButton; AEnabled, AVisible: Boolean);
  var
    Btn: NSButton;
  begin
    Btn := AWindow.standardWindowButton(AButton);
    if Assigned(Btn) then
    begin
      {$ifdef BOOLFIX}
      Btn.setHidden_(Ord(not AVisible));
      Btn.setEnabled_(Ord(AEnabled));
      {$else}
      Btn.setHidden(not AVisible);
      Btn.setEnabled(AEnabled);
      {$endif}
    end;
  end;

var
  btn : NSButton;
  url : NSURL;
  b   : NSBundle;

const
  // mimic Windows border styles
  isIconVisible : array [TFormBorderStyle] of Boolean = (
    false, // bsNone
    true,  // bsSingle
    true,  // bsSizeable
    false, // bsDialog
    false, // bsToolWindow
    false  // bsSizeToolWin
  );

begin
  SetWindowButtonState(NSWindowMiniaturizeButton, biMinimize in ABorderIcons, (ABorderStyle in [bsSingle, bsSizeable]) and (biSystemMenu in ABorderIcons));
  SetWindowButtonState(NSWindowZoomButton, (biMaximize in ABorderIcons) and (ABorderStyle in [bsSizeable, bsSizeToolWin]), (ABorderStyle in [bsSingle, bsSizeable]) and (biSystemMenu in ABorderIcons));
  SetWindowButtonState(NSWindowCloseButton, True, (ABorderStyle <> bsNone) and (biSystemMenu in ABorderIcons));

  if CocoaConfigGlobal.useIcon then
    Exit;

  btn := AWindow.standardWindowButton(NSWindowDocumentIconButton);
  url := nil;
  if isIconVisible[ABorderStyle] then
  begin
    b := NSBundle.mainBundle;
    if Assigned(b) then url := b.bundleURL;
  end;
  AWindow.setRepresentedURL(url);
end;

class procedure TCocoaWSCustomForm.UpdateWindowMask(AWindow: NSWindow;
  ABorderStyle: TFormBorderStyle; ABorderIcons: TBorderIcons);
var
  StyleMask: NSUInteger;
begin
  StyleMask := GetStyleMaskFor(ABorderStyle, ABorderIcons);
  AWindow.setStyleMask(StyleMask);
  UpdateWindowIcons(AWindow, ABorderStyle, ABorderIcons);
end;

class function TCocoaWSCustomForm.GetWindowFromHandle(const ACustomForm: TCustomForm): TCocoaWindow;
begin
  Result := nil;
  if not ACustomForm.HandleAllocated then Exit;
  Result := TCocoaWindow(TCocoaWindowContent(ACustomForm.Handle).lclOwnWindow);
end;

class function TCocoaWSCustomForm.GetWindowContentFromHandle(const ACustomForm: TCustomForm): TCocoaWindowContent;
begin
  Result := nil;
  if not ACustomForm.HandleAllocated then Exit;
  Result := TCocoaWindowContent(ACustomForm.Handle);
end;

// Some projects that use the LCL need to override this
class function TCocoaWSCustomForm.AllocWindowHandle: TCocoaWindow;
begin
  Result := TCocoaWindow(TCocoaWindow.alloc);
end;


function hasScrollBar(AScrollBar: TControlScrollBar): Boolean; inline;
begin
  Result:= TControlScrollBarAccess(AScrollBar).ScrollBarShouldBeVisible;
end;

function hasHorzScrollBar(AWinControl: TWinControl): Boolean; inline;
begin
  Result:= hasScrollBar( TScrollingWinControl(AWinControl).HorzScrollBar );
end;

function hasVertScrollBar(AWinControl: TWinControl): Boolean; inline;
begin
  Result:= hasScrollBar( TScrollingWinControl(AWinControl).VertScrollBar );
end;

class function TCocoaWSCustomForm.CreateHandle(const AWinControl: TWinControl;
  const AParams: TCreateParams): TLCLHandle;
var
  Form: TCustomForm absolute AWinControl;
  win: TCocoaWindow;
  cnt: TCocoaWindowContent;
  doc: TCocoaWindowContentDocument;
  ns: NSString;
  R: NSRect;
  LR: NSRect;
  lDestView: NSView;
  ds: TCocoaDesignOverlay;
  cb: TLCLWindowCallback;

{$if NOT defined(DisableCocoaModernForm)}
  procedure applyCocoaConfigToolBar( const config: TCocoaConfigToolBar );
  var
    toolbar: TCocoaToolBar;
  begin
    toolbar:= TCocoaToolBarUtils.createToolBar( config );
    win.setToolbarStyle( config.style );
    win.setToolbar( toolbar );
    // in win.setToolBar(), macOS may incorrectly calculate the window size
    // (ignoring the original TitleBar size) and need to be reset.
    win.contentView.lclSetFrame( AWinControl.BoundsRect );
  end;

  procedure applyCocoaConfigTitleBar( const config: TCocoaConfigTitleBar );
  begin
    win.setTitlebarAppearsTransparent( config.transparent );
    win.setTitlebarSeparatorStyle( config.separatorStyle );
  end;

  function getFormConfig: PCocoaConfigForm;
  begin
    Result:= TCocoaFormUtils.getConfigByName( AWinControl.Name );
    if Assigned(Result) then
      Exit;

    Result:= TCocoaFormUtils.getConfigByClassName( AWinControl.ClassName );
    if Assigned(Result) then
      Exit;

    if Application.MainForm = AWinControl then begin
      Result:= TCocoaFormUtils.getConfigForMainForm;
      if Assigned(Result) then
        Exit;
    end;

    Result:= TCocoaFormUtils.getConfigByName( '*' );
    if Assigned(Result) then
      Exit;

    Result:= TCocoaFormUtils.getConfigByClassName( '*' );
    if Assigned(Result) then
      Exit;
  end;

  procedure applyCocoaConfigForm;
  var
    pFormConfig: PCocoaConfigForm;
  begin
    if NSAppKitVersionNumber < NSAppKitVersionNumber11_0 then
      Exit;

    pFormConfig:= getFormConfig;
    if NOT Assigned(pFormConfig) then
      Exit;

    applyCocoaConfigTitleBar( pFormConfig^.titleBar );
    applyCocoaConfigToolBar( pFormConfig^.toolBar );

    if Assigned(pFormConfig^.onCreate) then
      pFormConfig^.onCreate( form );
  end;
{$endif}

begin
  //todo: create TCocoaWindow or TCocoaPanel depending on the border style
  //      if parent is specified neither Window nor Panel needs to be created
  //      the only thing that needs to be created is Content

  R := TCocoaTypeUtil.toRect(AParams);
  if R.size.width<1 then R.size.width:=1;
  if R.size.height<1 then R.size.height:=1;

  LR := R;
  LR.origin.x := 0;
  LR.origin.y := 0;
  doc := TCocoaWindowContentDocument.alloc.initWithFrame(LR);
  cnt := TCocoaWindowContent.alloc.initWithFrame(R);
  cb := TLCLWindowCallback.Create(doc, AWinControl, cnt);

  cnt.callback := cb;
  doc.wincallback := cb;
  doc.callback := cb;
  cnt.wincallback := cb;
  cnt.isCustomRange := true;

  cnt.setHasHorizontalScroller( hasHorzScrollBar(AWinControl) );
  cnt.setHasVerticalScroller( hasVertScrollBar(AWinControl) );
  cnt.setVerticalScrollElasticity(NSScrollElasticityNone);
  cnt.setHorizontalScrollElasticity(NSScrollElasticityNone);
  cnt.setDocumentView(doc);
  cnt.setDrawsBackground(false); // everything is covered anyway
  doc.setHidden(false);

  doc.setAutoresizesSubviews(true);
  doc.setAutoresizingMask(NSViewMaxXMargin or NSViewMinYMargin or NSViewHeightSizable or NSViewWidthSizable);

  if (AParams.Style and WS_CHILD) = 0 then
  begin

    win := AllocWindowHandle;

    if not Assigned(win) then
    begin
      Result := 0;
      Exit;
    end;

    {$ifdef BOOLFIX}
    win := TCocoaWindow(win.initWithContentRect_styleMask_backing_defer_(R,
      GetStyleMaskFor(GetDesigningBorderStyle(Form), Form.BorderIcons), NSBackingStoreBuffered, Ord(False)));
    {$else}
    win := TCocoaWindow(win.initWithContentRect_styleMask_backing_defer(R,
      GetStyleMaskFor(GetDesigningBorderStyle(Form), Form.BorderIcons), NSBackingStoreBuffered, False));
    {$endif}
    TCocoaWSCustomFormHelper.delaySetCollectionBehavior(Form, win);
    UpdateWindowIcons(win, GetDesigningBorderStyle(Form), Form.BorderIcons);
    // For safety, it is better to not apply any setLevel & similar if the form is just a standard style
    // see issue http://bugs.freepascal.org/view.php?id=28473
    if not (csDesigning in AWinControl.ComponentState) then
      WindowSetFormStyle(win, Form.FormStyle);

    TCocoaWindow(win).callback := cb;
    cb.window := win;

    win.setDelegate(win);
    ns := TCocoaControlUtil.toMacOSTitle( AWinControl.Caption );
    win.setTitle(ns);
    {$ifdef BOOLFIX}
    win.setReleasedWhenClosed_(Ord(False)); // do not release automatically
    win.setAcceptsMouseMovedEvents_(Ord(True));
    {$else}
    win.setReleasedWhenClosed(False); // do not release automatically
    win.setAcceptsMouseMovedEvents(True);
    {$endif}

    if win.respondsToSelector(ObjCSelector('setTabbingMode:')) then
      win.setTabbingMode(NSWindowTabbingModeDisallowed);

    if AWinControl.Perform(WM_NCHITTEST, 0, 0)=HTTRANSPARENT then
    begin
      {$ifdef BOOLFIX}
      win.setIgnoresMouseEvents_(Ord(True));
      {$else}
      win.setIgnoresMouseEvents(True);
      {$endif}
    end;

    cnt.callback.IsOpaque:=true;
    cnt.wincallback := TCocoaWindow(win).callback;
    win.setContentView(cnt);

{$if NOT defined(DisableCocoaModernForm)}
    applyCocoaConfigForm;
{$endif}

    win.makeFirstResponder(doc);
  end
  else
  begin
    if AParams.WndParent <> 0 then
    begin
      cnt.isembedded:= true;
      lDestView := NSObject(AParams.WndParent).lclContentView;
      lDestView.addSubView(cnt);
      //cnt.setAutoresizingMask(NSViewMaxXMargin or NSViewMinYMargin);
      if cnt.window <> nil then
         cnt.window.setAcceptsMouseMovedEvents(True);
      cnt.callback.IsOpaque:=true;
      //  todo: We have to find a way to remove the following notifications save before cnt will be released
      //  NSNotificationCenter.defaultCenter.addObserver_selector_name_object(cnt, objcselector('didBecomeKeyNotification:'), NSWindowDidBecomeKeyNotification, cnt.window);
      //  NSNotificationCenter.defaultCenter.addObserver_selector_name_object(cnt, objcselector('didResignKeyNotification:'), NSWindowDidResignKeyNotification, cnt.window);
    end;
  end;

  if IsFormDesign(AWinControl) then begin
    ds:=(TCocoaDesignOverlay.alloc).initWithFrame(cnt.frame);
    ds.callback := cnt.callback;
    ds.setFrame( NSMakeRect(0,0, cnt.frame.size.width, cnt.frame.size.height));
    ds.setAutoresizingMask(
      //NSViewWidthSizable or NSViewHeightSizable
      NSViewMinXMargin
      or NSViewWidthSizable
      or NSViewMaxXMargin
      or NSViewMinYMargin
      or NSViewHeightSizable
      or NSViewMaxYMargin
    );

    cnt.addSubview_positioned_relativeTo(ds, NSWindowAbove, nil);
    doc.overlay := ds;
    ds.release;
  end;
  doc.release;

  Result := TLCLHandle(cnt);
end;

class procedure TCocoaWSCustomForm.DestroyHandle(const AWinControl: TWinControl
  );
var
  win : NSWindow;
  cb  : ICommonCallback;
  obj : TObject;
  wcb : TLCLWindowCallback;
begin
  if not AWinControl.HandleAllocated then
    Exit;

  win := TCocoaWindowContent(AWinControl.Handle).lclOwnWindow;

  if Assigned(win) then
  begin
    // this is needed for macOS 10.6.
    // if window has been created with a parent (on ShowModal)
    // it should be removed from "parentWindow"
    if Assigned(win.parentWindow) then
      win.parentWindow.removeChildWindow(win);
    win.setLevel(NSNormalWindowLevel);
    win.close;
    win.setContentView(nil);
    cb := win.lclGetCallback();
    if Assigned(cb) then
    begin
      obj := cb.GetCallbackObject;
      if (obj is TLCLWindowCallback) then
        TLCLWindowCallback(obj).window := nil;
    end;
    win.lclClearCallback();
    win.release;
  end;

  TCocoaWSWinControl.DestroyHandle(AWinControl);
end;


class function TCocoaWSCustomForm.GetText(const AWinControl: TWinControl; var AText: String): Boolean;
var
  win : NSWindow;
begin
  Result := AWinControl.HandleAllocated;
  if Result then
  begin
    win := TCocoaWindowContent(AWinControl.Handle).lclOwnWindow;
    if not Assigned(win) then
      AText := NSStringToString(TCocoaWindowContent(AWinControl.Handle).stringValue)
    else
      AText := NSStringToString(win.title);
  end;
end;

class function TCocoaWSCustomForm.GetTextLen(const AWinControl: TWinControl; var ALength: Integer): Boolean;
var
  win : NSWindow;
begin
  Result := AWinControl.HandleAllocated;
  if Result then
  begin
    win := TCocoaWindowContent(AWinControl.Handle).lclOwnWindow;
    if Assigned(win) then
      ALength := NSWindow(AWinControl.Handle).title.length
    else
    begin
      ALength := TCocoaWindowContent(AWinControl.Handle).stringValue.length
    end;
  end;
end;

class procedure TCocoaWSCustomForm.SetText(const AWinControl: TWinControl; const AText: String);
var
  ns: NSString;
  win : NSWindow;
begin
  if not AWinControl.HandleAllocated then
    Exit;
  win := TCocoaWindowContent(AWinControl.Handle).lclOwnWindow;
  ns := TCocoaControlUtil.toMacOSTitle( AText );
  if Assigned(win) then
    NSwindow(win).setTitle(ns)
  else
    TCocoaWindowContent(AWinControl.Handle).setStringValue(ns);
end;

class procedure TCocoaWSCustomForm.CloseModal(const ACustomForm: TCustomForm);
begin
  CocoaWidgetSetModalService.endModal(NSView(ACustomForm.Handle).window);
end;

function createWindowImageRep(win: NSWindow): NSBitmapImageRep;
var
  rect: NSRect;
  imageRef: CGImageRef;
begin
  rect:= win.contentRectForFrameRect( win.frame );
  rect:= TCocoaTypeUtil.toRect( TCocoaScreenUtil.toLCL(rect) );
  imageRef:= CGWindowListCreateImage(
    rect,
    kCGWindowListOptionIncludingWindow,
    win.windowNumber,
    kCGWindowImageDefault );
  Result:= NSBitmapImageRep.alloc.initWithCGImage( imageRef );
  CGImageRelease( imageRef );
end;

class procedure TCocoaWSCustomForm.PaintTo(const AWinControl: TWinControl; ADC: HDC; X, Y: Integer);
var
  bc : TCocoaBitmapContext;
  win: NSWindow;
  imageRep: NSBitmapImageRep;
begin
  if not (TObject(ADC) is TCocoaBitmapContext) then Exit;
  bc := TCocoaBitmapContext(ADC);
  win:= NSView(AWinControl.Handle).window;
  imageRep:= createWindowImageRep(win);
  bc.DrawImageRep(
    NSMakeRect(0,0, bc.size.Width, bc.size.Height),
    NSMakeRect(0,0, imageRep.size.width, imageRep.size.height),
    imageRep );
  imageRep.release;
end;

class procedure TCocoaWSCustomForm.ShowModal(const ACustomForm: TCustomForm);
var
  lWinContent: TCocoaWindowContent;
  win: TCocoaWindow;
  {$ifdef COCOA_USE_NATIVE_MODAL}
  win: TCocoaWindow;
  {$endif}
  fullscreen: Boolean;
begin
  // Another possible implementation is to have modal started in ShowHide with (fsModal in AForm.FormState)

  // Handle PopupParent
  lWinContent := GetWindowContentFromHandle(ACustomForm);

  fullscreen := ACustomForm.WindowState = wsFullScreen;
  if (not fullscreen) and (lWinContent.window.isKindOfClass(TCocoaWindow)) then
    fullscreen := TCocoaWindow(lWinContent.window).lclIsFullScreen;

  // LCL initialization code would cause the custom form to be disabled
  // (due to the fact, ShowModal() has not been called yet, and a previous form
  // might be disabled at the time.
  // ...
  // The fact there's a single global variable is used to indicate, that there's
  // a modal form (neglecting the need for stack of modal forms)
  // makes a developer want to rewrite the whole approach for something more
  // Cocoa and good-practicies friendly.
  // ...
  // At this point of time, we simply force enabling of the new modal form
  // (which is happening in LCL code, but at the wrong time)
  NSObject(ACustomForm.Handle).lclSetEnabled(true);

  // Another possible implementation is using a session, but this requires
  //  disabling the other windows ourselves
  win := TCocoaWSCustomForm.GetWindowFromHandle(ACustomForm);
  if win = nil then Exit;
  CocoaWidgetSetModalService.startModal(NSView(ACustomForm.Handle).window, Assigned(ACustomForm.Menu));

  // Another possible implementation is using runModalForWindow
  {$ifdef COCOA_USE_NATIVE_MODAL}
  win := TCocoaWSCustomForm.GetWindowFromHandle(ACustomForm);
  if win = nil then Exit;
  NSApp.runModalForWindow(win);
  {$endif}
end;

// If ShowModal will not be fully blocking in the future this can be removed
class procedure TCocoaWSCustomForm.SetModalResult(const ACustomForm: TCustomForm;
  ANewValue: TModalResult);
begin
  if (CocoaWidgetSetModalService.currentModal = NSView(ACustomForm.Handle).window) and (ANewValue <> 0) then
    CloseModal(ACustomForm);
end;

class procedure TCocoaWSCustomForm.SetAllowDropFiles(const AForm: TCustomForm;
  AValue: Boolean);
var
  view : NSView;
begin
  if AForm.HandleAllocated then
  begin
    view := NSView(AForm.Handle).lclContentView;
    if AValue then
      view.registerForDraggedTypes(NSArray.arrayWithObjects_count(@NSFilenamesPboardType, 1))
    else
      view.unregisterDraggedTypes
  end;
end;

class procedure TCocoaWSCustomForm.SetAlphaBlend(const ACustomForm: TCustomForm; const AlphaBlend: Boolean; const Alpha: Byte);
var
  win : NSWindow;
begin
  if ACustomForm.HandleAllocated then
  begin
    win := TCocoaWindowContent(ACustomForm.Handle).lclOwnWindow;
    if not Assigned(win) then
      Exit;
    if AlphaBlend then
      win.setAlphaValue(Alpha / 255)
    else
      win.setAlphaValue(1);
  end;
end;

class procedure TCocoaWSCustomForm.SetBorderIcons(const AForm: TCustomForm;
  const ABorderIcons: TBorderIcons);
var
  win : NSWindow;
begin
  if AForm.HandleAllocated then
  begin
    win := NSWindow(TCocoaWindowContent(AForm.Handle).lclOwnWindow);
    if Assigned(win) then
      UpdateWindowMask(win, GetDesigningBorderStyle(AForm), ABorderIcons);
  end;
end;

class procedure TCocoaWSCustomForm.SetFormBorderStyle(const AForm: TCustomForm;
  const AFormBorderStyle: TFormBorderStyle);
var
  win : NSWindow;
begin
  if AForm.HandleAllocated then
  begin
    win := NSWindow(TCocoaWindowContent(AForm.Handle).lclOwnWindow);
    if Assigned(win) then
      UpdateWindowMask(win, AFormBorderStyle, AForm.BorderIcons);
  end;
end;

class procedure TCocoaWSCustomForm.SetFormStyle(const AForm: TCustomform;
  const AFormStyle, AOldFormStyle: TFormStyle);
var
  win : NSWindow;
begin
  if AForm.HandleAllocated and not (csDesigning in AForm.ComponentState) then
  begin
    win := TCocoaWindowContent(AForm.Handle).lclOwnWindow;
    WindowSetFormStyle(win, AFormStyle);
  end;
end;

class procedure TCocoaWSCustomForm.SetIcon(const AForm: TCustomForm;
  const Small, Big: HICON);
var
  win : NSWindow;
  trg : NSImage;
  btn : NSButton;
begin
  if CocoaConfigGlobal.useIcon then Exit;
  if not AForm.HandleAllocated then Exit;

  win := TCocoaWindowContent(AForm.Handle).lclOwnWindow;
  if Assigned(win) then
  begin
    if Small <> 0 then
      trg := TCocoaBitmap(Small).image
    else if Big <> 0 then
      trg := TCocoaBitmap(Big).image
    else
      trg := nil;

    btn := win.standardWindowButton(NSWindowDocumentIconButton);
    if Assigned(btn) then btn.setImage(trg);
  end;
end;

class procedure TCocoaWSCustomForm.SetRealPopupParent(
  const ACustomForm: TCustomForm; const APopupParent: TCustomForm);
var
  win : NSWindow;
begin
  if not ACustomForm.HandleAllocated then Exit;

  win := TCocoaWindowContent(ACustomForm.Handle).lclOwnWindow;
  if Assigned(win.parentWindow) then
    win.parentWindow.removeChildWindow(win);
  if Assigned(APopupParent) then begin
     writeln('SetRealPopupParent ',APopupParent.ClassName);
    NSWindow( NSView(APopupParent.Handle).window).addChildWindow_ordered(win, NSWindowAbove);
  end;
end;

class procedure TCocoaWSCustomForm.ShowHide(const AWinControl: TWinControl);
const
  WindowStateToFlags: array[TWindowState] of Integer = (
    { wsNormal     } SW_SHOWNORMAL,
    { wsMinimized  } SW_SHOWMINIMIZED,
    { wsMaximized  } SW_SHOWMAXIMIZED,
    { wsFullScreen } SW_SHOWFULLSCREEN );
var
  w : NSWindow;
  form: TCustomForm absolute AWinControl;
begin
  if csDesigning in AWinControl.ComponentState then
  begin
    TCocoaWindowUtil.showWindow(AWinControl.Handle, SW_SHOWNORMAL);
    exit;
  end;

  w := TCocoaWindowContent(AWinControl.Handle).lclOwnWindow;
  if not Assigned(w) then
  begin
    TCocoaWSWinControl.ShowHide(AWinControl);
    exit;
  end;

  if AWinControl.HandleObjectShouldBeVisible then
  begin
    TCocoaWindowUtil.showWindow(AWinControl.Handle, WindowStateToFlags[form.WindowState]);
    if form.WindowState<>wsMinimized then
      TCocoaWSWinControl.ShowHide(AWinControl);
    // ShowHide() also actives (sets focus to) the window
    if not w.isKindOfClass(NSPanel) then
      w.makeKeyWindow;
    ArrangeTabOrder(AWinControl);
  end
  else
  begin
    // macOS 10.6. If a window with a parent window is hidden, then parent is also hidden.
    // Detaching from the parent first!
    if Assigned(w.parentWindow) then
      w.parentWindow.removeChildWindow(w);
    // if the same control needs to be shown again, it will be redrawn
    // without this invalidation, Cocoa might should the previously cached contents
    TCocoaWindowContent(AWinControl.Handle).documentView.setNeedsDisplay_(true);
    TCocoaWSWinControl.ShowHide(AWinControl);
  end;
end;

class function TCocoaWSCustomForm.GetClientBounds(
  const AWincontrol: TWinControl; var ARect: TRect): Boolean;
begin
  Result := False;
  if not AWinControl.HandleAllocated then Exit;
  ARect := NSObject(AWinControl.Handle).lclClientFrame;
  Result := True;
end;

class function TCocoaWSCustomForm.GetClientRect(const AWincontrol: TWinControl;
  var ARect: TRect): Boolean;
var
  x, y: Integer;
begin
  Result := AWinControl.HandleAllocated;
  if not Result then Exit;
  ARect := NSObject(AWinControl.Handle).lclClientFrame;
  x := 0;
  y := 0;
  NSObject(AWinControl.Handle).lclLocalToScreen(x, y);
  MoveRect(ARect, x, y);
end;

class procedure TCocoaWSCustomForm.SetBounds(const AWinControl: TWinControl;
  const ALeft, ATop, AWidth, AHeight: Integer);
begin
  if AWinControl.HandleAllocated then
  begin
    //debugln('TCocoaWSCustomForm.SetBounds: '+AWinControl.Name+'Bounds='+dbgs(Bounds(ALeft, ATop, AWidth, AHeight)));
    NSObject(AWinControl.Handle).lclSetFrame(Bounds(ALeft, ATop, AWidth, AHeight));
    TCocoaWindowContent(AwinControl.Handle).callback.boundsDidChange(NSObject(AWinControl.Handle));
  end;
end;

{ TCocoaWSCustomFormHelper }

// 1. on MacOS, when a FORM is full screen, it has its own separate Space.
// 2. in this case, if a new FORM opened, no matter what's the properties of the
//    FORM, Cocoa will automatically set this FORM to full screen,
//    even if it is a Modal Form (ModalSession exactly).
// 3. in order to still show according to the settings of the newly opened FORM,
//    we need to:
//    (1) first set the corresponding NSWindow.CollectionBehavior to
//        NSWindowCollectionBehaviorFullScreenAuxiliary
//    (2) after the FORM is shown, decide whether to set CollectionBehavior to
//        NSWindowCollectionBehaviorFullScreenPrimary based on the properties of
//        the FORM.
class procedure TCocoaWSCustomFormHelper.delaySetCollectionBehavior(
  const form: TCustomForm; const window: TCocoaWindow );
var
  helper: TCocoaWSCustomFormHelper;
begin
  if form.WindowState=wsFullScreen then
    exit;

  window.retain;                                 // release in doSetCollectionBehavior()
  window.setCollectionBehavior( NSWindowCollectionBehaviorFullScreenAuxiliary );
  helper:= TCocoaWSCustomFormHelper.alloc.init;  // release in doSetCollectionBehavior()
  helper._window:= window;
  helper.performSelector_withObject_afterDelay( ObjCSelector('doSetCollectionBehavior'), nil, 0 );
end;

procedure TCocoaWSCustomFormHelper.doSetCollectionBehavior;
begin
  try
    if CocoaWidgetSetModalService.isModalSession then
      exit;
    if (_window.styleMask and NSResizableWindowMask)=0 then
      exit;
    if _window.lclIsFullScreen then
      exit;
    _window.setCollectionBehavior( NSWindowCollectionBehaviorFullScreenPrimary );
  finally
    _window.release;
    release;
  end;
end;

end.
