{******************************************************************************}
{*                                                                            *}
{* PasDesigner 0.1 - Package loading                                          *}
{*                                                                            *}
{* (C) 2003-2004 Andreas Hausladen                                            *}
{*                                                                            *}
{******************************************************************************}

unit PDPackageLoader;

{$I jedi.inc}

interface

uses
  {$IFDEF MSWINDOWS}
  Windows, Graphics, ImgList, Controls, ActnList,
  {$ENDIF}
  {$IFDEF LINUX}
  Libc, QGraphics, QImgList, QActnList,
  {$ENDIF}
  {$IFDEF COMPILER6_UP}
  Types,
  {$ENDIF COMPILER6_UP}
  SysUtils, Classes, TypInfo;

const
  sNoIconPalette = '.';

type
{ Interfaces }

  IPackage = interface;
  IPackageLoader = interface;
  IUnit = interface;
  IComponentItem = interface;
  IActionItem = interface;

  IPackage = interface
    ['{7BA3BF7B-EA58-46B1-8E8B-0159F567287C}']
  {private}
    function GetIsRunOnly: Boolean;
    function GetFlags: Integer;
    function GetName: string;
    function GetDescription: string;
    function GetDcpBpiName: string;
    function GetHandle: THandle;

    function GetUnitCount: Integer;
    function GetUnits(Index: Integer): IUnit;

    function GetRequireCount: Integer;
    function GetRequires(Index: Integer): string;

    function GetComponentCount: Integer;
    function GetComponents(Index: Integer): IComponentItem;

    function GetActionCount: Integer;
    function GetActions(Index: Integer): IActionItem;
  {public}
    property IsRunOnly: Boolean read GetIsRunOnly;
    property Flags: Integer read GetFlags;

    property Name: string read GetName;
    property Description: string read GetDescription;
    property DcpBpiName: string read GetDcpBpiName;
    property Handle: THandle read GetHandle;

    property UnitCount: Integer read GetUnitCount;
    property Units[Index: Integer]: IUnit read GetUnits;

    property RequireCount: Integer read GetRequireCount;
    property Requires[Index: Integer]: string read GetRequires;

    property ComponentCount: Integer read GetComponentCount;
    property Components[Index: Integer]: IComponentItem read GetComponents;

    property ActionCount: Integer read GetActionCount;
    property Actions[Index: Integer]: IActionItem read GetActions;
  end;

  IPackageLoader = Interface
    ['{E69EEA58-87A9-4D23-B716-7735F7B0D7B9}']
  {private}
    function GetUnitCount: Integer;
    function GetUnits(Index: Integer): IUnit;

    function GetPackageCount: Integer;
    function GetPackages(Index: Integer): IPackage;

    function GetComponentCount: Integer;
    function GetComponents(Index: Integer): IComponentItem;

    function GetActionCount: Integer;
    function GetActions(Index: Integer): IActionItem;
  {public}
    function AddPackage(const FileName: string): Boolean; overload;
    function AddPackage(Handle: THandle): Boolean; overload;

    procedure RemovePackage(const Name: string); overload;
    procedure RemovePackage(Index: Integer); overload;

    function IndexOf(const Name: string): Integer; overload;
    function IndexOf(Handle: THandle): Integer; overload;

    procedure BeginUpdate;
    procedure EndUpdate;
    procedure UpdateImageList;

    property UnitCount: Integer read GetUnitCount;
    property Units[Index: Integer]: IUnit read GetUnits;

    property PackageCount: Integer read GetPackageCount;
    property Packages[Index: Integer]: IPackage read GetPackages;

    property ComponentCount: Integer read GetComponentCount;
    property Components[Index: Integer]: IComponentItem read GetComponents;

    property ActionCount: Integer read GetActionCount;
    property Actions[Index: Integer]: IActionItem read GetActions;
  end;

  IUnit = interface
    ['{C3D68986-4D02-48E9-8395-B830DB9D533F}']
  {private}
    function GetName: string;
    function GetFlags: Integer;
  {public}
    property Name: string read GetName;
    property Flags: Integer read GetFlags;
  end;

  IComponentItem = interface
    ['{9086C9A0-1149-48A1-8AD5-6009A104DFFD}']
  {private}
    function GetComponentClass: TComponentClass;
    function GetPalette: string;
    function GetImageIndex: Integer;
    procedure SetImageIndex(Value: Integer);
    function GetUnitName: string;
  {public}
    property Palette: string read GetPalette;
    property ComponentClass: TComponentClass read GetComponentClass;
    property ImageIndex: Integer read GetImageIndex write SetImageIndex;
    property UnitName: string read GetUnitName;
  end;

  IActionItem = interface
    ['{985AF26E-C584-4C9F-AADD-82DF0958CF8E}']
  {private}
    function GetActionClass: TBasicActionClass;
    function GetCategory: string;
    function GetResource: TComponentClass;
    function GetUnitName: string;
  {public}
    property Category: string read GetCategory;
    property ActionClass: TBasicActionClass read GetActionClass;
    property Resource: TComponentClass read GetResource;
    property UnitName: string read GetUnitName;
  end;


