{==============================================================================

  SkinForm Control
  Copyright (C) 2000-2001 by Evgeny Kryukov
  All rights reserved

  All contents of this file and all other files included in this archive
  are Copyright (C) 2001 Evgeny Kryukov. Use and/or distribution of
  them requires acceptance of the License Agreement.

  See License.txt for licence information

===============================================================================}

unit KsSkinForms;

{$I KsSkinVer.Inc}
{$T-,W-,X+,P+}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ExtCtrls, Buttons,
  KsForms, KsBitmap, KsItems, KsSkinItems, KsSkinVersion, KsSkinObjects, KsSkinSource,
  KsSkinEngine, KsSkinObjects2;

type

{ TSeSkinForm class }

  TCustomActionEvent = procedure (SkinObject: TSeSkinObject) of object;

{ TSeSkinForm is a inheritance of TForm with advanced features }
  TSeSkinForm = class(TKsCustomForm)
  private
    FSkinEngine: TSeSkinEngine;
    FTimer: TTimer;
    FMouseObject: TSeSkinObject;
    FObjectBuffer: TKsBitmap;
    FSkinObject: string;
    FSkinForm: TSeSkinObject;
    FDownObject: TSeSkinObject;
    FSkinClient: TSeSkinObject;
    FSkinTitle: TSeSkinObject;
    FOnCustomAction: TCustomActionEvent;
    procedure DoTimer(Sender: TObject);

    procedure WMGetSkinForm(var Msg: TMessage); message WM_GETSKINFORM;
    procedure WMInvalidateSkinObject(var Msg: TMessage); message WM_INVALIDATESKINOBJECT;
    procedure WMBeforeChange(var Msg: TMessage); message WM_BEFORECHANGE;
    procedure WMSkinChange(var Msg: TMessage); message WM_SKINCHANGE;

    function GetVersion: TSeSkinVersion;
    procedure SetVersion(const Value: TSeSkinVersion);
    procedure SetSkinEngine(const Value: TSeSkinEngine);
    procedure SetSkinObject(const Value: string);
  protected
    function UseSkin: boolean;
    procedure InvalidateObject(SkinObject: TSeSkinObject);
    procedure UpdateLinkedObject;

    { Protected }
    procedure ChangeSize; override;
    function GetRegion: HRgn; override;
    { Protected menus }
    function CreateMenuItem(AOwner: TComponent): TKsCustomItem; override;
    { Protected Routines }
    function GetClientRect: TRect; override;
    function GetButtonRect(Button: TKsBorderIcon): TRect; override;
    { WindowState's Rect }
    function GetRollupRect: TRect; override;
    function GetMinimizedRect: TRect; override;
    function GetMaximizedRect: TRect; override;
    { Tracking Size }
    function GetMaxTrackSize: TPoint; override;
    function GetMinTrackSize: TPoint; override;
    { Painting }
    procedure PaintNCArea(Buffer: TKsBitmap); override;
    procedure PaintClientArea(Buffer: TKsBitmap); override;
    { Mouse Routines }
    procedure NCMouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
    procedure NCMouseMove(Shift: TShiftState; X, Y: integer); override;
    procedure NCMouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;

    function GetHitTest(X, Y: integer): TKsHitTest; override;
    { VCL protected  }
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;

    procedure UpdateForm;
    procedure UpdateControls;
  published
    property Active;
    property Blend;
    property BlendValue;
    property BorderIcons;
    property BorderStyle;
    property CanResize;
    property CanMove;
    property Magnetic;
    property MagneticValue;
    property MenuBar;
    { Link to TSeSkinEngine component. Specifies the appearance and behavior by using specified skins from TSeSkinEngine components
      See Also
        TSeSkinEngine
    }
    property SkinEngine: TSeSkinEngine read FSkinEngine write SetSkinEngine;
    { Specifies the SkinObject name, your can change default value to set custom skin object for the control
      For Example:
        For TSeSkinButton the SkinObject property set to 'Button', but your can use you own name - example 'My Button' 
    }
    property SkinObject: string read FSkinObject write SetSkinObject;
    property Version: TSeSkinVersion read GetVersion write SetVersion stored False;
    property WindowState;
    property OnCustomAction: TCustomActionEvent read FOnCustomAction
      write FOnCustomAction;
  end;

implementation {===============================================================}

uses KsUtils;

{ TSeSkinForm ===============================================================}

constructor TSeSkinForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Active := true;

//  FEraseBkgnd := true; { Erase all client area }

  FTimer := TTimer.Create(Self);
  FTimer.Interval := 100;
  FTimer.OnTimer := DoTimer;
  FTimer.Enabled := not (csDesigning in ComponentState);

  FObjectBuffer := TKsBitmap.Create;

  FSkinObject := 'Form';
end;

destructor TSeSkinForm.Destroy;
begin
  if FSkinForm <> nil then FSkinForm.Free;

  FObjectBuffer.Free;
  FTimer.Free;
  inherited Destroy;
end;

procedure TSeSkinForm.Loaded;
begin
  if BorderStyle = kbsToolWindow then
  begin
    FSkinObject := 'ToolWindow';
    SkinEngine := FSkinEngine;
  end;

  inherited Loaded;

  { No resizeable form }
  if (FSkinForm <> nil) and (FSkinForm.FindObjectByKind(skClient) = nil) then
  begin
    Form.SetBounds(Form.Left, Form.Top, FSkinForm.Width, FSkinForm.Height);
  end;

  UpdateControls;
end;

{ Internal routines }

function TSeSkinForm.UseSkin: boolean;
begin
  if (csDestroying in ComponentState) or (csLoading in ComponentState) then
    Result := false
  else
    if (FSkinEngine <> nil) and (FSkinEngine.SkinSource <> nil) and
       (not FSkinEngine.SkinSource.IsChanging) and
       (FSkinEngine.SkinSource.Count > 0) and
       (FSkinForm <> nil)
    then
      Result := true
    else
      Result := false;
end;

procedure TSeSkinForm.InvalidateObject(SkinObject: TSeSkinObject);
begin
  if UseSkin and (FSkinForm <> nil) and (SkinObject.Kind <> skClient) then
  begin
    if (Form <> nil) and (Form.FormStyle = fsMDIChild) and
       (WindowState = kwsMaximized) then
    begin
      if (SkinObject.Owner is TSeSkinObject) and
         (FSkinClient <> nil) and (FSkinClient.FindObjectByName(SkinObject.Name) = nil)
      then
        Exit;
    end;

    UpdateNCArea(0);
  end;
end;

procedure TSeSkinForm.UpdateLinkedObject;

  procedure UpdateLink(SkinObject: TSeSkinObject);
  var
    R: TRect;
    LinkedControl: TControl;
  begin
    if not (SkinObject is TSeLinkSkinObject) then Exit;
    if SkinObject.Owner = nil then Exit;
    IntersectRect(R, (SkinObject.Owner as TSeSkinObject).BoundsRect, SkinObject.BoundsRect);
    if (RectWidth(R) = 0) or (RectHeight(R) = 0) then
    begin
      LinkedControl := Form.FindChildControl((SkinObject as TSeLinkSkinObject).LinkControl);
      if LinkedControl <> nil then
        LinkedControl.BoundsRect := Rect(0, 0, 0, 0);
      Exit;
    end;

    { Update linked control }
    with (SkinObject as TSeLinkSkinObject) do
    begin
      LinkedControl := Form.FindChildControl(LinkControl);
      if LinkedControl <> nil then
      begin
        R := BoundsRect;
        with GetClientRect do
          OffsetRect(R, -Left, -Top);
        LinkedControl.BoundsRect := R;
      end;
    end;
  end;

  procedure UpdateObject(SkinObject: TSeSkinObject);
  var
    i: integer;
  begin
    if SkinObject = nil then Exit;

    for i := 0 to SkinObject.Count - 1 do
    begin
      UpdateLink(SkinObject.Objects[i]);
      if SkinObject.Objects[i].Count > 0 then
        UpdateObject(SkinObject.Objects[i]);
    end;
  end;

begin
  if UseSkin and (FSkinForm <> nil) then
    UpdateObject(FSkinForm);
end;

procedure TSeSkinForm.UpdateForm;
var
  R: TRect;
begin
  if csLoading in ComponentState then Exit;

  UpdateNCArea(0);
  if (Form <> nil) and (Form.FormStyle = fsMDIForm) then
  begin
    R := Rect(0, 0, FWidth, FHeight);
    InvalidateRect(Form.ClientHandle, @R, true);
  end
  else
  begin
    R := Rect(0, 0, FWidth, FHeight);
    InvalidateRect(Form.Handle, @R, true);
  end;
end;

procedure TSeSkinForm.UpdateControls;
var
  i: integer;
begin
  if csLoading in ComponentState then Exit;
  
  if Form <> nil then
    for i := 0 to Form.ComponentCount - 1 do
    begin
      {$IFDEF SE_COMPILER5}
      if (Form.Components[i] is TCustomFrame) then     
        SendMessage((Form.Components[i] as TCustomFrame).Handle, WM_INVALIDATESKINOBJECT, 0, 0);
      {$ENDIF}

      if (Form.Components[i] is TWinControl) then
        SendMessage((Form.Components[i] as TWinControl).Handle, WM_INVALIDATESKINOBJECT, 0, 0);

      if (Form.Components[i] is TGraphicControl) then
        (Form.Components[i] as TGraphicControl).Perform(WM_INVALIDATESKINOBJECT, 0, 0);
    end;
end;

procedure TSeSkinForm.DoTimer(Sender: TObject);
var
  MouseObject: TSeSkinObject;
  MousePoint: TPoint;
begin
  if FFormActive and UseSkin and (FSkinForm <> nil) then
  begin
    GetCursorPos(MousePoint);
    MousePoint := NormalizePoint(Point(MousePoint.X, MousePoint.Y));
    MouseObject := FSkinForm.FindObjectByPoint(MousePoint);

    { Hover and Leave events }
    if (MouseObject = nil) then
    begin
      if (FMouseObject <> nil) then
      begin
        FMouseObject.MouseLeave;
        FMouseObject := nil;
      end;
    end
    else
      if (MouseObject <> nil) and (MouseObject.Visible) and (MouseObject <> FMouseObject) then
      begin
        if (FMouseObject <> nil) then
        begin
          FMouseObject.MouseLeave;
        end;
        FMouseObject := MouseObject;
        if (FDownObject <> nil) then
        begin
          if (FDownObject = FMouseObject) then
            FMouseObject.MouseHover
        end
        else
          FMouseObject.MouseHover;
      end;
  end;
end;

{ Internal messages }

procedure TSeSkinForm.WMInvalidateSkinObject(var Msg: TMessage);
var
  SkinObject: TSeSkinObject;
begin
  if Pointer(Msg.lParam) <> nil then
  begin
    SkinObject := TSeSkinObject(Msg.lParam);
    InvalidateObject(SkinObject);
  end;
end;

procedure TSeSkinForm.WMGetSkinForm(var Msg: TMessage);
begin
  Msg.Result := Integer(Self);
  MessageHandled;
end;

procedure TSeSkinForm.WMBeforeChange(var Msg: TMessage);
begin
  if Pointer(Msg.LParam) = nil then Exit;
  if TSeSkinEngine(Msg.LParam) <> FSkinEngine then Exit;

  FTimer.Enabled := false;
  Application.ProcessMessages;
  FMouseObject := nil;
  FDownObject := nil;
  FSkinClient := nil;
  FSkinTitle := nil;
  if FSkinForm <> nil then FSkinForm.Free;
  FSkinForm := nil;
  Application.ProcessMessages;
end;

procedure TSeSkinForm.WMSkinChange(var Msg: TMessage);
begin
  if Pointer(Msg.LParam) = nil then Exit;
  if TSeSkinEngine(Msg.LParam) <> FSkinEngine then Exit;

  { Set properties }
  SkinEngine := FSkinEngine;
  { }
  FTimer.Enabled := true;
end;

{ Protected overrides =========================================================}

function TSeSkinForm.CreateMenuItem(AOwner: TComponent): TKsCustomItem;
begin
  Result := TSeSkinItem.Create(AOwner);
  TSeSkinItem(Result).SkinEngine := SkinEngine; 
end;

procedure TSeSkinForm.PaintClientArea(Buffer: TKsBitmap);
begin
  if UseSkin then
  begin
    if (FSkinClient <> nil) then
    begin
      FSkinClient.Active := FFormActive;
      FSkinClient.Draw(Buffer);
    end
    else
      if FSkinForm <> nil then
      begin
        FSkinForm.Active := FFormActive;
        FSkinForm.Draw(Buffer);
      end
  end
  else
    inherited ;
end;

procedure TSeSkinForm.PaintNCArea(Buffer: TKsBitmap);
var
  SkinObject: TSeSkinObject;
begin
  if UseSkin and (FSkinForm <> nil) then
  begin
    if (Form <> nil) and (Form.FormStyle = fsMDIChild) and (WindowState = kwsMaximized) then
    begin
      inherited ;
      Exit;
    end;
    if BorderStyle = kbsNone then
    begin
      inherited ;
      Exit;
    end;

    FSkinForm.Active := FFormActive;
    { Set skinobjects state }
    case WindowState of
      kwsNormal: FSkinForm.State := ssNormal;
      kwsMaximized: FSkinForm.State := ssMaximized;
      kwsMinimized: FSkinForm.State := ssMinimized;
      kwsRollup: FSkinForm.State := ssRollup;
      kwsTray: FSkinForm.State := ssMinimized;
    end;
    { Set title text }
    if FSkinTitle <> nil then
      FSkinTitle.Text := Form.Caption;
    { Draw }
    if FSkinClient <> nil then
      FSkinClient.Visible := false;

    FSkinForm.Draw(Buffer);

    if FSkinClient <> nil then
      FSkinClient.Visible := true;
  end
  else
    inherited ;
end;

function TSeSkinForm.GetButtonRect(Button: TKsBorderIcon): TRect;
begin
  if UseSkin then
  begin
    Result := Rect(0, 0, 0, 0);
  end
  else
    Result := inherited GetButtonRect(Button);
end;

function TSeSkinForm.GetClientRect: TRect;
begin
  if ((Form <> nil) and (Form.FormStyle = fsMDIChild) and (WindowState = kwsMaximized)) or not UseSkin then
  begin
    Result := inherited GetClientRect;
    Exit;
  end;

  if BorderStyle = kbsNone then
    Result := inherited GetClientRect
  else
    if FSkinClient <> nil then
      Result := FSkinClient.BoundsRect
    else
      Result := Rect(0, 0, FWidth, FHeight);
end;

function TSeSkinForm.GetHitTest(X, Y: integer): TKsHitTest;
var
  SkinObject: TSeSkinObject;
begin
  Result := khtClient;

  if UseSkin then
  begin
    if FSkinForm <> nil then
    begin
      SkinObject := FSkinForm.FindObjectByPoint(Point(X, Y));
      if SkinObject <> nil then
      begin
        case SkinObject.Kind of
          skClient: Result := khtClient;
          skCaption, skTitle: Result := khtCaption;
          skTop: Result := khtTop;
          skTopLeft: Result := khtTopLeft;
          skTopRight: Result := khtTopRight;
          skLeft: Result := khtLeft;
          skRight: Result := khtRight;
          skBottom: Result := khtBottom;
          skBottomLeft: Result := khtBottomLeft;
          skBottomRight: Result := khtBottomRight;
          skSysButton: Result := khtNonClient;
        end;
      end;
    end
    else
      Result := khtClient;
  end
  else
    Result := inherited GetHitTest(X, Y);
end;

function TSeSkinForm.GetMaximizedRect: TRect;
var
  R: TRect;
begin
  if UseSkin then
  begin
    if StayOnTop then
      R := Rect(0, 0, Screen.Width, Screen.Height) 
    else
      SystemParametersInfo(SPI_GETWORKAREA, 0, @R, 0);

    { for DualMonotors }
    IntersectRect(R, R, Rect(Screen.DesktopLeft, Screen.DesktopTop,
      Screen.DesktopLeft + Screen.DesktopWidth, Screen.DesktopTop + Screen.DesktopHeight));

    Result := R;

    if (Form <> nil) and (Form.FormStyle = fsMDIChild) then
      Result := inherited GetMaximizedRect;
  end
  else
    Result := inherited GetMaximizedRect;
end;

function TSeSkinForm.GetMaxTrackSize: TPoint;
var
  R: TRect;
begin
  if UseSkin then
  begin
    if StayOnTop then
      R := Rect(0, 0, Screen.Width, Screen.Height) 
    else
      SystemParametersInfo(SPI_GETWORKAREA, 0, @R, 0);
    Result := Point(RectWidth(R), RectHeight(R));

    if (Form <> nil) and (Form.FormStyle = fsMDIChild) then
      Result := inherited GetMaxTrackSize;
  end
  else
    Result := inherited GetMaxTrackSize;
end;

function TSeSkinForm.GetMinimizedRect: TRect;
begin
  Result := inherited GetMinimizedRect;
end;

function TSeSkinForm.GetMinTrackSize: TPoint;
begin
  Result := inherited GetMinTrackSize;
end;

procedure TSeSkinForm.ChangeSize;
begin
  inherited ChangeSize;
end;

function TSeSkinForm.GetRegion: HRgn;
begin
  if UseSkin and (FSkinForm <> nil) then
  begin
    if ((Form <> nil) and (Form.FormStyle = fsMDIChild) and
       (WindowState = kwsMaximized)) or (BorderStyle = kbsNone) then
    begin
      if FSkinClient <> nil then
        FSkinClient.BoundsRect := Rect(0, 0, FWidth, FHeight);

      Result := inherited GetRegion;

      Exit
    end;

    if FSkinClient <> nil then
      FSkinForm.BoundsRect := Rect(0, 0, FWidth, FHeight);
    { Update linked object }
    UpdateLinkedObject;
    { Result }
    Result := FSkinForm.GetRegion;
  end
  else
    Result := inherited GetRegion;
end;

function TSeSkinForm.GetRollupRect: TRect;
begin
  Result := inherited GetRollupRect;
end;

procedure TSeSkinForm.NCMouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: integer);
var
  SkinObject: TSeSkinObject;
