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

  SkinEngine VCL by Evgeny Kryukov
  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 KsSkinSource;

{$I KsSkinVer.Inc}

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, KsBitmap, KsSkinObjects;

type

  TSeSkinSource = class;

{ TSeSkinFilter }

  TSeSkinFilter = class(TPersistent)
  private
    FSkinSource: TSeSkinSource;
    procedure SetSkinSource(const Value: TSeSkinSource);
  protected
    procedure ReadSkinSource(Stream: TStream);
    procedure WriteSkinSource(Stream: TStream);
  public
    constructor Create; virtual;
    destructor Destroy; override;
    { }
    class function GetFileExtension: string; virtual;
    class function GetFilterName: string; virtual;
    { Filter }
    procedure ReadSkin(Stream: TStream); virtual;
    procedure WriteSkin(Stream: TStream); virtual;
    { Property }
    property SkinSource: TSeSkinSource read FSkinSource write SetSkinSource;
  end;

  TSeSkinFilterClass = class of TSeSkinFilter;

{ TSeSkinFilterUncompressed }

  TSeSkinFilterUncompressed = class(TSeSkinFilter)
  public
    constructor Create; override;
    { }
    class function GetFileExtension: string; override;
    class function GetFilterName: string; override;
    { Filter }
    procedure ReadSkin(Stream: TStream); override;
    procedure WriteSkin(Stream: TStream); override;
  end;

{ TSeSkinFilterCompressed }

  TSeSkinFilterCompressed = class(TSeSkinFilter)
  public
    constructor Create; override;
    { }
    class function GetFileExtension: string; override;
    class function GetFilterName: string; override;
    { Filter }
    procedure ReadSkin(Stream: TStream); override;
    procedure WriteSkin(Stream: TStream); override;
  end;

{ TSeSkinFilterUltraCompressed }

  TSeSkinFilterUltraCompressed = class(TSeSkinFilter)
  public
    constructor Create; override;
    { }
    class function GetFileExtension: string; override;
    class function GetFilterName: string; override;
    { Filter }
    procedure ReadSkin(Stream: TStream); override;
    procedure WriteSkin(Stream: TStream); override;
  end;

{ TSeSkinSource }

  TSeSkinSource = class(TComponent)
  private
    { Private declarations }
    FSkinName: string;
    FAuthorURL: string;
    FSkinVersion: string;
    FAuthor: string;
    FAuthorEMail: string;
    { Objects and images }
    FObjects: TList;
    FBitmaps: TKsBitmapList;
    FSounds: TList;
    { States }
    FIsChanging: boolean;
    FDeltaHue: integer;
    FBitmapHue: integer;
    { Objects }
    function GetForm: TSeSkinObject;
    { Properties }
    function GetObject(index: integer): TSeSkinObject;
    function GetCount: integer;
    procedure SetDeltaHue(const Value: integer);
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    { I/O methods}
    procedure LoadFromStream(Stream: TStream); dynamic;
    procedure SaveToStream(Stream: TStream); dynamic;
    procedure LoadFromFile(FileName: string); dynamic;
    procedure SaveToFile(FileName: string); dynamic;
    { Assign and copy methods }
//    procedure Assign(Source: TPersistent); override;
    procedure Clear;
    { Objects }
    procedure Add(SkinObject: TSeSkinObject);
    procedure Remove(SkinObject: TSeSkinObject);
    function GetObjectByKind(Kind: TSeKind): TSeSkinObject;
    function GetObjectByName(Name: string): TSeSkinObject;
    procedure ReplaceBitmap(Source, Dest: TKsBitmap);
    { Public property }
    property Count: integer read GetCount;
    property Objects[index: integer]: TSeSkinObject read GetObject; default;
    property Bitmaps: TKsBitmapList read FBitmaps;
    property Sounds: TList read FSounds;
    property DeltaHue: integer read FDeltaHue write SetDeltaHue;
    { Necessary objects }
    property Form: TSeSkinObject read GetForm;
    { States }
    property IsChanging: boolean read FIsChanging write FIsChanging;
  published
    { Published declarations }
    property SkinName: string read FSkinName write FSkinName;
    property SkinVersion: string read FSkinVersion write FSkinVersion;
    property Author: string read FAuthor write FAuthor;
    property AuthorEMail: string read FAuthorEMail write FAuthorEMail;
    property AuthorURL: string read FAuthorURL write FAuthorURL;
  end;

const

  DefaultFilter: TSeSkinFilterClass = TSeSkinFilterCompressed;

procedure RegisterSkinFilter(AFilter: TSeSkinFilterClass);
procedure UnregisterSkinFilter(AFilter: TSeSkinFilterClass);

function GetSkinFilterByFileName(FileName: string): TSeSkinFilterClass;
function GetDialogFilter: string;

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

