unit DelphiData;

interface

uses
  Windows, SysUtils, Classes, Contnrs, Registry, ShlObj;

const
  BDSVersions: array[1..5] of record
                                Name: string;
                                VersionStr: string;
                                Version: Integer;
                                CIV: string; // coreide version
                                ProjectDirResId: Integer;
                                Supported: Boolean;
                              end = (
    (Name: 'C#Builder'; VersionStr: '1.0'; Version: 1; CIV: '71'; ProjectDirResId: 64507; Supported: False),
    (Name: 'Delphi'; VersionStr: '8'; Version: 8; CIV: '71'; ProjectDirResId: 64460; Supported: False),
    (Name: 'Delphi'; VersionStr: '2005'; Version: 9; CIV: '90'; ProjectDirResId: 64431; Supported: True),
    (Name: 'BDS'; VersionStr: '2006'; Version: 10; CIV: '100'; ProjectDirResId: 64719; Supported: True),
    // { TODO : change ProjectDirResId when BDS 2007 is available }
    (Name: 'BDS'; VersionStr: '2007'; Version: 11; CIV: '110'; ProjectDirResId: 0; Supported: True)
  );

type
  TDelphiIDE = class;
  TDelphiIDEList = class;
  TDelphiPackage = class;
  TDelphiPackageList = class;
  TIDEPersonality = class;

  TDelphiIDEList = class(TObjectList)
  private
    function GetItems(Index: Integer): TDelphiIDE;
    procedure LoadIDEs(const SubKey, HKCUSubKey: string);
    function IsBDSSupported(const IDEVersionStr: string): Boolean;
  public
    constructor Create;
    property Items[Index: Integer]: TDelphiIDE read GetItems; default;
  end;

  TIDEPersonality = class(TObject)
  private
    FIDE: TDelphiIDE;
    FName: string;

    FBrowsingPaths: TStringList;
    FDCPOutputDir: string;
    FBPLOutputDir: string;
    FPackageSearchPaths: TStringList;
    FSearchPaths: TStringList;
    FDebugDcuPaths: TStringList;
    FNamespaceSearchPaths: TStringList;

    FDisabledPackages: TDelphiPackageList;
    FKnownPackages: TDelphiPackageList;
    FKnownIDEPackages: TDelphiPackageList;

    function GetBplDir: string;
    function GetDcpDir: string;
  protected
    procedure LoadPackagesFromRegistry(APackageList: TDelphiPackageList; const SubKey: string);
    procedure SavePackagesToRegistry(APackageList: TDelphiPackageList; const SubKey: string);
    procedure LoadFromRegistry; virtual; abstract;
  public
    constructor Create(const AName: string; AIDE: TDelphiIDE);
    destructor Destroy; override;

    procedure SavePaths; virtual; abstract;
      { writes BrowsingPaths, SearchPaths, DebugDcuPath, PackaggeSearchPaths to the registry }
    procedure SavePackagesLists; virtual; abstract;
      { writes KnownIDEPackages, KnownPackages and DisabledPackages to the registry }

    function FindPackage(const PackageName: string): TDelphiPackage;
    function FindPackageEx(const PackageNameStart: string): TDelphiPackage;

    property Name: string read FName;
    property IDE: TDelphiIDE read FIDE;

    property BrowsingPaths: TStringList read FBrowsingPaths; // with macros
    property DCPOutputDir: string read FDCPOutputDir; // with macros
    property BPLOutputDir: string read FBPLOutputDir; // with macros
    property PackageSearchPaths: TStringList read FPackageSearchPaths; // BDS // with macros
    property NamespaceSearchPaths: TStringList  read FNamespaceSearchPaths; // BDS (e.g.: Borland.Vcl)
    property SearchPaths: TStringList read FSearchPaths; // with macros
    property DebugDcuPaths: TStringList read FDebugDcuPaths; // with macros

    property BplDir: string read GetBplDir; // macros are expanded
    property DcpDir: string read GetDcpDir; // macros are expanded

    property KnownIDEPackages: TDelphiPackageList read FKnownIDEPackages;
    property KnownPackages: TDelphiPackageList read FKnownPackages;
    property DisabledPackages: TDelphiPackageList read FDisabledPackages;
  end;

  { Delphi Win32 }
  TDelphiPersonality = class(TIDEPersonality)
  protected
    procedure LoadFromRegistry; override;
  public
    procedure SavePaths; override;
    procedure SavePackagesLists; override;
  end;

  { C++ Win32 }
  TCppPersonality = class(TDelphiPersonality)
  protected
    procedure LoadFromRegistry; override;
  public
    constructor Create(const AName: string; IDE: TDelphiIDE);
    procedure SavePaths; override;
    procedure SavePackagesLists; override;
  end;

  { Delphi/C++ Win32 (BDS) }
  TDelphiCppPersonality = class(TCppPersonality)
  protected
    procedure LoadFromRegistry; override;
  public
    constructor Create(const AName: string; IDE: TDelphiIDE);
    procedure SavePaths; override;
    procedure SavePackagesLists; override;
  end;

  { C# and Delphi.NET }
  TDotNetPersonality = class(TDelphiPersonality)
  protected
    procedure LoadFromRegistry; override;
  public
    procedure SavePaths; override;
  end;

  TDelphiIDE = class(TObject)
  private
    FIsValid: Boolean;
    FName: string;
    FIDEName: string;
    FPersonalities: TObjectList;
    FLatestRTLPatch: Integer;
    FLatestUpdate: Integer;
    FIDEVersion: Integer;
    FIDEVersionStr: string;
    FVersion: Integer;
    FVersionStr: string;
    FExecutable: string;
    FEdition: string;
    FDCMName: string;
    FRootDir: string;
    FBDSProjectsDir: string;
    FHKLMRegistryKey: string;
    FRegistryKey: string;
    function GetPersonality(Index: Integer): TIDEPersonality;
    function GetPersonalityCount: Integer;
  protected
    procedure LoadFromRegistry;
    function ReadBDSProjectsDir: string;
    function GetHomepage: string;
    procedure GetBDSVersion(out Name: string; out Version: Integer; out VersionStr: string);
    function GetMake: string;
    function GetProjectDir: string;
  public
    constructor Create(const AName, AVersion, ARegSubKey: string);
    destructor Destroy; override;

    function IsBCB: Boolean;
    function IsBDS: Boolean;
    function IsPersonal: Boolean;
    function DisplayName: string;

    function VersionedDCP(const Filename: string): string;
      { returns the filename + version + extension for Delphi 5 and BCB 5
        else it returns the Filename. }

    function ExpandDirMacros(const Dir: string): string;
    function InsertDirMacros(const Dir: string): string;

    property Homepage: string read GetHomepage;
    property RegistryKey: string read FRegistryKey;
    property HKLMRegistryKey: string read FHKLMRegistryKey;

    property Make: string read GetMake;

    property Name: string read FName;
    property Version: Integer read FVersion;
    property VersionStr: string read FVersionStr;
    property IDEName: string read FIDEName;
    property IDEVersion: Integer read FIDEVersion;
    property IDEVersionStr: string read FIDEVersionStr;
    property Executable: string read FExecutable; // [Reg->App] x:\path\Delphi.exe
    property RootDir: string read FRootDir; // [Reg->RootDir] x:\path
    property Edition: string read FEdition; // [Reg->Version] PER/PRO/CSS
    property LatestUpdate: Integer read FLatestUpdate;
    property LatestRTLPatch: Integer read FLatestRTLPatch;

    property PersonalityCount: Integer read GetPersonalityCount;
    property Personalities[Index: Integer]: TIDEPersonality read GetPersonality;

    property BDSProjectsDir: string read FBDSProjectsDir;
    property ProjectDir: string read GetProjectDir; // Delphi 5-7: RootDir\Projects BDS: BDSProjectDir\Projects

    property IsValid: Boolean read FIsValid; // is True if the installation is valid
  end;

  TDelphiPackageList = class(TObjectList)
  private
    function GetItems(Index: Integer): TDelphiPackage;
  public
    function IndexOfFilename(const Filename: string): Integer;
    procedure Add(const Filename, Description: string);
    procedure Remove(const Filename: string);

    property Items[Index: Integer]: TDelphiPackage read GetItems; default;
  end;

  TDelphiPackage = class(TObject)
  private
    FFilename: string;
    FDescription: string;
    function GetName: string;
  public
    constructor Create(const AFilename, ADescription: string);

    property Name: string read GetName;
    property Filename: string read FFilename;
    property Description: string read FDescription;
  end;

const
  KeyBorland = '\SOFTWARE\Borland\'; // do not localize

implementation

uses
  CmdLineUtils, Utils;

{ TDelphiIDEList }

constructor TDelphiIDEList.Create;
var
  Reg: TRegistry;
  List: TStringList;
  i: Integer;
begin
  inherited Create;
  if CmdOptions.RegistryKeyDelphi = '' then
    CmdOptions.RegistryKeyDelphi := 'Delphi'; // do not localize
  if CmdOptions.RegistryKeyBCB = '' then
    CmdOptions.RegistryKeyBCB := 'C++Builder'; // do not localize
  if CmdOptions.RegistryKeyBDS = '' then
    CmdOptions.RegistryKeyBDS := 'BDS'; // do not localize

  if not CmdOptions.IgnoreDelphi then
    LoadIDEs('Delphi', CmdOptions.RegistryKeyDelphi); // do not localize
  if not CmdOptions.IgnoreBCB then
    LoadIDEs('C++Builder', CmdOptions.RegistryKeyBCB); // do not localize
  if not CmdOptions.IgnoreDelphi then
    LoadIDEs('BDS', CmdOptions.RegistryKeyBDS); // do not localize

  // DCM support for BDS (Delphi and BCB are not supported because their version numbers are the same)
  if not CmdOptions.IgnoreDelphi then
  begin
    Reg := TRegistry.Create;
    try
      if (CmdOptions.RegistryKeyBDS = 'BDS') and // user has not specified a special regkey
          Reg.OpenKeyReadOnly(KeyBorland + 'DCM') then
      begin
        List := TStringList.Create;
        try
          Reg.GetKeyNames(List);
          for i := 0 to List.Count - 1 do
            LoadIDEs('BDS', 'DCM\' + List[i]); // do not localize
        finally
          List.Free;
        end;
      end;
    finally
      Reg.Free;
    end;
  end;
end;

function TDelphiIDEList.GetItems(Index: Integer): TDelphiIDE;
begin
  Result := TDelphiIDE(inherited Items[Index]);
end;

procedure TDelphiIDEList.LoadIDEs(const SubKey, HKCUSubKey: string);
var
  Reg, HKCUReg: TRegistry;
  List: TStrings;
  i: Integer;
begin
  Reg := TRegistry.Create;
  HKCUReg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    HKCUReg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKeyReadOnly(KeyBorland + SubKey) then
    begin
      List := TStringList.Create;
      try
        Reg.GetKeyNames(List);
        for i := 0 to List.Count - 1 do
          if List[i][1] in ['1'..'9'] then // only version numbers (not "BDS\DBExpress")
            if (SubKey <> 'BDS') or IsBDSSupported(List[i]) then
            begin
              if HKCUReg.KeyExists(KeyBorland + HKCUSubKey + '\' + List[i]) then
                Add(TDelphiIDE.Create(SubKey, List[i], HKCUSubKey));
            end;
      finally
        List.Free;
      end;
    end;
  finally
    HKCUReg.Free;
    Reg.Free;
  end;
end;

function TDelphiIDEList.IsBDSSupported(const IDEVersionStr: string): Boolean;
var
  IDEVersion: Integer;
begin
  Result := False;
  IDEVersion := StrToInt(IDEVersionStr[1]);
  if (IDEVersion >= Low(BDSVersions)) and (IDEVersion <= High(BDSVersions)) then
    Result := BDSVersions[IDEVersion].Supported;
end;

{ TDelphiIDE }

constructor TDelphiIDE.Create(const AName, AVersion, ARegSubKey: string);
begin
  inherited Create;
  FPersonalities := TObjectList.Create;
  FIDEName := AName;
  FIDEVersionStr := AVersion;
  FIDEVersion := StrToIntDef(Copy(FIDEVersionStr, 1, Pos('.', FIDEVersionStr) - 1), 0);
  if AName <> ARegSubKey then
    FDCMName := ExtractFileName(ARegSubKey);
  if not IsBDS then
  begin
    FName := FIDEName;
    FVersion := FIDEVersion;
    FVersionStr := FIDEVersionStr;
  end
  else
    GetBDSVersion(FName, FVersion, FVersionStr);

  FHKLMRegistryKey := KeyBorland + IDEName + '\' + IDEVersionStr;
  FRegistryKey := KeyBorland + ARegSubKey + '\' + IDEVersionStr;

  if IsBDS then
  begin
    if Version <= 9 then
      FPersonalities.Add(TDelphiPersonality.Create('Delphi Win32', Self))
    else
      FPersonalities.Add(TDelphiCppPersonality.Create('Delphi/C++ Win32', Self));
    FPersonalities.Add(TDotNetPersonality.Create('.NET', Self));
  end
  else
  if IsBCB then
  begin
    if Version in [6, 7] then
    begin
      FPersonalities.Add(TCppPersonality.Create('BCB VCL ', Self));
      FPersonalities.Add(TCppPersonality.Create('BCB VisualCLX ', Self));
    end
    else
      FPersonalities.Add(TCppPersonality.Create('BCB Win32', Self))
  end
  else
  begin
    if Version in [6, 7] then
    begin
      FPersonalities.Add(TDelphiPersonality.Create('Delphi VCL ', Self));
      FPersonalities.Add(TDelphiPersonality.Create('Delphi VisualCLX ', Self));
    end
    else
      FPersonalities.Add(TDelphiPersonality.Create('Delphi Win32', Self));
  end;

  LoadFromRegistry;

  FIsValid := (RootDir <> '') and (Executable <> '') and FileExists(Executable) and FileExists(RootDir + '\bin\dcc32.exe');
end;

destructor TDelphiIDE.Destroy;
begin
  FPersonalities.Free;
  inherited Destroy;
end;

function TDelphiIDE.DisplayName: string;
begin
  if FDCMName <> '' then
    Result := Format('%s %s [%s] (%s)', [Name, VersionStr, FDCMName, Edition]) // do not localize
  else
    Result := Format('%s %s (%s)', [Name, VersionStr, Edition]); // do not localize
end;

function TDelphiIDE.ExpandDirMacros(const Dir: string): string;
var
  I, EndPs: Integer;
  S, NewS: string;
begin
  Result := Dir;
  I := 1;
  while I < Length(Result) do
  begin
    if (Result[I] = '$') and (Result[I + 1] = '(') then
    begin
      EndPs := I + 2;
      while (EndPs <= Length(Result)) and (Result[EndPs] <> ')') do
        Inc(EndPs);
      S := AnsiLowerCase(SubStr(Result, I + 2, EndPs - 1));
      NewS := S;

     // available macros
      if (S = 'delphi') or (S = 'bcb') or (S = 'bds') then // do not localize
        NewS := FRootDir
      else if IsBDS and (S = 'bdsprojectsdir') then
        NewS := BDSProjectsDir
      else
        NewS := GetEnvironmentVariable(S);

      if NewS <> S then
      begin
        Delete(Result, i, EndPs - I + 1);
        Insert(NewS, Result, I);
        Inc(I, Length(NewS) - 1);
        NewS := '';
      end;
    end;
    Inc(I);
  end;
end;

function TDelphiIDE.InsertDirMacros(const Dir: string): string;
begin
  Result := Dir;
  if AnsiStartsText(RootDir + PathDelim, Dir) then
  begin
    if IsBCB then
      Result := '$(BCB)' // do not localize
    else if not IsBDS then
      Result := '$(DELPHI)' // do not localize
    else
      Result := '$(BDS)'; // do not localize
    Result := Result + Copy(Dir, Length(RootDir) + 1, MaxInt);
  end;
  if IsBDS then
  begin
    if AnsiStartsText(BDSProjectsDir + PathDelim, Dir) then
    begin
      Result := '$(BDSPROJECTSDIR)'; // do not localize
      Result := Result + Copy(Dir, Length(BDSProjectsDir) + 1, MaxInt);
    end;
  end;
end;

function TDelphiIDE.IsBCB: Boolean;
begin
  Result := (CompareText(Name, 'Delphi') <> 0);
end;

function TDelphiIDE.IsBDS: Boolean;
begin
  Result := CompareText(IDEName, 'BDS') = 0;
end;

function TDelphiIDE.IsPersonal: Boolean;
begin
  Result := (CompareText(Edition, 'PER') = 0) or // do not localize
            (CompareText(Edition, 'PERS') = 0) or // do not localize
            (CompareText(Edition, 'Personal') = 0) or // do not localize
            (CompareText(Edition, 'PersonalEdition') = 0) or // do not localize
            (CompareText(Edition, 'Personal Edition') = 0) or // do not localize
            (CompareText(Edition, 'STD') = 0); // do not localize
end;

procedure TDelphiIDE.LoadFromRegistry;
var
  Reg: TRegistry;
  List: TStrings;
  i: Integer;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;

    if Reg.OpenKeyReadOnly(HKLMRegistryKey) then
    begin
      if Reg.ValueExists('Edition') then // do not localize
        FEdition := Reg.ReadString('Edition') // do not localize
      else
      if Reg.ValueExists('Version') then // do not localize
        FEdition := Reg.ReadString('Version') // do not localize
      else
        FEdition := 'Pers'; // do not localize

      FExecutable := Reg.ReadString('App'); // do not localize
      FRootDir := ExcludeTrailingPathDelimiter(Reg.ReadString('RootDir')); // do not localize

      if IsBDS then
        FBDSProjectsDir := ReadBDSProjectsDir // reads from COREIDExx.XX's resource strings
      else
        FBDSProjectsDir := RootDir + '\Projects';

      // obtain updates state
      List := TStringList.Create;
      try
        Reg.GetValueNames(List);
        for i := 1 to 10 do
        begin
          if Reg.ValueExists('Update #' + IntToStr(i)) then // do not localize
            FLatestUpdate := i;
          if i = 1 then
          begin
            if Reg.ValueExists('Pascal RTL Patch') then // do not localize
              FLatestRTLPatch := i;
          end
          else
            if Reg.ValueExists('Pascal RTL Patch #' + IntToStr(i)) then // do not localize
              FLatestRTLPatch := i;
        end;
      finally
        List.Free;
      end;
    end;

    Reg.RootKey := HKEY_CURRENT_USER;

    if Reg.OpenKeyReadOnly(RegistryKey) then
    begin
      // obtain updates state
      List := TStringList.Create;
      try
        Reg.GetValueNames(List);
        for i := 1 to 10 do
        begin
          if Reg.ValueExists('Update #' + IntToStr(i)) then // do not localize
            if FLatestUpdate < i then
              FLatestUpdate := i;
          if i = 1 then
          begin
            if Reg.ValueExists('Pascal RTL Patch') then // do not localize
              if FLatestRTLPatch < i then
                FLatestRTLPatch := i;
          end
          else
            if Reg.ValueExists('Pascal RTL Patch #' + IntToStr(i)) then // do not localize
              if FLatestRTLPatch < i then
                FLatestRTLPatch := i;
        end;
      finally
        List.Free;
      end;
      Reg.CloseKey;
    end;

    for i := 0 to PersonalityCount - 1  do
      Personalities[i].LoadFromRegistry;
  finally
    Reg.Free;
  end;
end;

function TDelphiIDE.GetHomepage: string;
begin
  if IsBCB then
    Result := 'http://www.borland.com/downloads/download_cbuilder.html' // do not localize
  else
  begin
    if Version = 5 then
      Result := 'http://info.borland.com/devsupport/delphi/downloads/index.html' // do not localize
    else
      Result := 'http://www.borland.com/downloads/download_delphi.html' // do not localize
  end;
end;

function TDelphiIDE.GetMake: string;
begin
  Result := RootDir + '\Bin\make.exe'; // do not localize
end;

procedure TDelphiIDE.GetBDSVersion(out Name: string; out Version: Integer; out VersionStr: string);
begin
  if (IDEVersion >= Low(BDSVersions)) and (IDEVersion <= High(BDSVersions)) then
  begin
    Name := BDSVersions[IDEVersion].Name;
    VersionStr := BDSVersions[IDEVersion].VersionStr;
    Version := BDSVersions[IDEVersion].Version;
  end
  else
  begin
    Name := IDEName;
    Version := IDEVersion;
    VersionStr := IDEVersionStr;
  end;
end;

var
  _SHGetSpecialFolderPathA: function(hwndOwner: HWND; lpszPath: PAnsiChar;
    nFolder: Integer; fCreate: BOOL): BOOL; stdcall;

function TDelphiIDE.ReadBDSProjectsDir: string;
var
  h: HMODULE;
  LocaleName: array[0..4] of Char;
  Filename: string;
  PersDir: string;
  Reg: TRegistry;
begin
  if IsBDS and (IDEVersion >= Low(BDSVersions)) and (IDEVersion <= High(BDSVersions)) then
  begin
    Reg := TRegistry.Create;
    try
      Reg.RootKey := HKEY_CURRENT_USER;
      // update BDSProjectsDir if the user has defined an IDE-environment variable
      if Reg.OpenKeyReadOnly(HKLMRegistryKey + '\Environment Variables') then // do not localize
      begin
        if Reg.ValueExists('BDSPROJECTSDIR') then // do not localize
          FBDSProjectsDir := ExpandDirMacros(Reg.ReadString('BDSPROJECTSDIR')); // do not localize
        Reg.CloseKey;
        Exit;
      end;
    finally
      Reg.Free;
    end;

    Result := 'Borland Studio Projects'; // do not localize

    FillChar(LocaleName, SizeOf(LocaleName[0]), 0);
    GetLocaleInfo(GetThreadLocale, LOCALE_SABBREVLANGNAME, LocaleName, SizeOf(LocaleName));
    if LocaleName[0] <> #0 then
    begin
      Filename := RootDir + '\Bin\coreide' + BDSVersions[IDEVersion].CIV + '.'; // do not localize
      if FileExists(Filename + LocaleName) then
        Filename := Filename + LocaleName
      else
      begin
        LocaleName[2] := #0;
        if FileExists(Filename + LocaleName) then
          Filename := Filename + LocaleName
        else
          Filename := '';
      end;

      if Filename <> '' then
      begin
        h := LoadLibraryEx(PChar(Filename), 0,
          LOAD_LIBRARY_AS_DATAFILE or DONT_RESOLVE_DLL_REFERENCES);
        if h <> 0 then
        begin
          SetLength(Result, 1024);
          SetLength(Result, LoadString(h, BDSVersions[IDEVersion].ProjectDirResId, PChar(Result), Length(Result) - 1));
          FreeLibrary(h);
        end;
      end;
    end;

    if not Assigned(_SHGetSpecialFolderPathA) then
      _SHGetSpecialFolderPathA := GetProcAddress(GetModuleHandle('shell32.dll'), 'SHGetSpecialFolderPathA'); // do not localize
    SetLength(PersDir, MAX_PATH);
    if Assigned(_SHGetSpecialFolderPathA) and
       _SHGetSpecialFolderPathA(0, PChar(PersDir), CSIDL_PERSONAL, False) then
    begin
      SetLength(PersDir, StrLen(PChar(PersDir)));
      Result := ExcludeTrailingPathDelimiter(PersDir) + '\' + Result;
    end
    else
      Result := '';
  end
  else
    Result := '';
end;

function TDelphiIDE.VersionedDCP(const Filename: string): string;
begin
  if Version > 5 then
    Result := Filename
  else
    Result := ChangeFileExt(Filename, '') + IntToStr(Version) + '0' + ExtractFileExt(Filename);
end;

function TDelphiIDE.GetProjectDir: string;
begin
  if IsBDS then
    Result := BDSProjectsDir
  else
    Result := RootDir;
  Result := Result + PathDelim + 'Projects';
end;

function TDelphiIDE.GetPersonalityCount: Integer;
begin
  Result := FPersonalities.Count;
end;

function TDelphiIDE.GetPersonality(Index: Integer): TIDEPersonality;
begin
  Result := TIDEPersonality(FPersonalities[Index]);
end;

{ TDelphiPackageList }

procedure TDelphiPackageList.Add(const Filename, Description: string);
var
  Item: TDelphiPackage;
begin
  if Description = '' then
    Item := TDelphiPackage.Create(Filename, ChangeFileExt(ExtractFileName(Filename), ''))
  else
    Item := TDelphiPackage.Create(Filename, Description);
  inherited Add(Item);
end;

procedure TDelphiPackageList.Remove(const Filename: string);
var
  i: Integer;
begin
  for i := 0 to Count - 1 do
    if CompareText(Items[i].Filename, Filename) = 0 then
    begin
      Delete(i);
      Exit;
    end;
end;

function TDelphiPackageList.GetItems(Index: Integer): TDelphiPackage;
begin
  Result := TDelphiPackage(inherited Items[Index]);
end;

function TDelphiPackageList.IndexOfFilename(const Filename: string): Integer;
begin
  for Result := 0 to Count - 1 do
    if CompareText(Items[Result].Filename, Filename) = 0 then
      Exit;
  Result := -1;
end;

{ TDelphiPackage }

constructor TDelphiPackage.Create(const AFilename, ADescription: string);
begin
  inherited Create;
  FFilename := AFilename;
  FDescription := ADescription;
end;

function TDelphiPackage.GetName: string;
begin
  Result := ExtractFileName(Filename);
end;

{ TIDEPersonality }

constructor TIDEPersonality.Create(const AName: string; AIDE: TDelphiIDE);
begin
  inherited Create;
  FName := AName;
  FIDE := AIDE;

  FBrowsingPaths := TStringList.Create;
  FPackageSearchPaths := TStringList.Create;
  FSearchPaths := TStringList.Create;
  FDebugDcuPaths := TStringList.Create;
  FNamespaceSearchPaths := TStringList.Create;

  FBrowsingPaths.Duplicates := dupIgnore;
  FPackageSearchPaths.Duplicates := dupIgnore;
  FSearchPaths.Duplicates := dupIgnore;
  FDebugDcuPaths.Duplicates := dupIgnore;
  FNamespaceSearchPaths.Duplicates := dupIgnore;

  FDisabledPackages := TDelphiPackageList.Create;
  FKnownIDEPackages := TDelphiPackageList.Create;
  FKnownPackages := TDelphiPackageList.Create;
end;

destructor TIDEPersonality.Destroy;
begin
  FBrowsingPaths.Free;
  FPackageSearchPaths.Free;
  FSearchPaths.Free;
  FDebugDcuPaths.Free;
  FNamespaceSearchPaths.Free;

  FDisabledPackages.Free;
  FKnownIDEPackages.Free;
  FKnownPackages.Free;
  inherited Destroy;
end;

function TIDEPersonality.FindPackage(const PackageName: string): TDelphiPackage;

  function Find(List: TDelphiPackageList): TDelphiPackage;
  var
    i: Integer;
  begin
    for i := 0 to List.Count - 1 do
      if CompareText(PackageName, List[i].Name) = 0 then
      begin
        Result := List[i];
        Exit;
      end;
    Result := nil;
  end;

begin
  Result := Find(KnownIDEPackages);
  if Result = nil then
    Result := Find(KnownPackages);
end;

function TIDEPersonality.FindPackageEx(const PackageNameStart: string): TDelphiPackage;

  function Find(List: TDelphiPackageList): TDelphiPackage;
  var
    i: Integer;
  begin
    for i := 0 to List.Count - 1 do
      if AnsiStartsText(PackageNameStart, List[i].Name) then
      begin
        Result := List[i];
        Exit;
      end;
    Result := nil;
  end;

begin
  Result := Find(KnownIDEPackages);
  if Result = nil then
    Result := Find(KnownPackages);
end;

function TIDEPersonality.GetBplDir: string;
begin
  Result := IDE.ExpandDirMacros(BPLOutputDir);
end;

function TIDEPersonality.GetDcpDir: string;
begin
  Result := IDE.ExpandDirMacros(DCPOutputDir);
end;

procedure TIDEPersonality.LoadPackagesFromRegistry(APackageList: TDelphiPackageList;
  const SubKey: string);
var
  Reg: TRegistry;
  List: TStrings;
  I: Integer;
begin
  APackageList.Clear;
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKeyReadOnly(IDE.RegistryKey + '\' + SubKey) then
    begin
      List := TStringList.Create;
      try
        Reg.GetValueNames(List);
        for i := 0 to List.Count - 1 do
          APackageList.Add(List[i], Reg.ReadString(List[i]));
      finally
        List.Free;
      end;
    end;
  finally
    Reg.Free;
  end;
end;

procedure TIDEPersonality.SavePackagesToRegistry(APackageList: TDelphiPackageList;
  const SubKey: string);
var
  Reg: TRegistry;
  List: TStrings;
  I: Integer;
begin
{  for I := 0 to APackageList.Count - 1 do
    APackageList[I].FFilename := InsertDirMacros(APackageList[I].FFilename);}

  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey(IDE.RegistryKey + '\' + SubKey, False) then
    begin
      List := TStringList.Create;
      try
        Reg.GetValueNames(List);

       // remove old packages
        for I := 0 to List.Count - 1 do
          if APackageList.IndexOfFilename(List[I]) = -1 then
            Reg.DeleteValue(List[I]);

       // add new packages
        for I := 0 to APackageList.Count - 1 do
          if List.IndexOf(APackageList[I].Filename) = -1 then
            Reg.WriteString(APackageList[I].Filename, APackageList[I].Description);
      finally
        List.Free;
      end;
    end;
  finally
    Reg.Free;
  end;
end;

{ TDelphiPersonality }

procedure TDelphiPersonality.LoadFromRegistry;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    // get library paths
    if Reg.OpenKeyReadOnly(IDE.RegistryKey + '\Library') then // do not localize
    begin
      FDCPOutputDir := ExcludeTrailingPathDelimiter(Reg.ReadString('Package DCP Output')); // do not localize
      FBPLOutputDir := ExcludeTrailingPathDelimiter(Reg.ReadString('Package DPL Output')); // do not localize
      ConvertPathList(Reg.ReadString('Browsing Path'), FBrowsingPaths); // do not localize
      ConvertPathList(Reg.ReadString('Search Path'), FSearchPaths); // do not localize
      if IDE.IsBDS then
      begin
        ConvertPathList(Reg.ReadString('Namespace Search Path'), FNamespaceSearchPaths); // do not localize
        ConvertPathList(Reg.ReadString('Package Search Path'), FPackageSearchPaths); // do not localize
        ConvertPathList(Reg.ReadString('Debug DCU Path'), FDebugDcuPaths); // do not localize
      end;
    end;
    if not IDE.IsBDS then
    begin
      if Reg.OpenKeyReadOnly(IDE.RegistryKey + '\Debugging') then // do not localize
        ConvertPathList(Reg.ReadString('Debug DCUs Path'), FDebugDcuPaths); // do not localize
      if Reg.OpenKeyReadOnly(IDE.RegistryKey + '\Environment Variables') then // do not localize
        ConvertPathList(Reg.ReadString('Path'), FPackageSearchPaths); // do not localize
    end;
  finally
    Reg.Free;
  end;

  LoadPackagesFromRegistry(FKnownIDEPackages, 'Known IDE Packages'); // do not localize
  LoadPackagesFromRegistry(FKnownPackages, 'Known Packages'); // do not localize
  LoadPackagesFromRegistry(FDisabledPackages, 'Disabled Packages'); // do not localize
end;

procedure TDelphiPersonality.SavePaths;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey(IDE.RegistryKey + '\Library', False) then // do not localize
    begin
      Reg.WriteString('Browsing Path', ConvertPathList(FBrowsingPaths)); // do not localize
      Reg.WriteString('Search Path', ConvertPathList(FSearchPaths)); // do not localize
      if IDE.IsBDS then
      begin
        Reg.WriteString('Namespace Search Path', ConvertPathList(FNamespaceSearchPaths)); // do not localize
        Reg.WriteString('Package Search Path', ConvertPathList(FPackageSearchPaths)); // do not localize
        Reg.WriteString('Debug DCU Path', ConvertPathList(FDebugDcuPaths)); // do not localize
      end;
    end;
    if not IDE.IsBDS then
    begin
      if Reg.OpenKey(IDE.RegistryKey + '\Debugging', True) then // do not localize
        Reg.WriteString('Debug DCUs Path', ConvertPathList(FDebugDcuPaths)); // do not localize
      if (FPackageSearchPaths.Count > 0) and Reg.OpenKey(IDE.RegistryKey + '\Environment Variables', True) then // do not localize
        Reg.WriteString('Path', ConvertPathList(FPackageSearchPaths)); // do not localize
    end;
  finally
    Reg.Free;
  end;
end;

procedure TDelphiPersonality.SavePackagesLists;
begin
  SavePackagesToRegistry(FKnownPackages, 'Known Packages'); // do not localize
  SavePackagesToRegistry(FDisabledPackages, 'Disabled Packages'); // do not localize
  SavePackagesToRegistry(FKnownIDEPackages, 'Known IDE Packages'); // do not localize
end;

{ TCppPersonality }

constructor TCppPersonality.Create(const AName: string; IDE: TDelphiIDE);
begin
  inherited Create(AName, IDE);
end;

procedure TCppPersonality.SavePaths;
begin
  inherited SavePaths
end;

procedure TCppPersonality.LoadFromRegistry;
begin
  inherited LoadFromRegistry
end;

procedure TCppPersonality.SavePackagesLists;
begin
  inherited SavePackagesLists
end;

{ TDelphiCppPersonality }

constructor TDelphiCppPersonality.Create(const AName: string; IDE: TDelphiIDE);
begin
  inherited Create(AName, IDE);
end;

procedure TDelphiCppPersonality.SavePaths;
begin
  inherited SavePaths
end;

procedure TDelphiCppPersonality.LoadFromRegistry;
begin
  inherited LoadFromRegistry
end;

procedure TDelphiCppPersonality.SavePackagesLists;
begin
  inherited SavePackagesLists
end;

{ TDotNetPersonality }

procedure TDotNetPersonality.SavePaths;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    if Reg.OpenKey(IDE.RegistryKey + '\Library .NET', False) then // do not localize
    begin
      Reg.WriteString('Browsing Path', ConvertPathList(FBrowsingPaths)); // do not localize
      Reg.WriteString('Search Path', ConvertPathList(FSearchPaths)); // do not localize
      Reg.WriteString('Namespace Search Path', ConvertPathList(FNamespaceSearchPaths)); // do not localize
      Reg.WriteString('Package Search Path', ConvertPathList(FPackageSearchPaths)); // do not localize
      Reg.WriteString('Debug DCUIL Path', ConvertPathList(FDebugDcuPaths)); // do not localize
    end;
  finally
    Reg.Free;
  end;
end;

procedure TDotNetPersonality.LoadFromRegistry;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CURRENT_USER;
    // get library paths
    if Reg.OpenKeyReadOnly(IDE.RegistryKey + '\Library .NET') then // do not localize
    begin
      FDCPOutputDir := ExcludeTrailingPathDelimiter(Reg.ReadString('Package DCPIL Output')); // do not localize
      FBPLOutputDir := ExcludeTrailingPathDelimiter(Reg.ReadString('Package DPL Output')); // do not localize
      ConvertPathList(Reg.ReadString('Namespace Search Path'), FNamespaceSearchPaths); // do not localize
      ConvertPathList(Reg.ReadString('Browsing Path'), FBrowsingPaths); // do not localize
      ConvertPathList(Reg.ReadString('Package Search Path'), FPackageSearchPaths); // BDS // do not localize
      ConvertPathList(Reg.ReadString('Search Path'), FSearchPaths); // do not localize
      ConvertPathList(Reg.ReadString('Debug DCUIL Path'), FDebugDcuPaths); // do not localize
    end;
  finally
    Reg.Free;
  end;

  LoadPackagesFromRegistry(FKnownIDEPackages, 'Known IDE Assemblies'); // do not localize
  LoadPackagesFromRegistry(FKnownPackages, 'Known Assemblies'); // do not localize
  LoadPackagesFromRegistry(FDisabledPackages, 'Disabled Assemblies'); // do not localize
end;

end.