begin
  inherited;

  if UseSkin and (FSkinForm <> nil) then
  begin
    SkinObject := FSkinForm.FindObjectByPoint(Point(X, Y));
    if SkinObject <> nil then
    begin
      if (ssDouble in Shift) then
        SkinObject.MouseDouble(Button, X - SkinObject.Left, Y - SkinObject.Top)
      else
        SkinObject.MouseDown(Button, X - SkinObject.Left, Y - SkinObject.Top);
    end;

    FDownObject := SkinObject;
  end;
end;

procedure TSeSkinForm.NCMouseMove(Shift: TShiftState; X, Y: integer);
var
  SkinObject: TSeSkinObject;
begin
  inherited;
  if UseSkin and (FSkinForm <> nil) then
  begin
    SkinObject := FSkinForm.FindObjectByPoint(Point(X, Y));
    if SkinObject <> nil then
      if ((ssLeft in Shift) or (ssRight in Shift)) then
      begin
        if FDownObject = SkinObject then
          SkinObject.MouseMove(Shift, X - SkinObject.Left, Y - SkinObject.Top);
      end
      else
        SkinObject.MouseMove(Shift, X - SkinObject.Left, Y - SkinObject.Top);
  end;
end;

procedure TSeSkinForm.NCMouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: integer);
var
  SkinObject: TSeSkinObject;