uses KsCompress, KsUtils, KsSkinVersion, ZKsStream;

var
  Filters: TList;

{ Filter routines }

procedure RegisterSkinFilter(AFilter: TSeSkinFilterClass);
begin
  Filters.Add(AFilter);
end;

procedure UnregisterSkinFilter(AFilter: TSeSkinFilterClass);
begin
  Filters.Remove(AFilter);
end;

function GetSkinFilterByFileName(FileName: string): TSeSkinFilterClass;
var
  i: integer;
begin
  Result := nil;

  for i := 0 to Filters.Count - 1 do
    if LowerCase(ExtractFileExt(FileName)) = '.'+LowerCase(TSeSkinFilterClass(Filters[i]).GetFileExtension) then
    begin
      Result := TSeSkinFilterClass(Filters[i]);
      Exit;
    end;
end;

function GetDialogFilter: string;
var
  i: integer;
  AllStr, Str: string;
begin
  AllStr := '';
  Result := '';

  for i := 0 to Filters.Count - 1 do
  begin
    if i <> 0 then
    begin
      Result := Result + '|';
      AllStr := AllStr + ';';
    end;
    
    Str := '*.' + TSeSkinFilterClass(Filters[i]).GetFileExtension;
    AllStr := AllStr + Str; 
    Result := Result + TSeSkinFilterClass(Filters[i]).GetFilterName + ' ' + '(' + Str + ')' + '|' + Str;
  end;

  Result := 'All (' + AllStr + ')|' + AllStr + '|' + Result;
end;

{ TSeSkinSource ===============================================================}

constructor TSeSkinSource.Create(AOwner: TComponent);
begin
  inherited;
  FSkinName := 'SkinEngine Skin';
  FSkinVersion := sSeSkinVersion;
  FAuthor := 'KS Development';
  FAuthorEMail := 'skins@ksdev.com';
  FAuthorURL := 'http://www.ksdev.com';
  
  FObjects := TList.Create;
  FBitmaps := TKsBitmapList.Create;
  FSounds := TList.Create;
end;

destructor TSeSkinSource.Destroy;
begin
  Clear;

  FSounds.Free;
  FBitmaps.Free;
  FObjects.Free;
  inherited;
end;

procedure TSeSkinSource.Clear;
var
  i: integer;
begin
{  for i := 0 to FSounds.Count-1 do
    TscSound(FSounds[i]).Free; }
  FSounds.Clear;

  FBitmaps.Clear;

  for i := 0 to FObjects.Count-1 do
    TSeSkinObject(FObjects[i]).Free;
  FObjects.Clear;
end;

{ I/O Routines ================================================================}

procedure TSeSkinSource.LoadFromStream(Stream: TStream);
var
  Filter: TSeSkinFilter;
begin
  Filter := DefaultFilter.Create;
  try
    Filter.SkinSource := Self;
    Filter.ReadSkin(Stream);
  finally
    Filter.Free;
  end;
end;

procedure TSeSkinSource.SaveToStream(Stream: TStream);
var
  Filter: TSeSkinFilter;
begin
  Filter := DefaultFilter.Create;
  try
    Filter.SkinSource := Self;
    Filter.WriteSkin(Stream);
  finally
    Filter.Free;
  end;
end;

procedure TSeSkinSource.LoadFromFile(FileName: string);
var
  Stream: TStream;
  Filter: TSeSkinFilter;
  FilterClass: TSeSkinFilterClass;
begin
  { Check file }
  if FileName = '' then Exit;
  if not FileExists(FileName) then
  begin
    if FileName[2] <> ':' then
    begin
      FileName := ExtractFilePath(ParamStr(0))+FileName;
      if not FileExists(FileName) then Exit;
    end
    else
      Exit;
  end;

  { Get filter }
  FilterClass := GetSkinFilterByFileName(FileName);

  if FilterClass <> nil then
  begin
    Stream := TfileStream.Create(FileName, fmOpenRead);
    try
      Filter := FilterClass.Create;
      try
        Filter.SkinSource := Self;

        Filter.ReadSkin(Stream);
      finally
        Filter.Free;
      end;
    finally
      Stream.Free;
    end;
  end;
end;

procedure TSeSkinSource.SaveToFile(FileName: string);
var
  Stream: TStream;
  Filter: TSeSkinFilter;
  FilterClass: TSeSkinFilterClass;
begin
  if FileName = '' then Exit;

  { Get filter }
  FilterClass := GetSkinFilterByFileName(FileName);
  if FilterClass = nil then
    FilterClass := DefaultFilter;

  if FilterClass <> nil then
  begin
    Stream := TFileStream.Create(FileName, fmCreate);
    try
      Filter := FilterClass.Create;
      try
        Filter.SkinSource := Self;

        Filter.WriteSkin(Stream);
      finally
        Filter.Free;
      end;
    finally
      Stream.Free;
    end;
  end;
