{/***************************************************************************
                             DesignerProcs.pas
                             -----------------

 ***************************************************************************/

 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

}
unit DesignerProcs;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Types, typinfo, Contnrs,
  // LazUtils
  LazLoggerBase, LazTracer,
  // LCL
  LCLIntf, LCLType, Forms, Controls, Graphics, Menus, ActnList,
  // IdeIntf
  FormEditingIntf, ComponentReg;

type
  TDesignerDCFlag = (
    ddcDCOriginValid,         // please comment
    ddcFormOriginValid,       //
    ddcFormClientOriginValid, //
    ddcSizeValid              //
  );
  TDesignerDCFlags = set of TDesignerDCFlag;

  { TDesignerDeviceContext }

  TDesignerDeviceContext = class
  private
    FCanvas: TCanvas;
    FDC: HDC;
    FDCControl: TControl;
    FDCOrigin: TPoint;   // DC origin on desktop
    FFlags: TDesignerDCFlags;
    FFormClientOrigin: TPoint; // Form client origin on desktop
    FFormOrigin: TPoint; // DC origin relative to designer Form
    FDcSize: TPoint;
    FForm: TCustomForm;
    FSavedDC: HDC;
    FPaintCount: integer;
    function GetDCOrigin: TPoint;
    function GetDCSize: TPoint;
    function GetFormClientOrigin: TPoint;
    function GetFormOrigin: TPoint;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetDC(AForm: TCustomForm; ADCControl: TControl; ADC: HDC);
    procedure Clear;
    procedure BeginPainting;
    procedure EndPainting;
    function RectVisible(ALeft, ATop, ARight, ABottom: integer): boolean;
    property Canvas: TCanvas read FCanvas;
    property DC: HDC read FDC;
    property Form: TCustomForm read FForm;
    property FormOrigin: TPoint read GetFormOrigin;// DC origin relative to designer Form
    property DCOrigin: TPoint read GetDCOrigin; // DC origin on Desktop
    property FormClientOrigin: TPoint read GetFormClientOrigin;// Form Client Origin on desktop
    property DCSize: TPoint read GetDCSize;
  end;

const
  NonVisualCompBorder = 2;

procedure ScaleNonVisual(aParent: TComponent; AFromPPI, AToPPI: Integer);
function NonVisualCompWidth: integer;
function GetParentLevel(AControl: TControl): integer;
function ControlIsInDesignerVisible(AControl: TControl): boolean;
function ComponentIsInvisible(AComponent: TComponent): boolean;
function ComponentIsNonVisual(AComponent: TComponent): boolean;
function ComponentBoundsDesignable(AComponent: TComponent): boolean;

function GetParentFormRelativeTopLeft(Component: TComponent): TPoint;
function GetParentFormRelativeBounds(Component: TComponent): TRect;
function GetParentFormRelativeClientOrigin(Component: TComponent): TPoint;
function GetParentFormRelativeParentClientOrigin(Component: TComponent): TPoint;

function GetFormRelativeMousePosition(Form: TCustomForm): TPoint;

procedure GetComponentBounds(AComponent: TComponent;
  out Left, Top, Width, Height: integer);
function GetComponentLeft(AComponent: TComponent): integer;
function GetComponentTop(AComponent: TComponent): integer;
function GetComponentWidth(AComponent: TComponent): integer;
function GetComponentHeight(AComponent: TComponent): integer;

procedure InvalidateDesignerRect(aHandle: HWND; ARect: pRect);
procedure WriteComponentStates(aComponent: TComponent; Recursive: boolean;
  const Prefix: string = '');


implementation

var
  // Speed optimization for invisible components. No need to search
  // the whole palette if found in InvisibleClasses.
  InvisibleClasses: TClassList;

function GetParentFormRelativeTopLeft(Component: TComponent): TPoint;
var
  FormOrigin: TPoint;
  ParentForm: TCustomForm;
  Parent: TWinControl;
  p: TPoint;
begin
  if Component is TControl then
  begin
    ParentForm := GetDesignerForm(TControl(Component));
    Parent := TControl(Component).Parent;
    if (Parent = nil) or (ParentForm = nil) then
    begin
      Result := Point(0, 0);
    end else
    begin
      Result := Parent.ClientOrigin;
      FormOrigin := ParentForm.ClientOrigin;
      //DebugLn(['GetParentFormRelativeTopLeft Component=',dbgsName(Component),' Parent=',dbgsName(Parent),' ',dbgs(Result),' ParentForm=',dbgsName(ParentForm),' ',dbgs(FormOrigin)]);
      Result.X := Result.X - FormOrigin.X + TControl(Component).Left;
      Result.Y := Result.Y - FormOrigin.Y + TControl(Component).Top;
    end;
  end else
  begin
    Result.X := LeftFromDesignInfo(Component.DesignInfo);
    Result.Y := TopFromDesignInfo(Component.DesignInfo);
    if Component.Owner is TWinControl then
    begin
      Parent:=TWinControl(Component.Owner);
      ParentForm := GetDesignerForm(Parent);
      if (ParentForm<>nil) and (ParentForm<>Parent) then
      begin
        p:=Parent.ClientOrigin;
        FormOrigin := ParentForm.ClientOrigin;
        inc(Result.X,p.X-FormOrigin.X);
        inc(Result.Y,p.Y-FormOrigin.Y);
      end;
    end;
  end;