{ Classes }

  TPackage = class;
  TPackageLoader = class;
  TUnit = class;
  TComponentItem = class;

  TPackage = class(TInterfacedObject, IPackage)
  private
    FPackageLoader: TPackageLoader;
    FHandle: THandle;
    FUnits: TInterfaceList;
    FComponents: TInterfaceList;
    FActions: TInterfaceList;
    FFlags: Integer;
    FRequires: TStrings;
    FName: string;
    FDescription: string;
    FDcpBpiName: string;

    function GetIsRunOnly: Boolean;
    function GetFlags: Integer;
    function GetName: string;
    function GetDescription: string;
    function GetDcpBpiName: string;
    function GetHandle: THandle;

    function GetUnitCount: Integer;
    function GetUnits(Index: Integer): IUnit;

    function GetRequireCount: Integer;
    function GetRequires(Index: Integer): string;

    function GetComponentCount: Integer;
    function GetComponents(Index: Integer): IComponentItem;

    function GetActionCount: Integer;
    function GetActions(Index: Integer): IActionItem;
  public
    constructor Create(AHandle: THandle; APackageLoader: TPackageLoader);
    destructor Destroy; override;

    property IsRunOnly: Boolean read GetIsRunOnly;
    property Flags: Integer read GetFlags;

    property Name: string read GetName;
    property Description: string read GetDescription;
    property DcpBpiName: string read GetDcpBpiName;
    property Handle: THandle read GetHandle;

    property UnitCount: Integer read GetUnitCount;
    property Units[Index: Integer]: IUnit read GetUnits;

    property RequireCount: Integer read GetRequireCount;
    property Requires[Index: Integer]: string read GetRequires;

    property ComponentCount: Integer read GetComponentCount;
    property Components[Index: Integer]: IComponentItem read GetComponents;

    property ActionCount: Integer read GetActionCount;
    property Actions[Index: Integer]: IActionItem read GetActions;
  end;

  TPackageLoader = class(TComponent, IPackageLoader)
  private
    FPackages: TInterfaceList;
    FComponents: TInterfaceList;
    FActions: TInterfaceList;
    FLock: TRTLCriticalSection;
    FCurrentPackage: TPackage;
    FImageList: TImageList;
    FLockCount: Integer;

    function GetUnitCount: Integer;
    function GetUnits(Index: Integer): IUnit;
    
    function GetPackageCount: Integer;
    function GetPackages(Index: Integer): IPackage;

    function GetComponentCount: Integer;
    function GetComponents(Index: Integer): IComponentItem;

    function GetActionCount: Integer;
    function GetActions(Index: Integer): IActionItem;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    function AddPackage(const FileName: string): Boolean; overload;
    function AddPackage(Handle: THandle): Boolean; overload;

    procedure RemovePackage(const Name: string); overload;
    procedure RemovePackage(Index: Integer); overload;

    function IndexOf(const Name: string): Integer; overload;
    function IndexOf(Handle: THandle): Integer; overload;

    procedure BeginUpdate;
    procedure EndUpdate;
    procedure UpdateImageList;

    property UnitCount: Integer read GetUnitCount;
    property Units[Index: Integer]: IUnit read GetUnits;

    property PackageCount: Integer read GetPackageCount;
    property Packages[Index: Integer]: IPackage read GetPackages;

    property ComponentCount: Integer read GetComponentCount;
    property Components[Index: Integer]: IComponentItem read GetComponents;

    property ActionCount: Integer read GetActionCount;
    property Actions[Index: Integer]: IActionItem read GetActions;

    property ImageList: TImageList read FImageList write FImageList;
  end;

  TUnit = class(TInterfacedObject, IUnit)
  private
    FName: string;
    FFlags: Integer;
    function GetName: string;
    function GetFlags: Integer;
  public
    constructor Create(const AName: string; AFlags: Integer);
    property Name: string read GetName;
    property Flags: Integer read GetFlags;
  end;

  TComponentItem = class(TInterfacedObject, IComponentItem)
  private
    FComponentClass: TComponentClass;
    FPalette: string;
    FImageIndex: Integer;
    function GetComponentClass: TComponentClass;
    function GetPalette: string;
    function GetImageIndex: Integer;
    procedure SetImageIndex(Value: Integer);
    function GetUnitName: string;
  public
    constructor Create(const APalette: string; AComponentClass: TComponentClass;
      AImageIndex: Integer);
    property Palette: string read GetPalette;
    property ComponentClass: TComponentClass read GetComponentClass;
    property ImageIndex: Integer read GetImageIndex write SetImageIndex;
    property UnitName: string read GetUnitName;
  end;

  TActionItem = class(TInterfacedObject, IActionItem)
  private
    FCategory: string;
    FActionClass: TBasicActionClass;
    FResource: TComponentClass;
    function GetActionClass: TBasicActionClass;
    function GetCategory: string;
    function GetResource: TComponentClass;
    function GetUnitName: string;
  public
    constructor Create(const ACategory: string; AActionClass: TBasicActionClass;
      AResource: TComponentClass);

    property Category: string read GetCategory;
    property ActionClass: TBasicActionClass read GetActionClass;
    property Resource: TComponentClass read GetResource;
    property UnitName: string read GetUnitName;
  end;