end;

{ Objects routines ============================================================}

procedure TSeSkinSource.Add(SkinObject: TSeSkinObject);
begin
  FObjects.Add(SkinObject);
end;

procedure TSeSkinSource.Remove(SkinObject: TSeSkinObject);
begin
  FObjects.Remove(SkinObject);
end;

{ Get form object =============================================================}

function TSeSkinSource.GetObjectByKind(Kind: TSeKind): TSeSkinObject;
var
  i: integer;
begin
  for i := 0 to FObjects.Count-1 do
    if Objects[i].Kind = Kind then
    begin
      Result := Objects[i];
      Exit;
    end;
  Result := nil;
end;

function TSeSkinSource.GetObjectByName(Name: string): TSeSkinObject;
var
  i: integer;
begin
  for i := 0 to FObjects.Count-1 do
    if LowerCase(Objects[i].Name) = LowerCase(Name) then
    begin
      Result := Objects[i];
      Exit;
    end;
  Result := nil;
end;

function TSeSkinSource.GetForm: TSeSkinObject;
begin
  Result := GetObjectByName('Form');
  if Result = nil then
    Result := GetObjectByKind(skForm);
end;

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

function TSeSkinSource.GetObject(index: integer): TSeSkinObject;
begin
  if (index >= 0) and (index < FObjects.Count) and (not FIsChanging) then
    Result := TSeSkinObject(FObjects[index])
  else
    Result := nil;
end;

function TSeSkinSource.GetCount: integer;
begin
  Result := FObjects.Count
end;

procedure TSeSkinSource.SetDeltaHue(const Value: integer);
var
  i: integer;
begin
  if FDeltaHue <> Value then
  begin
    FDeltaHue := Value;
    FBitmapHue := FDeltaHue - FBitmapHue;

    { Change Bitmaps Hue }
    for i := 0 to FBitmaps.Count - 1 do
      Bitmaps[i].ChangeHue(FBitmapHue);
    { Change Objects Hue }
    for i := 0 to FObjects.Count - 1 do
      Objects[i].ChangeHue(FBitmapHue);

    FBitmapHue := FDeltaHue;
  end;
end;

{ TSeSkinFilter ===============================================================}

constructor TSeSkinFilter.Create;
begin
  inherited;
end;

destructor TSeSkinFilter.Destroy;
begin
  inherited;
end;

class function TSeSkinFilter.GetFileExtension: string;
begin
end;

class function TSeSkinFilter.GetFilterName: string;
begin
end;

procedure TSeSkinFilter.ReadSkin(Stream: TStream);
begin
end;

procedure TSeSkinFilter.WriteSkin(Stream: TStream);
begin
end;

procedure TSeSkinFilter.ReadSkinSource(Stream: TStream);
var
  i, ACount: integer;
  SkinObject: TSeSkinObject;
  B: TKsBitmap;
begin
  if FSkinSource = nil then Exit;
  
  with FSkinSource do
  begin
    Clear;

    { Load SourceInfo }
    FSkinName := ReadString(Stream);
    FSkinVersion := ReadString(Stream);
    FAuthor := ReadString(Stream);
    FAuthorEMail := ReadString(Stream);
    FAuthorURL := ReadString(Stream);
    { Load Count }
    Stream.Read(ACount, SizeOf(Integer));
    { Load Images }
    for i := 0 to ACount-1 do
    begin
      B := TKsBitmap.Create;
      B.LoadFromStream(Stream);
      B.PerformTransparent(KColor(255, 0, 255, 0));
      FBitmaps.Add(B);
    end;
    { Load Count }
    Stream.Read(ACount, SizeOf(Integer));
    { Load Objects }
    for i := 0 to ACount-1 do
    begin
      SkinObject := LoadSkinObject(Stream, nil);
      if SkinObject <> nil then
        FObjects.Add(SkinObject);
      SkinObject.Bitmaps := FBitmaps;
      SkinObject.AfterLoad;
    end;
  end;
end;

procedure TSeSkinFilter.WriteSkinSource(Stream: TStream);
var
  i: integer;
begin
  if FSkinSource = nil then Exit;

  with FSkinSource do
  begin
    WriteString(Stream, FSkinName);
    WriteString(Stream, FSkinVersion);
    WriteString(Stream, FAuthor);
    WriteString(Stream, FAuthorEMail);
    WriteString(Stream, FAuthorURL);
    { Save Image Count }
    Stream.Write(FBitmaps.Count, SizeOf(Integer));
    { Save Images }
    for i := 0 to FBitmaps.Count-1 do
      FBitmaps[i].SaveToStream(Stream);
    { Save Object Count }
    Stream.Write(FObjects.Count, SizeOf(Integer));
    { Save Objects }
    for i := 0 to FObjects.Count-1 do
      SaveSkinObject(Stream, Objects[i]);
  end;