end;

function GetParentFormRelativeBounds(Component: TComponent): TRect;
var
  CTopLeft: TPoint;
begin
  CTopLeft := GetParentFormRelativeTopLeft(Component);
  Result.Left := CTopLeft.X;
  Result.Top := CTopLeft.Y;
  Result.Right := Result.Left + GetComponentWidth(Component);
  Result.Bottom := Result.Top + GetComponentHeight(Component);
end;

function GetParentFormRelativeClientOrigin(Component: TComponent): TPoint;
var
  FormOrigin: TPoint;
  ParentForm: TCustomForm;
begin
  if Component is TControl then
  begin
    ParentForm := GetDesignerForm(TControl(Component));
    if ParentForm = nil then
      Result := Point(0, 0)
    else
    begin
      Result := TControl(Component).ClientOrigin;
      FormOrigin := ParentForm.ClientOrigin;
      Result.X := Result.X - FormOrigin.X;
      Result.Y := Result.Y - FormOrigin.Y;
    end;
  end else
  begin
    Result.X := LeftFromDesignInfo(Component.DesignInfo);
    Result.Y := TopFromDesignInfo(Component.DesignInfo);
  end;
end;

function GetParentFormRelativeParentClientOrigin(Component: TComponent): TPoint;
var
  FormOrigin, ParentOrigin: TPoint;
  ParentForm: TCustomForm;
  Parent: TWinControl;
begin
  if Component is TControl then
  begin
    ParentForm := GetDesignerForm(TControl(Component));
    Parent := TControl(Component).Parent;
    if (Parent = nil) or (ParentForm = nil) then
      Result := Point(0, 0)
    else
    begin
      ParentOrigin := Parent.ClientOrigin;
      FormOrigin := ParentForm.ClientOrigin;
      Result.X := ParentOrigin.X - FormOrigin.X;
      Result.Y := ParentOrigin.Y - FormOrigin.Y;
    end;
  end
  else
  begin
    Result := Point(0, 0);
    ParentForm := GetDesignerForm(Component);
    if ParentForm = nil then
      Exit;
    if (Component <> nil) and (Component.Owner <> ParentForm) then
    begin
      Component := Component.Owner;
      if (csInline in Component.ComponentState) and (Component is TControl) then
      begin
        with ParentForm.ScreenToClient(TControl(Component).ClientToScreen(Point(0, 0))) do
        begin
          inc(Result.X, X);
          inc(Result.Y, Y);
        end;
      end;
    end;
  end;
end;

function GetFormRelativeMousePosition(Form: TCustomForm): TPoint;
var
  FormClientOrigin: TPoint;
begin
  Result.X:=0;
  Result.Y:=0;
  GetCursorPos(Result);
  FormClientOrigin:=Form.ClientOrigin;
  dec(Result.X,FormClientOrigin.X);
  dec(Result.Y,FormClientOrigin.Y);
end;

procedure GetComponentBounds(AComponent: TComponent; out Left, Top, Width,
  Height: integer);
begin
  if AComponent is TControl then 
  begin
    Left := TControl(AComponent).Left;
    Top := TControl(AComponent).Top;
    Width := TControl(AComponent).Width;
    Height := TControl(AComponent).Height;
  end else 
  begin
    Left := LeftFromDesignInfo(AComponent.DesignInfo);
    Top := TopFromDesignInfo(AComponent.DesignInfo);
    Width := NonVisualCompWidth;
    Height := Width;
  end;
end;

function GetComponentLeft(AComponent: TComponent): integer;
begin
  if AComponent is TControl then
    Result := TControl(AComponent).Left
  else
    Result := LeftFromDesignInfo(AComponent.DesignInfo);
end;

function GetComponentTop(AComponent: TComponent): integer;
begin
  if AComponent is TControl then
    Result := TControl(AComponent).Top
  else
    Result := TopFromDesignInfo(AComponent.DesignInfo);
end;