var
  PackageLoader: TPackageLoader;

implementation

uses
  PDHelpers;

{$R rcCoreIDE.res}

procedure GlobalRegisterComponentsProc(const Page: string;
  {$IFDEF COMPILER7}const{$ENDIF} ComponentClasses: array of TComponentClass);
var
  i: Integer;
  Item: IComponentItem;
begin
  for i := 0 to High(ComponentClasses) do
  begin
    Item := TComponentItem.Create(Page, ComponentClasses[i], 0);
    PackageLoader.FComponents.Add(Item);
    if PackageLoader.FCurrentPackage <> nil then
      PackageLoader.FCurrentPackage.FComponents.Add(Item);
  end;
end;

procedure GlobalRegisterNoIconProc({$IFDEF COMPILER7}const{$ENDIF} ComponentClasses: array of TComponentClass);
begin
  GlobalRegisterComponentsProc(sNoIconPalette, ComponentClasses);
end;

procedure GlobalRegisterActionsProc(const CategoryName: string;
  const AClasses: array of TBasicActionClass; Resource: TComponentClass);
var
  i: Integer;
  Item: IActionItem;
begin
  for i := 0 to High(AClasses) do
  begin
    Item := TActionItem.Create(CategoryName, AClasses[i], Resource);
    PackageLoader.FActions.Add(Item);
    if PackageLoader.FCurrentPackage <> nil then
      PackageLoader.FCurrentPackage.FActions.Add(Item);
  end;
end;

{ TPackageLoader }

function TPackageLoader.AddPackage(Handle: THandle): Boolean;
begin
  if Handle <> 0 then
  begin
    CSBlock(FLock);
    FCurrentPackage := TPackage.Create(Handle, Self);
    try
      FPackages.Add(FCurrentPackage);
    finally
      FCurrentPackage := nil;
    end;
    if FLockCount = 0 then
      UpdateImageList;
    Result := True;
  end
  else
    Result := False;
end;

function TPackageLoader.AddPackage(const FileName: string): Boolean;
begin
  Result := AddPackage(LoadPackage(FileName));
end;

procedure TPackageLoader.BeginUpdate;
begin
  Inc(FLockCount);
end;

constructor TPackageLoader.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPackages := TInterfaceList.Create;
  FComponents := TInterfaceList.Create;
  FActions := TInterfaceList.Create;
  InitializeCriticalSection(FLock);

  RegisterComponentsProc := GlobalRegisterComponentsProc;
  RegisterNoIconProc := GlobalRegisterNoIconProc;
  RegisterActionsProc := GlobalRegisterActionsProc;
end;

destructor TPackageLoader.Destroy;
begin
  FActions.Free;
  FComponents.Free;
  FPackages.Free;
  DeleteCriticalSection(FLock);
  inherited Destroy;
end;

procedure TPackageLoader.EndUpdate;
begin
  Dec(FLockCount);
  if FLockCount = 0 then
    UpdateImageList;
end;

function TPackageLoader.GetComponentCount: Integer;
begin
  CSBlock(FLock);
  Result := FComponents.Count;
end;

function TPackageLoader.GetComponents(Index: Integer): IComponentItem;
begin
  CSBlock(FLock);
  Result := FComponents[Index] as IComponentItem;
end;

function TPackageLoader.GetActionCount: Integer;
begin
  CSBlock(FLock);
  Result := FActions.Count;
end;