begin
  inherited;
  if UseSkin and (FSkinForm <> nil) then
  begin
    SkinObject := FSkinForm.FindObjectByPoint(Point(X, Y));
    if SkinObject <> nil then
      if FDownObject = SkinObject then
        SkinObject.MouseUp(Button, X - SkinObject.Left, Y - SkinObject.Top);

    FDownObject := nil;
  end;
end;

{ VCL overrides ===============================================================}

procedure TSeSkinForm.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent = FSkinEngine) then
    FSkinEngine := nil;
end;

{ Properties ==================================================================}

function TSeSkinForm.GetVersion: TSeSkinVersion;
begin
  Result := sSeSkinVersion;
end;

procedure TSeSkinForm.SetSkinEngine(const Value: TSeSkinEngine);
var
  R: TRect;
begin
  FSkinEngine := Value;

  if (FSkinEngine <> nil) and (FSkinEngine.SkinSource <> nil) and
     (not FSkinEngine.SkinSource.IsChanging) and
     (FSkinEngine.SkinSource.Count > 0) then
  begin
    if FSkinForm <> nil then FSkinForm.Free;
    FSkinForm := nil;
    FSkinClient := nil;
    FSkinTitle := nil;

    if (FSkinObject = 'Form') and (FSkinEngine.SkinSource.Form <> nil) then
      FSkinForm := FSkinEngine.SkinSource.Form.CreateCopy(nil)
    else
      if FSkinEngine.SkinSource.GetObjectByName(FSkinObject) <> nil then
        FSkinForm := FSkinEngine.SkinSource.GetObjectByName(FSkinObject).CreateCopy(nil)
      else
        FSkinForm := FSkinEngine.SkinSource.Form.CreateCopy(nil);

    if FSkinForm <> nil then
    begin
      FSkinForm.ParentControl := Form;
      FSkinTitle := FSkinForm.FindObjectByKind(skTitle);
      FSkinClient := FSkinForm.FindObjectByKind(skClient);
      { Set form properties }
      if Form <> nil then
      begin
        Form.Font := FSkinForm.Font;
        Form.Color := FSkinForm.Color;
      end;
    end;
  end
  else
  begin
    if FSkinForm <> nil then FSkinForm.Free;
    FSkinClient := nil;
    FSkinTitle := nil;
    if Form <> nil then
      Form.Color := clBtnFace;
  end;

  { No resizeable form }
  if UseSkin and (FSkinForm.FindObjectByKind(skClient) = nil) then
    Form.SetBounds(Form.Left, Form.Top, FSkinForm.Width, FSkinForm.Height);

  { Update }
  if not (csLoading in ComponentState) then
    Update;