function GetComponentWidth(AComponent: TComponent): integer;
begin
  if AComponent is TControl then
    Result := TControl(AComponent).Width
  else
    Result := NonVisualCompWidth;
end;

function GetComponentHeight(AComponent: TComponent): integer;
begin
  if AComponent is TControl then
    Result := TControl(AComponent).Height
  else
    Result := NonVisualCompWidth;
end;

procedure InvalidateDesignerRect(aHandle: HWND; ARect: pRect);
const
  ExtraInvalidateFrame = 3;
var
  InvRect: TRect;
begin
  InvRect:=ARect^;
  InvRect.Inflate(ExtraInvalidateFrame, ExtraInvalidateFrame);
  InvalidateRect(aHandle,@InvRect,false);
end;

procedure WriteComponentStates(aComponent: TComponent; Recursive: boolean;
  const Prefix: string);
var
  i: Integer;
begin
  if aComponent=nil then exit;
  debugln([Prefix,DbgSName(aComponent),' ',dbgs(aComponent.ComponentState)]);
  if Recursive then begin
    for i:=0 to aComponent.ComponentCount-1 do
      WriteComponentStates(aComponent.Components[i],true,Prefix+'  ');
  end;
end;

procedure ScaleNonVisual(aParent: TComponent; AFromPPI, AToPPI: Integer);
var
  I: Integer;
  Comp: TComponent;
  DsgnInfo: LongInt;
begin
  for I := 0 to aParent.ComponentCount-1 do
  begin
    Comp := aParent.Components[I];
    DsgnInfo := Comp.DesignInfo;
    LongRec(DsgnInfo).Lo:=MulDiv(LongRec(DsgnInfo).Lo, AToPPI, AFromPPI);
    LongRec(DsgnInfo).Hi:=MulDiv(LongRec(DsgnInfo).Hi, AToPPI, AFromPPI);
    Comp.DesignInfo := DsgnInfo;
  end;
end;

function NonVisualCompWidth: integer;
begin
  if Application.Scaled then
    Result := MulDiv(ComponentPaletteImageWidth, Screen.PixelsPerInch, 96) + 2 * NonVisualCompBorder
  else
    Result := ComponentPaletteImageWidth + 2 * NonVisualCompBorder
end;

function GetParentLevel(AControl: TControl): integer;
begin
  Result:=0;
  while AControl<>nil do begin
    inc(Result);
    AControl:=AControl.Parent;
  end;
end;

function ControlIsInDesignerVisible(AControl: TControl): boolean;
begin
  while AControl<>nil do begin
    if csNoDesignVisible in AControl.ControlStyle then
      exit(false);
    AControl:=AControl.Parent;
  end;
  Result:=true;
end;

function ComponentIsInvisible(AComponent: TComponent): boolean;
var
  RegComp: TRegisteredComponent;
begin
  if (AComponent is TControl) then
    Result:=(csNoDesignVisible in TControl(AComponent).ControlStyle)
  else begin
    if InvisibleClasses=Nil then begin
      InvisibleClasses:=TClassList.Create;
      InvisibleClasses.Add(TMenuItem);
      InvisibleClasses.Add(TAction);
    end;
    // Optimization: search class types from list first.
    if InvisibleClasses.IndexOf(AComponent.ClassType) > -1 then
      Exit(True);
    Assert(Assigned(IDEComponentPalette), 'ComponentIsInvisible: IDEComponentPalette=Nil');
    RegComp:=IDEComponentPalette.FindRegComponent(AComponent.ClassType);
    Result:=(RegComp=nil) or (RegComp.OrigPageName='');
    if Result then begin
      DebugLn(['---ComponentIsInvisible: Adding ', AComponent, ' to InvisibleClasses.---']);
      InvisibleClasses.Add(AComponent.ClassType);
    end;
  end;
end;

function ComponentIsNonVisual(AComponent: TComponent): boolean;
begin
  Result:=(AComponent<>nil)
          and (not (AComponent is TControl))
          and (not ComponentIsInvisible(AComponent));
end;

function ComponentBoundsDesignable(AComponent: TComponent): boolean;
begin
  Result:=(not ComponentIsInvisible(AComponent));
  if Result and (AComponent is TControl) then begin
    if [csDesignFixedBounds,csNoDesignVisible]*TControl(AComponent).ControlStyle
      <>[]
    then
      Result:=false;
  end;
end;


{ TDesignerDeviceContext }

function TDesignerDeviceContext.GetDCOrigin: TPoint;
// returns the DC origin in screen coordinates
var
  CurFormClientOrigin: TPoint;
  CurFormOrigin: TPoint;