end;

procedure TSeSkinSource.ReplaceBitmap(Source, Dest: TKsBitmap);
var
  i, Index: integer;
begin
  if (Source = nil) or (Dest = nil) then Exit;
  if Bitmaps.Count = 0 then Exit;
  if Count = 0 then Exit;

  Dest.Name := Source.Name;

  Index := FBitmaps.IndexOf(Source);

  if Index <> -1 then
  begin
    FBitmaps.Delete(Index);
    FBitmaps.Insert(Index, Dest);

    for i := 0 to Count - 1 do
      Objects[i].AfterLoad;

    Source.Free;
    Source := nil;
  end;
end;

{ Properties }

procedure TSeSkinFilter.SetSkinSource(const Value: TSeSkinSource);
begin
  FSkinSource := Value;
end;

{ TSeSkinFilterUncompressed =====================================================}

constructor TSeSkinFilterUncompressed.Create;
begin
  inherited Create;
end;

class function TSeSkinFilterUncompressed.GetFileExtension: string;
begin
  Result := 'uskn';
end;

class function TSeSkinFilterUncompressed.GetFilterName: string;
begin
  Result := 'Uncompressed Skin';
end;

procedure TSeSkinFilterUncompressed.ReadSkin(Stream: TStream);
begin
  { Read skin from stream }
  ReadSkinSource(Stream);
end;

procedure TSeSkinFilterUncompressed.WriteSkin(Stream: TStream);
begin
  { Save skin to stream }
  WriteSkinSource(Stream);
end;

{ TSeSkinFilterCompressed ===================================================}

constructor TSeSkinFilterCompressed.Create;
begin
  inherited;
end;

class function TSeSkinFilterCompressed.GetFileExtension: string;
begin
  Result := 'kskn';
end;

class function TSeSkinFilterCompressed.GetFilterName: string;
begin
  Result := 'Compressed Skin';
end;

procedure TSeSkinFilterCompressed.ReadSkin(Stream: TStream);
var
  DStream: TDecompressionStream;
  S: array [0..3] of Char;
begin
  { Read sign }
  Stream.Read(S, SizeOf(KsZSign));

  { Read skin }
  DStream := TDecompressionStream.Create(Stream);
  try
    ReadSkinSource(DStream);
  finally
    DStream.Free;
  end;
end;

procedure TSeSkinFilterCompressed.WriteSkin(Stream: TStream);
var
  CStream: TCompressionStream;
begin
  { Write sign }
  Stream.Write(KsZSign, SizeOf(KsZSign));
  { Write skin }
  CStream := TCompressionStream.Create(clFastest, Stream);
  try
    WriteSkinSource(CStream);
  finally
    CStream.Free;
  end;
end;

{ TSeSkinFilterUltraCompressed ===================================================}

constructor TSeSkinFilterUltraCompressed.Create;
begin
  inherited;
end;

class function TSeSkinFilterUltraCompressed.GetFileExtension: string;
begin
  Result := 'xskn';
end;

class function TSeSkinFilterUltraCompressed.GetFilterName: string;
begin
  Result := 'Ultra Compressed Skin';
end;

procedure TSeSkinFilterUltraCompressed.ReadSkin(Stream: TStream);
var
  DStream: TDecompressionStream;
  S: array [0..3] of Char;
begin
  { Read sign }
  Stream.Read(S, SizeOf(KsZSign));
  { Read skin }
  DStream := TDecompressionStream.Create(Stream);
  try
    ReadSkinSource(DStream);
  finally
    DStream.Free;
  end;
end;

procedure TSeSkinFilterUltraCompressed.WriteSkin(Stream: TStream);
var
  CStream: TCompressionStream;
begin
  { Write sign }
  Stream.Write(KsZSign, SizeOf(KsZSign));
  { Write skin }
  CStream := TCompressionStream.Create(clMax, Stream);
  try
    WriteSkinSource(CStream);
  finally
    CStream.Free;
  end;
end;


initialization
  Filters := TList.Create;
  RegisterSkinFilter(TSeSkinFilterUncompressed);
  RegisterSkinFilter(TSeSkinFilterCompressed);
  RegisterSkinFilter(TSeSkinFilterUltraCompressed);
finalization
  UnregisterSkinFilter(TSeSkinFilterUltraCompressed);
  UnregisterSkinFilter(TSeSkinFilterCompressed);
  UnregisterSkinFilter(TSeSkinFilterUncompressed);
  Filters.Free;
end.