end;

procedure TSeSkinForm.SetVersion(const Value: TSeSkinVersion);
begin
end;

procedure TSeSkinForm.SetSkinObject(const Value: string);
begin
  FSkinObject := Value;
end;

{$IFDEF SkinTrial}
function DelphiIsRunning: Boolean;
const
  A1: array[0..12] of char = 'TApplication'#0;
  A3: array[0..18] of char = 'TPropertyInspector'#0;
  A4: array[0..11] of char = 'TAppBuilder'#0;
  T1: array[0..10] of char = 'Delphi 6'#0;
  T2: array[0..10] of char = 'Delphi 3'#0;
  T3: array[0..10] of char = 'Delphi 4'#0;
  T4: array[0..10] of char = 'Delphi 5'#0;
  T5: array[0..12] of char = 'C++Builder 6'#0;
  T6: array[0..12] of char = 'C++Builder 3'#0;
  T7: array[0..12] of char = 'C++Builder 4'#0;
  T8: array[0..12] of char = 'C++Builder 5'#0;
begin
  Result :=
    (
    (FindWindow(A1, T1) <> 0) or {Delphi 6}
    (FindWindow(A1, T2) <> 0) or {Delphi 3}
    (FindWindow(A1, T3) <> 0) or {Delphi 4}
    (FindWindow(A1, T4) <> 0) or {Delphi 5}
    (FindWindow(A1, T5) <> 0) or {C++ Builder 6}
    (FindWindow(A1, T6) <> 0) or {C++ Builder 3}
    (FindWindow(A1, T7) <> 0) or {C++ Builder 4}
    (FindWindow(A1, T8) <> 0)    {C++ Builder 5}
    )
    and
    (FindWindow(A3, nil) <> 0) and
    (FindWindow(A4, nil) <> 0);
  if not Result then { Alert the developer/users of a problem. }
    Application.MessageBox(
   'This application makes use of unregistered shareware components. '+
   'You may want to inform the developer so that properly registered components may be included.'+
   'More information on http://www.ksdev.com and e-mail: contact@ksdev.com',
   'Sorry', mb_OK + mb_IconStop);
end; { DelphiIsRunning }

initialization
  if not DelphiIsRunning Then 
    Sleep(3000);
{$ENDIF}
end.