begin
  if not (ddcDCOriginValid in FFlags) then
  begin
    CurFormClientOrigin := FormClientOrigin;
    CurFormOrigin := FormOrigin;
    FDCOrigin.X := CurFormOrigin.X - CurFormClientOrigin.X;
    FDCOrigin.Y := CurFormOrigin.Y - CurFormClientOrigin.Y;
    Include(FFlags, ddcDCOriginValid);
  end;
  Result:=FDCOrigin;
end;

function TDesignerDeviceContext.GetDCSize: TPoint;
// returns the DC size
begin
  if not (ddcSizeValid in FFlags) then 
  begin
    GetDeviceSize(DC, FDCSize);
    Include(FFlags, ddcSizeValid);
  end;
  Result := FDCSize;
end;

function TDesignerDeviceContext.GetFormClientOrigin: TPoint;
// returns the Form Client Origin on desktop
begin
  if not (ddcFormClientOriginValid in FFlags) then
  begin
    FFormClientOrigin := FForm.ClientOrigin;
    Include(FFlags, ddcFormClientOriginValid);
  end;
  Result := FFormClientOrigin;
end;

function TDesignerDeviceContext.GetFormOrigin: TPoint;
// returns the DC origin relative to the form client origin
// For example: The DC of the client area of the form itself will return 0,0
var
  AControlOrigin: TPoint;
begin
  if not (ddcFormOriginValid in FFlags) then 
  begin
    if not GetDCOriginRelativeToWindow(DC, FForm.Handle, FFormOrigin) then
    begin
      // For some reason we cannot retrieve the DC origin. It can happen for example
      // when DC is not a control DC but a double buffer DC. Lets use another trick
      if FDCControl <> nil then
      begin
        if FDCControl.Parent <> nil then
          AControlOrigin := FDCControl.Parent.ClientToScreen(FDCControl.BoundsRect.TopLeft)
        else
          AControlOrigin := FDCControl.ClientToScreen(Point(0, 0));
        FFormOrigin := FForm.ClientToScreen(Point(0, 0));
        FFormOrigin.X := AControlOrigin.X - FFormOrigin.X;
        FFormOrigin.Y := AControlOrigin.Y - FFormOrigin.Y;
      end
      else
        FFormOrigin := Point(0, 0);
      if GetWindowOrgEx(DC, @AControlOrigin) <> 0 then
      begin
        Dec(FFormOrigin.X, AControlOrigin.X);
        Dec(FFormOrigin.Y, AControlOrigin.Y);
      end;
    end;
    Include(FFlags, ddcFormOriginValid);
    // DebugLn(['New origin: ', FFormOrigin.X, ':', FFormOrigin.Y]);
  end;
  Result := FFormOrigin;
end;

constructor TDesignerDeviceContext.Create;
begin
  inherited Create;
  FCanvas:=TCanvas.Create;
end;

destructor TDesignerDeviceContext.Destroy;
begin
  FCanvas.Free;
  inherited Destroy;
end;

procedure TDesignerDeviceContext.SetDC(AForm: TCustomForm;
  ADCControl: TControl; ADC: HDC);
begin
  Clear;
  FDC := ADC;
  FDCControl := ADCControl;
  FForm := AForm;
end;

procedure TDesignerDeviceContext.Clear;
begin
  if (FSavedDC<>0) or (FPaintCount>0) then
    RaiseGDBException('');
  FDC := 0;
  FFlags := FFlags - [ddcFormOriginValid, ddcFormClientOriginValid, ddcDCOriginValid, ddcSizeValid];
end;

procedure TDesignerDeviceContext.BeginPainting;
begin
  if FSavedDC = 0 then
  begin
    FSavedDC := SaveDC(DC);
    FCanvas.Handle := DC;
  end;
  inc(FPaintCount);
  //DebugLn(['TDesignerDeviceContext.BeginPainting ',FPaintCount]);
end;

procedure TDesignerDeviceContext.EndPainting;
begin
  //DebugLn(['TDesignerDeviceContext.EndPainting ',FPaintCount]);
  dec(FPaintCount);
  if (FPaintCount=0) and (FSavedDC <> 0) then
  begin
    FCanvas.Handle := 0;
    RestoreDC(DC, FSavedDC);
    FSavedDC := 0;
  end;
end;

function TDesignerDeviceContext.RectVisible(ALeft, ATop, ARight,
  ABottom: integer): boolean;
// coordinates must be relative to DC origin
var
  ASize: TPoint;
begin
  if (ARight < 0) or (ABottom < 0) then
    Result := False
  else begin
    ASize := DCSize;
    if (ALeft >= ASize.X) or (ATop >= ASize.Y) then
      Result := False
    else
      Result := True;
  end;
end;

initialization
  OnGetDesignerForm:=nil;

finalization
  InvisibleClasses.Free;

end.