function TPackageLoader.GetActions(Index: Integer): IActionItem;
begin
  CSBlock(FLock);
  Result := FActions[Index] as IActionItem;
end;

function TPackageLoader.GetUnitCount: Integer;
var
  i: Integer;
begin
  CSBlock(FLock);
  Result := 0;
  for i := 0 to PackageCount - 1 do
    Inc(Result, Packages[i].UnitCount);
end;

function TPackageLoader.GetUnits(Index: Integer): IUnit;
var
  i: Integer;
begin
  CSBlock(FLock);
  for i := 0 to PackageCount - 1 do
  begin
    if Index < Packages[i].UnitCount then
    begin
      Result := Packages[i].Units[Index];
      Exit;
    end;
    Dec(Index, Packages[i].UnitCount);
  end;
end;

function TPackageLoader.GetPackageCount: Integer;
begin
  CSBlock(FLock);
  Result := FPackages.Count;
end;

function TPackageLoader.GetPackages(Index: Integer): IPackage;
begin
  CSBlock(FLock);
  Result := FPackages[Index] as IPackage;
end;

function TPackageLoader.IndexOf(const Name: string): Integer;
begin
  CSBlock(FLock);

  for Result := 0 to PackageCount - 1 do
    if CompareText(Packages[Result].Name, Name) = 0 then
      Exit;
  Result := -1;
end;

function TPackageLoader.IndexOf(Handle: THandle): Integer;
begin
  CSBlock(FLock);

  for Result := 0 to PackageCount - 1 do
    if Packages[Result].Handle = Handle then
      Exit;
  Result := -1;
end;

procedure TPackageLoader.RemovePackage(const Name: string);
var Index: Integer;
begin
  CSBlock(FLock);

  Index := IndexOf(Name);
  if Index <> -1 then
  begin
    FPackages.Delete(Index);
    if FLockCount = 0 then
      UpdateImageList;
  end;
end;

procedure TPackageLoader.RemovePackage(Index: Integer);
begin
  CSBlock(FLock);

  if (Index >= 0) and (Index < PackageCount) then
  begin
    FPackages.Delete(Index);
    if FLockCount = 0 then
      UpdateImageList;
  end;
end;

procedure TPackageLoader.UpdateImageList;
const
  DefaultImageIndex = 1;
var
  i, CompIndex: Integer;
  Bmp: TBitmap;
  h: THandle;
  cName: string;
  C: TComponentClass;
begin
  ImageList.Clear;
  for i := 0 to ComponentCount - 1 do
    Components[i].ImageIndex := DefaultImageIndex;

  Bmp := TBitmap.Create;
  try
    Bmp.LoadFromResourceName(HInstance, 'TSELECTOR');
    Bmp.Width := ImageList.Width;
    Bmp.Height := ImageList.Height;
    ImageList.Add(Bmp, nil);

    Bmp.Assign(nil); // fixes GDI resource leak
    Bmp.LoadFromResourceName(HInstance, 'DEFAULTCOMPONENT');
    ImageList.Add(Bmp, nil);

    for i := 0 to PackageCount - 1 do
    begin
      h := Packages[i].Handle;
      for CompIndex := 0 to Packages[i].ComponentCount - 1 do
      begin
        C := Packages[i].Components[CompIndex].ComponentClass;
        while (C <> nil) do
        begin
          cName := C.ClassName;
          if FindResource(h, PChar(cName), RT_BITMAP) <> 0 then
            Break;
          if C.ClassParent.InheritsFrom(TComponent) then
            C := TComponentClass(C.ClassParent)
          else
            C := nil;
        end;
        if C <> nil then
        begin
          Bmp.Assign(nil); // fixes GDI resource leak
          Bmp.LoadFromResourceName(h, C.ClassName);
          Bmp.Width := ImageList.Width;
          Bmp.Height := ImageList.Height;
          Packages[i].Components[CompIndex].ImageIndex := ImageList.Add(Bmp, nil);
        end;
      end;
    end;
  finally
    Bmp.Free;
  end;
end;

{ TPackage }

procedure PackageInfoProc(const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer);
var
  RegisterProc: procedure;
  Package: TPackage;
begin
  Package := TPackage(Param);
  case NameType of
    ntRequiresPackage:
      Package.FRequires.Add(Name);

    {$IFDEF COMPILER6_UP}
    ntDcpBpiName:
      Package.FDcpBpiName := Name;
    {$ENDIF COMPILER6_UP}

    ntContainsUnit:
      begin
        if (Flags in [0, ufWeakPackageUnit]) and (CompareText(Name, 'SysInit') <> 0) then
        begin
          @RegisterProc := GetProcAddress(Package.Handle, PChar(Format('@%s@Register$qqrv', [UpName(Name)])));
          if Assigned(RegisterProc) then
            RegisterProc;
        end;
        if Flags and (ufMainUnit or ufPackageUnit) <> 0 then
          Package.FName := Name
        else
          Package.FUnits.Add(TUnit.Create(Name, Flags));
      end;
  end;
