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

  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 KsSkinGroupBoxs;

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

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  KsUtils, KsBitmap, KsControls, KsGroupBoxs, KsSkinVersion, KsSkinObjects,
  KsSkinSource, KsSkinEngine;

type

{ TSeSkinGroupBox class }

{ The TSeSkinGroupBox component represents a advanced Windows group box, used to group related controls on a form. When another control component is placed within a group box, the group box becomes the parent of that component. }
  TSeSkinGroupBox = class(TKsCustomGroupBox)
  private
    FSkinEngine: TSeSkinEngine;
    FSkinObject: string;
    FSkinGroupBox: TseSkinObject;
    function GetVersion: TSeSkinVersion;
    procedure SetVersion(const Value: TSeSkinVersion);
    procedure SetSkinEngine(const Value: TSeSkinEngine);
    procedure SetSkinObject(const Value: string);
  protected
    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 UseSkin: boolean;
    { Inherited }
    procedure PaintBuffer; override;
    { VCL protected  }
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;
  published
    property Anchors;
    property Align;
    property Blend;
    property BlendValue;
    property Caption;
    property CaptionMargin;
    property Font;
    { 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 Transparent;
    property Version: TSeSkinVersion read GetVersion write SetVersion
      stored False;
  end;

{ TSeSkinRadioGroup }

  TSeSkinRadioGroup = class(TSeSkinGroupBox)
  private
    FButtons: TList;
    FItems: TStrings;
    FItemIndex: Integer;
    FColumns: Integer;
    FReading: Boolean;
    FUpdating: Boolean;
    procedure ArrangeButtons;
    procedure ButtonClick(Sender: TObject);
    procedure ItemsChange(Sender: TObject);
    procedure SetButtonCount(Value: Integer);
    procedure SetColumns(Value: Integer);
    procedure SetItemIndex(Value: Integer);
    procedure SetItems(Value: TStrings);
    procedure UpdateButtons;
    procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;
    procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
  protected
    procedure WMSkinChange(var Msg: TMessage); message WM_SKINCHANGE;
    { VCL }
    procedure Loaded; override;
    procedure ReadState(Reader: TReader); override;
    function CanModify: Boolean; virtual;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure FlipChildren(AllLevels: Boolean); override;
  published
    property Columns: Integer read FColumns write SetColumns default 1;
    property ItemIndex: Integer read FItemIndex write SetItemIndex default -1;
    property Items: TStrings read FItems write SetItems;
  end;

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

uses KsSkinCheckBoxs;

{ TSeSkinGroupBox }

constructor TSeSkinGroupBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Transparent := true;
  FSkinObject := 'GroupBox'; 
end;

destructor TSeSkinGroupBox.Destroy;
begin
  if FSkinGroupBox <> nil then FSkinGroupBox.Free;
  inherited Destroy;
end;

procedure TSeSkinGroupBox.Loaded;
begin
  inherited;
{  SkinEngine := FSkinEngine; }
end;

function TSeSkinGroupBox.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
       (FSkinEngine.SkinSource.GetObjectByName(FSkinObject) <> nil) and
       (FSkinGroupBox <> nil)
    then
      Result := true
    else
      Result := false;
end;

{ Drawing }

procedure TSeSkinGroupBox.PaintBuffer;
var
  R, CaptionRect: TRect;
  SkinObject: TSeSkinObject;
begin
  if not UseSkin then
    inherited
  else
  begin
    with Buffer do
    begin
      if not Transparent then
        Clear(KColor(Color));

      R := GetBoxRect;

      CaptionRect := Rect(CaptionMargin, 0, CaptionMargin + TextWidth(Caption), TextHeight(Caption));
      InflateRect(CaptionRect, 3, 0);

      { Draw Frame }
      SkinObject := FSkinGroupBox.FindObjectByName('Frame');
      if SkinObject <> nil then
      begin
        SkinObject.BoundsRect := R;
        SkinObject.Draw(Buffer);
      end;

      { Draw Caption }
      SkinObject := FSkinGroupBox.FindObjectByName('Caption');
      if SkinObject <> nil then
      begin
        Buffer.Font := SkinObject.Font; 
        SkinObject.BoundsRect := CaptionRect;
        SkinObject.Draw(Buffer);
      end;

      { Draw Text }
      DrawText(Caption, CaptionRect, DT_CENTER or DT_VCENTER or DT_SINGLELINE);
    end;
  end;
end;

procedure TSeSkinGroupBox.WMInvalidateSkinObject(var Msg: TMessage);
begin
  Invalidate;
end;

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

  if FSkinGroupBox <> nil then FSkinGroupBox.Free;
  FSkinGroupBox := nil;
end;

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

  SkinEngine := FSkinEngine;
end;

{ Properties }

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

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

procedure TSeSkinGroupBox.SetSkinEngine(const Value: TSeSkinEngine);
begin
  FSkinEngine := Value;

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

    if FSkinEngine.SkinSource.GetObjectByName(FSkinObject) <> nil then
      FSkinGroupBox := FSkinEngine.SkinSource.GetObjectByName(FSkinObject).CreateCopy(nil);

    if FSkinGroupBox <> nil then
      FSkinGroupBox.ParentControl := Self;
  end
  else
  begin
    if FSkinGroupBox <> nil then FSkinGroupBox.Free;
    FSkinGroupBox := nil;
  end;

  Invalidate;
end;

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

{ TSeSkinGroupButton ================================================================}

type
  TSeSkinGroupButton = class(TSeSkinRadioButton)
  private
    FInClick: Boolean;
    procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;
  protected
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyPress(var Key: Char); override;
  public
    constructor InternalCreate(RadioGroup: TSeSkinRadioGroup);
    destructor Destroy; override;
  end;

constructor TSeSkinGroupButton.InternalCreate(RadioGroup: TSeSkinRadioGroup);
begin
  inherited Create(RadioGroup);
  RadioGroup.FButtons.Add(Self);
  Visible := False;
  Enabled := RadioGroup.Enabled;
  ParentShowHint := False;
  OnClick := RadioGroup.ButtonClick;
  Parent := RadioGroup;
end;

destructor TSeSkinGroupButton.Destroy;
begin
  TSeSkinRadioGroup(Owner).FButtons.Remove(Self);
  inherited Destroy;
end;

procedure TSeSkinGroupButton.CNCommand(var Message: TWMCommand);
begin
  if not FInClick then
  begin
    FInClick := True;
    try
      if ((Message.NotifyCode = BN_CLICKED) or
        (Message.NotifyCode = BN_DOUBLECLICKED)) and
        TSeSkinRadioGroup(Parent).CanModify then
        inherited;
    except
      Application.HandleException(Self);
    end;
    FInClick := False;
  end;
end;

procedure TSeSkinGroupButton.KeyPress(var Key: Char);
begin
  inherited KeyPress(Key);
  TSeSkinRadioGroup(Parent).KeyPress(Key);
  if (Key = #8) or (Key = ' ') then
  begin
    if not TSeSkinRadioGroup(Parent).CanModify then Key := #0;
  end;
end;

procedure TSeSkinGroupButton.KeyDown(var Key: Word; Shift: TShiftState);
begin
  inherited KeyDown(Key, Shift);
  TSeSkinRadioGroup(Parent).KeyDown(Key, Shift);
end;

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

{ TSeSkinRadioGroup }

constructor TSeSkinRadioGroup.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := [csSetCaption, csDoubleClicks];
  FButtons := TList.Create;
  FItems := TStringList.Create;
  TStringList(FItems).OnChange := ItemsChange;
  FItemIndex := -1;
  FColumns := 1;
end;

destructor TSeSkinRadioGroup.Destroy;
begin
  SetButtonCount(0);
  TStringList(FItems).OnChange := nil;
  FItems.Free;
  FButtons.Free;
  inherited Destroy;
end;

procedure TSeSkinRadioGroup.FlipChildren(AllLevels: Boolean); 
begin
  { The radio buttons are flipped using BiDiMode }
end;

procedure TSeSkinRadioGroup.ArrangeButtons;
var
  ButtonsPerCol, ButtonWidth, ButtonHeight, TopMargin, I: Integer;
  DC: HDC;
  SaveFont: HFont;
  Metrics: TTextMetric;
  DeferHandle: THandle;
  ALeft: Integer;
begin
  if (FButtons.Count <> 0) and not FReading then
  begin
    DC := GetDC(0);
    SaveFont := SelectObject(DC, Font.Handle);
    GetTextMetrics(DC, Metrics);
    SelectObject(DC, SaveFont);
    ReleaseDC(0, DC);
    ButtonsPerCol := (FButtons.Count + FColumns - 1) div FColumns;
    ButtonWidth := (Width - 10) div FColumns;
    I := Height - Metrics.tmHeight - 5;
    ButtonHeight := I div ButtonsPerCol;
    TopMargin := Metrics.tmHeight + 1 + (I mod ButtonsPerCol) div 2;
    DeferHandle := BeginDeferWindowPos(FButtons.Count);
    try
      for I := 0 to FButtons.Count - 1 do
        with TSeSkinGroupButton(FButtons[I]) do
        begin
          BiDiMode := Self.BiDiMode;
          ALeft := (I div ButtonsPerCol) * ButtonWidth + 8;
          if UseRightToLeftAlignment then
            ALeft := Self.ClientWidth - ALeft - ButtonWidth;
          DeferHandle := DeferWindowPos(DeferHandle, Handle, 0,
            ALeft,
            (I mod ButtonsPerCol) * ButtonHeight + TopMargin,
            ButtonWidth, ButtonHeight,
            SWP_NOZORDER or SWP_NOACTIVATE);
          Visible := True;
        end;
    finally
      EndDeferWindowPos(DeferHandle);
    end;
  end;
end;

procedure TSeSkinRadioGroup.ButtonClick(Sender: TObject);
begin
  if not FUpdating then
  begin
    FItemIndex := FButtons.IndexOf(Sender);
    Changed;
    Click;
  end;
end;

procedure TSeSkinRadioGroup.ItemsChange(Sender: TObject);
begin
  if not FReading then
  begin
    if FItemIndex >= FItems.Count then FItemIndex := FItems.Count - 1;
    UpdateButtons;
  end;
end;

procedure TSeSkinRadioGroup.Loaded;
begin
  inherited Loaded;
  ArrangeButtons;
  UpdateButtons;
end;

procedure TSeSkinRadioGroup.ReadState(Reader: TReader);
begin
  FReading := True;
  inherited ReadState(Reader);
  FReading := False;
  UpdateButtons;
end;

procedure TSeSkinRadioGroup.WMSkinChange(var Msg: TMessage);
begin
  inherited ;
  UpdateButtons;
end;

procedure TSeSkinRadioGroup.SetButtonCount(Value: Integer);
begin
  while FButtons.Count < Value do TSeSkinGroupButton.InternalCreate(Self);
  while FButtons.Count > Value do TSeSkinGroupButton(FButtons.Last).Free;
end;

procedure TSeSkinRadioGroup.SetColumns(Value: Integer);
begin
  if Value < 1 then Value := 1;
  if Value > 16 then Value := 16;
  if FColumns <> Value then
  begin
    FColumns := Value;
    ArrangeButtons;
    Invalidate;
  end;
end;

procedure TSeSkinRadioGroup.SetItemIndex(Value: Integer);
begin
  if FReading then FItemIndex := Value else
  begin
    if Value < -1 then Value := -1;
    if Value >= FButtons.Count then Value := FButtons.Count - 1;
    if FItemIndex <> Value then
    begin
      if FItemIndex >= 0 then
        TSeSkinGroupButton(FButtons[FItemIndex]).Checked := False;
      FItemIndex := Value;
      if FItemIndex >= 0 then
        TSeSkinGroupButton(FButtons[FItemIndex]).Checked := True;
    end;
  end;
end;

procedure TSeSkinRadioGroup.SetItems(Value: TStrings);
begin
  FItems.Assign(Value);
end;

procedure TSeSkinRadioGroup.UpdateButtons;
var
  I: Integer;
begin
  SetButtonCount(FItems.Count);
  for I := 0 to FButtons.Count - 1 do
  begin
    TSeSkinGroupButton(FButtons[I]).Caption := FItems[I];
    TSeSkinGroupButton(FButtons[I]).SkinEngine := FSkinEngine;
  end;

  if FItemIndex >= 0 then
  begin
    FUpdating := True;
    TSeSkinGroupButton(FButtons[FItemIndex]).Checked := True;
    FUpdating := False;
  end;
  ArrangeButtons;
  Invalidate;
end;

procedure TSeSkinRadioGroup.CMEnabledChanged(var Message: TMessage);
var
  I: Integer;
begin
  inherited;
  for I := 0 to FButtons.Count - 1 do
    TSeSkinGroupButton(FButtons[I]).Enabled := Enabled;
end;

procedure TSeSkinRadioGroup.CMFontChanged(var Message: TMessage);
begin
  inherited;
  ArrangeButtons;
end;

procedure TSeSkinRadioGroup.WMSize(var Message: TWMSize);
begin
  inherited;
  ArrangeButtons;
end;

function TSeSkinRadioGroup.CanModify: Boolean;
begin
  Result := True;
end;

procedure TSeSkinRadioGroup.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
end;

end.