end;

constructor TPackage.Create(AHandle: THandle; APackageLoader: TPackageLoader);
var
  Buf: array[0..MAX_PATH] of Char;
begin
  inherited Create;
  FHandle := AHandle;
  FPackageLoader := APackageLoader;
  FRequires := TStringList.Create;
  FUnits := TInterfaceList.Create;
  FComponents := TInterfaceList.Create;
  FActions := TInterfaceList.Create;

  FPackageLoader.FCurrentPackage := Self;
  // fill data fields
  GetModuleFileName(AHandle, Buf, SizeOf(Buf));
  FDescription := GetPackageDescription(Buf);
  GetPackageInfo(FHandle, Self, FFlags, PackageInfoProc);
end;

destructor TPackage.Destroy;
begin
  FRequires.Free;
  FActions.Free;
  FComponents.Free;
  FUnits.Free;
  UnRegisterModuleClasses(FHandle);
  UnloadPackage(FHandle);
  inherited Destroy;
end;

function TPackage.GetComponentCount: Integer;
begin
  Result := FComponents.Count;
end;

function TPackage.GetComponents(Index: Integer): IComponentItem;
begin
  Result := FComponents[Index] as IComponentItem;
end;

function TPackage.GetActionCount: Integer;
begin
  Result := FActions.Count;
end;

function TPackage.GetActions(Index: Integer): IActionItem;
begin
  Result := FActions[Index] as IActionItem;
end;

function TPackage.GetDcpBpiName: string;
begin
  Result := FDcpBpiName;
end;

function TPackage.GetFlags: Integer;
begin
  Result := FFlags;
end;

function TPackage.GetHandle: THandle;
begin
  Result := FHandle;
end;

function TPackage.GetIsRunOnly: Boolean;
begin
  Result := (Flags and pfRunOnly <> 0) and (Flags and pfDesignOnly = 0);
end;

function TPackage.GetName: string;
begin
  Result := FName;
end;

function TPackage.GetRequireCount: Integer;
begin
  Result := FRequires.Count;
end;

function TPackage.GetRequires(Index: Integer): string;
begin
  Result := FRequires[Index];
end;

function TPackage.GetUnitCount: Integer;
begin
  Result := FUnits.Count;
end;

function TPackage.GetUnits(Index: Integer): IUnit;
begin
  Result := FUnits[Index] as IUnit;
end;

function TPackage.GetDescription: string;
begin
  Result := FDescription;
end;

{ TUnit }

constructor TUnit.Create(const AName: string; AFlags: Integer);
begin
  inherited Create;
  FName := AName;
  FFlags := AFlags;
end;

function TUnit.GetFlags: Integer;
begin
  Result := FFlags;
end;

function TUnit.GetName: string;
begin
  Result := FName;
end;

{ TComponentItem }

constructor TComponentItem.Create(const APalette: string;
  AComponentClass: TComponentClass; AImageIndex: Integer);
begin
  inherited Create;
  FPalette := APalette;
  FComponentClass := AComponentClass;
  FImageIndex := AImageIndex;
end;

function TComponentItem.GetComponentClass: TComponentClass;
begin
  Result := FComponentClass;
end;

function TComponentItem.GetImageIndex: Integer;
begin
  Result := FImageIndex;
end;

function TComponentItem.GetPalette: string;
begin
  Result := FPalette;
end;

function TComponentItem.GetUnitName: string;
begin
  Result := GetTypeData(ComponentClass.ClassInfo).UnitName;
end;

procedure TComponentItem.SetImageIndex(Value: Integer);
begin
  FImageIndex := Value;
end;

{ TActionItem }

constructor TActionItem.Create(const ACategory: string; AActionClass: TBasicActionClass;
  AResource: TComponentClass);
begin
  inherited Create;
  FCategory := ACategory;
  FActionClass := AActionClass;
  FResource := AResource;
end;

function TActionItem.GetActionClass: TBasicActionClass;
begin
  Result := FActionClass;
end;

function TActionItem.GetCategory: string;
begin
  Result := FCategory;
end;

function TActionItem.GetResource: TComponentClass;
begin
  Result := FResource;
end;

function TActionItem.GetUnitName: string;
begin
  Result := GetTypeData(ActionClass.ClassInfo).UnitName;
end;

end.
