{
 /***************************************************************************
                            pkgmanager.pas
                            --------------


 ***************************************************************************/

 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************

  Author: Mattias Gaertner

  Abstract:
    TPkgManager is the class for the global PkgBoss variable, which controls
    the whole package system in the IDE.
}
unit PkgManager;

{$mode objfpc}{$H+}

interface

{$I ide.inc}

{$DEFINE UseLRS}
{off $DEFINE VerbosePkgEditDrag}

uses
  {$IFDEF IDE_MEM_CHECK}
  MemCheck,
  {$ENDIF}
  // RTL, FCL
  TypInfo, Math, Classes, SysUtils, Contnrs, AVL_Tree,
  // LCL
  Forms, Controls, Dialogs, Menus, ComCtrls, LResources,
  // LazUtils
  LazUTF8, Laz2_XMLCfg, LazTracer, LazUtilities, LazStringUtils,
  LazFileUtils, LazFileCache, StringHashList, AvgLvlTree, ObjectLists, Translations,
  // Codetools
  CodeToolsConfig, CodeToolManager, CodeCache, BasicCodeTools,
  FileProcs, CodeTree, CTUnitGraph,
  // BuildIntf
  ProjPackIntf, ProjectIntf, PackageIntf, PackageDependencyIntf, PackageLinkIntf,
  NewItemIntf, CompOptsIntf, IDEExternToolIntf, MacroIntf, ComponentReg, BaseIDEIntf,
  // IdeIntf
  IDECommands, MenuIntf, IDEWindowIntf, LazIDEIntf, IDEMsgIntf, SrcEditorIntf,
  IdeIntfStrConsts, ComponentEditors, PropEdits, IDEDialogs,
  UnitResources, InputHistory,
  // IdeUtils
  IdeUtilsPkgStrConsts,
  // IdeConfig
  EnvironmentOpts, IDEOptionDefs, ModeMatrixOpts, RecentListProcs, SearchPathProcs,
  TransferMacros, IDECmdLine, IDEProcs, ParsedCompilerOpts, CompilerOptions,
  // IdePackager
  IdePackagerStrConsts,
  // FCL registration
  LazarusPackageIntf,
  // IDE
  LazarusIDEStrConsts, DialogProcs, MiscOptions,
  Project, ProjPackEditing, AddToPackageDlg, PackageDefs, PackageLinks, PackageSystem,
  OpenInstalledPkgDlg, PkgGraphExplorer, BrokenDependenciesDlg,
  IDETranslations, BuildLazDialog, NewDialog, FindInFilesDlg,
  ProjectInspector, PackageEditor, SourceEditor, ProjPackChecks, AddFileToAPackageDlg,
  PublishModuleDlg, PkgLinksDlg, InterPkgConflictFiles, InstallPkgSetDlg,
  ConfirmPkgListDlg, NewPkgComponentDlg, BaseBuildManager, BasePkgManager,
  MainBar, MainIntf, MainBase;

type

  TPackagePackageArray = specialize TObjectArray<TLazPackageID, TLazPackageID>;
  TOwnerPackageArray = specialize TObjectArray<TObject, TLazPackageID>;

  { TPkgManager }

  TPkgManager = class(TBasePkgManager)
  private
    // event handlers - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // package editor
    function PackageEditorAddToProject(Sender: TObject; APackage: TLazPackage;
                                       OnlyTestIfPossible: boolean): TModalResult;
    function PackageEditorCompilePackage(Sender: TObject; APackage: TLazPackage;
                          CompileClean, CompileRequired: boolean): TModalResult;
    procedure PackageEditorCopyMoveFiles(Sender: TObject);
    function PackageEditorCreateFile(Sender: TObject;
                                     Params: TAddToPkgResult): TModalResult;
    function PackageEditorCreateMakefile(Sender: TObject;
                                         APackage: TLazPackage): TModalResult;
    function PackageEditorCreateFpmakeFile(Sender: TObject;
                                           APackage: TLazPackage): TModalResult;
    function PackageEditorDeleteAmbiguousFiles(Sender: TObject;
      {%H-}APackage: TLazPackage; const Filename: string): TModalResult;
    procedure PackageEditorDragDropTreeView(Sender, Source: TObject; X, Y: Integer);
    function PackageEditorDragOverTreeView(Sender, Source: TObject; X, Y: Integer;
      out TargetTVNode: TTreeNode; out TargetTVType: TTreeViewInsertMarkType): boolean;
    function PackageEditorFindInFiles(Sender: TObject; APackage: TLazPackage): TModalResult;
    function PackageEditorInstallPackage(Sender: TObject;
                                         APackage: TLazPackage): TModalResult;
    function PackageEditorOpenPackage(Sender: TObject; APackage: TLazPackage): TModalResult;
    function PackageEditorOpenPkgFile(Sender: TObject; PkgFile: TPkgFile): TModalResult;
    function PackageEditorPublishPackage(Sender: TObject; APackage: TLazPackage): TModalResult;
    function PackageEditorRevertPackage(Sender: TObject; APackage: TLazPackage): TModalResult;
    function PackageEditorSavePackage(Sender: TObject; APackage: TLazPackage;
                                      SaveAs: boolean): TModalResult;
    function PackageEditorUninstallPackage(Sender: TObject;
                                           APackage: TLazPackage): TModalResult;
    function PackageEditorViewPkgSource(Sender: TObject;
                                        APackage: TLazPackage): TModalResult;
    procedure AfterWritePackage(Sender: TObject; Restore: boolean);
    procedure BeforeReadPackage(Sender: TObject);
    procedure PackageEditorFreeEditor(APackage: TLazPackage);
    function PackageGraphCheckInterPkgFiles(IDEObject: TObject;
                          PkgList: TFPList; out FilesChanged: boolean): boolean;
    // package graph
    function PackageGraphExplorerOpenPackage(Sender: TObject;
                                           APackage: TLazPackage): TModalResult;
    function PackageGraphExplorerOpenProject(Sender: TObject;
                                             AProject: TProject): TModalResult;
    function PackageGraphExplorerUninstallPackage(Sender: TObject;
                                           APackage: TLazPackage): TModalResult;
    procedure PackageGraphAddPackage(Pkg: TLazPackage);
    procedure PackageGraphBeginUpdate(Sender: TObject);
    procedure PackageGraphChangePackageName(APackage: TLazPackage;
                                            const OldName: string);
    procedure PackageGraphDeletePackage(APackage: TLazPackage);
    procedure PackageGraphDependencyModified(ADependency: TPkgDependency);
    procedure PackageGraphEndUpdate(Sender: TObject; GraphChanged: boolean);
    procedure PackageGraphFindFPCUnit(const AUnitName, Directory: string;
                                      var Filename: string);
    function ShowMessageHandler(aUrgency: TMessageLineUrgency;
                         aMsg, aSrcFilename: string; aLineNumber, aColumn: integer;
                         aViewCaption: string): TMessageLine;
    // menu
    procedure MainIDEitmPkgOpenPackageFileClick(Sender: TObject);
    procedure MainIDEitmPkgPkgGraphClick(Sender: TObject);
    procedure MainIDEitmPkgEditInstallPkgsClick(Sender: TObject);
    procedure MainIDEitmPkgAddCurFileToPkgClick(Sender: TObject);
    procedure MainIDEitmPkgNewComponentClick(Sender: TObject);
    procedure MainIDEitmPkgOpenPackageOfCurUnitClicked(Sender: TObject);
    procedure MainIDEitmOpenRecentPackageClicked(Sender: TObject);
    procedure MainIDEitmPkgOpenLoadedPackageClicked(Sender: TObject);
    procedure MainIDEitmPkgNewPackageClick(Sender: TObject);
    procedure MainIDEitmPackageLinksClicked(Sender: TObject);

    // source editor
    procedure OpenPackageForCurrentSrcEditFile(Sender: TObject);

    // LCL
    procedure ApplicationIdleHandler(Sender: TObject; var {%H-}Done: Boolean);

    // misc
    procedure GetDependencyOwnerDescription(Dependency: TPkgDependency;
                                            out Description: string);
    procedure GetDependencyOwnerDirectory(Dependency: TPkgDependency;
                                          out Directory: string);
    procedure PackageFileLoaded(Sender: TObject);
    function DoBeforeCompilePackages(aPkgList: TFPList): TModalResult;
    function LoadDependencyList(FirstDependency: TPkgDependency;
                                Quiet: boolean): TModalResult;
    procedure CreateIDEWindow(Sender: TObject; aFormName: string;
                          var AForm: TCustomForm; DoDisableAutoSizing: boolean);
    function PackageGraphSrcEditFileIsModified(const SrcFilename: string): boolean;
  public
    // component palette
    procedure IDEComponentPaletteOpenPackage(Sender: TObject); override;
    procedure IDEComponentPaletteOpenUnit(Sender: TObject); override;
    // end event handlers - - - - - - - - - - - - - - - - - - - - - - - - - - -

  private
    // helper functions
    FLastLazarusSrcDir: string;
    {$IFDEF UseLRS}
    FIconLRSSource: string;
    {$ENDIF}
    function DoShowSavePackageAsDialog(APackage: TLazPackage): TModalResult;
    function CheckPackageGraphForCompilation(APackage: TLazPackage;
                                 FirstDependency: TPkgDependency;
                                 const Directory: string;
                                 ShowAbort: boolean): TModalResult;
    procedure SaveAutoInstallDependencies;
    procedure LoadStaticCustomPackages;
    function LoadInstalledPackage(const PackageName: string;
                    AddToAutoInstall: boolean; var Quiet: boolean): TLazPackage;
    procedure LoadAutoInstallPackages;
    procedure AddUnitToProjectMainUsesSection(AProject: TProject;
                                    const AnUnitName, AnUnitInFilename: string);
    procedure AddToIconResource(const aIconFile, aResName: string);
    // move files
    function CheckDrag(Sender, Source: TObject; X, Y: Integer;
      out SrcFilesEdit, TargetFilesEdit: IFilesEditorInterface;
      out aFileCount, aDependencyCount, aDirectoryCount: integer;
      out TargetTVNode: TTreeNode; out TargetTVType: TTreeViewInsertMarkType
      ): boolean;
    procedure FilesEditDragDrop(Sender, Source: TObject; X, Y: Integer);
    function MoveFiles(TargetFilesEdit, SrcFilesEdit: IFilesEditorInterface;
      TargetDirectory: string): boolean;
    function MoveFiles(TargetFilesEdit, SrcFilesEdit: IFilesEditorInterface;
      IDEFiles: TFPList; TargetDirectory: string): boolean;
    function CopyMoveFiles(Sender: TObject): boolean;
    function ResolveBrokenDependenciesOnline(ABrokenDependencies: TFPList): TModalResult;
    function ShowBrokenDependenciesReport(Dependencies: TFPList): TModalResult;
    // Components
    function FilterMissingDepsForUnit(const UnitFilename: string;
      InputPackageList: TPackagePackageArray;
      out OutputPackageList: TOwnerPackageArray): TModalResult;
    function GetUnitsAndDepsForComps(ComponentClasses: TClassList;
      out PackageList: TPackagePackageArray; out UnitList: TStringList): TModalResult;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;

    // initialization and menu
    procedure ConnectMainBarEvents; override;
    procedure ConnectSourceNotebookEvents; override;
    procedure SetupMainBarShortCuts; override;
    procedure SetRecentPackagesMenu; override;
    procedure AddToMenuRecentPackages(const Filename: string);
    procedure SaveSettings; override;
    procedure ProcessCommand(Command: word; var Handled: boolean); override;
    procedure OnSourceEditorPopupMenu(const AddMenuItemProc: TAddMenuItemProc); override;
    procedure TranslateResourceStrings; override;

    // files
    function GetDefaultSaveDirectoryForFile(const Filename: string): string; override;
    function OnRenameFile(const OldFilename, NewFilename: string;
                          IsPartOfProject: boolean): TModalResult; override;
    function FindIncludeFileInProjectDependencies(aProject: TProject;
                          const Filename: string): string; override;
    function GetOwnersOfUnit(const UnitFilename: string): TFPList; override;
    procedure ExtendOwnerListWithUsedByOwners(OwnerList: TFPList); override;
    function GetSourceFilesOfOwners(OwnerList: TFPList): TStrings; override;
    function GetUnitsOfOwners(OwnerList: TFPList; Flags: TPkgIntfGatherUnitTypes): TStrings; override;
    function GetPossibleOwnersOfUnit(const UnitFilename: string;
                                     Flags: TPkgIntfOwnerSearchFlags): TFPList; override;
    function GetPackageOfCurrentSourceEditor(out APackage: TIDEPackage): TPkgFile;
    function GetPackageOfSourceEditor(out APackage: TIDEPackage; ASrcEdit: TObject): TLazPackageFile; override;
    function FindVirtualUnitSource(PkgFile: TPkgFile): string;
    function SearchFile(const AFilename: string;
                        SearchFlags: TSearchIDEFileFlags;
                        InObject: TObject): TPkgFile; override;
    function SearchUnitInDesigntimePackages(const AnUnitName: string;
                        InObject: TObject): TPkgFile; override;
    function ShowFindInPackageFilesDlg(APackage: TLazPackage): TModalResult;

    // package graph
    function AddPackageToGraph(APackage: TLazPackage): TModalResult;
    procedure DoShowPackageGraph(Show: boolean);
    procedure DoShowPackageGraphPathList(PathList: TFPList); override;
    function CheckUserSearchPaths(aCompilerOptions: TBaseCompilerOptions): TModalResult; override;
    procedure LazarusSrcDirChanged; override;
    function GetPackageCount: integer; override;
    function GetPackages(Index: integer): TIDEPackage; override;
    function FindPackageWithName(const PkgName: string; IgnorePackage: TIDEPackage = nil): TIDEPackage; override;
    function FindInstalledPackageWithUnit(const AnUnitName: string
      ): TIDEPackage; override;
    function IsPackageInstalled(const PkgName: string): TIDEPackage; override;
    function IsOwnerDependingOnPkg(AnOwner: TObject; const PkgName: string;
                                   out DependencyOwner: TObject): boolean; override;
    procedure GetRequiredPackages(AnOwner: TObject; out PkgList: TFPList;
                                  Flags: TPkgIntfRequiredFlags = []); override;
    function AddDependencyToOwners(OwnerList: TFPList; APackage: TIDEPackage;
                   OnlyTestIfPossible: boolean = false): TModalResult; override;
    function AddDependencyToUnitOwners(const OwnedFilename,
                              RequiredUnitname: string): TModalResult; override;
    function RedirectPackageDependency(APackage: TIDEPackage): TIDEPackage; override;
    procedure GetPackagesChangedOnDisk(out ListOfPackages: TStringList; IgnoreModifiedFlag: boolean = False); override;
    function RevertPackages(APackageList: TStringList): TModalResult; override;

    // project
    function OpenProjectDependencies(AProject: TProject;
                                ReportMissing: boolean): TModalResult; override;
    function CheckProjectHasInstalledPackages(AProject: TProject; 
                                  Interactive: boolean): TModalResult; override;
    function CanOpenDesignerForm(AnUnitInfo: TUnitInfo; 
                                 Interactive: boolean): TModalResult; override;
    function AddProjectDependency(AProject: TProject; APackage: TLazPackage;
                                  OnlyTestIfPossible: boolean = false): TModalResult; override;
    function AddProjectDependency(AProject: TProject;
                                  ADependency: TPkgDependency): TModalResult; override;
    function AddProjectDependencies(AProject: TProject; const Packages: string;
                                  OnlyTestIfPossible: boolean = false): TModalResult; override;
    function ProjectInspectorAddDependency(Sender: TObject;
                           ADependency: TPkgDependency): TModalResult; override;
    function ProjectInspectorRemoveDependency(Sender: TObject;
                           ADependency: TPkgDependency): TModalResult; override;
    function ProjectInspectorReAddDependency(Sender: TObject;
                           ADependency: TPkgDependency): TModalResult; override;
    procedure ProjectInspectorDragDropTreeView(Sender, Source: TObject;
      X, Y: Integer); override;
    function ProjectInspectorDragOverTreeView(Sender, Source: TObject;
      X, Y: Integer; out TargetTVNode: TTreeNode;
      out TargetTVType: TTreeViewInsertMarkType): boolean; override;
    procedure ProjectInspectorCopyMoveFiles(Sender: TObject); override;

    // package editors
    function CanClosePackageEditor(APackage: TLazPackage): TModalResult; override;
    function CanCloseAllPackageEditors: TModalResult; override;
    function DoOpenPkgFile(PkgFile: TPkgFile): TModalResult;
    function DoNewPackage: TModalResult; override;
    function DoShowLoadedPkgDlg: TModalResult; override;
    function DoOpenPackage(APackage: TLazPackage; Flags: TPkgOpenFlags;
                           ShowAbort: boolean): TModalResult; override;
    function DoOpenPackageWithName(const APackageName: string;
                         Flags: TPkgOpenFlags; ShowAbort: boolean): TModalResult; override;
    function DoOpenPackageFile(AFilename: string;
                         Flags: TPkgOpenFlags;
                         ShowAbort: boolean): TModalResult; override;
    function IsPackageEditorForm(AForm: TCustomForm): boolean; override;
    procedure OpenHiddenModifiedPackages; override;
    function DoSavePackage(APackage: TLazPackage;
                           Flags: TPkgSaveFlags): TModalResult; override;
    function DoSaveAllPackages(Flags: TPkgSaveFlags): TModalResult; override;
    function DoClosePackageEditor(APackage: TLazPackage): TModalResult; override;
    function DoCloseAllPackageEditors: TModalResult; override;
    function DoAddActiveUnitToAPackage: TModalResult;
    function DoNewPackageComponent: TModalResult;
    function SavePackageFiles(APackage: TLazPackage): TModalResult;
    function WarnAboutMissingPackageFiles(APackage: TLazPackage): TModalResult;
    function AddPackageDependency(APackage: TLazPackage; const ReqPackage: string;
                                  OnlyTestIfPossible: boolean = false): TModalResult; override;
    function ApplyDependency(CurDependency: TPkgDependency): TModalResult; override;
    function GetPackageOfEditorItem(Sender: TObject): TIDEPackage; override;

    // package compilation
    function DoCompileProjectDependencies(AProject: TProject;
                               Flags: TPkgCompileFlags): TModalResult; override;
    function DoCompilePackage(APackage: TIDEPackage; Flags: TPkgCompileFlags;
                              ShowAbort: boolean): TModalResult; override;
    function DoCreatePackageMakefile(APackage: TLazPackage;
                                     ShowAbort: boolean): TModalResult;
    function DoCreatePackageFpmakefile(APackage: TLazPackage;
                                       ShowAbort: boolean): TModalResult;

    // package installation
    procedure LoadInstalledPackages; override;
    procedure UnloadInstalledPackages;
    function DoInstallPackage(APackage: TLazPackage): TModalResult;
    function DoUninstallPackage(APackage: TLazPackage;
                   Flags: TPkgUninstallFlags; ShowAbort: boolean): TModalResult;
    function CheckInstallPackageList(InstallPkgIDList: TObjectList;
      UninstallPkgIDList: TObjectList = nil; Flags: TPkgInstallInIDEFlags = []
      ): boolean; override;
    function InstallPackages(PkgIdList: TObjectList;
                             Flags: TPkgInstallInIDEFlags = []): TModalResult; override;
    function UninstallPackage(APackage: TIDEPackage; ShowAbort: boolean): TModalResult; override;
    procedure DoTranslatePackage(APackage: TLazPackage);
    function DoOpenPackageSource(APackage: TLazPackage): TModalResult;
    function DoCompileAutoInstallPackages(Flags: TPkgCompileFlags;
                                          OnlyBase: boolean): TModalResult; override;
    function DoSaveAutoInstallConfig: TModalResult; override;
    function DoPublishPackage(APackage: TLazPackage; Flags: TPkgSaveFlags;
                              ShowDialog: boolean): TModalResult;
                              
    // components
    function AddUnitDepsForCompClasses(const UnitFilename: string;
      ComponentClasses: TClassList; Quiet: boolean): TModalResult; override;
    function GetUsableComponentUnits(CurRoot: TPersistent): TFPList; override; // list of TUnitInfo
    procedure IterateComponentNames(CurRoot: TPersistent; TypeData: PTypeData;
                                    Proc: TGetStrProc); override;
    function FindUsableComponent(CurRoot: TPersistent;
                        const ComponentPath: string): TComponent; override;
    function FindReferencedRootComponent(CurRoot: TPersistent; 
         const ComponentName: string): TComponent; override;
  end;


  { TLazPackageDescriptors }

  TLazPackageDescriptors = class(TPackageDescriptors)
  private
    fDestroying: boolean;
    fItems: TFPList; // list of TProjectDescriptor
  protected
    function GetItems(Index: integer): TPackageDescriptor; override;
  public
    constructor Create;
    destructor Destroy; override;
    function Count: integer; override;
    function GetUniqueName(const Name: string): string; override;
    function IndexOf(const Name: string): integer; override;
    function FindByName(const Name: string): TPackageDescriptor; override;
    procedure RegisterDescriptor(Descriptor: TPackageDescriptor); override;
    procedure UnregisterDescriptor(Descriptor: TPackageDescriptor); override;
    procedure AddDefaultPackageDescriptors;
  public
    property Items[Index: integer]: TPackageDescriptor read GetItems; default;
  end;
  
  
  { TPackageDescriptorStd }
  
  TPackageDescriptorStd = class(TPackageDescriptor)
  public
    constructor Create; override;
    function GetLocalizedName: string; override;
    function GetLocalizedDescription: string; override;
  end;

var
  LazPackageDescriptors: TLazPackageDescriptors;

implementation

const
  constNewPackageName = 'NewPackage'; //must be valid Pascal identifier, thus should not be allowed to be translated

{ TPkgManager }

procedure TPkgManager.MainIDEitmPkgOpenPackageFileClick(Sender: TObject);
var
  OpenDialog: TOpenDialog;
  AFilename: string;
  I: Integer;
  OpenFlags: TPkgOpenFlags;
begin
  OpenDialog:=TOpenDialog.Create(nil);
  try
    InputHistories.ApplyFileDialogSettings(OpenDialog);
    OpenDialog.Title:=lisOpenPackageFile;
    OpenDialog.Options:=OpenDialog.Options+[ofAllowMultiSelect];
    OpenDialog.Filter:=dlgFilterLazarusPackage+' (*.lpk)|*.lpk'
                     +'|'+dlgFilterAll+' ('+FileMask+')|'+FileMask;
    if OpenDialog.Execute and (OpenDialog.Files.Count>0) then begin
      OpenFlags:=[pofAddToRecent];
      For I := 0 to OpenDialog.Files.Count-1 do
        Begin
          AFilename:=CleanAndExpandFilename(OpenDialog.Files.Strings[i]);
          if i<OpenDialog.Files.Count-1 then
            Include(OpenFlags,pofMultiOpen)
          else
            Exclude(OpenFlags,pofMultiOpen);
          if DoOpenPackageFile(AFilename,OpenFlags,true)=mrAbort then begin
            break;
          end;
        end;
    end;
    InputHistories.StoreFileDialogSettings(OpenDialog);
  finally
    OpenDialog.Free;
  end;
end;

procedure TPkgManager.MainIDEitmPkgPkgGraphClick(Sender: TObject);
begin
  DoShowPackageGraph(true);
end;

procedure TPkgManager.MainIDEitmPkgEditInstallPkgsClick(Sender: TObject);
var
  RebuildIDE: Boolean;
  PkgIDList: TObjectList;
begin
  if ShowEditInstallPkgsDialog(PackageGraph.FirstInstallDependency, PkgIDList, RebuildIDE) then
    if RebuildIDE
      then InstallPackages(PkgIDList, [piiifSkipChecks, piiifClear, piiifRebuildIDE])
      else InstallPackages(PkgIDList, [piiifSkipChecks, piiifClear]);
  FreeThenNil(PkgIDList);
end;

procedure TPkgManager.IDEComponentPaletteOpenPackage(Sender: TObject);
begin
  if (Sender=nil) or (not (Sender is TLazPackage)) then exit;
  DoOpenPackage(TLazPackage(Sender),[],false);
end;

procedure TPkgManager.IDEComponentPaletteOpenUnit(Sender: TObject);
var
  PkgComponent: TPkgComponent;
  PkgFile: TPkgFile;
  Filename: String;
begin
  if (Sender=nil) then exit;
  if (Sender is TPkgFile) then
    DoOpenPkgFile(TPkgFile(Sender))
  else if (Sender is TPkgComponent) then begin
    PkgComponent:=TPkgComponent(Sender);
    PkgFile:=PkgComponent.PkgFile;
    if PkgFile=nil then exit;
    Filename:='';
    if PkgFile.FileType=pftVirtualUnit then
      Filename:=FindVirtualUnitSource(PkgFile);
    if Filename='' then
      Filename:=PkgFile.GetFullFilename;
    MainIDE.DoOpenFileAndJumpToIdentifier(
      Filename,PkgComponent.ComponentClass.ClassName,
      -1, -1, // open page somewhere
      [ofOnlyIfExists,ofAddToRecent,ofRegularFile,ofConvertMacros]);
  end;
end;

procedure TPkgManager.GetDependencyOwnerDescription(
  Dependency: TPkgDependency; out Description: string);
begin
  GetDescriptionOfDependencyOwner(Dependency,Description);
end;

procedure TPkgManager.GetDependencyOwnerDirectory(Dependency: TPkgDependency;
  out Directory: string);
begin
  GetDirectoryOfDependencyOwner(Dependency,Directory);
end;

procedure TPkgManager.PackageFileLoaded(Sender: TObject);
begin
  DoCallNotifyHandler(pihtPackageFileLoaded,Sender);
end;

function TPkgManager.DoBeforeCompilePackages(aPkgList: TFPList): TModalResult;
// called before a bunch of packages are compiled

  function GetIgnorePkgOutDirID(CurPkg: TLazPackage): string;
  begin
    Result:='PkgOutDir#'+CurPkg.Filename+':'+CurPkg.GetOutputDirectory;
  end;

var
  PkgWithProjOverriddenOutDirs: TFPList;
  i: Integer;
  CurPkg: TLazPackage;
  OutDir: String;
  IgnoreItem: TIgnoreIDEQuestionItem;
  s: String;
begin
  Result:=mrOk;
  if MainIDEBar=nil then exit; // not interactive
  if InputHistories=nil then exit;

  if not Assigned(OnGetMatrixOutputDirectoryOverride) then exit;
  PkgWithProjOverriddenOutDirs:=TFPList.Create;
  try
    for i:=0 to aPkgList.Count-1 do
    begin
      CurPkg:=TLazPackage(aPkgList[i]);
      OutDir:='';
      OnGetMatrixOutputDirectoryOverride(CurPkg,OutDir,[bmgtProject,bmgtSession]);
      if OutDir<>'' then begin
        IgnoreItem:=InputHistories.Ignores.Find(GetIgnorePkgOutDirID(CurPkg));
        if (IgnoreItem=nil) then
          PkgWithProjOverriddenOutDirs.Add(CurPkg);
      end;
    end;
    if PkgWithProjOverriddenOutDirs.Count>0 then
    begin
      s:='';
      for i:=0 to PkgWithProjOverriddenOutDirs.Count-1 do begin
        CurPkg:=TLazPackage(PkgWithProjOverriddenOutDirs[i]);
        OutDir:=CreateRelativePath(CurPkg.GetOutputDirectory,CurPkg.Directory);
        s+=CurPkg.Name+': '+OutDir+LineEnding;
      end;
      if IDEMessageDialog(lisConfirmation,
        Format(lisPkgTheProjectOverridesTheOutputDirectoryOfTheFollowin,
               [LineEnding, LineEnding+LineEnding, s]),
        mtWarning, [mbOk, mbCancel])<>mrOk
      then
        exit(mrCancel);
      // remember the answer
      for i:=0 to PkgWithProjOverriddenOutDirs.Count-1 do begin
        CurPkg:=TLazPackage(PkgWithProjOverriddenOutDirs[i]);
        InputHistories.Ignores.Add(GetIgnorePkgOutDirID(CurPkg),iiidForever);
      end;
    end;
  finally
    PkgWithProjOverriddenOutDirs.Free;
  end;
end;

function TPkgManager.LoadDependencyList(FirstDependency: TPkgDependency;
  Quiet: boolean): TModalResult;
var
  CurDependency: TPkgDependency;
  OpenResult: TLoadPackageResult;
begin
  Result:=mrCancel;
  // load all packages
  CurDependency:=FirstDependency;
  while CurDependency<>nil do begin
    OpenResult:=PackageGraph.OpenDependency(CurDependency,false);
    if OpenResult<>lprSuccess then begin
      if not Quiet then
        IDEMessageDialog(lisCCOErrorCaption,
          Format(lisUnableToLoadPackage, [CurDependency.AsString]),
          mtError,[mbCancel]);
      exit;
    end;
    CurDependency:=CurDependency.NextRequiresDependency;
  end;
  Result:=mrOk;
end;

procedure TPkgManager.OpenPackageForCurrentSrcEditFile(Sender: TObject);
var
  APackage: TIDEPackage;
begin
  GetPackageOfCurrentSourceEditor(APackage);
  if APackage is TLazPackage then
    DoOpenPackage(TLazPackage(APackage),[],false);
end;

procedure TPkgManager.CreateIDEWindow(Sender: TObject; aFormName: string; var
  AForm: TCustomForm; DoDisableAutoSizing: boolean);
var
  APackageName: String;
  NewDependency: TPkgDependency;
  APackage: TLazPackage;
  LoadResult: TLoadPackageResult;
begin
  //debugln(['TPkgManager.CreateIDEWindow ',aFormName]);
  if SysUtils.CompareText(aFormName,NonModalIDEWindowNames[nmiwPkgGraphExplorer])=0
  then begin
    DoShowPackageGraph(false);
    AForm:=PackageGraphExplorer;
    if DoDisableAutoSizing then
      AForm.DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TPkgManager.CreateIDEWindow'){$ENDIF};
  end else if SysUtils.CompareText(PackageEditorWindowPrefix,
    copy(aFormName,1,length(PackageEditorWindowPrefix)))=0
  then begin
    APackageName:=copy(aFormName,length(PackageEditorWindowPrefix)+1,length(aFormName));
    if not IsValidPkgName(APackageName) then exit;
    NewDependency:=TPkgDependency.Create;
    try
      NewDependency.PackageName:=APackageName;
      NewDependency.DependencyType:=pdtLazarus;
      LoadResult:=PackageGraph.OpenDependency(NewDependency,false);
      if LoadResult<>lprSuccess then exit;
    finally
      NewDependency.Free;
    end;
    APackage:=PackageGraph.FindPackageWithName(APackageName,nil);
    if APackage=nil then exit;
    AForm:=PackageEditors.CreateEditor(APackage,DoDisableAutoSizing);
  end;
end;

function TPkgManager.PackageGraphSrcEditFileIsModified(const SrcFilename: string
  ): boolean;
var
  SrcEdit: TSourceEditor;
begin
  SrcEdit:=SourceEditorManager.SourceEditorIntfWithFilename(SrcFilename);
  Result:=(SrcEdit<>nil) and SrcEdit.Modified;
end;

procedure TPkgManager.MainIDEitmPkgAddCurFileToPkgClick(Sender: TObject);
begin
  DoAddActiveUnitToAPackage;
end;

procedure TPkgManager.MainIDEitmPkgNewComponentClick(Sender: TObject);
begin
  DoNewPackageComponent;
end;

procedure TPkgManager.MainIDEitmPkgOpenPackageOfCurUnitClicked(Sender: TObject);
var
  ActiveSourceEditor: TSourceEditorInterface;
  ActiveUnitInfo: TUnitInfo;
  PkgFile: TPkgFile;
begin
  MainIDE.GetCurrentUnitInfo(ActiveSourceEditor,ActiveUnitInfo);
  if ActiveSourceEditor=nil then exit;
  PkgFile:=PackageGraph.FindFileInAllPackages(ActiveUnitInfo.Filename,true,
                                            not ActiveUnitInfo.IsPartOfProject);
  if PkgFile=nil then
    IDEMessageDialog(lisProjAddPackageNotFound,
        lisPkgThisFileIsNotInAnyLoadedPackage, mtInformation, [mbCancel])
  else
    DoOpenPackageFile(PkgFile.LazPackage.Filename,[pofAddToRecent],false);
end;

procedure TPkgManager.AfterWritePackage(Sender: TObject; Restore: boolean);
var
  Pkg: TLazPackage;
begin
  Pkg := (Sender as TPackageIDEOptions).Package;
  //debugln(['TPkgManager.AfterWritePackage ',Pkg, ' Restore=',Restore]);
  Pkg.DefineTemplates.AllChanged(false);
  if Restore then
    Pkg.RestoreOptions;
end;

procedure TPkgManager.BeforeReadPackage(Sender: TObject);
begin
  (Sender as TPackageIDEOptions).Package.BackupOptions;
end;

function TPkgManager.PackageEditorCompilePackage(Sender: TObject;
  APackage: TLazPackage; CompileClean, CompileRequired: boolean): TModalResult;
var
  Flags: TPkgCompileFlags;
begin
  Flags:=[];
  if CompileClean then Include(Flags,pcfCleanCompile);
  if CompileRequired then Include(Flags,pcfCompileDependenciesClean);
  //debugln('TPkgManager.OnPackageEditorCompilePackage OS=',Globals.TargetOS);
  Result:=DoCompilePackage(APackage,Flags,false);
end;

procedure TPkgManager.PackageEditorCopyMoveFiles(Sender: TObject);
begin
  CopyMoveFiles(Sender);
end;

function TPkgManager.PackageEditorCreateMakefile(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoCreatePackageMakefile(APackage,false);
end;

function TPkgManager.PackageEditorCreateFpmakeFile(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoCreatePackageFpmakefile(APackage,false);
end;

{$IFDEF UseLRS}
procedure TPkgManager.AddToIconResource(const aIconFile, aResName: string);
var
  BinFileStream: TFileStream;
  ResMemStream: TMemoryStream;
  ResType: String;
  OldLen, NewLen: integer;
begin
  try
    BinFileStream:=TFileStream.Create(aIconFile,fmOpenRead);
    try
      ResMemStream:=TMemoryStream.Create;
      try
        Assert(BinFileStream.Position=0, 'TPkgManager.AddToIconResource: Stream.Position > 0');
        ResType:=UpperCase(ExtractFileExt(aIconFile));
        if ResType<>'' then
          Delete(ResType, 1, 1);
        BinaryToLazarusResourceCode(BinFileStream,ResMemStream,aResName,ResType);
        ResMemStream.Position:=0;
        OldLen:=Length(FIconLRSSource);
        NewLen:=ResMemStream.Size;
        if NewLen>0 then begin
          SetLength(FIconLRSSource,OldLen+NewLen);
          ResMemStream.Read(FIconLRSSource[OldLen+1],NewLen);
        end;
      finally
        ResMemStream.Free;
      end;
    finally
      BinFileStream.Free;
    end;
  except
    on E: Exception do begin
      MessageDlg(lisCCOErrorCaption,
        Format(lisErrorLoadingFile2,[aIconFile]) + LineEnding + E.Message,
      mtError, [mbCancel], 0);
    end;
  end;
end;
{$ELSE}
  // ToDo: Use FPC's resource type (.res)
{$ENDIF}

function TPkgManager.PackageEditorCreateFile(Sender: TObject;
  Params: TAddToPkgResult): TModalResult;
var
  LE: String;
  UsesLine: String;
  NewSource: String;
  UnitDirectives: String;
  IconLRSFilename: String;
  ResName: String;
  CodeBuf: TCodeBuffer;
begin
  Result:=mrCancel;
  // create icon resource
  if Params.IconNormFile<>'' then
  begin
    IconLRSFilename:=ChangeFileExt(Params.UnitFilename,'')+'_icon.lrs';
    CodeBuf:=CodeToolBoss.CreateFile(IconLRSFilename);
    if CodeBuf=nil then begin
      debugln(['Error: (lazarus) [TPkgManager.PackageEditorCreateFile] file create failed: ',IconLRSFilename]);
      exit;
    end;
    FIconLRSSource:='';
    ResName:=ExtractFileNameOnly(Params.NewClassName);
    AddToIconResource(Params.IconNormFile, ResName);
    if Params.Icon150File<>'' then
      AddToIconResource(Params.Icon150File, ResName+'_150');
    if Params.Icon200File<>'' then
      AddToIconResource(Params.Icon200File, ResName+'_200');
    CodeBuf.Source:=FIconLRSSource;
    Result:=SaveCodeBuffer(CodeBuf);
    if Result<>mrOk then exit;
  end
  else
    IconLRSFilename:='';
  // create sourcecode
  UsesLine:='Classes, SysUtils';
  if PackageGraph.FindDependencyRecursively(Params.Pkg.FirstRequiredDependency,'LCL')<>nil
  then
    UsesLine:=UsesLine+', LResources, Forms, Controls, Graphics, Dialogs';
  if (System.Pos(Params.UsedUnitname,UsesLine)<1) and (Params.UsedUnitname<>'') then
    UsesLine:=UsesLine+', '+Params.UsedUnitname;
  UnitDirectives:='{$mode objfpc}{$H+}';
  if Params.Pkg<>nil then
    UnitDirectives:=TFileDescPascalUnit.CompilerOptionsToUnitDirectives(
                                                    Params.Pkg.CompilerOptions);
  LE:=LineEnding;
  NewSource:=
     'unit '+Params.Unit_Name+';'+LE
    +LE
    +UnitDirectives+LE
    +LE
    +'interface'+LE
    +LE
    +'uses'+LE
    +'  '+UsesLine+';'+LE
    +LE
    +'type'+LE
    +'  '+Params.NewClassName+' = class('+Params.AncestorType+')'+LE
    +'  private'+LE
    +LE
    +'  protected'+LE
    +LE
    +'  public'+LE
    +LE
    +'  published'+LE
    +LE
    +'  end;'+LE
    +LE
    +'procedure Register;'+LE
    +LE
    +'implementation'+LE
    +LE
    +'procedure Register;'+LE
    +'begin'+LE;
  if IconLRSFilename<>'' then
    NewSource:=NewSource
      +'  {$I '+ExtractFileName(IconLRSFilename)+'}'+LE;
  NewSource:=NewSource
    +'  RegisterComponents('''+Params.PageName+''',['+Params.NewClassName+']);'+LE
    +'end;'+LE
    +LE
    +'end.'+LE;

  FileDescriptorUnit.Owner:=Params.Pkg;
  try
    Result:=MainIDE.DoNewEditorFile(FileDescriptorUnit,
       Params.UnitFilename,NewSource,
       [nfOpenInEditor,nfIsNotPartOfProject,nfSave,nfAddToRecent]);
  finally
    FileDescriptorUnit.Owner:=nil;
  end;
end;

function TPkgManager.PackageEditorDeleteAmbiguousFiles(Sender: TObject;
  APackage: TLazPackage; const Filename: string): TModalResult;
begin
  Result:=BuildBoss.DeleteAmbiguousFiles(Filename);
end;

procedure TPkgManager.PackageEditorDragDropTreeView(Sender, Source: TObject;
  X, Y: Integer);
begin
  FilesEditDragDrop(Sender, Source, X, Y);
end;

function TPkgManager.PackageEditorDragOverTreeView(Sender, Source: TObject;
  X, Y: Integer; out TargetTVNode: TTreeNode;
  out TargetTVType: TTreeViewInsertMarkType): boolean;
var
  aFileCount: integer;
  aDependencyCount: integer;
  aDirectoryCount: integer;
  TargetFilesEdit: IFilesEditorInterface;
  SrcFilesEdit: IFilesEditorInterface;
begin
  Result:=CheckDrag(Sender, Source, X, Y, TargetFilesEdit, SrcFilesEdit, aFileCount,
    aDependencyCount, aDirectoryCount, TargetTVNode, TargetTVType);
end;

function TPkgManager.PackageEditorFindInFiles(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=ShowFindInPackageFilesDlg(APackage);
end;

function TPkgManager.PackageEditorAddToProject(Sender: TObject;
  APackage: TLazPackage; OnlyTestIfPossible: boolean): TModalResult;
begin
  Result:=AddProjectDependency(Project1,APackage,OnlyTestIfPossible);
end;

function TPkgManager.PackageEditorInstallPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoInstallPackage(APackage);
end;

function TPkgManager.PackageEditorPublishPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoPublishPackage(APackage,[],true);
end;

function TPkgManager.PackageEditorRevertPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  if (not FilenameIsAbsolute(APackage.Filename))
  or (not FileExistsUTF8(APackage.Filename)) then
    exit(mrCancel);
  Result:=DoOpenPackageFile(APackage.Filename,[pofRevert],false);
end;

function TPkgManager.PackageEditorUninstallPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoUninstallPackage(APackage,[],false);
end;

function TPkgManager.PackageEditorOpenPkgFile(Sender: TObject;
  PkgFile: TPkgFile): TModalResult;
begin
  Result:=DoOpenPkgFile(PkgFile);
end;

procedure TPkgManager.PackageEditorFreeEditor(APackage: TLazPackage);
begin
  APackage.Editor:=nil;
  PackageGraph.ClosePackage(APackage);
end;

function TPkgManager.PackageGraphCheckInterPkgFiles(IDEObject: TObject;
  PkgList: TFPList; out FilesChanged: boolean): boolean;
begin
  Result:=CheckInterPkgFiles(IDEObject,PkgList,FilesChanged);
end;

function TPkgManager.PackageEditorOpenPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoOpenPackage(APackage,[],false);
end;

function TPkgManager.PackageEditorSavePackage(Sender: TObject;
  APackage: TLazPackage; SaveAs: boolean): TModalResult;
begin
  if SaveAs then
    Result:=DoSavePackage(APackage,[psfSaveAs])
  else
    Result:=DoSavePackage(APackage,[]);
end;

function TPkgManager.PackageEditorViewPkgSource(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoOpenPackageSource(APackage);
end;

procedure TPkgManager.PackageGraphBeginUpdate(Sender: TObject);
begin
  if PackageGraphExplorer<>nil then PackageGraphExplorer.BeginUpdate;
end;

procedure TPkgManager.PackageGraphChangePackageName(APackage: TLazPackage;
  const OldName: string);
begin
  if PackageGraphExplorer<>nil then
    PackageGraphExplorer.UpdatePackageName(APackage,OldName);
end;

procedure TPkgManager.PackageGraphDeletePackage(APackage: TLazPackage);
begin
  if APackage.Editor<>nil then begin
    APackage.Editor.Hide;
    APackage.Editor.Free;
  end;
end;

procedure TPkgManager.PackageGraphDependencyModified(ADependency: TPkgDependency);
var
  DepOwner: TObject;
begin
  DepOwner:=ADependency.Owner;
  if DepOwner is TLazPackage then
    TLazPackage(DepOwner).Modified:=true
  else if DepOwner is TProject then
    TProject(DepOwner).Modified:=true;
end;

function TPkgManager.PackageGraphExplorerOpenPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoOpenPackage(APackage,[pofAddToRecent],false);
end;

function TPkgManager.PackageGraphExplorerOpenProject(Sender: TObject;
  AProject: TProject): TModalResult;
begin
  if AProject<>Project1 then exit(mrCancel);
  MainIDE.DoShowProjectInspector;
  Result:=mrOk;
end;

procedure TPkgManager.PackageGraphAddPackage(Pkg: TLazPackage);
begin
  if FileExistsUTF8(Pkg.FileName) then LazPackageLinks.AddUserLink(Pkg);
  if PackageGraphExplorer<>nil then
    PackageGraphExplorer.UpdatePackageAdded(Pkg);
end;

procedure TPkgManager.PackageGraphEndUpdate(Sender: TObject; GraphChanged: boolean);
begin
  if GraphChanged then IncreaseCompilerParseStamp;
  if PackageGraphExplorer<>nil then begin
    if GraphChanged then PackageGraphExplorer.UpdateAll;
    PackageGraphExplorer.EndUpdate;
  end;
  if GraphChanged then begin
    if PackageEditors<>nil then
      PackageEditors.UpdateAllEditors(false);
    if ProjInspector<>nil then
      ProjInspector.UpdateRequiredPackages;
    DoCallNotifyHandler(pihtGraphChanged,Self);
  end;
end;

procedure TPkgManager.PackageGraphFindFPCUnit(const AUnitName,
  Directory: string; var Filename: string);
begin
  if (Directory<>'') and not FilenameIsAbsolute(Directory) then
    RaiseGDBException(Directory);
  //DebugLn('TPkgManager.PackageGraphFindFPCUnit "',Directory,'"');
  Filename:=CodeToolBoss.DirectoryCachePool.FindUnitInUnitLinks(Directory, AUnitName);
end;

function TPkgManager.ShowMessageHandler(aUrgency: TMessageLineUrgency;
  aMsg, aSrcFilename: string; aLineNumber, aColumn: integer; aViewCaption: string
  ): TMessageLine;
begin
  Assert(Assigned(IDEMessagesWindow), 'TPkgManager.ShowMessageHandler: IDEMessagesWindow=Nil');
  Result:=IDEMessagesWindow.AddCustomMessage(aUrgency, aMsg, aSrcFilename, aLineNumber, aColumn, aViewCaption);
end;

function TPkgManager.PackageGraphExplorerUninstallPackage(Sender: TObject;
  APackage: TLazPackage): TModalResult;
begin
  Result:=DoUninstallPackage(APackage,[],false);
end;

procedure TPkgManager.MainIDEitmPkgNewPackageClick(Sender: TObject);
begin
  DoNewPackage;
end;

procedure TPkgManager.MainIDEitmPkgOpenLoadedPackageClicked(Sender: TObject);
begin
  DoShowLoadedPkgDlg;
end;

procedure TPkgManager.MainIDEitmPackageLinksClicked(Sender: TObject);
begin
  ShowPackageLinks;
end;

procedure TPkgManager.MainIDEitmOpenRecentPackageClicked(Sender: TObject);

  procedure UpdateEnvironment;
  begin
    SetRecentPackagesMenu;
    MainIDE.SaveEnvironment;
  end;

var
  AFilename: string;
begin
  // Hint holds the full filename, Caption may have a shortened form.
  AFileName:=(Sender as TIDEMenuItem).Hint;
  if DoOpenPackageFile(AFilename,[pofAddToRecent],false)=mrOk then begin
    UpdateEnvironment;
  end else begin
    // open failed
    if not FileExistsUTF8(AFilename) then begin
      // file does not exist -> delete it from recent file list
      EnvironmentOptions.RemoveFromRecentPackageFiles(AFilename);
      UpdateEnvironment;
    end;
  end;
end;

procedure TPkgManager.ApplicationIdleHandler(Sender: TObject; var Done: Boolean);
begin
  if PackageGraph = nil then Exit;
  if MainIDE.ToolStatus<>itNone then exit;
  if (Screen.ActiveCustomForm<>nil)
  and (fsModal in Screen.ActiveCustomForm.FormState) then exit;
  PackageGraph.CloseUnneededPackages;
end;

function TPkgManager.DoShowSavePackageAsDialog(APackage: TLazPackage): TModalResult;
var
  OldPkgFilename: String;
  SaveDialog: TSaveDialog;
  NewFileName: String;
  NewPkgName: String;
  ConflictPkg: TLazPackage;
  PkgFile: TPkgFile;
  LowerFilename: String;
  BrokenDependencies: TFPList;
  RenameDependencies: Boolean;
  OldPkgName: String;
  NewMainUnitFileName: String;
  
  procedure RenamePackageInProject;
  var
    AProject: TProject;
    OldUnitName: String;
    NewUnitName: String;
  begin
    AProject:=Project1;
    if (pfMainUnitIsPascalSource in AProject.Flags)
    and (AProject.MainUnitInfo<>nil) then begin
      OldUnitName:=OldPkgName;
      NewUnitName:=APackage.Name;
      if (OldUnitName<>NewUnitName) then begin
        MainIDE.SaveSourceEditorChangesToCodeCache(nil);
        if CodeToolBoss.RenameUsedUnit(
          AProject.MainUnitInfo.Source,OldUnitName,NewUnitName,'')
        then
          AProject.MainUnitInfo.Modified:=true;
      end;
    end;
  end;
  
begin
  OldPkgFilename:=APackage.Filename;
  OldPkgName:=APackage.Name;

  SaveDialog:=TSaveDialog.Create(nil);
  try
    InputHistories.ApplyFileDialogSettings(SaveDialog);
    SaveDialog.Title:=Format(lisPkgMangSavePackageLpk, [APackage.IDAsString]);
    SaveDialog.Filter:=dlgFilterLazarusPackage+' (*.lpk)|*.lpk'
                     +'|'+dlgFilterAll+' ('+FileMask+')|'+FileMask;
    if APackage.HasDirectory then
      SaveDialog.InitialDir:=APackage.Directory;

    // build a nice package filename suggestion
    NewFileName:=APackage.Name+'.lpk';
    SaveDialog.FileName:=NewFileName;

    repeat
      Result:=mrCancel;

      if not SaveDialog.Execute then begin
        // user cancels
        Result:=mrCancel;
        exit;
      end;
      NewFileName:=CleanAndExpandFilename(SaveDialog.Filename);
      NewPkgName:=ExtractFileNameOnly(NewFilename);
      if APackage.MainUnitHasPkgName then
        NewMainUnitFileName:=ChangeFileExt(NewFileName,'.pas')
      else
        NewMainUnitFileName:='';

      if PackageEditors.FindEditor(NewPkgName) <> nil then
      begin
        Result:=IDEMessageDialog(lisPkgMangInvalidPackageName,
          Format(lisPkgMangSaveAsAlreadyOpenedPackage, [NewPkgName]),
          mtInformation,[mbRetry,mbAbort]);
        if Result=mrAbort then exit;
        continue; // try again
      end;
      
      // check file extension
      if ExtractFileExt(NewFilename)='' then begin
        // append extension
        NewFileName:=NewFileName+'.lpk';
      end
      else if not FilenameExtIs(NewFilename,'.lpk',true) then begin
        Result:=IDEMessageDialog(lisPkgMangInvalidPackageFileExtension,
          lisPkgMangPackagesMustHaveTheExtensionLpk,
          mtInformation,[mbRetry,mbAbort]);
        if Result=mrAbort then exit;
        continue; // try again
      end;

      // check filename
      if not IsValidPkgName(NewPkgName) then begin
        Result:=IDEMessageDialog(lisPkgMangInvalidPackageName,
          Format(lisPkgMangThePackageNameIsNotAValidPackageNamePleaseChooseAn,
                 [NewPkgName, LineEnding]),
          mtInformation,[mbRetry,mbAbort]);
        if Result=mrAbort then exit;
        continue; // try again
      end;

      // apply naming conventions
      
      if lowercase(NewPkgName) <> NewPkgName then
      begin
        LowerFilename:=ExtractFilePath(NewFilename)+lowercase(ExtractFileName(NewFilename));
        case EnvironmentOptions.CharcaseFileAction of
        ccfaAsk:
          if IDEMessageDialog(lisPkgMangRenameFileLowercase,
            Format(lisPkgMangShouldTheFileRenamedLowercaseTo,[LineEnding, LowerFilename]),
            mtConfirmation,[mbYes,mbNo])=mrYes
          then
            NewFileName:=LowerFilename;
        ccfaAutoRename: NewFileName:=LowerFilename;
        ccfaIgnore: ;
        end;
      end;

      // check unit name conflict
      if NewMainUnitFileName<>'' then
      begin
        PkgFile:=APackage.FindUnit(NewPkgName);
        if PkgFile<>nil then begin
          Result:=IDEMessageDialog(lisNameConflict,
            lisThePackageAlreadyContainsAUnitWithThisName,
            mtWarning,[mbRetry,mbAbort]);
          if Result=mrAbort then exit;
          continue; // try again
        end;
      end;

      // check package name conflict
      ConflictPkg:=PackageGraph.FindPackageWithName(NewPkgName,APackage);
      if ConflictPkg<>nil then begin
        Result:=IDEMessageDialog(lisPkgMangPackageNameAlreadyExists,
          Format(lisPkgMangThereIsAlreadyAnotherPackageWithTheName,
            [NewPkgName, LineEnding, ConflictPkg.IDAsString, LineEnding, ConflictPkg.Filename]),
          mtInformation,[mbRetry,mbAbort,mbIgnore]);
        if Result=mrAbort then exit;
        if Result<>mrIgnore then continue; // try again
      end;
      
      // check file name conflict with project
      if (NewMainUnitFileName<>'')
      and (Project1.ProjectUnitWithFilename(NewMainUnitFileName)<>nil) then begin
        Result:=IDEMessageDialog(lisPkgMangFilenameIsUsedByProject,
          Format(lisPkgMangTheFileNameIsPartOfTheCurrentProject,[NewFilename,LineEnding]),
          mtInformation,[mbRetry,mbAbort]);
        if Result=mrAbort then exit;
        continue; // try again
      end;
      
      // check file name conflicts with files in other packages
      if (NewMainUnitFileName<>'') then
      begin
        PkgFile:=PackageGraph.FindFileInAllPackages(NewMainUnitFileName,true,false);
        if PkgFile<>nil then begin
          Result:=IDEMessageDialog(lisPkgMangFilenameIsUsedByOtherPackage,
            Format(lisPkgMangTheFileNameIsUsedByThePackageInFile, [NewFilename, LineEnding,
                   PkgFile.LazPackage.IDAsString, LineEnding, PkgFile.LazPackage.Filename]),
            mtWarning,[mbRetry,mbAbort]);
          if Result=mrAbort then exit;
          continue; // try again
        end;
      end;

      // check for broken dependencies
      BrokenDependencies:=PackageGraph.GetBrokenDependenciesWhenChangingPkgID(
        APackage,NewPkgName,APackage.Version);
      RenameDependencies:=false;
      try
        if BrokenDependencies.Count>0 then begin
          Result:=ShowBrokenDependencies(BrokenDependencies);
          if Result=mrOK then             // = Yes
            RenameDependencies:=true
          else if Result<>mrClose then    // <> Ignore
            exit;
        end;
      finally
        BrokenDependencies.Free;
      end;
      
      // check existing file
      if (CompareFilenames(NewFileName,OldPkgFilename)<>0) then
      begin
        if FileExistsUTF8(NewFileName) then begin
          Result:=IDEMessageDialog(lisPkgMangReplaceFile,
            Format(lisPkgMangReplaceExistingFile, [NewFilename]),
            mtConfirmation,[mbOk,mbCancel]);
          if Result<>mrOk then exit;
        end;
        if FileExistsUTF8(NewMainUnitFileName) then
        begin
          Result:=IDEMessageDialog(lisPkgMangReplaceFile,
            Format(lisPkgMangReplaceExistingFile, [NewFilename]),
            mtConfirmation,[mbOk,mbCancel]);
          if Result<>mrOk then exit;
        end;
      end;

      // check if new file is read/writable
      Result:=CheckCreatingFile(NewFileName,true);
      if Result=mrAbort then exit;

    until Result<>mrRetry;
  finally
    InputHistories.StoreFileDialogSettings(SaveDialog);
    SaveDialog.Free;
  end;
  
  // set filename
  APackage.Filename:=NewFilename;
  if Assigned(APackage.Editor) then
    APackage.Editor.LazPackage := APackage;//force package editor name change!
  
  // rename package
  PackageGraph.ChangePackageID(APackage,NewPkgName,APackage.Version,
                               RenameDependencies,true);
  SaveAutoInstallDependencies;
  RenamePackageInProject;

  //update LastOpenPackages list
  EnvironmentOptions.LastOpenPackages.Remove(OldPkgFilename);
  EnvironmentOptions.LastOpenPackages.Add(NewFileName);
  MainIDE.SaveEnvironment;

  // clean up old package file to reduce ambiguousities
  if FileExistsUTF8(OldPkgFilename)
  and (CompareFilenames(OldPkgFilename,NewFilename)<>0) then begin
    if IDEMessageDialog(lisPkgMangDeleteOldPackageFile,
      Format(lisPkgMangDeleteOldPackageFile2, [OldPkgFilename]),
      mtConfirmation,[mbYes,mbNo])=mrYes
    then begin
      if DeleteFileUTF8(OldPkgFilename) then begin
        RemoveFromRecentList(OldPkgFilename,
                             EnvironmentOptions.RecentPackageFiles,rltFile);
      end else begin
        IDEMessageDialog(lisPkgMangDeleteFailed,
          Format(lisPkgMangUnableToDeleteFile, [OldPkgFilename]), mtError, [mbOk]);
      end;
    end;
  end;

  // success
  Result:=mrOk;
end;

function TPkgManager.CheckPackageGraphForCompilation(APackage: TLazPackage;
  FirstDependency: TPkgDependency; const Directory: string; ShowAbort: boolean
  ): TModalResult;
var
  PathList: TFPList;
  Dependency: TPkgDependency;
  PkgFile1,PkgFile2: TPkgFile;
  ConflictPkg: TLazPackage;
  s: String;
  Btns: TMsgDlgButtons;
  PkgList: TFPList;
  i: Integer;
begin
  {$IFDEF VerbosePkgCompile}
  debugln('TPkgManager.CheckPackageGraphForCompilation A');
  {$ENDIF}
  if ShowAbort
  then Btns := [mbCancel] // will be replaced to Ignore
  else Btns := [mbOK];
  PathList:=nil;
  PkgList:=nil;
  try
    // check for unsaved packages
    PathList:=PackageGraph.FindUnsavedDependencyPath(APackage,FirstDependency);
    if PathList<>nil then begin
      DoShowPackageGraphPathList(PathList);
      Result:=IDEMessageDialogAb(lisPkgMangUnsavedPackage,
        lisPkgMangThereIsAnUnsavedPackageInTheRequiredPackages,
        mtError,[mbCancel],ShowAbort);
      exit;
    end;

    // check for broken dependencies
    PathList:=PackageGraph.FindBrokenDependencyPath(APackage,FirstDependency);
    if PathList<>nil then begin
      if (PathList.Count=1) then begin
        Dependency:=TPkgDependency(PathList[0]);
        if Dependency is TPkgDependency then begin
          // check if project
          if Dependency.Owner is TProject then begin
            MainIDE.DoShowProjectInspector;
            Result:=IDEMessageDialogAb(lisPkgMangBrokenDependency,
              Format(lisPkgMangTheProjectRequiresThePackageButItWasNotFound,
                    [Dependency.AsString, LineEnding]),
              mtError,Btns,ShowAbort);
            if not ShowAbort then
              Result := mrCancel; // User confirmed error, implicitly cancel the action
            exit;
          end;
        end;
      end;
      DoShowPackageGraphPathList(PathList);
      Result:=IDEMessageDialogAb(lisPkgMangBrokenDependency,
        lisPkgMangRequiredPackagesWereNotFound,
        mtError,Btns,ShowAbort);
      if not ShowAbort then
        Result := mrCancel; // User confirmed error, implicitly cancel the action
      exit;
    end;

    // check for cycle dependencies
    PathList:=PackageGraph.FindCycleDependencyPath(APackage,FirstDependency);
    if PathList<>nil then begin
      DoShowPackageGraphPathList(PathList);
      Result:=IDEMessageDialogAb(lisPkgMangCircularDependencies,
        lisPkgMangThereIsACircularDependency,
        mtError,Btns,ShowAbort);
      if not ShowAbort then
        Result := mrCancel; // User confirmed error, implicitly cancel the action
      exit;
    end;

    // check for all used package with wrong
    PackageGraph.GetAllRequiredPackages(APackage,FirstDependency,PkgList);
    if (PkgList<>nil) then begin
      for i:=0 to PkgList.Count-1 do begin
        Result:=CheckUserSearchPaths(TLazPackage(PkgList[i]).CompilerOptions);
        if Result<>mrOk then
          exit(mrCancel);
      end;
    end;

    // check for a package that compiles to the default FPC search path
    PathList:=PackageGraph.FindPkgOutputInFPCSearchPath(APackage,FirstDependency);
    if PathList<>nil then begin
      ConflictPkg:=TObject(PathList[PathList.Count-1]) as TLazPackage;
      DoShowPackageGraphPathList(PathList);
      Result:=IDEMessageDialogAb(lisPkgMangCircularDependencies,
        Format(lisPkgMangThePackageIsCompiledAutomaticallyAndItsOutputDirec,
          [ConflictPkg.Name, ConflictPkg.GetOutputDirectory, LineEnding+LineEnding, LineEnding]),
        mtError,Btns,ShowAbort);
      if not ShowAbort then
        Result := mrCancel; // User confirmed error, implicitly cancel the action
      exit;
    end;

    // check for ambiguous units between packages
    if PackageGraph.FindAmbiguousUnits(APackage,FirstDependency,
      PkgFile1,PkgFile2,ConflictPkg)
    then begin
      if (PkgFile1<>nil) and (PkgFile2<>nil) then begin
        s:=Format(lisPkgMangThereAreTwoUnitsWithTheSameName1From2From,
          [LineEnding+LineEnding, PkgFile1.Filename, PkgFile1.LazPackage.IDAsString,
           LineEnding, PkgFile2.Filename, PkgFile2.LazPackage.IDAsString]) + LineEnding;
      end else if (PkgFile1<>nil) and (ConflictPkg<>nil) then begin
        s:=Format(lisPkgMangThereIsAUnitWithTheSameNameAsAPackage1From2,
          [LineEnding+LineEnding, PkgFile1.Filename, PkgFile1.LazPackage.IDAsString,
           LineEnding, ConflictPkg.IDAsString]) + LineEnding;
      end else
        s:='Internal inconsistency FindAmbiguousUnits: '
          +'Please report this bug and how you got here.'+LineEnding;
      Result:=IDEMessageDialogAb(lisPkgMangAmbiguousUnitsFound, Format(
        lisPkgMangBothPackagesAreConnectedThisMeansEitherOnePackageU, [s]),
          mtError,Btns,ShowAbort);
      if not ShowAbort then
        Result := mrCancel; // User confirmed error, implicitly cancel the action
      exit;
    end;

    // check for ambiguous units between packages and FPC units
    if PackageGraph.FindFPCConflictUnit(APackage,FirstDependency,Directory,
      @PackageGraphFindFPCUnit,PkgFile1,ConflictPkg)
    then begin
      if (ConflictPkg<>nil) then begin
        s:=Format(lisPkgMangThereIsAFPCUnitWithTheSameNameAsAPackage,
          [LineEnding+LineEnding, ConflictPkg.IDAsString]) + LineEnding;
      end else if (PkgFile1<>nil) then begin
        s:=Format(lisPkgMangThereIsAFPCUnitWithTheSameNameFrom,
          [LineEnding+LineEnding, PkgFile1.Filename, PkgFile1.LazPackage.IDAsString]) + LineEnding;
      end else
        s:='Internal inconsistency FindFPCConflictUnits: '
          +'Please report this bug and how you got here.'+LineEnding;
      Result:=IDEMessageDialogAb(lisPkgMangAmbiguousUnitsFound, s,
          mtError,Btns,ShowAbort);
      if not ShowAbort then
        Result := mrCancel; // User confirmed error, implicitly cancel the action
      exit;
    end;

  finally
    PkgList.Free;
    PathList.Free;
  end;
  
  {$IFDEF VerbosePkgCompile}
  debugln('TPkgManager.CheckPackageGraphForCompilation END');
  {$ENDIF}
  Result:=mrOk;
end;

procedure TPkgManager.SaveAutoInstallDependencies;
var
  Dependency: TPkgDependency;
  sl: TStringListUTF8Fast;
begin
  sl:=TStringListUTF8Fast.Create;
  Dependency:=PackageGraph.FirstInstallDependency;
  while Dependency<>nil do begin
    if (Dependency.LoadPackageResult=lprSuccess)
    and (not Dependency.RequiredPackage.Missing)
    and (not PackageGraph.IsCompiledInBasePackage(Dependency.PackageName))
    and (not (Dependency.RequiredPackage.PackageType in [lptRunTime,lptRunTimeOnly]))
    then begin
      if sl.IndexOf(Dependency.PackageName)<0 then begin
        sl.Add(Dependency.PackageName);
        //DebugLn('TPkgManager.SaveAutoInstallDependencies A ',Dependency.PackageName);
      end;
    end;
    Dependency:=Dependency.NextRequiresDependency;
  end;
  MiscellaneousOptions.BuildLazProfiles.StaticAutoInstallPackages.Assign(sl);
  MiscellaneousOptions.Save;
  sl.Free;
end;

procedure TPkgManager.LoadStaticCustomPackages;
var
  StaticPackages: TFPList;
  StaticPackage: PRegisteredPackage;
  i: Integer;
  APackage: TLazPackage;
  Quiet: Boolean;
begin
  StaticPackages:=LazarusPackageIntf.RegisteredPackages;
  if StaticPackages=nil then exit;
  Quiet:=GetSkipCheck(skcMissingPackageFile) or GetSkipCheck(skcAll);
  PackageGraph.AbortRegistration:=false;
  // register components in Lazarus packages
  for i:=0 to StaticPackages.Count-1 do begin
    StaticPackage:=PRegisteredPackage(StaticPackages[i]);
    //debugln(['TPkgManager.LoadStaticCustomPackages ',StaticPackage^.Name]);
    // check package name
    if not IsValidPkgName(StaticPackage^.Name) then begin
      DebugLn('Warning: (lazarus) [TPkgManager.LoadStaticCustomPackages] Invalid Package Name: "',
        BinaryStrToText(StaticPackage^.Name),'"');
      continue;
    end;
    // check Register procedure
    if (StaticPackage^.RegisterProc=nil) then begin
      DebugLn('Warning: (lazarus) [TPkgManager.LoadStaticCustomPackages]',
        ' Package "',StaticPackage^.Name,'" has no register procedure.');
      continue;
    end;
    // load package
    APackage:=LoadInstalledPackage(StaticPackage^.Name,KeepInstalledPackages,Quiet);
    PackageGraph.RegisterStaticPackage(APackage,StaticPackage^.RegisterProc);
  end;
  PackageGraph.SortAutoInstallDependencies;
  ClearRegisteredPackages;
end;

function TPkgManager.LoadInstalledPackage(const PackageName: string;
  AddToAutoInstall: boolean; var Quiet: boolean): TLazPackage;
var
  NewDependency: TPkgDependency;
  PackageList: TStringList;
begin
  //DebugLn('TPkgManager.LoadInstalledPackage PackageName="',PackageName,'" Quiet=',Quiet);
  NewDependency:=TPkgDependency.Create;
  NewDependency.Owner:=Self;
  NewDependency.DependencyType:=pdtLazarus;
  NewDependency.PackageName:=PackageName;
  PackageGraph.OpenInstalledDependency(NewDependency,pitStatic,Quiet);
  Result:=NewDependency.RequiredPackage;
  if AddToAutoInstall and (Result<>nil) then begin
    if FindDependencyByNameInList(
           PackageGraph.FirstInstallDependency,pddRequires,PackageName)=nil
    then begin
      NewDependency.RequiredPackage.AutoInstall:=pitStatic;
      NewDependency.AddToList(PackageGraph.FirstInstallDependency,pddRequires)
    end else
      NewDependency.Free;
    PackageList:=MiscellaneousOptions.BuildLazProfiles.StaticAutoInstallPackages;
    if PackageList.IndexOf(PackageName)<0 then
      PackageList.Add(PackageName);
  end else begin
    NewDependency.Free;
  end;
end;

procedure TPkgManager.LoadAutoInstallPackages;
begin
  FLastLazarusSrcDir:=EnvironmentOptions.GetParsedLazarusDirectory;
  PackageGraph.LoadAutoInstallPackages(
    MiscellaneousOptions.BuildLazProfiles.StaticAutoInstallPackages);
end;

procedure TPkgManager.AddUnitToProjectMainUsesSection(AProject: TProject;
  const AnUnitName, AnUnitInFilename: string);
begin
  // add unit to project main source file
  if (pfMainUnitIsPascalSource in AProject.Flags)
  and (pfMainUnitHasUsesSectionForAllUnits in AProject.Flags)
  and (AProject.MainUnitInfo<>nil) then begin
    //debugln('TPkgManager.AddUnitToProjectMainUsesSection B ',AnUnitName);
    if (AnUnitName<>'') then begin
      MainIDE.SaveSourceEditorChangesToCodeCache(nil);
      if CodeToolBoss.AddUnitToMainUsesSectionIfNeeded(
        AProject.MainUnitInfo.Source,AnUnitName,AnUnitInFilename)
      then
        AProject.MainUnitInfo.Modified:=true;
    end;
  end;
end;

function TPkgManager.CheckDrag(Sender, Source: TObject; X, Y: Integer; out
  SrcFilesEdit, TargetFilesEdit: IFilesEditorInterface; out aFileCount,
  aDependencyCount, aDirectoryCount: integer; out TargetTVNode: TTreeNode; out
  TargetTVType: TTreeViewInsertMarkType): boolean;

  function GetFilesEditIntf(o: TObject): IFilesEditorInterface;
  var
    PkgEdit: TPackageEditorForm;
    aProjInsp: TProjectInspectorForm;
  begin
    Result:=nil;
    if o is TTreeView then begin
      PkgEdit:=PackageEditors.TreeViewToPkgEditor(TTreeView(o));
      if PkgEdit<>nil then
        Result:=PkgEdit
      else begin
        aProjInsp:=ProjInspector.TreeViewToInspector(TTreeView(o));
        if aProjInsp<>nil then
          Result:=aProjInsp;
      end;
    end;
  end;

var
  i: Integer;
  TVNode: TTreeNode;
  NodeData: TPENodeData;
  Item: TObject;
  Directory: String;
  TV: TTreeView;
begin
  Result:=false;
  SrcFilesEdit:=nil;
  TargetFilesEdit:=nil;
  aFileCount:=0;
  aDependencyCount:=0;
  aDirectoryCount:=0;
  TargetTVNode:=nil;
  TargetTVType:=tvimNone;

  // get source
  SrcFilesEdit:=GetFilesEditIntf(Source);
  if (SrcFilesEdit=nil) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: unknown src=',DbgSName(Source)]);
    {$ENDIF}
    exit;
  end;
  if SrcFilesEdit.FilesOwnerReadOnly
  or (not FilenameIsAbsolute(SrcFilesEdit.FilesBaseDirectory)) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: src=',DbgSName(SrcFilesEdit.FilesOwner),' readonly=',SrcFilesEdit.FilesOwnerReadOnly,' basedir=',SrcFilesEdit.FilesBaseDirectory]);
    {$ENDIF}
    exit;
  end;

  // get target
  TargetFilesEdit:=GetFilesEditIntf(Sender);
  if (TargetFilesEdit=nil) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: unknown target=',DbgSName(Sender)]);
    {$ENDIF}
    exit;
  end;
  if TargetFilesEdit.FilesOwnerReadOnly
  or (not FilenameIsAbsolute(TargetFilesEdit.FilesBaseDirectory)) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: target=',DbgSName(TargetFilesEdit.FilesOwner),' readonly=',SrcFilesEdit.FilesOwnerReadOnly,' basedir=',SrcFilesEdit.FilesBaseDirectory]);
    {$ENDIF}
    exit;
  end;

  //debugln(['TPkgManager.CheckDrag Src=',SrcFilesEdit.FilesOwnerName,' Target=',TargetFilesEdit.FilesOwnerName]);

  // check items
  aFileCount:=0;
  aDependencyCount:=0;
  aDirectoryCount:=0;
  for i:=0 to SrcFilesEdit.FilesEditTreeView.SelectionCount-1 do begin
    TVNode:=SrcFilesEdit.FilesEditTreeView.Selections[i];
    if SrcFilesEdit.GetNodeDataItem(TVNode,NodeData,Item) then begin
      if NodeData.Removed then exit; // removed things cannot be moved
      if Item is TIDEOwnedFile then begin
        if (Item is TUnitInfo) and (TUnitInfo(Item)=TUnitInfo(Item).Project.MainUnitInfo)
        then
          continue;
        inc(aFileCount);
      end else if Item is TPkgDependency then begin
        inc(aDependencyCount);
      end;
    end else if SrcFilesEdit.IsDirectoryNode(TVNode) then begin
      inc(aDirectoryCount);
    end;
  end;
  if aFileCount+aDependencyCount+aDirectoryCount=0 then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: nothing useful dragged']);
    {$ENDIF}
    exit;
  end;
  if aDirectoryCount>0 then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: move directory is not implemented']);
    {$ENDIF}
    exit;
  end;
  if aDependencyCount>0 then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: move dependency is not implemented']);
    {$ENDIF}
    exit;
  end;
  if Sign(aFileCount)+Sign(aDependencyCount)+Sign(aDirectoryCount)>1 then begin
    // more than one type, but only one type can be dragged
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: more than one type Files=',aFileCount,' Deps=',aDependencyCount,' Dirs=',aDirectoryCount]);
    {$ENDIF}
    exit;
  end;

  TV:=TargetFilesEdit.FilesEditTreeView;
  TargetTVNode:=TV.GetNodeAt(X,Y);
  if TargetTVNode=nil then begin
    if aDependencyCount>0 then begin
      TargetTVNode:=TargetFilesEdit.TVNodeRequiredPackages;
    end else begin
      TargetTVNode:=TargetFilesEdit.TVNodeFiles;
    end;
    TargetTVType:=tvimAsFirstChild;
  end;
  if TargetFilesEdit.GetNodeDataItem(TargetTVNode,NodeData,Item) then begin
    // move to specific position is not yet supported
    // => redirect to parent nodes
    repeat
      TargetTVNode:=TargetTVNode.Parent;
      if TargetTVNode=nil then
        exit;
    until (TargetTVNode=TargetFilesEdit.TVNodeFiles)
      or (TargetTVNode=TargetFilesEdit.TVNodeRequiredPackages)
      or TargetFilesEdit.IsDirectoryNode(TargetTVNode);
    TargetTVType:=tvimAsFirstChild;
  end;
  if TargetFilesEdit.IsDirectoryNode(TargetTVNode)
  or (TargetTVNode=TargetFilesEdit.TVNodeFiles)
  then begin
    Directory:=TargetFilesEdit.GetNodeFilename(TargetTVNode);
    if not FilenameIsAbsolute(Directory) then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.CheckDrag: invalid target directory="',Directory,'"']);
      {$ENDIF}
      exit;
    end;
    TargetTVType:=tvimAsFirstChild;
    if aFileCount>0 then begin
      // drag files
    end else if aDirectoryCount>0 then begin
      // drag directory
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.CheckDrag drag directory not implemented yet']);
      {$ENDIF}
      exit;
    end else begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.CheckDrag failed: expected files or directory']);
      {$ENDIF}
      exit;
    end;
  end else if TargetTVNode=TargetFilesEdit.TVNodeRequiredPackages then begin
    if aDependencyCount=0 then exit;
    // drag dependencies
    TargetTVType:=tvimAsFirstChild;
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag drag dependencies not implemented yet']);
    {$ENDIF}
    exit;
  end else begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: invalid target node: ',TargetTVNode.Text]);
    {$ENDIF}
    exit;
  end;

  if (SrcFilesEdit=TargetFilesEdit)
  and (TargetTVNode.Selected or TargetTVNode.MultiSelected)
  then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.CheckDrag failed: target is selected']);
    {$ENDIF}
    exit;
  end;

  Result:=true;
end;

procedure TPkgManager.FilesEditDragDrop(Sender, Source: TObject; X, Y: Integer);
var
  aFileCount: integer;
  aDependencyCount: integer;
  aDirectoryCount: integer;
  TargetTVNode: TTreeNode;
  TargetTVType: TTreeViewInsertMarkType;
  NodeData: TPENodeData;
  Item: TObject;
  aFile: TIDEOwnedFile;
  Directory: String;
  SrcFilesEdit: IFilesEditorInterface;
  TargetFilesEdit: IFilesEditorInterface;
begin
  if not CheckDrag(Sender, Source, X, Y, SrcFilesEdit, TargetFilesEdit,
   aFileCount, aDependencyCount, aDirectoryCount, TargetTVNode, TargetTVType)
  then begin
    ShowMessage('drop failed, dragover was wrong');
    exit;
  end;

  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.FilesEditDragDrop START Src=',SrcFilesEdit.FilesOwnerName,' Target=',TargetFilesEdit.FilesOwnerName,' FileCount=',aFileCount,' DepCount=',aDependencyCount,' DirCount=',aDirectoryCount]);
  {$ENDIF}
  if TargetFilesEdit.GetNodeDataItem(TargetTVNode,NodeData,Item) then begin
    if Item is TIDEOwnedFile then begin
      aFile:=TIDEOwnedFile(Item);
      if aFileCount=0 then exit;
      // drag files
      Directory:=ExtractFilePath(aFile.GetFullFilename);
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.FilesEditDragDrop drag files to directory of ',aFile.Filename]);
      {$ENDIF}
      MoveFiles(TargetFilesEdit,SrcFilesEdit,Directory);
    end else if Item is TPkgDependency then begin
      if aDependencyCount=0 then exit;
      // ToDo: drag dependencies
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.FilesEditDragDrop: drag dependencies']);
      {$ENDIF}
      ShowMessage('Not implemented yet: drag dependencies');
    end;
  end else if TargetFilesEdit.IsDirectoryNode(TargetTVNode)
  or (TargetTVNode=TargetFilesEdit.TVNodeFiles)
  then begin
    Directory:=TargetFilesEdit.GetNodeFilename(TargetTVNode);
    if aFileCount>0 then begin
      // drag files
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.FilesEditDragDrop drag files to ',TargetFilesEdit.FilesBaseDirectory]);
      {$ENDIF}
      MoveFiles(TargetFilesEdit,SrcFilesEdit,Directory);
    end else if aDirectoryCount>0 then begin
      // drag directory
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.FilesEditDragDrop: drag directory']);
      {$ENDIF}
      ShowMessage('Not implemented yet: drag directory');
    end else begin
      ShowMessage('I cannot drag that to a directory');
    end;
  end else if TargetTVNode=TargetFilesEdit.TVNodeRequiredPackages then begin
    if aDependencyCount=0 then exit;
    // ToDo: drag dependencies
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.FilesEditDragDrop: drag dependencies']);
    {$ENDIF}
    ShowMessage('Not implemented yet: drag dependencies');
  end else begin
    {$IFDEF VerbosePkgEditDrag}
    if TargetTVNode=nil then
      debugln(['TPkgManager.FilesEditDragDrop TargetTVNode=nil'])
    else
      debugln(['TPkgManager.FilesEditDragDrop TargetTVNode="',TargetTVNode.Text,'"']);
    {$ENDIF}
  end;
  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.FilesEditDragDrop END']);
  {$ENDIF}
end;

function TPkgManager.MoveFiles(TargetFilesEdit, SrcFilesEdit: IFilesEditorInterface;
  TargetDirectory: string): boolean;
var
  Files: TFPList; // list of TPkgFile
  i: Integer;
  TVNode: TTreeNode;
  NodeData: TPENodeData;
  Item: TObject;
begin
  Result:=false;
  if not FilenameIsAbsolute(TargetDirectory) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.MoveFiles invalid TargetDirectory=',TargetDirectory]);
    {$ENDIF}
    exit;
  end;
  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.MoveFiles Target=',TargetFilesEdit.FilesOwnerName,' Src=',SrcFilesEdit.FilesOwnerName,' Dir="',TargetDirectory,'"']);
  {$ENDIF}
  Files:=TFPList.Create;
  try
    for i:=0 to SrcFilesEdit.FilesEditTreeView.SelectionCount-1 do begin
      TVNode:=SrcFilesEdit.FilesEditTreeView.Selections[i];
      if not SrcFilesEdit.GetNodeDataItem(TVNode, NodeData, Item) then continue;
      if NodeData.Removed then continue;
      if not (Item is TIDEOwnedFile) then continue;
      if (Item is TUnitInfo) and (TUnitInfo(Item)=TUnitInfo(Item).Project.MainUnitInfo)
      then
        continue;
      Files.Add(Item);
    end;
    if Files.Count=0 then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles no file feasable for moving']);
      {$ENDIF}
      exit(true);
    end;

    Result:=MoveFiles(TargetFilesEdit,SrcFilesEdit,Files,TargetDirectory);
  finally
    Files.Free;
  end;
end;

function TPkgManager.MoveFiles(TargetFilesEdit, SrcFilesEdit: IFilesEditorInterface;
  IDEFiles: TFPList; TargetDirectory: string): boolean;
var
  ChangedFilenames: TFilenameToStringTree; // old to new file name
  AllChangedFilenames: TFilenameToStringTree; // including resouce files
  NewFileToOldOwnedFile: TFilenameToPointerTree; // filename to TIDEOwnedFile
  DeleteOld: Boolean;
  UnitFilenameToResFileList: TFilenameToPointerTree; // filename to TStringList
  SrcDirToPkg: TFilenameToPointerTree;
  SrcPackage, TargetPackage: TLazPackage;
  SrcProject, TargetProject: TProject;
  SrcIsTarget: Boolean;

  procedure DeleteNonExistingPkgFiles;
  var
    i: Integer;
    CurFile: TIDEOwnedFile;
    aFilename: String;
  begin
    // ignore non existing files
    for i:=IDEFiles.Count-1 downto 0 do begin
      CurFile:=TIDEOwnedFile(IDEFiles[i]);
      aFilename:=CurFile.GetFullFilename;
      if not FileExistsCached(aFilename) then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['TPkgManager.MoveFiles WARNING: file not found: ',aFilename]);
        {$ENDIF}
        IDEFiles.Delete(i);
      end;

      if (CurFile is TUnitInfo) and (TUnitInfo(CurFile)=TUnitInfo(CurFile).Project.MainUnitInfo)
      then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['TPkgManager.MoveFiles WARNING: main unit of project cannot be moved: ',aFilename]);
        {$ENDIF}
        IDEFiles.Delete(i);
      end;
    end;
  end;

  function GetPkgProj(FilesEdit: IFilesEditorInterface; out aPkg: TLazPackage;
    out aProject: TProject): boolean;
  var
    MainUnit: TUnitInfo;
    Code: TCodeBuffer;
    Tool: TCodeTool;
  begin
    Result:=false;
    aPkg:=nil;
    aProject:=nil;
    if not FilenameIsAbsolute(FilesEdit.FilesBaseDirectory) then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles base dir not absolute: ',FilesEdit.FilesBaseDirectory]);
      {$ENDIF}
      exit;
    end;

    if FilesEdit.FilesOwner is TLazPackage then begin
      aPkg:=TLazPackage(FilesEdit.FilesOwner);
      Result:=true;
    end else if FilesEdit.FilesOwner is TProject then begin
      aProject:=TProject(FilesEdit.FilesOwner);
      MainUnit:=aProject.MainUnitInfo;
      if (MainUnit<>nil) and (pfMainUnitIsPascalSource in aProject.Flags) then
      begin
        // check project main source for syntax errors
        if LoadCodeBuffer(Code,MainUnit.Filename,[lbfUpdateFromDisk,lbfCheckIfText],false)<>mrOk
        then exit;
        if not CodeToolBoss.Explore(Code,Tool,true) then begin
          {$IFDEF VerbosePkgEditDrag}
          debugln(['TPkgManager.MoveFiles project main source has errors: ',Code.Filename]);
          {$ENDIF}
          LazarusIDE.DoJumpToCodeToolBossError;
          exit;
        end;
      end;
      Result:=true;
    end;
  end;

  function FileIsUnit(aFile: TIDEOwnedFile): boolean;
  begin
    if aFile is TPkgFile then
      Result:=TPkgFile(aFile).FileType in PkgFileRealUnitTypes
    else
      Result:=FilenameIsPascalSource(aFile.Filename);
  end;

  procedure AddResFile(ResFiles: TStringList; ResFile: string);
  var
    NewResFile: String;
  begin
    if not FilenameIsAbsolute(ResFile) then exit;
    if AllChangedFilenames.Contains(ResFile) then exit;
    if IndexInRecentList(ResFiles,rltFile,ResFile)>=0 then exit;
    if not FileExistsCached(ResFile) then exit;
    ResFiles.Add(ResFile);
    NewResFile:=TargetDirectory+ExtractFilename(ResFile);
    AllChangedFilenames[ResFile]:=NewResFile;
  end;

  function CollectFiles(out MoveFileCount: integer): boolean;
  var
    i: Integer;
    aFile: TIDEOwnedFile;
    OldFilename: String;
    NewFilename: String;
    ResFileList: TStringList;
    UnitResArr: TUnitResourcefileFormatArr;
    j: Integer;
    aFilename: String;
    S2PItem: PStringToPointerTreeItem;
  begin
    Result:=false;
    MoveFileCount:=0;
    for i:=0 to IDEFiles.Count-1 do begin
      aFile:=TIDEOwnedFile(IDEFiles[i]);
      OldFilename:=aFile.GetFullFilename;
      NewFilename:=TargetDirectory+ExtractFilename(OldFilename);

      // check if two copied/moved files will get the same new file name
      if NewFileToOldOwnedFile.Contains(NewFilename) then begin
        IDEMessageDialog(lisConflictDetected,
          Format(lisTwoMovedFilesWillHaveTheSameFileNameIn,
                 [#13, aFile.Filename, #13, TIDEOwnedFile(NewFileToOldOwnedFile[NewFilename]).Filename,
                  #13, TargetFilesEdit.FilesOwnerName]), mtError, [mbCancel]);
        exit;
      end;
      NewFileToOldOwnedFile[NewFilename]:=aFile;

      if CompareFilenames(NewFilename,OldFilename)<>0 then begin
        // file be copied/moved to another directory
        debugln(['Hint: (lazarus) CollectFiles Old="',OldFilename,'"']);
        debugln(['Hint: (lazarus) New="',NewFilename,'"']);
        inc(MoveFileCount);
        ChangedFilenames[OldFilename]:=NewFilename;
        AllChangedFilenames[OldFilename]:=NewFilename;

        // check resource file
        if FileIsUnit(aFile) then begin
          ResFileList:=TStringList.Create;
          UnitFilenameToResFileList[OldFilename]:=ResFileList;
          AddResFile(ResFileList,ChangeFileExt(OldFilename,'.lfm'));
          AddResFile(ResFileList,ChangeFileExt(OldFilename,'.dfm'));
          AddResFile(ResFileList,ChangeFileExt(OldFilename,'.lrs'));
          UnitResArr:=GetUnitResourcefileFormats;
          for j:=0 to length(UnitResArr)-1 do begin
            aFilename:=UnitResArr[j].GetUnitResourceFilename(OldFilename,true);
            AddResFile(ResFileList,aFilename);
          end;
        end;
      end;
    end;

    // remove res files, that are in IDEFiles
    for S2PItem in UnitFilenameToResFileList do begin
      OldFilename:=S2PItem^.Name;
      ResFileList:=TStringList(S2PItem^.Value);
      for i:=ResFileList.Count-1 downto 0 do begin
        if ChangedFilenames.Contains(ResFileList[i]) then
          ResFileList.Delete(i);
      end;
    end;

    Result:=true;
  end;

  function CheckNewFilesDoNotExist: boolean;
  var
    S2SItem: PStringToStringItem;
    OldFilename: String;
    NewFilename: String;
    ConflictFile: TIDEOwnedFile;
    CurName: String;
    ShortFilename: String;
    r: TModalResult;
    i: Integer;
    WarnUnitClash: Boolean;
    WarnNameClash: Boolean;
    Cnt: Integer;
    SrcEdit: TSourceEditor;
  begin
    Result:=false;
    WarnUnitClash:=true;
    WarnNameClash:=true;
    for S2SItem in AllChangedFilenames do begin
      OldFilename:=S2SItem^.Name;
      NewFilename:=S2SItem^.Value;
      if CompareFilenames(OldFilename,NewFilename)=0 then continue;

      // check file does not exist
      if FileExistsCached(NewFilename) then begin
        IDEMessageDialog(lisConflictDetected,
          Format(lisThereIsAlreadyAFileIn, [#13, NewFilename, #13,
            TargetFilesEdit.FilesOwnerName]), mtError, [mbCancel]);
        exit;
      end;

      // close source editor
      repeat
        SrcEdit:=SourceEditorManager.SourceEditorIntfWithFilename(NewFilename);
        if SrcEdit=nil then break;
        if LazarusIDE.DoCloseEditorFile(SrcEdit,[cfSaveFirst,
          cfCloseDependencies,cfSaveDependencies])<>mrOk then exit;
      until false;

      if (not SrcIsTarget) then begin
        // warn duplicate names
        if FilenameIsPascalUnit(NewFilename) then begin
          // warn duplicate unit name
          CurName:=ExtractFileNameOnly(NewFilename);
          if TargetPackage<>nil then
            ConflictFile:=TargetPackage.FindUnit(CurName,true)
          else if TargetProject<>nil then
            ConflictFile:=TargetProject.UnitWithUnitname(CurName)
          else
            ConflictFile:=nil;
          if (ConflictFile<>nil) and WarnUnitClash then begin
            ShortFilename:=NewFilename;
            ShortFilename:=CreateRelativePath(ShortFilename,TargetFilesEdit.FilesBaseDirectory);
            r:=IDEMessageDialog(lisDuplicateUnit,
              Format(lisThereIsAlreadyAUnitInOldNewYouHaveToMakeSur, [
                CurName, TargetFilesEdit.FilesOwnerName, #13,
                ConflictFile.GetShortFilename(true), #13,
                ShortFilename, #13, #13, #13])
              ,mtWarning,[mbYes,mbYesToAll,mbCancel]);
            case r of
            mrYes: ;
            mrYesToAll: WarnUnitClash:=false;
            else exit;
            end;
          end;
        end else begin
          // warn duplicate file
          if TargetPackage<>nil then
            Cnt:=TargetPackage.FileCount
          else if TargetProject<>nil then
            Cnt:=TargetProject.FileCount
          else
            Cnt:=0;
          for i:=0 to Cnt-1 do begin
            if not WarnNameClash then continue;
            if TargetPackage<>nil then
              ConflictFile:=TargetPackage.Files[i]
            else if TargetProject<>nil then
              ConflictFile:=TargetProject.Files[i]
            else
              continue;
            ShortFilename:=ExtractFilename(NewFilename);
            CurName:=ExtractFileName(ConflictFile.Filename);
            if (AnsiCompareText(CurName,ShortFilename)<>0)
            and (CompareFilenames(CurName,ShortFilename)<>0) then
              continue;
            // name clash on this or other platforms => warn
            ShortFilename:=NewFilename;
            ShortFilename:=CreateRelativePath(ShortFilename,TargetFilesEdit.FilesBaseDirectory);
            r:=IDEMessageDialog(lisDuplicateFileName,
              Format(lisThereIsAlreadyAFileInOldNewContinue, [CurName,
                TargetFilesEdit.FilesOwnerName, #13,
                ConflictFile.GetShortFilename(true), #13,
                ShortFilename, #13, #13])
              ,mtWarning,[mbYes,mbYesToAll,mbCancel]);
            case r of
            mrYes: ;
            mrYesToAll: WarnNameClash:=false;
            else exit;
            end;
          end;
        end;
      end;
    end;
    Result:=true;
  end;

  function CloseSrcEditors: boolean;
  var
    i: Integer;
    SrcEdit: TSourceEditorInterface;
  begin
    for i:=SourceEditorManagerIntf.SourceEditorCount-1 downto 0 do begin
      SrcEdit:=SourceEditorManagerIntf.SourceEditors[i];
      if not AllChangedFilenames.Contains(SrcEdit.FileName) then continue;
      if LazarusIDE.DoCloseEditorFile(SrcEdit,
        [cfSaveFirst,cfCloseDependencies])<>mrOk
      then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['CloseSrcEditors failed']);
        {$ENDIF}
        exit(false);
      end;
    end;
    Result:=true;
  end;

  function ClearOldCompiledFiles: boolean;
  var
    OutDir: String;
    CurFiles: TStrings;
    OutFilename: String;
    CurUnitName: String;
    S2SItem: PStringToStringItem;
    OldFilename: String;
    SeparateOutDir: Boolean;
    r: TModalResult;
  begin
    Result:=false;
    // => clear output directory of Src
    if SrcPackage<>nil then begin
      if PackageGraph.PreparePackageOutputDirectory(SrcPackage,true)<>mrOk then
      begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['TPkgManager.MoveFiles PreparePackageOutputDirectory failed']);
        {$ENDIF}
        exit;
      end;
    end else if SrcProject<>nil then begin
      OutDir:=ChompPathDelim(SrcProject.GetOutputDirectory);
      if not FilenameIsAbsolute(OutDir) then exit(true);
      CurFiles:=nil;
      try
        CodeToolBoss.DirectoryCachePool.GetListing(OutDir,CurFiles,false);
        for OutFilename in CurFiles do begin
          CurUnitName:=ExtractFilenameOnly(OutFilename);
          for S2SItem in ChangedFilenames do begin
            OldFilename:=S2SItem^.Name;
            if not FilenameIsPascalSource(OldFilename) then continue;
            if CompareTextCT(CurUnitName,ExtractFileNameOnly(OldFilename))<>0 then
              continue;
            // output filename and source have same unitname
            SeparateOutDir:=CompareFilenames(ChompPathDelim(ExtractFilePath(OldFilename)),OutDir)<>0;
            if FilenameExtIn(OutFilename,['ppu','o','ppl','rst','lrt'])
            or (SeparateOutDir and FilenameExtIn(OutFilename,['lrs','lfm'])) then
            begin
              // automatically created file found => delete
              r:=DeleteFileInteractive(OutFilename,[mbCancel,mbIgnore]);
              if not (r in [mrOk,mrIgnore]) then exit;
            end;
            break;
          end;
        end;
      finally
        CurFiles.Free;
      end;
    end;
    Result:=true;
  end;

  function CheckUsesSection(Tool: TCodeTool; UsesNode: TCodeTreeNode;
    NewUnitFilename: string): boolean;
  // true if no warnings
  var
    AnUnitName, AnUnitInFilename: string;
    OldUsedUnitCode: TCodeBuffer;
    OldUsedUnitFilename: string;
    Node: TCodeTreeNode;
    NamePos: Integer;
    OldCompiledUnitname: String;
    CodePos: TCodeXYPosition;
    Msg: String;
    PkgName: String;
    UsedPkg: TLazPackage;
    NewUsedUnitFilename: String;
  begin
    Result:=true;
    if UsesNode=nil then exit;
    // check that all used units are available in the target package
    Node:=UsesNode.FirstChild;
    while Node<>nil do begin
      // read unit name
      AnUnitInFilename:='';
      AnUnitName:=Tool.ExtractUsedUnitName(Node,@AnUnitInFilename);
      NamePos:=Node.StartPos;
      Node:=Node.NextBrother;
      if AnUnitName='' then continue;
      // find unit file
      OldUsedUnitCode:=Tool.FindUnitSource(AnUnitName,AnUnitInFilename,false,NamePos);
      if (OldUsedUnitCode=nil) then begin
        // no source found
        // => search for ppu
        OldCompiledUnitname:=AnUnitName+'.ppu';
        OldUsedUnitFilename:=Tool.DirectoryCache.FindCompiledUnitInCompletePath(
                                                     OldCompiledUnitname,false);
        if OldUsedUnitFilename='' then begin
          // unit not found
          // (that is ok, e.g. if the unit is used on another platform)
          // => only warn
          Msg:=Format(lisUnitNotFound, [AnUnitName]);
          if not Tool.CleanPosToCaret(NamePos,CodePos) then continue;
          Result:=false;
          IDEMessagesWindow.AddCustomMessage(mluWarning,Msg,
            CodePos.Code.Filename, CodePos.Y, CodePos.X, lisMoveFiles);
          continue;
        end;
      end else begin
        // unit found
        OldUsedUnitFilename:=OldUsedUnitCode.Filename;
        if AllChangedFilenames.Contains(OldUsedUnitFilename) then begin
          // this unit will be moved too => ok
          continue;
        end;
      end;
      // OldUsedUnitFilename is now either a .pas/pp/p or .ppu file

      // search unit in new position
      NewUsedUnitFilename:=CodeToolBoss.DirectoryCachePool.FindUnitSourceInCompletePath(
        ExtractFilePath(NewUnitFilename),AnUnitName,AnUnitInFilename);
      if (NewUsedUnitFilename='') and (AnUnitInFilename='') then
        NewUsedUnitFilename:=CodeToolBoss.DirectoryCachePool.FindCompiledUnitInCompletePath(
          ExtractFilePath(NewUnitFilename),AnUnitName);
      if CompareFilenames(OldUsedUnitFilename,NewUsedUnitFilename)=0 then
        continue;
      // not found or a different unit found

      if not Tool.CleanPosToCaret(NamePos,CodePos) then continue;

      // find package of used unit
      PkgName:='';
      UsedPkg:=TLazPackage(SrcDirToPkg[ExtractFilePath(OldUsedUnitFilename)]);
      if UsedPkg<>nil then
        PkgName:=UsedPkg.Name;

      if NewUsedUnitFilename='' then begin
        // at the new position the unit cannot be found
        if PkgName='' then begin
          Msg:=Format(lisUnitNotFoundAtNewPosition, [AnUnitName, NewUnitFilename]);
        end else begin
          Msg:=Format(lisUnitRequiresPackage, [AnUnitName, PkgName]);
        end;
      end else begin
        // a different unit will be used
        Msg:=Format(lisDifferentUnitFoundAtNewPosition, [AnUnitName,
          NewUnitFilename]);
      end;

      Result:=false;
      IDEMessagesWindow.AddCustomMessage(mluWarning,Msg,
        CodePos.Code.Filename, CodePos.Y, CodePos.X, lisMoveFiles);
    end;
  end;

  function CheckUsesSections: boolean;
  // check that all used units are available in the target package
  var
    i: Integer;
    aFile: TIDEOwnedFile;
    OldFilename: String;
    Code: TCodeBuffer;
    Tool: TCodeTool;
    NewFilename: String;
  begin
    if SrcIsTarget then
      exit(true);
    // moving files to another package/project
    if (SrcPackage<>nil) and (PackageGraph.FindDependencyRecursively(
      TargetFilesEdit.FirstRequiredDependency,SrcPackage)<>nil)
    then begin
      // units are moved to higher level package/project
      // => no check needed
      exit(true);
    end;

    // check that all used units are available in the target
    Result:=true;
    for i:=0 to IDEFiles.Count-1 do begin
      aFile:=TIDEOwnedFile(IDEFiles[i]);
      if not FileIsUnit(aFile) then continue;
      OldFilename:=aFile.GetFullFilename;
      NewFilename:=ChangedFilenames[OldFilename];
      if CompareFilenames(ExtractFilePath(OldFilename),ExtractFilePath(NewFilename))=0
      then continue;
      if LoadCodeBuffer(Code,OldFilename,[lbfUpdateFromDisk,lbfCheckIfText],false)<>mrOk
      then exit;
      CodeToolBoss.Explore(Code,Tool,false);
      if not CheckUsesSection(Tool,Tool.FindMainUsesNode,NewFilename) then
        Result:=false;
      if not CheckUsesSection(Tool,Tool.FindImplementationUsesNode,NewFilename) then
        Result:=false;
    end;
    if not Result then begin
      if IDEMessageDialog(lisCCOWarningCaption,
        lisMovingTheseUnitsWillBreakTheirUsesSectionsSeeMessa,
        mtWarning,[mbIgnore,mbCancel])<>mrIgnore
      then
        exit;
      Result:=true;
    end;
  end;

  function ExtendSearchPaths: boolean;
  var
    i: Integer;
    aFile: TIDEOwnedFile;
    NewDir: String;
    NewUnitPaths: String;
    NewIncPaths: String;
    OldFilename: String;
    FileType: TPkgFileType;
  begin
    NewUnitPaths:='';
    NewIncPaths:='';
    for i:=0 to IDEFiles.Count-1 do begin
      aFile:=TIDEOwnedFile(IDEFiles[i]);
      OldFilename:=aFile.GetFullFilename;
      NewDir:=ChompPathDelim(ExtractFilePath(ChangedFilenames[OldFilename]));
      if aFile is TPkgFile then
        FileType:=TPkgFile(aFile).FileType
      else
        FileType:=FileNameToPkgFileType(OldFilename);
      case FileType of
      pftUnit,pftMainUnit:
        MergeSearchPaths(NewUnitPaths,NewDir);
      pftInclude:
        MergeSearchPaths(NewIncPaths,NewDir);
      end;
    end;
    // unit paths
    if (NewUnitPaths<>'') and not TargetFilesEdit.ExtendUnitSearchPath(NewUnitPaths)
    then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['ExtendSearchPaths ExtendUnitSearchPath failed: NewUnitPaths="',NewUnitPaths,'"']);
      {$ENDIF}
      exit(false);
    end;
    // include paths
    if (NewIncPaths<>'') and not TargetFilesEdit.ExtendIncSearchPath(NewIncPaths)
    then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['ExtendSearchPaths ExtendIncSearchPath failed: NewIncPaths="',NewIncPaths,'"']);
      {$ENDIF}
      exit(false);
    end;
    Result:=true;
  end;

  function MoveOrCopyFile(OldFilename: string;
    MovedFiles: TFilenameToPointerTree): boolean;
  var
    NewFilename: String;
    r: TModalResult;
    OldPkgFile: TPkgFile;
    NewPkgFile: TPkgFile;
    NewFileType: TPkgFileType;
    NewUnitName: String;
    NewCompPrio: TComponentPriority;
    NewResourceBaseClass: TPFComponentBaseClass;
    NewHasRegisterProc: Boolean;
    NewAddToUses: Boolean;
    OldProjFile: TUnitInfo;
    Code: TCodeBuffer;
    NewProjFile: TUnitInfo;
  begin
    Result:=false;
    // check if needed
    NewFilename:=TargetDirectory+ExtractFilename(OldFilename);
    if CompareFilenames(NewFilename,OldFilename)=0 then
      exit(true);
    // check if already moved
    if MovedFiles.Contains(OldFilename) then
      exit(true);
    MovedFiles[OldFilename]:=Self;
    // copy or move file
    if FileExistsUTF8(OldFilename) then begin
      if DeleteOld then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['MoveOrCopyFile rename "',OldFilename,'" to "',NewFilename,'"']);
        {$ENDIF}
        r:=RenameFileWithErrorDialogs(OldFilename,NewFilename,[mbAbort,mbIgnore]);
      end else begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['MoveOrCopyFile copy "',OldFilename,'" to "',NewFilename,'"']);
        {$ENDIF}
        r:=CopyFileWithErrorDialogs(OldFilename,NewFilename,[mbAbort,mbIgnore]);
      end;
      if not (r in [mrIgnore,mrOK]) then begin
        debugln(['Error: (lazarus) MoveOrCopyFile: rename/copy failed: "',OldFilename,'" to "',NewFilename,'"']);
        exit;
      end;
    end else begin
      if IDEMessageDialog(lisCCOWarningCaption,
        Format(lisFileNotFound5, [#13, OldFilename]), mtWarning, [mbIgnore,
          mbCancel])<>mrIgnore
      then
        exit;
    end;

    OldPkgFile:=nil;
    OldProjFile:=nil;
    if SrcPackage<>nil then begin
      OldPkgFile:=SrcPackage.FindPkgFile(OldFilename,true,false);
      if OldPkgFile=nil then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['MoveOrCopyFile old file not in lpk: "',OldFilename,'" pkg=',SrcPackage.Name]);
        {$ENDIF}
        // this is a resource file
        // => do not create an entry in the target
        exit(true);
      end;
    end else if SrcProject<>nil then begin
      OldProjFile:=SrcProject.UnitInfoWithFilename(OldFilename,[pfsfOnlyProjectFiles]);
      if OldProjFile=nil then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['MoveOrCopyFile old file not in lpi: "',OldFilename,'"']);
        {$ENDIF}
        // this is a resource file
        // => do not create an entry in the target
        exit(true);
      end;
    end else begin
      raise Exception.Create('implement me');
    end;

    if OldPkgFile<>nil then begin
      NewUnitName:=OldPkgFile.Unit_Name;
      NewFileType:=OldPkgFile.FileType;
      if NewFileType=pftMainUnit then NewFileType:=pftUnit;
      NewCompPrio:=OldPkgFile.ComponentPriority;
      NewResourceBaseClass:=OldPkgFile.ResourceBaseClass;
      NewHasRegisterProc:=OldPkgFile.HasRegisterProc;
      NewAddToUses:=OldPkgFile.AddToUsesPkgSection;
    end else begin
      NewUnitName:=OldProjFile.Unit_Name;
      NewFileType:=FileNameToPkgFileType(OldFilename);
      NewCompPrio:=ComponentPriorityNormal;
      NewResourceBaseClass:=OldProjFile.ResourceBaseClass;
      NewHasRegisterProc:=false;
      NewAddToUses:=true;
      if NewFileType=pftUnit then begin
        Code:=CodeToolBoss.LoadFile(OldFilename,true,false);
        if (Code<>nil) and (TargetPackage<>nil) then
          NewHasRegisterProc:=CodeToolBoss.HasInterfaceRegisterProc(Code);
      end;
    end;

    NewPkgFile:=nil;
    NewProjFile:=nil;
    if TargetPackage<>nil then begin
      // create new TPkgFile
      NewPkgFile:=TargetPackage.FindPkgFile(NewFilename,true,false);
      if NewPkgFile=nil then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['MoveOrCopyFile create new "',NewFilename,'" pkg=',TargetPackage.Name]);
        {$ENDIF}
        NewPkgFile:=TargetPackage.AddFile(NewFilename,NewUnitName,
          NewFileType,[],NewCompPrio.Category);
      end else begin
        NewPkgFile.Unit_Name:=NewUnitName;
        NewFileType:=NewFileType;
        NewPkgFile.FileType:=NewFileType;
      end;
      NewPkgFile.ComponentPriority:=NewCompPrio;
      NewPkgFile.ResourceBaseClass:=NewResourceBaseClass;
      NewPkgFile.HasRegisterProc:=NewHasRegisterProc;
      if NewAddToUses
      and (TargetPackage.FindUsedUnit(ExtractFileNameOnly(NewFilename),NewPkgFile)<>nil)
      then begin
        // another unit with this name is already used
        NewPkgFile.AddToUsesPkgSection:=false;
      end else begin
        NewPkgFile.AddToUsesPkgSection:=NewAddToUses;
      end;
    end else if TargetProject<>nil then begin
      // create new TUnitInfo

      NewProjFile:=TargetProject.UnitInfoWithFilename(NewFilename);
      if NewProjFile=nil then begin
        NewProjFile:=TUnitInfo.Create(nil);
        NewProjFile.Filename:=NewFilename;
        TargetProject.AddFile(NewProjFile,false);
      end;
      NewProjFile.IsPartOfProject:=true;
      NewProjFile.ResourceBaseClass:=NewResourceBaseClass;
      if OldProjFile<>nil then begin
        NewProjFile.HasResources:=OldProjFile.HasResources;
        NewProjFile.ComponentName:=OldProjFile.ComponentName;
        NewProjFile.ComponentResourceName:=OldProjFile.ComponentResourceName;
        NewProjFile.BuildFileIfActive:=OldProjFile.BuildFileIfActive;
        NewProjFile.RunFileIfActive:=OldProjFile.RunFileIfActive;
        NewProjFile.DefaultSyntaxHighlighter:=OldProjFile.DefaultSyntaxHighlighter;
        NewProjFile.DisableI18NForLFM:=OldProjFile.DisableI18NForLFM;
        NewProjFile.CustomDefaultHighlighter:=OldProjFile.CustomDefaultHighlighter;
      end;
      if (not SrcIsTarget)
      and (pfMainUnitHasUsesSectionForAllUnits in TargetProject.Flags) then
      begin
        CodeToolBoss.AddUnitToMainUsesSection(
                       TargetProject.MainUnitInfo.Source,NewUnitName,'');
        CodeToolBoss.SourceChangeCache.Apply;
        TargetProject.MainUnitInfo.Modified:=true;
      end;
    end else begin
      raise Exception.Create('implement me');
    end;

    // delete old
    if DeleteOld then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['MoveOrCopyFile delete "',OldFilename,'" from=',SrcFilesEdit.FilesOwnerName]);
      {$ENDIF}
      if OldPkgFile<>nil then begin
        SrcPackage.DeleteFile(OldPkgFile);
      end else if OldProjFile<>nil then begin
        OldProjFile.IsPartOfProject:=false;
        if (not SrcIsTarget)
        and (pfMainUnitHasUsesSectionForAllUnits in SrcProject.Flags) then
        begin
          CodeToolBoss.RemoveUnitFromAllUsesSections(
                         SrcProject.MainUnitInfo.Source,NewUnitName);
          CodeToolBoss.SourceChangeCache.Apply;
          SrcProject.MainUnitInfo.Modified:=true;
        end;
      end else begin
        raise Exception.Create('implement me');
      end;
    end;
    TargetFilesEdit.UpdateAll;
    SrcFilesEdit.UpdateAll;
    Result:=true;
  end;

  function MoveOrCopyFiles: boolean;
  var
    i: Integer;
    OldFilename: String;
    MovedFiles: TFilenameToPointerTree;
    ResFileList: TStringList;
    j: Integer;
  begin
    Result:=false;
    TargetFilesEdit.BeginUpdate;
    SrcFilesEdit.BeginUpdate;
    MovedFiles:=TFilenameToPointerTree.Create(false);
    try
      for i:=0 to IDEFiles.Count-1 do begin
        OldFilename:=TIDEOwnedFile(IDEFiles[i]).GetFullFilename;
        if not MoveOrCopyFile(OldFilename,MovedFiles) then exit;
        ResFileList:=TStringList(UnitFilenameToResFileList[OldFilename]);
        if ResFileList=nil then continue;
        for j:=0 to ResFileList.Count-1 do
          if not MoveOrCopyFile(ResFileList[j],MovedFiles) then exit;
      end;
    finally
      MovedFiles.Free;
      SrcFilesEdit.EndUpdate;
      TargetFilesEdit.EndUpdate;
    end;
    Result:=true;
  end;

var
  MoveFileCount: Integer;
  MsgResult: TModalResult;
begin
  Result:=false;

  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.MoveFiles Self=',TargetFilesEdit.FilesOwnerName,' Src=',SrcFilesEdit.FilesOwnerName,' Dir="',TargetDirectory,'" FileCount=',IDEFiles.Count]);
  {$ENDIF}
  if not GetPkgProj(SrcFilesEdit,SrcPackage,SrcProject) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.MoveFiles invalid src=',DbgSName(SrcFilesEdit.FilesOwner)]);
    {$ENDIF}
    exit;
  end;
  if not GetPkgProj(TargetFilesEdit,TargetPackage,TargetProject) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.MoveFiles invalid target=',DbgSName(TargetFilesEdit.FilesOwner)]);
    {$ENDIF}
    exit;
  end;

  DeleteNonExistingPkgFiles;
  if IDEFiles.Count=0 then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.MoveFiles PkgFiles.Count=0']);
    {$ENDIF}
    exit(true);
  end;

  if TargetFilesEdit.FilesOwnerReadOnly then begin
    IDEMessageDialog(lisTargetIsReadOnly,
      Format(lisTheTargetIsNotWritable, [TargetFilesEdit.FilesOwnerName]),
        mtError, [mbCancel]);
    exit;
  end;

  if not FilenameIsAbsolute(TargetDirectory) then begin
    {$IFDEF VerbosePkgEditDrag}
    debugln(['TPkgManager.MoveFiles invalid target dir=',TargetDirectory]);
    {$ENDIF}
    exit;
  end;
  TargetDirectory:=AppendPathDelim(TargetDirectory);

  // check TargetDirectory
  if CheckDirectoryIsWritable(TargetDirectory)<>mrOk then begin
    debugln(['Warning: (lazarus) TPkgManager.MoveFiles not writable TargetDirectory=',TargetDirectory]);
    exit;
  end;

  SrcIsTarget:=SrcFilesEdit.FilesOwner=TargetFilesEdit.FilesOwner;

  IDEMessagesWindow.Clear;

  NewFileToOldOwnedFile:=TFilenameToPointerTree.Create(false);
  ChangedFilenames:=TFilenameToStringTree.Create(false);
  AllChangedFilenames:=TFilenameToStringTree.Create(false);
  UnitFilenameToResFileList:=TFilenameToPointerTree.Create(false);
  UnitFilenameToResFileList.FreeValues:=true;
  SrcDirToPkg:=nil;
  try
    // collect all affected files including resource files
    if not CollectFiles(MoveFileCount) then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles CollectFiles failed']);
      {$ENDIF}
      exit;
    end;

    // check if new position is free
    if not CheckNewFilesDoNotExist then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles CheckNewFilesDoNotExist failed']);
      {$ENDIF}
      exit;
    end;

    if (MoveFileCount=0) and (SrcIsTarget) then begin
      // no move, only change order in package
      // ToDo: check this case in ItemsTreeViewDragDrop
      ShowMessage('Changing order via drag and drop is not implemented.');
      exit;
    end;

    // ask for confirmation
    if IDEFiles.Count=MoveFileCount then begin
      MsgResult:=IDEQuestionDialog(lisMoveOrCopyFiles,
        Format(lisMoveOrCopyFileSFromToTheDirectoryOfPackage, [
          IntToStr(MoveFileCount),
          SrcFilesEdit.FilesOwnerName, #13,
          TargetDirectory, #13,
          TargetFilesEdit.FilesOwnerName]),
        mtConfirmation, [100, lisMove, 101, lisCopy, mrCancel]);
      case MsgResult of
      100: DeleteOld:=true;
      101: DeleteOld:=false;
      else exit;
      end;
    end else begin
      if IDEMessageDialog(lisMoveFiles2,
        Format(lisMoveFileSFromToTheDirectoryOf, [
          IntToStr(MoveFileCount),
          SrcFilesEdit.FilesOwnerName, #13,
          TargetDirectory, #13,
          TargetFilesEdit.FilesOwnerName]),
        mtConfirmation,[mbOk,mbCancel])<>mrOK
      then exit;
      DeleteOld:=true;
    end;

    // fetch used packages
    SrcDirToPkg:=PackageGraph.GetMapSourceDirectoryToPackage;

    // check uses sections
    if not CheckUsesSections then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles CheckUsesSections failed']);
      {$ENDIF}
      exit;
    end;

    if DeleteOld then begin
      // close files and res files in source editor
      if not CloseSrcEditors then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['TPkgManager.MoveFiles CloseSrcEditors failed']);
        {$ENDIF}
        exit;
      end;
    end;

    if (not SrcIsTarget) then begin
      // files will be moved to another package/project
      if not ClearOldCompiledFiles then begin
        {$IFDEF VerbosePkgEditDrag}
        debugln(['TPkgManager.MoveFiles ClearOldCompiledFiles failed']);
        {$ENDIF}
        exit;
      end;
    end;

    // extend unit/include path of LazPackage
    if not ExtendSearchPaths then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles ExtendSearchPaths failed']);
      {$ENDIF}
      exit;
    end;

    // move/copy files
    if not MoveOrCopyFiles then begin
      {$IFDEF VerbosePkgEditDrag}
      debugln(['TPkgManager.MoveFiles MoveOrCopyFiles failed']);
      {$ENDIF}
      exit;
    end;

    Result:=true;
  finally
    SrcDirToPkg.Free;
    UnitFilenameToResFileList.Free;
    AllChangedFilenames.Free;
    ChangedFilenames.Free;
    NewFileToOldOwnedFile.Free;
  end;
end;

function TPkgManager.CopyMoveFiles(Sender: TObject): boolean;
var
  SelDirDlg: TSelectDirectoryDialog;
  FilesEdit: IFilesEditorInterface;
  TargetDir: String;
begin
  Result:=false;
  if Sender is TPackageEditorForm then
    FilesEdit:=TPackageEditorForm(Sender)
  else if Sender is TProjectInspectorForm then
    FilesEdit:=TProjectInspectorForm(Sender)
  else begin
    debugln(['Error: (lazarus) TPkgManager.CopyMoveFiles wrong Sender: ',DbgSName(Sender)]);
    exit;
  end;
  SelDirDlg:=TSelectDirectoryDialog.Create(nil);
  try
    SelDirDlg.InitialDir:=FilesEdit.FilesBaseDirectory;
    SelDirDlg.Title:=lisSelectTargetDirectory;
    SelDirDlg.Options:=SelDirDlg.Options+[ofPathMustExist,ofFileMustExist];
    if not SelDirDlg.Execute then exit;
    TargetDir:=CleanAndExpandDirectory(SelDirDlg.FileName);
    Result:=MoveFiles(FilesEdit,FilesEdit,TargetDir);
  finally
    SelDirDlg.Free;
  end;
end;

constructor TPkgManager.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  OnGetDependencyOwnerDescription:=@GetDependencyOwnerDescription;
  OnGetDependencyOwnerDirectory:=@GetDependencyOwnerDirectory;
  OnPackageFileLoaded:=@PackageFileLoaded;

  // package links
  LazPackageLinks:=TLazPackageLinks.Create;
  PkgLinks:=LazPackageLinks;
  LazPackageLinks.UpdateAll;

  // package graph
  PackageGraph:=TLazPackageGraph.Create;
  PackageGraphInterface:=PackageGraph;
  PackageGraph.OnAddPackage:=@PackageGraphAddPackage;
  PackageGraph.OnBeforeCompilePackages:=@DoBeforeCompilePackages;
  PackageGraph.OnBeginUpdate:=@PackageGraphBeginUpdate;
  PackageGraph.OnChangePackageName:=@PackageGraphChangePackageName;
  PackageGraph.OnCheckInterPkgFiles:=@PackageGraphCheckInterPkgFiles;
  PackageGraph.OnDeleteAmbiguousFiles:=@BuildBoss.DeleteAmbiguousFiles;
  PackageGraph.OnDeletePackage:=@PackageGraphDeletePackage;
  PackageGraph.OnDependencyModified:=@PackageGraphDependencyModified;
  PackageGraph.OnEndUpdate:=@PackageGraphEndUpdate;
  PackageGraph.OnTranslatePackage:=@DoTranslatePackage;
  PackageGraph.OnUninstallPackage:=@DoUninstallPackage;
  PackageGraph.OnSrcEditFileIsModified:=@PackageGraphSrcEditFileIsModified;
  PackageGraph.OnShowMessage:=@ShowMessageHandler;

  // package editors
  PackageEditors:=TPackageEditors.Create;
  PackageEditors.OnAddToProject:=@PackageEditorAddToProject;
  PackageEditors.OnAfterWritePackage:=@AfterWritePackage;
  PackageEditors.OnBeforeReadPackage:=@BeforeReadPackage;
  PackageEditors.OnCompilePackage:=@PackageEditorCompilePackage;
  PackageEditors.OnCopyMoveFiles:=@PackageEditorCopyMoveFiles;
  PackageEditors.OnCreateFpmakeFile:=@PackageEditorCreateFpmakeFile;
  PackageEditors.OnCreateMakefile:=@PackageEditorCreateMakefile;
  PackageEditors.OnCreateNewFile:=@PackageEditorCreateFile;
  PackageEditors.OnDeleteAmbiguousFiles:=@PackageEditorDeleteAmbiguousFiles;
  PackageEditors.OnDragDropTreeView:=@PackageEditorDragDropTreeView;
  PackageEditors.OnDragOverTreeView:=@PackageEditorDragOverTreeView;
  PackageEditors.OnShowFindInFiles:=@PackageEditorFindInFiles;
  PackageEditors.OnFreeEditor:=@PackageEditorFreeEditor;
  PackageEditors.OnGetIDEFileInfo:=@MainIDE.GetIDEFileState;
  PackageEditors.OnInstallPackage:=@PackageEditorInstallPackage;
  PackageEditors.OnOpenFile:=@MainIDE.DoOpenMacroFile;
  PackageEditors.OnOpenPackage:=@PackageEditorOpenPackage;
  PackageEditors.OnOpenPkgFile:=@PackageEditorOpenPkgFile;
  PackageEditors.OnPublishPackage:=@PackageEditorPublishPackage;
  PackageEditors.OnRevertPackage:=@PackageEditorRevertPackage;
  PackageEditors.OnSavePackage:=@PackageEditorSavePackage;
  PackageEditors.OnUninstallPackage:=@PackageEditorUninstallPackage;
  PackageEditors.OnViewPackageSource:=@PackageEditorViewPkgSource;

  // package macros
  CodeToolBoss.DefineTree.MacroFunctions.AddExtended(
    'PKGDIR',nil,@PackageGraph.MacroFunctionCTPkgDir);
  CodeToolBoss.DefineTree.MacroFunctions.AddExtended(
    'PKGSRCPATH',nil,@PackageGraph.MacroFunctionCTPkgSrcPath);
  CodeToolBoss.DefineTree.MacroFunctions.AddExtended(
    'PKGUNITPATH',nil,@PackageGraph.MacroFunctionCTPkgUnitPath);
  CodeToolBoss.DefineTree.MacroFunctions.AddExtended(
    'PKGINCPATH',nil,@PackageGraph.MacroFunctionCTPkgIncPath);
  CodeToolBoss.DefineTree.MacroFunctions.AddExtended(
    'PKGNAME',nil,@PackageGraph.MacroFunctionCTPkgName);
  CodeToolBoss.DefineTree.MacroFunctions.AddExtended(
    'PKGOUTDIR',nil,@PackageGraph.MacroFunctionCTPkgOutDir);

  LazPackageDescriptors:=TLazPackageDescriptors.Create;
  LazPackageDescriptors.AddDefaultPackageDescriptors;

  // idle handler
  Application.AddOnIdleHandler(@ApplicationIdleHandler,true);
end;

destructor TPkgManager.Destroy;
begin
  FreeThenNil(LazPackageDescriptors);
  PackageGraph.FreeAutoInstallDependencies;
  FreeThenNil(PackageGraphExplorer);
  FreeThenNil(PackageEditors);
  FreeThenNil(PackageGraph);
  FreeThenNil(LazPackageLinks);
  FreeThenNil(PackageDependencies);
  inherited Destroy;
end;

procedure TPkgManager.ConnectMainBarEvents;
begin
  with MainIDEBar do begin
    itmPkgNewPackage.OnClick:=@MainIDEitmPkgNewPackageClick;
    itmPkgOpenLoadedPackage.OnClick:=@MainIDEitmPkgOpenLoadedPackageClicked;
    itmPkgOpenPackageFile.OnClick:=@MainIDEitmPkgOpenPackageFileClick;
    itmPkgOpenPackageOfCurUnit.OnClick:=@MainIDEitmPkgOpenPackageOfCurUnitClicked;
    itmPkgAddCurFileToPkg.OnClick:=@MainIDEitmPkgAddCurFileToPkgClick;
    itmPkgAddNewComponentToPkg.OnClick:=@MainIDEitmPkgNewComponentClick;
    itmPkgPkgGraph.OnClick:=@MainIDEitmPkgPkgGraphClick;
    itmPkgPackageLinks.OnClick:=@MainIDEitmPackageLinksClicked;
    itmPkgEditInstallPkgs.OnClick:=@MainIDEitmPkgEditInstallPkgsClick;
  end;
  
  SetRecentPackagesMenu;

  IDEWindowCreators.Add(NonModalIDEWindowNames[nmiwPkgGraphExplorer],
                        nil,@CreateIDEWindow,'250','200','','');
  IDEWindowCreators.Add(PackageEditorWindowPrefix,
                        nil,@CreateIDEWindow,'250','200','','');
  RegisterStandardPackageEditorMenuItems;
end;

procedure TPkgManager.ConnectSourceNotebookEvents;
begin

end;

procedure TPkgManager.SetupMainBarShortCuts;
begin

end;

procedure TPkgManager.SetRecentPackagesMenu;
begin
  MainIDE.SetRecentSubMenu(itmPkgOpenRecent,
                           EnvironmentOptions.RecentPackageFiles,
                           @MainIDEitmOpenRecentPackageClicked);
end;

procedure TPkgManager.AddToMenuRecentPackages(const Filename: string);
begin
  EnvironmentOptions.AddToRecentPackageFiles(Filename);
  SetRecentPackagesMenu;
  MainIDE.SaveEnvironment;
end;

procedure TPkgManager.SaveSettings;
begin

end;

function TPkgManager.GetDefaultSaveDirectoryForFile(const Filename: string): string;
var
  APackage: TLazPackage;
  PkgFile: TPkgFile;
begin
  Result:='';
  if FilenameIsAbsolute(Filename) then
    exit(ExtractFilePath(Filename));
  PkgFile:=PackageGraph.FindFileInAllPackages(Filename,true,true);
  if PkgFile=nil then exit;
  APackage:=PkgFile.LazPackage;
  if APackage.IsVirtual or (not APackage.HasDirectory) then exit;
  Result:=APackage.Directory;
end;

procedure TPkgManager.LoadInstalledPackages;
begin
  IDEComponentPalette.BeginUpdate;
  try
    PackageGraph.LoadStaticBasePackages;
    LoadStaticCustomPackages;
    LoadAutoInstallPackages;
  finally
    IDEComponentPalette.EndUpdate;
  end;
end;

procedure TPkgManager.UnloadInstalledPackages;
var
  Dependency: TPkgDependency;
begin
  // unbind and free auto installed packages
  while PackageGraph.FirstInstallDependency<>nil do begin
    Dependency:=PackageGraph.FirstInstallDependency;
    Dependency.RequiredPackage:=nil;
    Dependency.RemoveFromList(PackageGraph.FirstInstallDependency,pddRequires);
    Dependency.Free;
  end;
end;

procedure TPkgManager.ProcessCommand(Command: word; var Handled: boolean);
begin
  Handled:=true;
  case Command of
  ecOpenPackage: MainIDEitmPkgOpenLoadedPackageClicked(Self);
  ecOpenPackageFile: MainIDEitmPkgOpenPackageFileClick(Self);
  ecOpenPackageOfCurUnit: MainIDEitmPkgOpenPackageOfCurUnitClicked(Self);
  ecAddCurFileToPkg: MainIDEitmPkgAddCurFileToPkgClick(Self);
  ecPackageGraph: MainIDEitmPkgPkgGraphClick(Self);
  ecEditInstallPkgs: MainIDEitmPkgEditInstallPkgsClick(Self);
  else
    Handled:=false;
  end;
end;

procedure TPkgManager.OnSourceEditorPopupMenu(const AddMenuItemProc: TAddMenuItemProc);
var
  APackage: TIDEPackage;
begin
  GetPackageOfCurrentSourceEditor(APackage);
  if APackage<>nil then
    AddMenuItemProc(Format(lisOpenPackage2, [APackage.Name]), true,
                    @OpenPackageForCurrentSrcEditFile);
end;

procedure TPkgManager.TranslateResourceStrings;
var
  PkgList: TFPList;
  i: Integer;
begin
  PkgList:=nil;
  PackageGraph.GetAllRequiredPackages(nil,
    PackageGraph.FirstInstallDependency,PkgList);
  if PkgList=nil then exit;
  for i:=0 to PkgList.Count-1 do
    if TObject(PkgList[i]) is TLazPackage then
      DoTranslatePackage(TLazPackage(PkgList[i]));
  PkgList.Free;
end;

procedure TPkgManager.DoTranslatePackage(APackage: TLazPackage);
var
  TranslatedUnits: TStringHashList;
  
  function UnitTranslated(const AnUnitName: string): boolean;
  begin
    Result:=(TranslatedUnits<>nil) and (TranslatedUnits.Find(AnUnitName)>=0);
  end;
  
  procedure TranslateUnit(const AFilename, AnUnitName: string);
  begin
    //DebugLn(['TranslateUnit AFilename="',AFilename,'" AnUnitName="',AnUnitName,'"']);
    if TranslatedUnits=nil then
      TranslatedUnits:=TStringHashList.Create(false);
    TranslatedUnits.Add(AnUnitName);
    TranslateUnitResourceStrings(AnUnitName,AFilename);
  end;

  procedure TranslateWithFileMask(APackage: TLazPackage;
    const Directory, Language: string);
  var
    CurUnitName: string;
    CurLang: string;
    Files: TStrings;
    Filename: String;
  begin
    if Language='' then exit;
    Files:=nil;
    try
      CodeToolBoss.DirectoryCachePool.GetListing(Directory,Files,false);
      for Filename in Files do begin
        if GetPOFilenameParts(Filename,CurUnitName,CurLang)
        and IsValidUnitName(CurUnitName)
        and (CurLang=Language)
        and (not UnitTranslated(CurUnitName))
        and (APackage.FindUnit(CurUnitName)<>nil)
        then begin
          TranslateUnit(AppendPathDelim(Directory)+Filename,CurUnitName);
        end;
      end;
    finally
      Files.Free;
    end;
  end;

var
  Directory: String;
  Lang: String;
  FallbackLang: String;
  Language: String;
begin
  //DebugLn(['TPkgManager.DoTranslatePackage ',APackage.Name,' from ', APackage.POOutputDirectory]);
  if (APackage.POOutputDirectory='') then exit;
  Directory:=AppendPathDelim(APackage.GetPOOutDirectory);

  Language:=EnvironmentOptions.LanguageID;
  if Language='' then begin
    Lang:=SystemLanguageID.LanguageID;
    FallbackLang:=SystemLanguageID.LanguageCode;
  end else begin
    Lang:=Language;
    FallbackLang:='';
  end;
  
  //DebugLn(['TPkgManager.DoTranslatePackage ', APackage.Name,' from ', APackage.POOutputDirectory,', Translated=',APackage.Translated,' Lang=',Lang]);
  if APackage.Translated=Lang then exit;
  APackage.Translated:=Lang;
  
  TranslatedUnits:=nil;
  try
    //DebugLn(['TPkgManager.DoTranslatePackage ',APackage.Name,' Directory=',Directory,' Lang=',Lang,' FallbackLang=',FallbackLang]);
    TranslateWithFileMask(APackage,Directory,Lang);
    if FallbackLang<>Lang then
      TranslateWithFileMask(APackage,Directory,FallbackLang);
  finally
    TranslatedUnits.Free;
    MainIDEInterface.PackageTranslated(APackage);
  end;
end;

function TPkgManager.AddPackageToGraph(APackage: TLazPackage): TModalResult;
var
  ConflictPkg: TLazPackage;
  Link: TPackageLink;
begin
  // check Package Name
  if not IsValidPkgName(APackage.Name) then begin
    Result:=IDEMessageDialog(lisPkgMangInvalidPackageName2,
      Format(lisPkgMangThePackageNameOfTheFileIsInvalid,
             [APackage.Name, LineEnding, APackage.Filename]),
      mtError,[mbCancel,mbAbort]);
    exit;
  end;

  // check if Package with same name is already loaded
  ConflictPkg:=PackageGraph.FindPackageWithName(APackage.Name,nil);
  if ConflictPkg<>nil then begin
    if not PackageGraph.PackageCanBeReplaced(ConflictPkg,APackage) then begin
      Result:=IDEMessageDialog(lisPkgMangPackageConflicts,
        Format(lisPkgMangThereIsAlreadyAPackageLoadedFromFile,
          [ConflictPkg.IDAsString, LineEnding, ConflictPkg.Filename, LineEnding, LineEnding]),
        mtError,[mbCancel,mbAbort]);
      exit;
    end;
    
    if ConflictPkg.Modified and (not ConflictPkg.ReadOnly) then begin
      Result:=IDEMessageDialog(lisPkgMangSavePackage,
        Format(lisPkgMangLoadingPackageWillReplacePackage, [
          APackage.IDAsString, ConflictPkg.IDAsString, LineEnding,
          ConflictPkg.Filename, LineEnding, LineEnding+LineEnding, ConflictPkg.Filename]),
        mtConfirmation,[mbYes,mbNo,mbCancel,mbAbort]);
      if Result=mrNo then Result:=mrOk;
      if Result=mrYes then begin
        Result:=DoSavePackage(ConflictPkg,[]);
      end;
      if Result<>mrOk then exit;
    end;
    
    // replace package
    PackageGraph.ReplacePackage(ConflictPkg,APackage);
  end else begin
    // add to graph
    PackageGraph.AddPackage(APackage);
  end;

  // save package file links
  //DebugLn(['TPkgManager.AddPackageToGraph ',APackage.Name]);
  Link:=LazPackageLinks.AddUserLink(APackage);
  if Link<>nil then
  begin
    //debugln(['Hint: (lazarus) TPkgManager.AddPackageToGraph LinkLastUsed=',DateToCfgStr(Link.LastUsed,DateTimeAsCfgStrFormat),' ',dbgs(Link.Origin)]);
    LazPackageLinks.SaveUserLinks;
  end;

  Result:=mrOk;
end;

function TPkgManager.ResolveBrokenDependenciesOnline(ABrokenDependencies: TFPList): TModalResult;
var
  Dependency: TPkgDependency;
  I: Integer;
  PkgLinks: TList;
  PkgsStr: String;
  PackageLink: TPackageLink;
begin
  Result := mrCancel;
  PkgLinks := TList.Create;
  try
    PkgsStr := '';
    for I := 0 to ABrokenDependencies.Count - 1  do begin
      Dependency := TPkgDependency(ABrokenDependencies[i]);
      PackageLink := LazPackageLinks.FindLinkWithPkgName(Dependency.AsString);
      if (PackageLink <> nil) {and (PackageLink.Origin = ploOnline)} then begin
        PkgLinks.Add(PackageLink);
        Dependency.LoadPackageResult:=lprAvailableOnline;
        if PkgsStr = '' then
          PkgsStr := '"' + PackageLink.Name + '"'
        else
          PkgsStr := PkgsStr + ', ' + '"' + PackageLink.Name + '"';
      end;
    end;
    if PkgLinks.Count > 0 then begin
      if IDEMessageDialog(lisNotInstalledPackages, Format(lisInstallPackagesMsg,[PkgsStr]),
                          mtConfirmation, [mbYes, mbNo]) = mrYes then
        Result := OPMInterface.InstallPackages(PkgLinks);
    end;
  finally
    PkgLinks.Free;
  end;
end;

function TPkgManager.OpenProjectDependencies(AProject: TProject;
  ReportMissing: boolean): TModalResult;
var
  BrokenDependencies: TFPList;
  OpmRes: TModalResult;
  Dependency: TPkgDependency;
  IgnorePackage: TLazPackage;
begin
  Result := mrOk;
  OpmRes := mrOk;

  Dependency:=AProject.FirstRequiredDependency;
  while Dependency<>nil do begin
    IgnorePackage:=PackageGraph.FindPackageWithName(Dependency.PackageName,nil);
    if (IgnorePackage<>nil) and Dependency.IsCompatible(IgnorePackage) then
      IgnorePackage:=nil;
    PackageGraph.OpenDependency(Dependency,false,IgnorePackage);
    Dependency:=Dependency.NextRequiresDependency;
  end;

  if ReportMissing then begin
    BrokenDependencies := PackageGraph.FindAllBrokenDependencies(nil,
                                               AProject.FirstRequiredDependency);
    if Assigned(BrokenDependencies) then
    begin
      if Assigned(OPMInterface) then
      begin
        OpmRes := ResolveBrokenDependenciesOnline(BrokenDependencies);
        FreeAndNil(BrokenDependencies);
        BrokenDependencies := PackageGraph.FindAllBrokenDependencies(nil,
                                               AProject.FirstRequiredDependency);
      end;
      Result := ShowBrokenDependenciesReport(BrokenDependencies);
      BrokenDependencies.Free;
    end;
  end;
  LazPackageLinks.SaveUserLinks;
  if OpmRes = mrRetry then    // mrRetry means the IDE must be rebuilt.
    MainIDEInterface.DoBuildLazarus([])
end;

function TPkgManager.AddProjectDependency(AProject: TProject;
  APackage: TLazPackage; OnlyTestIfPossible: boolean): TModalResult;
var
  NewDependency: TPkgDependency;
  ProvidingAPackage: TLazPackage;
  ConflictDependency: TPkgDependency;
begin
  Result:=mrCancel;

  // check if there is a dependency, that requires another version
  ConflictDependency:=PackageGraph.FindConflictRecursively(
    AProject.FirstRequiredDependency,APackage);
  if ConflictDependency<>nil then begin
    DebugLn(['Error: (lazarus) [TPkgManager.AddProjectDependency] ',APackage.IDAsString,' conflicts with ',ConflictDependency.AsString]);
    Result:=mrCancel;
    exit;
  end;

  // check if the dependency is already there
  if FindDependencyByNameInList(AProject.FirstRequiredDependency,pddRequires,
    APackage.Name)<>nil
  then begin
    // package already there
    Result:=mrOk;
    exit;
  end;

  ProvidingAPackage:=PackageGraph.FindPackageProvidingName(
    AProject.FirstRequiredDependency,APackage.Name);
  if ProvidingAPackage<>nil then
  begin
    // package is already provided by another package
    DebugLn(['Error: (lazarus) [TPkgManager.AddProjectDependency] ',APackage.Name,' is already provided by ',ProvidingAPackage.IDAsString]);
    Result:=mrOk;
    exit;
  end;

  if OnlyTestIfPossible then
    exit(mrOk);
  // add a dependency for the package to the project
  NewDependency:=APackage.CreateDependencyWithOwner(AProject);
  Result:=AddProjectDependency(AProject,NewDependency);
end;

function TPkgManager.AddProjectDependency(AProject: TProject;
  ADependency: TPkgDependency): TModalResult;
begin
  Result:=mrOk;
  AProject.AddRequiredDependency(ADependency);
  PackageGraph.OpenDependency(ADependency,false);
  Project1.DefineTemplates.AllChanged(false);
  if (ADependency.RequiredPackage<>nil)
  and (not ADependency.RequiredPackage.Missing)
  and ADependency.RequiredPackage.AddToProjectUsesSection
  and ((ADependency.RequiredPackage.PackageType<>lptDesignTime)
       or (pfUseDesignTimePackages in AProject.Flags))
  then begin
    AddUnitToProjectMainUsesSection(AProject,
      ExtractFileNameOnly(ADependency.RequiredPackage.GetCompileSourceFilename),'');
  end;
end;

function TPkgManager.AddProjectDependencies(AProject: TProject;
  const Packages: string; OnlyTestIfPossible: boolean): TModalResult;
var
  RequiredPackages: TStrings;
  i: Integer;
  PkgName: string;
  APackage: TLazPackage;
begin
  Result:=mrOk;
  RequiredPackages:=SplitString(Packages,';');
  try
    for i:=0 to RequiredPackages.Count-1 do begin
      PkgName:=Trim(RequiredPackages[i]);
      if not IsValidPkgName(PkgName) then continue;
      APackage:=PackageGraph.FindPackageWithName(PkgName,nil);
      if APackage=nil then begin
        DebugLn(['Error: (lazarus) [TPkgManager.AddProjectDependencies] package not found: ',PkgName]);
        if OnlyTestIfPossible then
          exit(mrCancel);
        continue;
      end;
      Result:=AddProjectDependency(AProject,APackage,OnlyTestIfPossible);
      if Result<>mrOk then exit;
    end;
  finally
    RequiredPackages.Free;
  end;
end;

function TPkgManager.CheckProjectHasInstalledPackages(AProject: TProject; 
  Interactive: boolean): TModalResult;
var
  MissingUnits: TFPList;
  i: Integer;
  PkgFile: TPkgFile;
  Msg: String;
  PkgList: TObjectList;
begin
  Result:=mrOk;
  MissingUnits:=PackageGraph.FindNotInstalledRegisterUnits(nil,
                                              AProject.FirstRequiredDependency);
  if MissingUnits<>nil then begin
    if Interactive then begin 
      Msg:=Format(lisProbablyYouNeedToInstallSomePackagesForBeforeConti,
        [LineEnding+LineEnding, LineEnding, LineEnding+LineEnding]) + LineEnding+LineEnding;
      PkgList:=TObjectList.Create(false);
      try
        for i:=0 to MissingUnits.Count-1 do begin
          PkgFile:=TPkgFile(MissingUnits[i]);
          if PkgList.IndexOf(PkgFile.LazPackage)<0 then
            PkgList.Add(PkgFile.LazPackage);
          Msg:=Format(lisUnitInPackage,
            [Msg, PkgFile.Unit_Name, PkgFile.LazPackage.IDAsString]) + LineEnding;
        end;
        Result:=IDEQuestionDialog(lisPackageNeedsInstallation, Msg,
                  mtWarning, [mrIgnore,'Continue without install',
                              mrYes,'Install these packages',
                              mrCancel,'Cancel','IsDefault']);
        if Result=mrIgnore then begin
          // continue
        end else if Result=mrYes then
        begin
          // install
          AProject.AutoOpenDesignerFormsDisabled:=true;
          InstallPackages(PkgList,[piiifRebuildIDE]);
          Result:=mrAbort;
        end else begin
          // do not warn again
          AProject.AutoOpenDesignerFormsDisabled:=true;
        end;
      finally
        PkgList.Free;
      end;
    end else
      Result:=mrCancel;    
    MissingUnits.Free;
  end;
end;

function TPkgManager.DoNewPackage: TModalResult;
var
  NewPackage: TLazPackage;
begin
  Result:=mrCancel;
  // create a new package with standard dependencies
  NewPackage:=PackageGraph.CreateNewPackage(constNewPackageName);
  PackageGraph.AddDependencyToPackage(NewPackage,
                PackageGraph.FCLPackage.CreateDependencyWithOwner(NewPackage));
  NewPackage.Modified:=false;

  // open a package editor
  PackageEditors.OpenEditor(NewPackage,true);

  Result:=DoSavePackage(NewPackage,[psfSaveAs]);
end;

function TPkgManager.DoShowLoadedPkgDlg: TModalResult;
var
  APackage: TLazPackage;
begin
  Result:=ShowOpenLoadedPkgDlg(APackage);
  if (Result<>mrOk) then exit;
  Result:=DoOpenPackage(APackage,[pofAddToRecent],false);
end;

function TPkgManager.DoOpenPackage(APackage: TLazPackage;
  Flags: TPkgOpenFlags; ShowAbort: boolean): TModalResult;
var
  AFilename: String;
begin
  AFilename:=APackage.Filename;
  // revert: if possible and wanted
  if (pofRevert in Flags) and (FileExistsCached(AFilename)) then
    exit(DoOpenPackageFile(AFilename,Flags,ShowAbort));
  // open a package editor
  PackageEditors.OpenEditor(APackage,true);
  PackageGraph.RebuildDefineTemplates;
  // add to recent packages
  if (pofAddToRecent in Flags) and FileExistsCached(AFilename) then
    AddToMenuRecentPackages(AFilename);
  Result:=mrOk;
end;

function TPkgManager.DoOpenPackageWithName(const APackageName: string;
  Flags: TPkgOpenFlags; ShowAbort: boolean): TModalResult;
var
  APackage: TLazPackage;
  NewDependency: TPkgDependency;
  LoadResult: TLoadPackageResult;
begin
  Result:=mrCancel;
  if not IsValidPkgName(APackageName) then exit;
  NewDependency:=TPkgDependency.Create;
  try
    NewDependency.PackageName:=APackageName;
    LoadResult:=PackageGraph.OpenDependency(NewDependency,ShowAbort);
    if LoadResult<>lprSuccess then exit;
  finally
    NewDependency.Free;
  end;
  APackage:=PackageGraph.FindPackageWithName(APackageName,nil);
  if APackage=nil then exit;
  Result:=DoOpenPackage(APackage,Flags,ShowAbort);
end;

function TPkgManager.DoOpenPackageFile(AFilename: string; Flags: TPkgOpenFlags;
  ShowAbort: boolean): TModalResult;

  procedure DoQuestionDlg(const Caption, Message: string);
  begin
    if pofMultiOpen in Flags then
      Result:=IDEQuestionDialog(Caption, Message,
                mtError, [mrIgnore, lisPkgMangSkipThisPackage,
                          mrAbort])
    else
      Result:=IDEQuestionDialog(Caption, Message, mtError, [mrAbort])
  end;

var
  APackage: TLazPackage;
  XMLConfig: TXMLConfig;
  AlternativePkgName: String;
  Code: TCodeBuffer;
  OpenEditor: Boolean;
begin
  // replace macros
  if pofConvertMacros in Flags then
    if not GlobalMacroList.SubstituteStr(AFilename) then exit(mrCancel);

  AFilename:=GetPhysicalFilenameCached(CleanAndExpandFilename(AFilename),false);

  // check file extension
  if not ( FilenameExtIs(AFilename,'lpk',true) or (pofRevert in Flags) ) then begin
    DoQuestionDlg(lisPkgMangInvalidFileExtension,
      Format(lisPkgMangTheFileIsNotALazarusPackage, [AFilename]));
    RemoveFromRecentList(AFilename,EnvironmentOptions.RecentPackageFiles,rltFile);
    SetRecentPackagesMenu;
    exit;
  end;
  
  // check filename
  AlternativePkgName:=ExtractFileNameOnly(AFilename);
  if (not (pofRevert in Flags))
  and (not IsValidPkgName(AlternativePkgName))
  then begin
    DoQuestionDlg(lisPkgMangInvalidPackageFilename,
      Format(lisPkgMangThePackageFileNameInIsNotAValidLazarusPackageName,
             [AlternativePkgName, LineEnding, AFilename]));
    RemoveFromRecentList(AFilename,EnvironmentOptions.RecentPackageFiles,rltFile);
    SetRecentPackagesMenu;
    exit;
  end;

  // add to recent packages
  if pofAddToRecent in Flags then begin
    AddToMenuRecentPackages(AFilename);
  end;

  OpenEditor:=not (pofDoNotOpenEditor in Flags);

  // check if package is already loaded
  APackage:=PackageGraph.FindPackageWithFilename(AFilename);
  if (APackage=nil) or (pofRevert in Flags) then begin
    // package not yet loaded or it should be reloaded
    
    if (pofRevert in Flags)
    and ((APackage=nil) or (APackage.Editor=nil)) then
      OpenEditor:=false;
    
    if not FileExistsUTF8(AFilename) then begin
      IDEMessageDialog(lisFileNotFound,
        Format(lisPkgMangFileNotFound, [AFilename]),
        mtError,[mbCancel]);
      RemoveFromRecentList(AFilename,EnvironmentOptions.RecentPackageFiles,rltFile);
      SetRecentPackagesMenu;
      Result:=mrCancel;
      exit;
    end;

    // create a new package
    Result:=mrCancel;
    APackage:=TLazPackage.Create;
    try
      // load the package file
      try
        XMLConfig:=TCodeBufXMLConfig.Create(nil);
        try
          APackage.Filename:=AFilename;
          Result:=LoadXMLConfigFromCodeBuffer(AFilename,XMLConfig,
                               Code,[lbfUpdateFromDisk,lbfRevert],ShowAbort);
          if Result<>mrOk then exit;
          APackage.LPKSource:=Code;
          APackage.LoadFromXMLConfig(XMLConfig,'Package/');
        finally
          XMLConfig.Free;
        end;
      except
        on E: Exception do begin
          DoQuestionDlg(lisPkgMangErrorReadingPackage,
            Format(lisPkgUnableToReadPackageFileError, [AFilename, LineEnding, E.Message]));
          exit;
        end;
      end;

      // newly loaded is not modified
      APackage.Modified:=false;

      // check if package name and file name correspond
      if (SysUtils.CompareText(AlternativePkgName,APackage.Name)<>0) then begin
        Result:=IDEMessageDialog(lisPkgMangFilenameDiffersFromPackagename,
          Format(lisPkgMangTheFilenameDoesNotCorrespondToThePackage,
            [ExtractFileName(AFilename), APackage.Name, LineEnding, AlternativePkgName]),
          mtConfirmation,[mbYes,mbCancel,mbAbort]);
        if Result<>mrYes then exit;
        APackage.Name:=AlternativePkgName;
      end;
      
      // integrate it into the graph
      Result:=AddPackageToGraph(APackage);
    finally
      if Result<>mrOk then APackage.Free;
    end;
  end;

  if OpenEditor then
    Result:=DoOpenPackage(APackage,[],ShowAbort)
  else
    Result:=mrOk;

  LazPackageLinks.SaveUserLinks;

  // the source editor highlighting depends on the compiler mode
  MainIDEInterface.UpdateHighlighters;
end;

function TPkgManager.IsPackageEditorForm(AForm: TCustomForm): boolean;
begin
  Result:=AForm is TPackageEditorForm;
end;

procedure TPkgManager.OpenHiddenModifiedPackages;
var
  i: Integer;
  APackage: TLazPackage;
begin
  for i:=0 to PackageGraph.Count-1 do begin
    APackage:=PackageGraph.Packages[i];
    if (APackage.Editor=nil) and APackage.Modified
    and (APackage.UserIgnoreChangeStamp<>APackage.ChangeStamp) then begin
      PackageEditors.OpenEditor(APackage,false);
    end;
  end;
end;

function TPkgManager.DoSavePackage(APackage: TLazPackage;
  Flags: TPkgSaveFlags): TModalResult;
var
  XMLConfig: TCodeBufXMLConfig;
  PkgLink: TPackageLink;
  Code: TCodeBuffer;
begin
  // do not save during compilation
  if not (MainIDE.ToolStatus in [itNone,itDebugger]) then begin
    Result:=mrAbort;
    exit;
  end;
  
  if APackage.IsVirtual then Include(Flags,psfSaveAs);

  if not ( (psfSaveAs in Flags) or APackage.ReadOnly or APackage.Modified
          or FileExistsCached(APackage.Filename)
          or (APackage.UserIgnoreChangeStamp<>APackage.ChangeStamp )) then
  begin
    Result:=mrOk;
    exit;
  end;

  // save new or changed files
  Result:=SavePackageFiles(APackage);
  if Result<>mrOk then exit;

  // warn about missing files
  Result:=WarnAboutMissingPackageFiles(APackage);
  if Result<>mrOk then exit;

  // save editor files to codetools
  MainIDE.SaveSourceEditorChangesToCodeCache(nil);

  // save package
  if (psfSaveAs in Flags) then begin
    Result:=DoShowSavePackageAsDialog(APackage);
    if Result<>mrOk then exit;
  end;
  
  // backup old file
  Result:=BuildBoss.BackupFileForWrite(APackage.Filename);
  if Result=mrAbort then exit;

  // delete ambiguous files
  Result:=BuildBoss.DeleteAmbiguousFiles(APackage.Filename);
  if Result=mrAbort then exit;

  // save
  try
    XMLConfig:=TCodeBufXMLConfig.Create(nil);
    try
      XMLConfig.Clear;
      XMLConfig.KeepFileAttributes:=true;
      APackage.SaveToXMLConfig(XMLConfig,'Package/');
      Code:=nil;
      Result:=SaveXMLConfigToCodeBuffer(APackage.Filename,XMLConfig,Code,true);
      if Result<>mrOk then exit;
      APackage.LPKSource:=Code;
      PkgLink:=LazPackageLinks.AddUserLink(APackage);
      if PkgLink<>nil then begin
        PkgLink.LPKFileDate:=FileDateToDateTimeDef(FileAgeUTF8(APackage.Filename));
        PkgLink.LPKFileDateValid:=true;
        LazPackageLinks.SaveUserLinks;
      end;
    finally
      XMLConfig.Free;
    end;
  except
    on E: Exception do begin
      Result:=IDEMessageDialog(lisPkgMangErrorWritingPackage,
        Format(lisPkgMangUnableToWritePackageToFileError,
          [APackage.IDAsString, LineEnding, APackage.Filename, LineEnding, E.Message]),
        mtError,[mbAbort,mbCancel]);
      exit;
    end;
  end;

  // success
  APackage.Modified:=false;
  // add to recent
  if (psfSaveAs in Flags) then begin
    AddToMenuRecentPackages(APackage.Filename);
  end;

  if APackage.Editor<>nil then
    APackage.Editor.UpdateAll(true);
  Result:=mrOk;
end;

procedure TPkgManager.DoShowPackageGraph(Show: boolean);
begin
  if PackageGraphExplorer=nil then begin
    PackageGraphExplorer:=TPkgGraphExplorerDlg.Create(Application);
    PackageGraphExplorer.OnOpenPackage:=@PackageGraphExplorerOpenPackage;
    PackageGraphExplorer.OnOpenProject:=@PackageGraphExplorerOpenProject;
    PackageGraphExplorer.OnUninstallPackage:=@PackageGraphExplorerUninstallPackage;
  end;
  if Show then
    IDEWindowCreators.ShowForm(PackageGraphExplorer,true);
end;

function TPkgManager.DoCloseAllPackageEditors: TModalResult;
var
  APackage: TLazPackage;
begin
  while PackageEditors.Count>0 do begin
    APackage:=PackageEditors.Editors[PackageEditors.Count-1].LazPackage;
    Result:=DoClosePackageEditor(APackage);
    if Result<>mrOk then exit;
  end;
  Result:=mrOk;
end;

procedure TPkgManager.DoShowPackageGraphPathList(PathList: TFPList);
begin
  DoShowPackageGraph(true);
  PackageGraphExplorer.ShowPath(PathList);
end;

function TPkgManager.ShowBrokenDependenciesReport(Dependencies: TFPList): TModalResult;
var
  Msg: String;
  i: Integer;
  ADependency: TPkgDependency;
begin
  Result:=mrOk;
  if (Dependencies=nil) or (Dependencies.Count=0) then exit;
  if Dependencies.Count=1 then
    Msg:=lisPkgMangTheFollowingPackageFailedToLoad
  else
    Msg:=lisPkgMangTheFollowingPackagesFailedToLoad;
  Msg:=Msg+LineEnding+LineEnding;
  for i:=0 to Dependencies.Count-1 do begin
    ADependency:=TPkgDependency(Dependencies[i]);
    Msg:=Msg+ADependency.AsString+LineEnding;
  end;
  
  // give some hints
  ADependency:=TPkgDependency(Dependencies[0]);
  if (ADependency.Owner is TProject) then begin
    // broken dependency used by project -> show project inspector
    if ADependency.Owner=Project1 then begin
      MainIDE.DoShowProjectInspector;
      Msg:=Format(lisSeeProjectProjectInspector, [Msg]);
    end;
  end;

  Result:=IDEMessageDialog(lisMissingPackages, Msg, mtError, [mbOk]);
end;

function TPkgManager.CheckUserSearchPaths(aCompilerOptions: TBaseCompilerOptions
  ): TModalResult;
var
  aPackage: TLazPackage;
  CurUnitPath: String;
  CurSrcPath: String;
  CurOutPath: String;
  SrcDirToPkg: TFilenameToPointerTree;

  function GetPkgOfSrcDirToPkg(Dir: string): TLazPackage;
  var
    MaskType: TSPMaskType;
    Item: PStringToPointerTreeItem;
    CurDir: String;
  begin
    MaskType:=GetSPMaskType(Dir);
    if MaskType=TSPMaskType.None then
    begin
      Result:=TLazPackage(SrcDirToPkg[Dir]);
      exit;
    end;
    Dir:=ExtractFilePath(Dir);
    for Item in SrcDirToPkg do
    begin
      CurDir:=Item^.Name;
      if FileIsInSPDirectory(AppendPathDelim(CurDir),Dir,MaskType) then
        exit(TLazPackage(Item^.Value));
    end;
    Result:=nil;
  end;

  function CheckPathContainsDirOfOtherPkg(Option: TParsedCompilerOptString
    ): TModalResult;
  var
    aSearchPath: String;
    p: Integer;
    Dir: String;
    OtherPackage: TLazPackage;
    aType: String;
    s: String;
  begin
    Result:=mrOk;
    case Option of
    pcosUnitPath:
      begin
        aType:='other unit files search path (aka unit path)';
        aSearchPath:=CurUnitPath;
      end;
    pcosSrcPath:
      begin
        aType:='other sources path';
        aSearchPath:=CurSrcPath;
      end;
    else
      exit;
    end;
    p:=1;
    repeat
      Dir:=GetNextDirectoryInSearchPath(aSearchPath,p);
      if Dir='' then break;
      Dir:=ChompPathDelim(Dir);
      if not FilenameIsAbsolute(Dir) then continue;
      OtherPackage:=GetPkgOfSrcDirToPkg(Dir);
      if (OtherPackage<>nil) and (OtherPackage<>aPackage) then begin
        // search path contains source directory of another package
        if Option=pcosIncludePath then;
        s:=aType+' of "'+aCompilerOptions.GetOwnerName+'" contains "'+Dir+'", which belongs to package "'+OtherPackage.Name+'"';
        debugln(['Warning: (lazarus) [TPkgManager.CheckUserSearchPaths]: ',s]);
        { ToDo: find out
           - which path it is in the unparsed path
           - if there is already the dependency
           - if the dependency can be added
           and ask the user to delete the path and to add the dependency

           if the user has already answered this question in the past, just warn }
        // warn user
        IDEMessagesWindow.AddCustomMessage(mluWarning,s);
        exit;
      end;
    until false;
  end;

  function CheckOutPathContainsSources: TModalResult;
  var
    Files: TStrings;
    i: Integer;
    aFilename: String;
    s: String;
  begin
    Result:=mrOk;
    if aPackage=nil then exit;
    if not FilenameIsAbsolute(CurOutPath) then exit;
    Files:=nil;
    CodeToolBoss.DirectoryCachePool.GetListing(CurOutPath,Files,false);
    try
      for i:=0 to Files.Count-1 do begin
        aFilename:=Files[i];
        if FilenameIsPascalUnit(aFilename) then begin
          // warning: packages output path contain unit source
          s:=Format(lisOutputDirectoryOfContainsPascalUnitSource, [
            aCompilerOptions.GetOwnerName, aFilename]);
          debugln(['Warning: (lazarus) [CheckOutPathContainsSources]: ',s]);
          { ToDo: if the OutPath is not the default: ask user and change it }
          IDEMessagesWindow.AddCustomMessage(mluWarning,s);
          exit;
        end;
      end;
    finally
      Files.Free;
    end;
  end;

  function CheckSrcPathIsInUnitPath: TModalResult;
  // warn: SrcPath should not contain directories of UnitPath
  var
    p: Integer;
    UnparsedUnitPath: String;
    UnparsedSrcPath: String;
    Dir: String;
    s: String;
  begin
    Result:=mrOk;
    UnparsedUnitPath:=aCompilerOptions.OtherUnitFiles;
    UnparsedSrcPath:=aCompilerOptions.SrcPath;
    p:=1;
    repeat
      Dir:=GetNextDirectoryInSearchPath(UnparsedSrcPath,p);
      if Dir='' then exit;
      if SearchDirectoryInMaskedSearchPath(UnparsedUnitPath,Dir)>0 then begin
        // Note: when changing this, update TQuickFixSrcPathOfPkgContains_OpenPkg
        s:=Format(lisOtherSourcesPathOfPackageContainsDirectoryWhichIsA, [
          aCompilerOptions.GetOwnerName, Dir]);
        debugln(['Warning: (lazarus) [CheckSrcPathIsInUnitPath]: ',s]);
        { ToDo: ask user and remove dir from unit path }
        IDEMessagesWindow.AddCustomMessage(mluWarning,s);
        exit;
      end;
    until false;
  end;

begin
  Result:=mrOk;
  if aCompilerOptions.CompilerPath='' then exit; // not a normal Pascal project

  aPackage:=nil;
  if aCompilerOptions.Owner is TLazPackage then
    aPackage:=TLazPackage(aCompilerOptions.Owner)
  else if not (aCompilerOptions.Owner is TProject) then
    exit;

  if (aPackage<>nil) and (aPackage.AutoUpdate=pupManually) then exit;

  CurUnitPath:=aCompilerOptions.ParsedOpts.GetParsedValue(pcosUnitPath);
  CurSrcPath:=aCompilerOptions.ParsedOpts.GetParsedValue(pcosSrcPath);
  CurOutPath:=aCompilerOptions.ParsedOpts.GetParsedValue(pcosOutputDir);
  //debugln(['TPkgManager.CheckUserSearchPaths CompOpts=',aCompilerOptions.GetOwnerName,' UnitPath="',CurUnitPath,'" IncPath="',CurIncPath,'" SrcPath="',CurSrcPath,'" OutPath="',CurOutPath,'"']);

  // create mapping source-directory to package
  SrcDirToPkg:=PackageGraph.GetMapSourceDirectoryToPackage(aPackage);
  try
    Result:=CheckPathContainsDirOfOtherPkg(pcosUnitPath);
    if Result<>mrOk then exit;

    Result:=CheckPathContainsDirOfOtherPkg(pcosSrcPath);
    if Result<>mrOk then exit;

    Result:=CheckOutPathContainsSources;
    if Result<>mrOk then exit;

    Result:=CheckSrcPathIsInUnitPath;
    if Result<>mrOk then exit;

    // ToDo: check if SrcPath is in inherited SrcPath
    // ToDo: check if UnitPath is in inherited UnitPath
  finally
    SrcDirToPkg.Free;
  end;
end;

procedure TPkgManager.LazarusSrcDirChanged;
const
  LazDirMacro = '$(LazarusDir)';
var
  NewLazarusSrcDir: String;
  OldLazarusSrcDir: String;
  VisitedPkgs: TStringToStringTree;
  ReloadPkgs: TStringList;

  function PkgInOldLazarusDir(APackage: TLazPackage): boolean;
  begin
    Result:=FileIsInPath(APackage.Filename,OldLazarusSrcDir)
      or PackageGraph.IsCompiledInBasePackage(APackage.Name)
      or (SysUtils.CompareText(copy(APackage.Filename,1,length(LazDirMacro)),LazDirMacro)=0)
  end;

  procedure GatherLazarusSrcPackages(APackage: TLazPackage);
  var
    ADependency: TPkgDependency;
  begin
    if APackage=nil then exit;
    if VisitedPkgs.Contains(APackage.Name) then exit;
    VisitedPkgs[APackage.Name]:='1';
    // search the dependencies first
    ADependency:=APackage.FirstRequiredDependency;
    while ADependency<>nil do begin
      GatherLazarusSrcPackages(ADependency.RequiredPackage);
      ADependency:=ADependency.NextRequiresDependency;
    end;
    if PkgInOldLazarusDir(APackage) then begin
      // this package was from the old lazarus source directory
      ReloadPkgs.Add(APackage.Name);
    end;
  end;

  function ReloadPkg(APackage: TLazPackage): boolean;
  var
    Link: TPackageLink;
    MsgResult: TModalResult;
    Filename: String;
  begin
    Result:=true;
    if APackage=nil then exit;
    if not PkgInOldLazarusDir(APackage) then exit;
    // this package was from the old lazarus source directory
    // check if there is a package in the new version
    Link:=LazPackageLinks.FindLinkWithPkgName(APackage.Name);
    if Link<>nil then begin
      Filename:=TrimFilename(Link.LPKFilename);
      if not FilenameIsAbsolute(Filename) then
        Filename:=AppendPathDelim(NewLazarusSrcDir)+Filename;
      if FileIsInPath(Filename,NewLazarusSrcDir)
      and FileExistsUTF8(Filename) then
      begin
        DebugLn(['Hint: (lazarus) [TPkgManager.LazarusSrcDirChanged] load: ',Filename]);
        // open package in new lazarus source directory
        MsgResult:=DoOpenPackageFile(Filename,[pofDoNotOpenEditor,pofRevert],true);
        if MsgResult=mrAbort then exit(false);
      end;
    end;
  end;

var
  i: Integer;
begin
  if PackageGraph=nil then exit;
  OldLazarusSrcDir:=FLastLazarusSrcDir;
  NewLazarusSrcDir:=EnvironmentOptions.GetParsedLazarusDirectory;
  FLastLazarusSrcDir:=NewLazarusSrcDir;
  if CompareFilenames(OldLazarusSrcDir,NewLazarusSrcDir)=0 then exit;
  debugln(['Hint: (lazarus) [TPkgManager.LazarusSrcDirChanged] loading new lpl files from ',
           LazPackageLinks.GetGlobalLinkDirectory]);
  if LazPackageLinks.IsUpdating then
    debugln(['Warning: (lazarus) [TPkgManager.LazarusSrcDirChanged] inconsistency: LazPackageLinks are locked']);
  LazPackageLinks.UpdateGlobalLinks;

  VisitedPkgs:=TStringToStringTree.Create(false);
  ReloadPkgs:=TStringList.Create;
  try
    // collect candidates
    for i:=0 to PackageGraph.Count-1 do
      GatherLazarusSrcPackages(PackageGraph.Packages[i]);
    // reload
    for i:=0 to ReloadPkgs.Count-1 do
      ReloadPkg(PackageGraph.FindPackageWithName(ReloadPkgs[i],nil));
  finally
    ReloadPkgs.Free;
    VisitedPkgs.Free;
  end;
end;

function TPkgManager.GetPackageCount: integer;
begin
  Result:=PackageGraph.Count;
end;

function TPkgManager.GetPackages(Index: integer): TIDEPackage;
begin
  Result:=PackageGraph.Packages[Index];
end;

function TPkgManager.FindPackageWithName(const PkgName: string;
  IgnorePackage: TIDEPackage): TIDEPackage;
begin
  Result:=PackageGraph.FindPackageWithName(PkgName, IgnorePackage as TLazPackage);
end;

function TPkgManager.FindInstalledPackageWithUnit(const AnUnitName: string
  ): TIDEPackage;
var
  PkgFile: TPkgFile;
begin
  PkgFile:=PackageGraph.FindUnitInInstalledPackages(AnUnitName, true);
  if PkgFile=nil then
    Result:=nil
  else
    Result:=PkgFile.LazPackage;
end;

function TPkgManager.IsPackageInstalled(const PkgName: string): TIDEPackage;
var
  LazPackage: TLazPackage;
begin
  Result := nil;
  LazPackage:=PackageGraph.FindPackageWithName(PkgName, nil);
  if (LazPackage<>nil) and (LazPackage.Installed<>pitNope) then
    Result:=LazPackage
end;

function TPkgManager.RedirectPackageDependency(APackage: TIDEPackage): TIDEPackage;
begin
  Result:=APackage;
  if Result=PackageGraph.LCLBasePackage then begin
    // Older Lazarus does not have a LCLBase and a component does not work
    // without an LCLBase implementation, so we have to use LCL instead.
    Result:=PackageGraph.LCLPackage;
  end;
end;

function TPkgManager.DoCompileProjectDependencies(AProject: TProject;
  Flags: TPkgCompileFlags): TModalResult;
var
  CompilePolicy: TPackageUpdatePolicy;
begin
  // check graph for cycles and broken dependencies
  if not (pcfDoNotCompileDependencies in Flags) then begin
    Result:=CheckPackageGraphForCompilation(nil,
                                            AProject.FirstRequiredDependency,
                                            AProject.Directory,false);
    if Result<>mrOk then exit;
  end;
  
  // save all open files
  if not (pcfDoNotSaveEditorFiles in Flags) then begin
    Result:=MainIDE.DoSaveForBuild(crCompile);
    if Result<>mrOk then exit;
  end;

  PackageGraph.BeginUpdate(false);
  try
    // automatically compile required packages
    if not (pcfDoNotCompileDependencies in Flags) then begin
      CompilePolicy:=pupAsNeeded;
      if pcfCompileDependenciesClean in Flags then
        CompilePolicy:=pupOnRebuildingAll;
      Result:=PackageGraph.CompileRequiredPackages(nil,
                                AProject.FirstRequiredDependency,
                                not (pfUseDesignTimePackages in AProject.Flags),
                                CompilePolicy);
      if Result<>mrOk then exit;
    end;
  finally
    PackageGraph.EndUpdate;
  end;
  
  Result:=mrOk;
end;

function TPkgManager.DoCompilePackage(APackage: TIDEPackage;
  Flags: TPkgCompileFlags; ShowAbort: boolean): TModalResult;
var
  OldToolStatus: TLazToolStatus;
begin
  Result:=mrCancel;

  DebugLn('Hint: (lazarus) compile package ',APackage.IDAsString,' Flags=',PkgCompileFlagsToString(Flags));
  
  if APackage.IsVirtual then exit;

  Result:=MainIDE.PrepareForCompile;
  if Result<>mrOk then exit;
  Assert(APackage is TLazPackage, 'TPkgManager.DoCompilePackage: APackage is not TLazPackage');

  // check graph for circles and broken dependencies
  if not (pcfDoNotCompileDependencies in Flags) then begin
    Result:=CheckPackageGraphForCompilation(TLazPackage(APackage),nil,APackage.Directory,ShowAbort);
    if Result<>mrOk then exit;
  end;
  
  // save all open files
  {$IFDEF VerboseSaveForBuild}
  DebugLn('TPkgManager.DoCompilePackage  ',APackage.IDAsString,' Flags=',PkgCompileFlagsToString(Flags));
  {$ENDIF}
  if not (pcfDoNotSaveEditorFiles in Flags) then begin
    Result:=MainIDE.DoSaveForBuild(crCompile);
    if Result<>mrOk then exit;
  end;

  // check user search paths
  Result:=CheckUserSearchPaths(TBaseCompilerOptions(APackage.LazCompilerOptions));
  if Result<>mrOk then exit;

  // compile
  if LazarusIDE<>nil then begin
    OldToolStatus:=LazarusIDE.ToolStatus;
    LazarusIDE.ToolStatus:=itBuilder;
  end;
  Result:=PackageGraph.CompilePackage(TLazPackage(APackage),Flags,false);
  if LazarusIDE<>nil then
    LazarusIDE.ToolStatus:=OldToolStatus;
end;

function TPkgManager.DoCreatePackageMakefile(APackage: TLazPackage;
  ShowAbort: boolean): TModalResult;
begin
  Result:=DoCompilePackage(APackage,[pcfDoNotCompileDependencies,
                       pcfDoNotCompilePackage,pcfCreateMakefile],ShowAbort);
end;

function TPkgManager.DoCreatePackageFpmakefile(APackage: TLazPackage;
  ShowAbort: boolean): TModalResult;
begin
  Result:=DoCompilePackage(APackage,[pcfDoNotCompileDependencies,
                       pcfDoNotCompilePackage,pcfCreateFpmakeFile],ShowAbort);
end;

function TPkgManager.OnRenameFile(const OldFilename, NewFilename: string;
  IsPartOfProject: boolean): TModalResult;
var
  OldPackage: TLazPackage;
  OldPkgFile: TPkgFile;
  NewPkgFile: TPkgFile;
begin
  Result:=mrOk;
  if (OldFilename=NewFilename) then
    exit;
  //debugln('TPkgManager.OnRenameFile A OldFilename="',OldFilename,'" New="',NewFilename,'"');
  OldPkgFile:=PackageGraph.FindFileInAllPackages(OldFilename,true,not IsPartOfProject);
  if (OldPkgFile=nil) or (OldPkgFile.LazPackage.ReadOnly) then
    exit;
  OldPackage:=OldPkgFile.LazPackage;
  debugln('Hint: (lazarus) [TPkgManager.OnRenameFile] OldPackage="',OldPackage.Name);
  NewPkgFile:=PackageGraph.FindFileInAllPackages(NewFilename,true,false);
  if (NewPkgFile<>nil) and (OldPackage<>NewPkgFile.LazPackage) then exit;

  OldPkgFile.Filename:=NewFilename;
  if OldPackage.Editor<>nil then
    OldPackage.Editor.UpdateAll(true);
  OldPackage.Modified:=true;
end;

{------------------------------------------------------------------------------
  function TPkgManager.FindIncludeFileInProjectDependencies(Project1: TProject;
    const Filename: string): string;
    
  Search filename in the include paths of all required packages
------------------------------------------------------------------------------}
function TPkgManager.FindIncludeFileInProjectDependencies(aProject: TProject;
  const Filename: string): string;
var
  APackage: TLazPackage;
  IncPath: String;
  PkgList: TFPList;
  i: Integer;
begin
  Result:='';
  if FilenameIsAbsolute(Filename) then
    exit(Filename);
  PkgList:=nil;
  PackageGraph.GetAllRequiredPackages(nil,aProject.FirstRequiredDependency,
    PkgList,[pirCompileOrder]);
  if PkgList=nil then exit;
  try
    for i:=0 to PkgList.Count-1 do begin
      APackage:=TLazPackage(PkgList[i]);
      IncPath:=APackage.CompilerOptions.GetIncludePath(false);
      Result:=SearchFileInSearchPath(Filename,APackage.Directory,IncPath);
      if Result<>'' then exit;
    end;
  finally
    PkgList.Free;
  end;
end;

type
  TPackageIterateHelper = class
  public
    PackageNames: TStrings;
    PackageList: TStrings;
    procedure AddDependency(APackageID: TLazPackageID);
  end;

procedure TPackageIterateHelper.AddDependency(APackageID: TLazPackageID);
begin
  { are we looking for this package? }
  if PackageNames.IndexOf(APackageID.Name)<0 then
    Exit;
  { was the package already added? }
  if PackageList.IndexOf(APackageID.Name)>=0 then
    Exit;
  PackageList.AddObject(APackageID.Name,APackageID);
end;

function TPkgManager.AddUnitDepsForCompClasses(const UnitFilename: string;
  ComponentClasses: TClassList; Quiet: boolean): TModalResult;
var
  UnitBuf: TCodeBuffer;
  UnitNames: TStringList;
  MissingDependencies: TOwnerPackageArray;

  function LoadAndParseUnitBuf: TModalResult;
  begin
    if not CodeToolBoss.GatherExternalChanges then begin
      Result:=mrCancel;
      MainIDE.DoJumpToCodeToolBossError;
      exit;
    end;
    UnitBuf:=CodeToolBoss.LoadFile(UnitFilename,false,false);
    if UnitBuf=nil then begin
      Result:=IDEMessageDialog(lisErrorLoadingFile,
        Format(lisLoadingFailed, [UnitFilename]),
        mtError,[mbCancel,mbAbort]);
      exit;
    end;
    Result:=mrOk;
  end;

  function RemoveExistingUnitnames: TModalResult;
  var
    ImplementationUsesSection: TStrings;
    MainUsesSection: TStrings;
    j: LongInt;
    i: Integer;
  begin
    Result:=LoadAndParseUnitBuf;
    if Result<>mrOk then exit;
    MainUsesSection:=nil;
    ImplementationUsesSection:=nil;
    try
      if not CodeToolBoss.FindUsedUnitNames(UnitBuf,MainUsesSection,
        ImplementationUsesSection)
      then begin
        MainIDE.DoJumpToCodeToolBossError;
        exit;
      end;
      for i:=0 to MainUsesSection.Count-1 do begin
        j:=UnitNames.IndexOf(MainUsesSection[i]);
        if j>=0 then UnitNames.Delete(j);
      end;
    finally
      MainUsesSection.Free;
      ImplementationUsesSection.Free;
    end;
    Result:=mrOk;
  end;
  
  function AskUser: TModalResult;
  var
    UsesAdditions: String;
    UnitOwner: TObject;
    RequiredPackage: TLazPackageID;
    i: Integer;
    PackageAdditions: String;
    Msg: String;
  begin
    UsesAdditions:='';
    for i:=0 to UnitNames.Count-1 do begin
      if UsesAdditions<>'' then UsesAdditions:=UsesAdditions+', ';
      UsesAdditions:=UsesAdditions+UnitNames[i];
    end;
    //DebugLn('TPkgManager.AddUnitDepsForCompClasses UsesAdditions=',UsesAdditions);
    PackageAdditions:='';
    if MissingDependencies<>nil then begin
      for i:=0 to MissingDependencies.Count-1 do begin
        UnitOwner:=MissingDependencies[i];
        RequiredPackage:=MissingDependencies.Objects[i];
        if RequiredPackage is TIDEPackage then
          RequiredPackage:=RedirectPackageDependency(TIDEPackage(RequiredPackage));
        if UnitOwner is TProject then begin
          PackageAdditions:=Format(lisPkgMangAddingNewDependencyForProjectPackage,
            [PackageAdditions, TProject(UnitOwner).GetTitle, RequiredPackage.Name]) + LineEnding+LineEnding;
        end else if UnitOwner is TLazPackage then begin
          PackageAdditions:=Format(lisPkgMangAddingNewDependencyForPackagePackage,
            [PackageAdditions, TLazPackage(UnitOwner).Name, RequiredPackage.Name]) + LineEnding+LineEnding;
        end;
      end;
    end;
    //DebugLn('TPkgManager.AddUnitDepsForCompClasses PackageAdditions=',PackageAdditions);
    Msg:='';
    if UsesAdditions<>'' then begin
      Msg:=Format(lisPkgMangTheFollowingUnitsWillBeAddedToTheUsesSectionOf,
        [Msg, LineEnding, UnitFilename, LineEnding, UsesAdditions]) + LineEnding+LineEnding;
    end;
    if PackageAdditions<>'' then begin
      Msg:=Msg+PackageAdditions;
    end;
    if Msg<>'' then begin
      Result:=IDEMessageDialog(lisConfirmChanges,Msg,mtConfirmation,[mbOk,mbAbort]);
      exit;
    end;
    Result:=mrOk;
  end;
  
  function AddDependencies: TModalResult;
  var
    i: Integer;
    UnitOwner: TObject;
    RequiredPackage: TLazPackageID;
    PkgDependency: TPkgDependency;
  begin
    if MissingDependencies<>nil then begin
      for i:=0 to MissingDependencies.Count-1 do begin
        UnitOwner:=MissingDependencies[i];
        RequiredPackage:=MissingDependencies.Objects[i];
        if RequiredPackage is TIDEPackage then
          RequiredPackage:=RedirectPackageDependency(TIDEPackage(RequiredPackage));
        if UnitOwner is TProject then begin
          DebugLn('Hint: (lazarus) [TPkgManager.AddUnitDepsForCompClasses] Adding Project Dependency ',TProject(UnitOwner).GetTitle,' -> ',RequiredPackage.Name);
          if RequiredPackage is TLazPackage then
            AddProjectDependency(TProject(UnitOwner),TLazPackage(RequiredPackage))
          else begin
            PkgDependency:=TPkgDependency.Create;
            PkgDependency.PackageName:=RequiredPackage.Name;
            AddProjectDependency(TProject(UnitOwner),PkgDependency);
          end;
        end else if UnitOwner is TLazPackage then begin
          DebugLn('Hint: (lazarus) [TPkgManager.AddUnitDepsForCompClasses] Adding Package Dependency ',TLazPackage(UnitOwner).Name,' -> ',RequiredPackage.Name);
          AddPackageDependency(TLazPackage(UnitOwner),RequiredPackage.Name);
        end;
      end;
    end;
    Result:=mrOk;
  end;

  function AddUsedUnits: TModalResult;
  var
    i: Integer;
  begin
    Result:=LoadAndParseUnitBuf;
    if Result<>mrOk then exit;
    for i:=0 to UnitNames.Count-1 do begin
      DebugLn('Hint: (lazarus) [TPkgManager.AddUnitDepsForCompClasses] Extending Uses ',
              UnitBuf.Filename,' ',UnitNames[i]);
      if not CodeToolBoss.AddUnitToMainUsesSection(UnitBuf,UnitNames[i],'') then
        MainIDE.DoJumpToCodeToolBossError;
    end;
    Result:=mrOk;
  end;

var
  Dependencies: TPackagePackageArray;
  UnitInfo: TUnitInfo;
begin
  UnitNames:=nil;
  Dependencies:=nil;
  MissingDependencies:=nil;
  try
    Result:=GetUnitsAndDepsForComps(ComponentClasses, Dependencies, UnitNames);
    if Result<>mrOk then exit;
    // Frame instances are not registered components, UnitNames is not assigned.
    // Find the frame from the project units.
    if UnitNames=nil then begin
      UnitNames:=TStringList.Create;
      Assert(ComponentClasses.Count=1, 'TPkgManager.AddUnitDepsForCompClasses: '
           + IntToStr(ComponentClasses.Count) + ' frame classes requested.');
      UnitInfo:=Project1.UnitWithComponentClass(TComponentClass(ComponentClasses[0]));
      if UnitInfo=nil then exit(mrCancel);
      UnitNames.Add(UnitInfo.Unit_Name);
    end;
    if (Dependencies<>nil) then
    begin
      Result:=FilterMissingDepsForUnit(UnitFilename,Dependencies,MissingDependencies);
      if Result<>mrOk then exit;
    end;

    Result:=RemoveExistingUnitnames;
    if Result<>mrOk then exit;

    if (UnitNames.Count=0)                                   // no change needed
    and ((MissingDependencies=nil) or (MissingDependencies.Count=0)) then exit;

    if not Quiet then begin
      Result:=AskUser;
      if Result<>mrOk then exit;
    end;
    
    Result:=AddDependencies;
    if Result<>mrOk then exit;

    Result:=AddUsedUnits;
    if Result<>mrOk then exit;

    Result:=mrOk;
  finally
    UnitNames.Free;
    Dependencies.Free;
    MissingDependencies.Free;
  end;
end;

function TPkgManager.GetUnitsAndDepsForComps(ComponentClasses: TClassList;
  out PackageList: TPackagePackageArray; out UnitList: TStringList): TModalResult;
// returns a list of packages and units needed to use the Component in the unit
var
  CurPackages: TStringListUTF8Fast;

  procedure AddPkgDep(PkgFile: TPkgFile);
  var
    RequiredPackage: TLazPackageID;
  begin
    if PkgFile=nil then exit;
    RequiredPackage:=PkgFile.LazPackage;
    RequiredPackage:=RedirectPackageDependency(TIDEPackage(RequiredPackage));
    if RequiredPackage=nil then exit;
    CurPackages.Add(RequiredPackage.Name);
  end;

var
  CurClassID: Integer;
  CurUnitIdx, CurPackageIdx: Integer;
  CurCompClass: TClass;
  CurRegComp: TRegisteredComponent;
  PkgFile: TPkgFile;
  CurUnitName: String;
  CurCompReqs: TComponentRequirements;
  ExistingPkgs, AllPackages, CurUnitNames: TStringListUTF8Fast;
  Helper: TPackageIterateHelper;
begin
  Result:=mrCancel;
  PackageList:=nil;
  UnitList:=nil;
  ExistingPkgs:=nil;
  AllPackages:=TStringListUTF8Fast.Create;
  AllPackages.Sorted:=True;
  AllPackages.Duplicates:=dupIgnore;
  CurPackages:=nil;
  CurUnitNames:=TStringListUTF8Fast.Create;
  try
    for CurClassID:=0 to ComponentClasses.Count-1 do
    begin
      CurCompClass:=ComponentClasses[CurClassID];
      CurRegComp:=IDEComponentPalette.FindRegComponent(CurCompClass);
      if CurRegComp is TPkgComponent then
      begin
        CurUnitName:='';
        CurUnitNames.Clear;
        CurCompReqs:=nil;
        if UnitList=nil then
        begin
          UnitList:=TStringListUTF8Fast.Create;
          UnitList.Sorted:=True;
          UnitList.Duplicates:=dupIgnore;
        end;
        if CurPackages=nil then
        begin
          CurPackages:=TStringListUTF8Fast.Create;
          CurPackages.Sorted:=True;
          CurPackages.Duplicates:=dupIgnore;
        end else
          CurPackages.Clear;

        if CurRegComp.ComponentClass<>nil then
        begin
          CurUnitName:=CurRegComp.ComponentClass.UnitName;
          CurCompReqs:=GetComponentRequirements(CurRegComp.ComponentClass);
        end;
        //DebugLn(['TPkgManager.GetUnitsAndDepsForComps: CurCompClass=',DbgSName(CurCompClass),' CurUnitName=',CurUnitName,' CurCompReq=',DbgSName(CurCompReqs)]);
        if CurUnitName='' then
          CurUnitName:=CurRegComp.GetUnitName;
        //Assert(CurUnitNames.IndexOf(CurUnitName)<0,
        //  'TPkgManager.GetUnitsAndDepsForComps: Name already in CurUnitNames.');
        CurUnitNames.Add(CurUnitName);
        if CurCompReqs<>nil then
          CurCompReqs.RequiredUnits(CurUnitNames);
        for CurUnitIdx:=0 to CurUnitNames.Count-1 do
        begin
          CurUnitName:=CurUnitNames[CurUnitIdx];
          UnitList.Add(CurUnitName);
          PkgFile:=PackageGraph.FindUnitInAllPackages(CurUnitName,true);
          AddPkgDep(PkgFile);
        end;
        // dont forget to add the designtime package
        AddPkgDep(TPkgComponent(CurRegComp).PkgFile);
        if CurCompReqs<>nil then
          CurCompReqs.RequiredPkgs(CurPackages);// let addons add extra pkgs
        AllPackages.AddStrings(CurPackages);
      end;
    end;  // for CurClassID:=...
    if AllPackages.Count>0 then
    begin
      ExistingPkgs:=TStringListUTF8Fast.Create;
      ExistingPkgs.Sorted:=True;
      ExistingPkgs.Duplicates:=dupIgnore;
      Helper:=TPackageIterateHelper.Create;
      try
        Helper.PackageNames:=AllPackages;
        Helper.PackageList:=ExistingPkgs;
        PackageGraph.IteratePackages(fpfSearchAllExisting,@Helper.AddDependency);
      finally
        Helper.Free;
      end;
      PackageList:=TPackagePackageArray.Create;
      for CurPackageIdx:=0 to ExistingPkgs.Count-1 do
        PackageList.Add(TLazPackageID(ExistingPkgs.Objects[CurPackageIdx]));
    end;
  finally
    CurUnitNames.Free;
    AllPackages.Free;
    CurPackages.Free;
    ExistingPkgs.Free;
  end;
  Result:=mrOk;
end;

function TPkgManager.FilterMissingDepsForUnit(const UnitFilename: string;
                     InputPackageList: TPackagePackageArray;
                     out OutputPackageList: TOwnerPackageArray): TModalResult;
// returns a list of packages that are not yet used by the project the unit belongs to
var
  UnitOwners: TFPList;
  UnitOwner: TObject;
  FirstDependency: TPkgDependency;
  CurOwnerID, CurPackageIdx: Integer;
  RequiredPackage: TLazPackageID;
begin
  Result:=mrCancel;
  OutputPackageList:=nil;
  if (InputPackageList=nil) or (InputPackageList.Count=0) then
    Exit(mrOK);
  UnitOwners:=GetOwnersOfUnit(UnitFilename);
  if (UnitOwners<>nil) then begin
    for CurOwnerID:=0 to UnitOwners.Count-1 do begin
      UnitOwner:=TObject(UnitOwners[CurOwnerID]);
      if UnitOwner is TProject then
        FirstDependency:=TProject(UnitOwner).FirstRequiredDependency
      else if UnitOwner is TLazPackage then
        FirstDependency:=TLazPackage(UnitOwner).FirstRequiredDependency
      else
        FirstDependency:=nil;
      for CurPackageIdx:=0 to InputPackageList.Count-1 do begin
        RequiredPackage:=InputPackageList.Items[CurPackageIdx];
        if (RequiredPackage<>nil)
        and (RequiredPackage<>UnitOwner)
        and (FindCompatibleDependencyInList(FirstDependency,pddRequires,RequiredPackage)=nil)
        and (PackageGraph.FindPackageProvidingName(FirstDependency,RequiredPackage.Name)=nil)
        then begin
          if OutputPackageList=nil then
            OutputPackageList:=TOwnerPackageArray.Create;
          OutputPackageList.AddObject(UnitOwner,RequiredPackage);
          //debugln(['TPkgManager.FilterMissingDependenciesForUnit A ',UnitOwner.ClassName,' ',RequiredPackage.Name]);
          //if TObject(OutputPackageList[OutputPackageList.Count-1])<>UnitOwner then RaiseGDBException('A');
          //if TObject(OutputPackageList.Objects[OutputPackageList.Count-1])<>RequiredPackage then RaiseGDBException('B');
        end;
      end;
    end;
    UnitOwners.Free;
  end else begin
    DebugLn(['Warning: (lazarus) [TPkgManager.FilterMissingDependenciesForUnit] unit has no owner: ',UnitFilename]);
  end;
  Result:=mrOk;
end;

function TPkgManager.GetOwnersOfUnit(const UnitFilename: string): TFPList;
begin
  Result:=GetPossibleOwnersOfUnit(UnitFilename,[]);
end;

procedure TPkgManager.ExtendOwnerListWithUsedByOwners(OwnerList: TFPList);
// use items (packages and projects) in OwnerList as leaves and create the
// list of all packages and projects using them.
// The result will be the topologically sorted list of projects and packages
// using the projects/packages in OwnerList, beginning with the top levels.
var
  AddedNonPackages: TFPList;

  procedure AddUsedByOwners(ADependenyOwner: TObject);
  var
    LazPackage: TLazPackage;
    Dependency: TPkgDependency;
  begin
    if ADependenyOwner is TProject then begin
      if AddedNonPackages.IndexOf(ADependenyOwner)>=0 then exit;
      AddedNonPackages.Add(ADependenyOwner);
      OwnerList.Add(ADependenyOwner);
    end else if ADependenyOwner is TLazPackage then begin
      LazPackage:=TLazPackage(ADependenyOwner);
      if lpfVisited in LazPackage.Flags then exit;
      LazPackage.Flags:=LazPackage.Flags+[lpfVisited];
      Dependency:=LazPackage.FirstUsedByDependency;
      while Dependency<>nil do begin
        AddUsedByOwners(Dependency.Owner);
        Dependency:=Dependency.NextUsedByDependency;
      end;
      OwnerList.Add(LazPackage);
    end;
  end;
  
var
  i: Integer;
  OldOwnerList: TFPList;
begin
  OldOwnerList:=TFPList.Create;
  for i:=0 to OwnerList.Count-1 do
    OldOwnerList.Add(OwnerList[i]);
  OwnerList.Clear;
  AddedNonPackages:=TFPList.Create;
  PackageGraph.MarkAllPackagesAsNotVisited;
  for i:=0 to OldOwnerList.Count-1 do
    AddUsedByOwners(TObject(OldOwnerList[i]));
  AddedNonPackages.Free;
  OldOwnerList.Free;
end;

function TPkgManager.GetSourceFilesOfOwners(OwnerList: TFPList): TStrings;

  procedure AddFile(TheOwner: TObject; const Filename: string);
  begin
    if Result=nil then
      Result:=TStringList.Create;
    Result.AddObject(Filename,TheOwner);
  end;

var
  CurOwner: TObject;
  CurPackage: TLazPackage;
  CurPkgFile: TPkgFile;
  CurProject: TProject;
  CurUnit: TUnitInfo;
  i: Integer;
  j: Integer;
begin
  Result:=nil;
  if OwnerList=nil then exit;
  for i:=0 to OwnerList.Count-1 do begin
    CurOwner:=TObject(OwnerList[i]);
    if CurOwner is TLazPackage then begin
      CurPackage:=TLazPackage(CurOwner);
      for j:=0 to CurPackage.FileCount-1 do begin
        CurPkgFile:=CurPackage.Files[j];
        if CurPkgFile.FileType in PkgFileUnitTypes then
          AddFile(CurOwner,CurPkgFile.GetFullFilename);
      end;
    end else if CurOwner is TProject then begin
      CurProject:=TProject(CurOwner);
      CurUnit:=CurProject.FirstPartOfProject;
      while CurUnit<>nil do begin
        if FilenameIsPascalSource(CurUnit.Filename) then
          AddFile(CurOwner,CurUnit.Filename);
        CurUnit:=CurUnit.NextPartOfProject;
      end;
    end;
  end;
end;

function TPkgManager.GetUnitsOfOwners(OwnerList: TFPList;
  Flags: TPkgIntfGatherUnitTypes): TStrings;
var
  Units: TFilenameToPointerTree;
  Graph: TUsesGraph;

  procedure AddUnit(ExpFilename: string);
  begin
    if not FileExistsCached(ExpFilename) then exit;
    if Units.Contains(ExpFilename) then exit;
    Units[ExpFilename]:=nil;
  end;

  procedure AddStartModule(ExpFilename: string);
  begin
    AddUnit(ExpFilename);
    Graph.AddStartUnit(ExpFilename);
  end;

var
  i, j: Integer;
  CurOwner: TObject;
  CurProject: TProject;
  CurPackage: TLazPackage;
  ProjFile: TLazProjectFile;
  PkgFile: TPkgFile;
  Completed: boolean;
  Node: TAVLTreeNode;
  UGUnit: TUGUnit;
begin
  debugln(['TPkgManager.GetUnitsOfOwners piguListed=',piguListed in Flags,' piguUsed=',piguUsed in Flags,' piguAllUsed=',piguAllUsed in Flags]);
  Result:=TStringList.Create;
  if (OwnerList=nil) or (OwnerList.Count=0) then exit;

  Units:=TFilenameToPointerTree.Create(false);
  Graph:=TUsesGraph.Create;
  try

    for i:=0 to OwnerList.Count-1 do
    begin
      CurOwner:=TObject(OwnerList[i]);
      if CurOwner is TProject then
      begin
        CurProject:=TProject(CurOwner);
        if (pfMainUnitIsPascalSource in CurProject.Flags)
        and (CurProject.MainUnitInfo<>nil) then
          AddStartModule(CurProject.MainUnitInfo.GetFullFilename);
        if piguListed in Flags then
        begin
          for j:=0 to CurProject.FileCount-1 do
          begin
            ProjFile:=CurProject.Files[j];
            if not FilenameIsPascalUnit(ProjFile.Filename) then continue;
            AddStartModule(ProjFile.GetFullFilename);
          end;
        end;
      end else if CurOwner is TLazPackage then
      begin
        CurPackage:=TLazPackage(CurOwner);
        if piguListed in Flags then
        begin
          for j:=0 to CurPackage.FileCount-1 do
          begin
            PkgFile:=CurPackage.Files[j];
            if not (PkgFile.FileType in PkgFileRealUnitTypes) then continue;
            AddStartModule(PkgFile.GetFullFilename);
          end;
        end;
      end;
    end;
    if Units.Count=0 then
    begin
      debugln(['TPkgManager.GetUnitsOfOwners no start modules END']);
      exit; // no start modules
    end;

    if [piguUsed,piguAllUsed]*Flags<>[] then
    begin
      // parse units recursively
      Graph.AddSystemUnitAsTarget;
      if piguAllUsed in Flags then
      begin
        // gather all used units
      end else if piguUsed in Flags then
      begin
        // ignore units of other packages
        for i:=0 to PackageGraph.Count-1 do
        begin
          CurPackage:=PackageGraph[i];
          if OwnerList.IndexOf(CurPackage)>=0 then continue;
          for j:=0 to CurPackage.FileCount-1 do
          begin
            PkgFile:=CurPackage.Files[j];
            if not (PkgFile.FileType in PkgFileRealUnitTypes) then continue;
            Graph.AddIgnoreUnit(PkgFile.GetFullFilename);
          end;
        end;
      end;

      // parse
      Graph.Parse(true,Completed);
      if Completed then ;

      // add parsed units
      Node:=Graph.FilesTree.FindLowest;
      while Node<>nil do
      begin
        UGUnit:=TUGUnit(Node.Data);
        if Graph.IgnoreFilesTree.Find(UGUnit)=nil then
          Units[UGUnit.Filename]:=nil;
        Node:=Node.Successor;
      end;
    end;

    Units.GetNames(Result);

  finally
    Graph.Free;
    Units.Free;
  end;
end;

function TPkgManager.GetPossibleOwnersOfUnit(const UnitFilename: string;
  Flags: TPkgIntfOwnerSearchFlags): TFPList;
var
  SrcDir: String;// ExtractFilePath(UnitFilename);

  procedure SearchInProject(AProject: TProject);
  var
    BaseDir: String;
    ProjectDirs: String;
    Add: Boolean;
  begin
    if AProject=nil then exit;
    Add:=false;

    // check if in units
    if not (piosfExcludeOwned in Flags) then begin
      //DebugLn(['SearchInProject ',AProject.ProjectInfoFile,' UnitFilename=',UnitFilename]);
      if (CompareFilenames(UnitFilename,AProject.ProjectInfoFile)=0)
      or (AProject.UnitInfoWithFilename(UnitFilename,[pfsfOnlyProjectFiles])<>nil)
      then
        Add:=true;
    end;

    BaseDir:=ExtractFilePath(AProject.ProjectInfoFile);

    // check if in virtual project
    if (not Add)
    and (piosfIncludeSourceDirectories in Flags)
    and (BaseDir='')
    and (ExtractFilePath(UnitFilename)='') then
      Add:=true;

    if (not Add)
    and (piosfIncludeSourceDirectories in Flags)
    and FilenameIsAbsolute(UnitFilename)
    and (BaseDir<>'') then begin
      // search in project source directories
      ProjectDirs:=AProject.LazCompilerOptions.OtherUnitFiles+';.';
      if IDEMacros.CreateAbsoluteSearchPath(ProjectDirs,BaseDir) then begin
        if FindPathInSearchPath(PChar(SrcDir),length(SrcDir),
          PChar(ProjectDirs),length(ProjectDirs))<>nil
        then
          Add:=true;
      end;
    end;

    if Add then
      Result.Add(AProject);
  end;

var
  PkgFile: TPkgFile;
  CurPackage: TLazPackage;
  i: Integer;
begin
  //DebugLn(['TPkgManager.GetPossibleOwnersOfUnit ',UnitFilename]);
  Result:=TFPList.Create;

  SrcDir:=ExtractFilePath(UnitFilename);

  // ToDo: create a cache

  SearchInProject(Project1);
  
  // find all packages owning file
  if piosfIncludeSourceDirectories in Flags then begin
    PackageGraph.FindPossibleOwnersOfUnit(UnitFilename,Result);
  end else if not (piosfExcludeOwned in Flags) then begin
    PkgFile:=PackageGraph.FindFileInAllPackages(UnitFilename,true,true);
    if (PkgFile<>nil) and (PkgFile.LazPackage<>nil) then
      Result.Add(PkgFile.LazPackage);
    //debugln(['TPkgManager.GetPossibleOwnersOfUnit ',UnitFilename,' ',PkgFile<>nil,' ',(PkgFile<>nil) and (PkgFile.LazPackage<>nil),' Result.Count=',Result.Count]);
    // check package source files (they usually do not have a TPkgFile)
    for i:=0 to PackageGraph.Count-1 do begin
      CurPackage:=PackageGraph.Packages[i];
      if ((CompareFilenames(UnitFilename,CurPackage.GetSrcFilename)=0)
          or (CompareFilenames(UnitFilename,CurPackage.Filename)=0))
      and (Result.IndexOf(CurPackage)<0) then
        Result.Add(CurPackage);
    end;
  end;

  // clean up
  if Result.Count=0 then
    FreeThenNil(Result);
end;

function TPkgManager.GetPackageOfCurrentSourceEditor(out APackage: TIDEPackage): TPkgFile;
var
  SrcEdit: TSourceEditor;
begin
  Result:=nil;
  APackage:=nil;
  SrcEdit:=SourceEditorManager.GetActiveSE;
  if SrcEdit=nil then exit;
  Result := TPkgFile(GetPackageOfSourceEditor(APackage, SrcEdit));
end;

function TPkgManager.GetPackageOfSourceEditor(out APackage: TIDEPackage;
  ASrcEdit: TObject): TLazPackageFile;
var
  Filename: String;
  i: Integer;
begin
  Result:=nil;
  APackage:=nil;
  if ASrcEdit=nil then exit;
  Filename:=TSourceEditor(ASrcEdit).FileName;
  Result:=SearchFile(Filename,[],nil);
  if Result<>nil then begin
    APackage:=Result.LazPackage;
    exit;
  end;
  for i:=0 to PackageGraph.Count-1 do begin
    APackage:=PackageGraph[i];
    if CompareFilenames(TLazPackage(APackage).GetSrcFilename,FileName)=0 then
      exit;
  end;
  APackage:=nil;
end;

function TPkgManager.IsOwnerDependingOnPkg(AnOwner: TObject;
  const PkgName: string; out DependencyOwner: TObject): boolean;
var
  FirstDep: TPkgDependency;
  Dep: TPkgDependency;
begin
  Result:=false;
  DependencyOwner:=nil;
  if (AnOwner=nil) or (PkgName='') then exit;
  if AnOwner is TProject then
    FirstDep:=TProject(AnOwner).FirstRequiredDependency
  else if AnOwner is TLazPackage then begin
    if CompareDottedIdentifiers(PChar(TLazPackage(AnOwner).Name),
      PChar(PkgName))=0
    then begin
      DependencyOwner:=AnOwner;
      exit(true);
    end;
    FirstDep:=TLazPackage(AnOwner).FirstRequiredDependency;
  end else
    exit(false);
  if PackageGraph=nil then exit;
  Dep:=PackageGraph.FindDependencyRecursively(FirstDep,PkgName);
  if Dep=nil then exit;
  DependencyOwner:=Dep.Owner;
  Result:=true;
end;

procedure TPkgManager.GetRequiredPackages(AnOwner: TObject; out PkgList: TFPList;
  Flags: TPkgIntfRequiredFlags);
var
  Dependency: TPkgDependency;
begin
  PkgList:=nil;
  Dependency:=nil;
  if AnOwner is TProject then
    Dependency:=TProject(AnOwner).FirstRequiredDependency
  else if AnOwner is TLazPackage then
    Dependency:=TLazPackage(AnOwner).FirstRequiredDependency
  else if AnOwner=PkgBoss then
    Dependency:=PackageGraph.FirstInstallDependency;
  if Dependency=nil then exit;
  PackageGraph.GetAllRequiredPackages(nil,Dependency,PkgList,Flags);
end;

function TPkgManager.AddDependencyToOwners(OwnerList: TFPList;
  APackage: TIDEPackage; OnlyTestIfPossible: boolean): TModalResult;
var
  i: Integer;
  Item: TObject;
  NewDependency: TPkgDependency;
  ADependency: TPkgDependency;
  r: TModalResult;
  Pkg: TLazPackage;
begin
  Pkg:=APackage as TLazPackage;
  if not OnlyTestIfPossible then begin
    Result:=AddDependencyToOwners(OwnerList,APackage,true);
    if Result<>mrOk then exit;
  end;

  Result:=mrCancel;
  for i:=0 to OwnerList.Count-1 do begin
    Item:=TObject(OwnerList[i]);
    if Item=APackage then continue;
    if Item is TProject then begin
      Result:=AddProjectDependency(TProject(Item),Pkg,OnlyTestIfPossible);
      if Result<>mrOk then exit;
    end
    else if Item is TLazPackage then begin
      NewDependency:=TPkgDependency.Create;
      try
        NewDependency.PackageName:=APackage.Name;
        r:=TPkgFileCheck.AddingDependency(TLazPackage(Item),NewDependency,false);
        if r=mrCancel then exit;
        if (not OnlyTestIfPossible) and (r<>mrIgnore) then begin
          ADependency:=NewDependency;
          NewDependency:=nil;
          PackageGraph.AddDependencyToPackage(TLazPackage(Item),ADependency);
        end;
      finally
        NewDependency.Free;
      end;
    end;
  end;
  Result:=mrOk;
end;

function TPkgManager.DoOpenPkgFile(PkgFile: TPkgFile): TModalResult;
var
  Filename: String;
begin
  if (PkgFile.FileType=pftVirtualUnit) then
    Filename:=FindVirtualUnitSource(PkgFile)
  else
    Filename:=PkgFile.GetFullFilename;
  if Filename<>'' then
    Result:=MainIDE.DoOpenEditorFile(Filename,-1,-1,
                                     [ofOnlyIfExists,ofAddToRecent,ofRegularFile]);
end;

function TPkgManager.FindVirtualUnitSource(PkgFile: TPkgFile): string;
begin
  Result:='';
  if (PkgFile.FileType=pftVirtualUnit)
  and (PkgFile.LazPackage<>nil)
  and (not FileExistsUTF8(PkgFile.Filename)) then begin
    Result:=MainIDE.FindSourceFile(PkgFile.GetShortFilename(false),
                                   PkgFile.LazPackage.Directory,[]);
  end;
end;

function TPkgManager.SearchFile(const AFilename: string;
  SearchFlags: TSearchIDEFileFlags; InObject: TObject): TPkgFile;
var
  APackage: TLazPackage;
  CurFilename: String;
begin
  if InObject is TLazPackage then begin
    APackage:=TLazPackage(InObject);
    CurFilename:=AFilename;
    APackage.ShortenFilename(CurFilename,true);
    Result:=APackage.SearchShortFilename(CurFilename,SearchFlags);
    if Result<>nil then exit;
  end;
  if not (siffDoNotCheckAllPackages in SearchFlags) then begin
    Result := PackageGraph.FindFileInAllPackages(AFilename, True, True);
    if Result<>nil then exit;
  end;
  Result:=nil;
end;

function TPkgManager.SearchUnitInDesigntimePackages(const AnUnitName: string;
  InObject: TObject): TPkgFile;
var
  i: Integer;
  APackage: TLazPackage;
begin
  if InObject is TLazPackage then begin
    APackage:=TLazPackage(InObject);
    Result:=APackage.FindUnit(AnUnitName);
    if Result<>nil then exit;
  end;
  for i:=0 to PackageGraph.Count-1 do begin
    APackage:=PackageGraph[i];
    if APackage.Installed=pitNope then continue;
    Result:=APackage.FindUnit(AnUnitName);
    if Result<>nil then exit;
  end;
  Result:=nil;
end;

function TPkgManager.ShowFindInPackageFilesDlg(APackage: TLazPackage): TModalResult;
var
  Dlg: TLazFindInFilesDialog;
begin
  Result:=mrOk;
  Dlg:=FindInFilesDialog;
  Dlg.FindInSearchPath(APackage.SourceDirectories.CreateSearchPathFromAllFiles);
end;

function TPkgManager.AddDependencyToUnitOwners(const OwnedFilename,
  RequiredUnitname: string): TModalResult;
var
  OwnersList: TFPList;
  RequiredPkgFile: TPkgFile;
  RequiredPkg: TLazPackage;
begin
  Result:=mrCancel;
  //DebugLn(['TPkgManager.AddDependencyToUnitOwners RequiredUnitname=',RequiredUnitname,' OwnedFilename=',OwnedFilename]);

  // find needed package
  RequiredPkgFile:=SearchUnitInDesigntimePackages(RequiredUnitName,nil);
  if RequiredPkgFile=nil then begin
    DebugLn(['Note: (lazarus) [TPkgManager.AddDependencyToUnitOwners] unit not in designtime package: ',RequiredUnitName]);
    exit;
  end;
  RequiredPkg:=RequiredPkgFile.LazPackage;

  // find owners of unit (package or project)
  OwnersList:=GetOwnersOfUnit(OwnedFilename);
  try
    if (OwnersList=nil) or (OwnersList.Count=0) then begin
      DebugLn(['Note: (lazarus) TPkgManager.AddDependencyToUnitOwners Owner not found of unit ',OwnedFilename]);
      exit;
    end;
    // add package dependency
    //DebugLn(['TPkgManager.AddDependencyToUnitOwners ',dbgsName(TObject(OwnersList[0])),' ',RequiredPkg.IDAsString]);
    RequiredPkg:=TLazPackage(RedirectPackageDependency(RequiredPkg));
    Result:=AddDependencyToOwners(OwnersList,RequiredPkg,false);
  finally
    OwnersList.Free;
  end;
end;

procedure TPkgManager.GetPackagesChangedOnDisk(out ListOfPackages: TStringList;
  IgnoreModifiedFlag: boolean);
begin
  if PackageGraph=nil then exit;
  PackageGraph.GetPackagesChangedOnDisk(ListOfPackages, IgnoreModifiedFlag);
end;

function TPkgManager.RevertPackages(APackageList: TStringList): TModalResult;
var
  i: Integer;
  APackage: TLazPackage;
  Filename: String;
begin
  if APackageList=nil then exit(mrOk);
  for i:=0 to APackageList.Count-1 do begin
    APackage:=TLazPackage(APackageList.Objects[i]);
    Filename:=APackageList[i];
    if Filename='' then
      Filename:=APackage.Filename;
    debugln(['Hint: (lazarus) [TPkgManager.RevertPackages] BEFORE Old=',APackage.Filename,' New=',Filename,' ',FileExistsCached(Filename)]);
    if FileExistsCached(Filename) then
      Result:=DoOpenPackageFile(Filename,[pofRevert],true)
    else begin
      APackage.LPKSource:=nil;
      APackage.Missing:=true;
      Result:=mrCancel;
    end;
    debugln(['Hint: (lazarus) [TPkgManager.RevertPackages] AFTER ',PackageGraph.FindPackageWithFilename(Filename)<>nil]);
    if Result=mrAbort then exit;
  end;
  Result:=mrOk;
end;

function TPkgManager.DoAddActiveUnitToAPackage: TModalResult;
var
  ActiveSourceEditor: TSourceEditorInterface;
  ActiveUnitInfo: TUnitInfo;
  PkgFile: TPkgFile;
  Filename: String;
begin
  MainIDE.GetCurrentUnitInfo(ActiveSourceEditor,ActiveUnitInfo);
  if ActiveSourceEditor=nil then exit(mrAbort);

  Filename:=ActiveUnitInfo.Filename;

  // check if filename is absolute
  if ActiveUnitInfo.IsVirtual or (not FileExistsUTF8(Filename)) then begin
    Result:=IDEMessageDialog(lisPkgMangFileNotSaved,
      lisPkgMangPleaseSaveTheFileBeforeAddingItToAPackage, mtWarning,[mbCancel]);
    exit;
  end;
  
  // check if file is part of project
  if ActiveUnitInfo.IsPartOfProject then begin
    Result:=IDEMessageDialog(lisPkgMangFileIsInProject,
      Format(lisPkgMangWarningTheFileBelongsToTheCurrentProject,[Filename,LineEnding]),
      mtWarning,[mbIgnore,mbCancel]);
    if Result<>mrIgnore then exit;
  end;

  // check if file is already in a package
  PkgFile:=PackageGraph.FindFileInAllPackages(Filename,true,true);
  if PkgFile<>nil then begin
    Result:=IDEMessageDialog(lisPkgMangFileIsAlreadyInPackage,
      Format(lisPkgMangTheFileIsAlreadyInThePackage,
             [Filename, LineEnding, PkgFile.LazPackage.IDAsString]),
      mtWarning,[mbIgnore,mbCancel]);
    if Result<>mrIgnore then exit;
  end;

  Result:=ShowAddFileToAPackageDlg(Filename);
end;

function TPkgManager.DoNewPackageComponent: TModalResult;
var
  APackage: TLazPackage;
  SaveFlags: TPkgSaveFlags;
  CurEditor: TPackageEditorForm;
begin
  Result:=ShowNewPkgComponentDialog(APackage);
  if Result<>mrOk then exit;
  SaveFlags:=[];
  if APackage=nil then begin
    // create new package
    // create a new package with standard dependencies
    APackage:=PackageGraph.CreateNewPackage(constNewPackageName);
    PackageGraph.AddDependencyToPackage(APackage,
                  PackageGraph.IDEIntfPackage.CreateDependencyWithOwner(APackage));
    APackage.Modified:=false;
    Include(SaveFlags,psfSaveAs);
  end;
  // open a package editor
  CurEditor:=PackageEditors.OpenEditor(APackage,true);
  // save
  Result:=DoSavePackage(APackage,SaveFlags);
  if Result<>mrOk then exit;
  Result:=CurEditor.ShowNewCompDialog;  // show new component dialog
end;

function TPkgManager.SavePackageFiles(APackage: TLazPackage): TModalResult;
var
  i: Integer;
  AFile: TPkgFile;
  AFilename: String;
  SaveFlags: TSaveFlags;
  SrcEdit: TSourceEditor;
begin
  Result:=mrOk;
  for i:=0 to APackage.FileCount-1 do begin
    AFile:=APackage.Files[i];
    if AFile.FileType=pftVirtualUnit then continue;
    AFilename:=AFile.Filename;
    if System.Pos('$(',AFilename)>0 then begin
      // filename contains macros -> skip
      //debugln(['TPkgManager.SavePackageFiles macros ',AFilename]);
      continue;
    end;
    // check if open in editor
    SrcEdit:=SourceEditorManager.SourceEditorIntfWithFilename(AFilename);
    if SrcEdit=nil then
    begin
      // not open in source editor => skip
      //debugln(['TPkgManager.SavePackageFiles no src edit ',AFilename]);
      continue;
    end;
    SaveFlags:=[sfCanAbort];
    if not FilenameIsAbsolute(AFilename) then
      SaveFlags:=[sfSaveAs];
    Result:=LazarusIDE.DoSaveEditorFile(SrcEdit,SaveFlags);
    if Result=mrIgnore then Result:=mrOk;
    if Result<>mrOk then begin
      debugln(['Error: (lazarus) [TPkgManager.SavePackageFiles] failed writing "',AFilename,'"']);
      exit;
    end;
  end;
end;

function TPkgManager.WarnAboutMissingPackageFiles(APackage: TLazPackage): TModalResult;
var
  i: Integer;
  AFile: TPkgFile;
  AFilename: String;
begin
  Result:=mrOk;
  for i:=0 to APackage.FileCount-1 do begin
    AFile:=APackage.Files[i];
    if AFile.FileType=pftVirtualUnit then continue;
    AFilename:=AFile.GetFullFilename;
    if System.Pos('$(',AFilename)>0 then begin
      // filename contains macros -> skip
      continue;
    end;
    if FilenameIsAbsolute(AFilename) and FileExistsCached(AFilename) then
      continue;
    Result:=IDEQuestionDialog(lisPkgSysPackageFileNotFound,
      Format(lisPkgMangTheFileOfPackageWasNotFound, [AFilename, APackage.IDAsString]),
      mtWarning, [mrIgnore,mrAbort]);
    if Result<>mrAbort then
      Result:=mrOk;
    // one warning is enough
    exit;
  end;
end;

function TPkgManager.AddPackageDependency(APackage: TLazPackage;
  const ReqPackage: string; OnlyTestIfPossible: boolean): TModalResult;
var
  NewDependency: TPkgDependency;
  ADependency: TPkgDependency;
begin
  NewDependency:=TPkgDependency.Create;
  try
    NewDependency.PackageName:=ReqPackage;
    Result:=TPkgFileCheck.AddingDependency(APackage,NewDependency,false);
    if Result=mrIgnore then exit(mrOk);
    if Result<>mrOk then exit;
    if not OnlyTestIfPossible then begin
      ADependency:=NewDependency;
      NewDependency:=nil;
      PackageGraph.AddDependencyToPackage(APackage,ADependency);
      Result:=mrOk;
    end;
  finally
    NewDependency.Free;
  end;
end;

function TPkgManager.ApplyDependency(CurDependency: TPkgDependency): TModalResult;
// apply
var
  OldPkg: TLazPackage;
  PkgEdit: TPackageEditorForm;
begin
  Result:=mrOk;
  OldPkg:=CurDependency.RequiredPackage;
  if (OldPkg<>nil) and CurDependency.IsCompatible(OldPkg) then
    exit(mrOk);

  PkgEdit:=PackageEditors.FindEditor(OldPkg);
  if PkgEdit<>nil then
  begin
    if PkgEdit.CanCloseEditor<>mrOk then
      exit(mrCancel);
  end;

  // Try to load the package again. Min/max version may have changed.
  CurDependency.LoadPackageResult := lprUndefined;
  // This calls UpdateRequiredPackages from PackageGraph.OnEndUpdate,
  //  and also updates all package editors which is useless here.
  if PackageGraph.OpenDependency(CurDependency, False, OldPkg)<>lprSuccess then
    Result:=mrCancel;
  //fForcedFlags:=[pefNeedUpdateRequiredPkgs];
end;

function TPkgManager.GetPackageOfEditorItem(Sender: TObject): TIDEPackage;
begin
  Result:=nil;
  if Sender is TIDEMenuItem then
    Sender:=TIDEMenuItem(Sender).MenuItem;
  while (Sender is TMenuItem) and (TMenuItem(Sender).Parent<>nil) do
    Sender:=TMenuItem(Sender).Parent;
  if (Sender is TMenuItem) and (TMenuItem(Sender).Menu<>nil) then
    Sender:=TMenuItem(Sender).Menu;
  if (Sender is TComponent) and (TComponent(Sender).Owner is TCustomForm) then
    Sender:=TCustomForm(TComponent(Sender).Owner);
  if Sender is TPackageEditorForm then
    Result:=TPackageEditorForm(Sender).LazPackage;
end;

function TPkgManager.DoInstallPackage(APackage: TLazPackage): TModalResult;
var
  PkgList: TFPList;
  FPMakeList: TFPList;
  
  function GetPkgListIndex(APackage: TLazPackage): integer;
  begin
    Result:=PkgList.Count-1;
    while (Result>=0) and (TLazPackage(PkgList[Result])<>APackage) do
      dec(Result);
  end;

  function WarnForSuspiciousPackage(APackage: TLazPackage): TModalResult;
  var
    IgnorePath: String;
    UnitPath: String;
  begin
    if APackage.UsageOptions.IncludePath<>'' then
    begin
      IgnorePath:='InstallPkgAddsIncPath/'+APackage.Filename;
      if InputHistories.Ignores.Find(IgnorePath)=nil then
      begin
        Result:=IDEQuestionDialog(lisSuspiciousIncludePath,
          Format(lisThePackageAddsThePathToTheIncludePathOfTheIDEThisI, [
            APackage.IDAsString, dbgstr(APackage.UsageOptions.IncludePath), LineEnding]
          ),
          mtWarning, [mrYes, lisContinue,
                      mrYesToAll, lisContinueAndDoNotAskAgain,
                      mrCancel]);
        case Result of
        mrYes: ;
        mrYesToAll:
          InputHistories.Ignores.Add(IgnorePath,iiidForever);
        else
          exit(mrCancel);
        end;
      end;
    end;
    UnitPath:=Trim(GetForcedPathDelims(APackage.UsageOptions.UnitPath));
    while (UnitPath<>'') and (UnitPath[1]=';') do
      UnitPath:=copy(UnitPath,2,Length(UnitPath));
    while (UnitPath<>'') and (RightStr(UnitPath,1)=';') do
      UnitPath:=copy(UnitPath,1,Length(UnitPath)-1);
    UnitPath:=ChompPathDelim(TrimFilename(UnitPath));
    if SysUtils.CompareText(UnitPath,'$(PkgOutDir)')<>0 then
    begin
      IgnorePath:='InstallPkgAddsUnitPath/'+APackage.Filename;
      if InputHistories.Ignores.Find(IgnorePath)=nil then
      begin
        Result:=IDEQuestionDialog(lisSuspiciousUnitPath,
          Format(lisThePackageAddsThePathToTheUnitPathOfTheIDEThisIsPr, [
            APackage.IDAsString, dbgstr(APackage.UsageOptions.UnitPath), LineEnding]),
          mtWarning, [mrYes, lisContinue,
                      mrYesToAll, lisContinueAndDoNotAskAgain,
                      mrCancel]);
        case Result of
        mrYes: ;
        mrYesToAll:
          InputHistories.Ignores.Add(IgnorePath,iiidForever);
        else
          exit(mrCancel);
        end;
      end;
    end;
    Result:=mrOk;
  end;
  
var
  Dependency: TPkgDependency;
  i: Integer;
  s: String;
  NeedSaving: Boolean;
  RequiredPackage: TLazPackage;
  BuildIDEFlags: TBuildLazarusFlags;
  Msg: string;
  Btns: TMsgDlgButtons;
  ConflictDep: TPkgDependency;
begin
  if not MainIDE.DoResetToolStatus([rfInteractive]) then exit(mrCancel);

  try
    BuildBoss.SetBuildTargetIDE;

    PackageGraph.BeginUpdate(true);
    PkgList:=nil;
    FPMakeList:=nil;
    try
      PackageGraph.ParseBasePackages(false);

      // check if package is designtime package
      if APackage.PackageType in [lptRunTime,lptRunTimeOnly] then begin
        Btns:=[mbAbort];
        if APackage.PackageType=lptRunTime then
          Include(Btns,mbIgnore);
        Result:=IDEMessageDialog(lisPkgMangPackageIsNoDesigntimePackage,
          Format(lisPkgMangThePackageIsARuntimeOnlyPackageRuntimeOnlyPackages,
                 [APackage.IDAsString, LineEnding]),
          mtError,Btns);
        if Result<>mrIgnore then exit;
      end;
      // check if package requires a runtime only package
      ConflictDep:=PackageGraph.FindRuntimePkgOnlyRecursively(
        APackage.FirstRequiredDependency);
      if ConflictDep<>nil then begin
        IDEQuestionDialog(lisNotADesigntimePackage,
          Format(lisThePackageCanNotBeInstalledBecauseItRequiresWhichI, [
            APackage.Name, ConflictDep.AsString]),
          mtError,
          [mrCancel]
          );
        exit;
      end;

      // save package
      if APackage.IsVirtual or APackage.Modified then begin
        Result:=DoSavePackage(APackage,[]);
        if Result<>mrOk then exit;
      end;

      // check consistency
      Result:=CheckPackageGraphForCompilation(APackage,nil,
                              EnvironmentOptions.GetParsedLazarusDirectory,false);
      if Result<>mrOk then exit;

      // get all required packages, which will also be auto installed
      APackage.GetAllRequiredPackages(PkgList,FPMakeList,false);
      if PkgList=nil then PkgList:=TFPList.Create;

      // remove packages already marked for installation
      for i:=PkgList.Count-1 downto 0 do begin
        RequiredPackage:=TLazPackage(PkgList[i]);
        if (RequiredPackage.AutoInstall<>pitNope) then
          PkgList.Delete(i);
      end;

      // now PkgList contains only the required packages that were added to the
      // list of installation packages
      // => show the user the list
      if PkgList.Count>0 then begin
        s:='';
        for i:=0 to PkgList.Count-1 do begin
          RequiredPackage:=TLazPackage(PkgList[i]);
          s:=s+RequiredPackage.IDAsString+LineEnding;
        end;
        if PkgList.Count=0 then
          Msg:=Format(lisPkgMangInstallingThePackageWillAutomaticallyInstallThePac,
                      [APackage.IDAsString])
        else
          Msg:=Format(lisPkgMangInstallingThePackageWillAutomaticallyInstallThePac2,
                      [APackage.IDAsString]);
        Result:=IDEMessageDialog(lisPkgMangAutomaticallyInstalledPackages,
          Msg+LineEnding+s,mtConfirmation,[mbOk,mbCancel]);
        if Result<>mrOk then exit;
      end;

      // warn for packages with suspicious settings
      Result:=WarnForSuspiciousPackage(APackage);
      if Result<>mrOk then exit;
      for i:=0 to PkgList.Count-1 do begin
        RequiredPackage:=TLazPackage(PkgList[i]);
        Result:=WarnForSuspiciousPackage(RequiredPackage);
        if Result<>mrOk then exit;
      end;

      // add packages to auto installed packages
      if GetPkgListIndex(APackage)<0 then
        PkgList.Add(APackage);
      NeedSaving:=false;
      for i:=0 to PkgList.Count-1 do begin
        RequiredPackage:=TLazPackage(PkgList[i]);
        if RequiredPackage.AutoInstall=pitNope then begin
          RequiredPackage.AutoInstall:=pitStatic;
          Dependency:=RequiredPackage.CreateDependencyWithOwner(Self);
          Dependency.AddToList(PackageGraph.FirstInstallDependency,pddRequires);
          PackageGraph.OpenDependency(Dependency,false);
          NeedSaving:=true;
        end;
      end;
    finally
      PackageGraph.EndUpdate;
      PkgList.Free;
      FPMakeList.Free;
    end;

    if NeedSaving then begin
      PackageGraph.SortAutoInstallDependencies;
      SaveAutoInstallDependencies;
    end;

    // save IDE build configs, so user can build IDE on command line
    BuildIDEFlags:=[blfDontClean,blfOnlyIDE];
    Result:=MainIDE.DoSaveBuildIDEConfigs(BuildIDEFlags);
    if Result<>mrOk then exit;
  finally
    BuildBoss.SetBuildTargetProject1;
  end;

  // ask user to rebuild Lazarus now
  Result:=IDEMessageDialog(lisPkgMangRebuildLazarus,
    Format(lisPkgMangThePackageWasMarkedForInstallationCurrentlyLazarus,
           [APackage.IDAsString, LineEnding, LineEnding+LineEnding]),
    mtConfirmation,[mbYes,mbNo]);
  if Result<>mrYes then begin
    Result:=mrOk;
    exit;
  end;

  // rebuild Lazarus
  Result:=MainIDE.DoBuildLazarus(BuildIDEFlags);
  if Result<>mrOk then exit;

  Result:=mrOk;
end;

function TPkgManager.DoUninstallPackage(APackage: TLazPackage;
  Flags: TPkgUninstallFlags; ShowAbort: boolean): TModalResult;
var
  DependencyPath: TFPList;
  ParentPackage: TLazPackage;
  Dependency: TPkgDependency;
  BuildIDEFlags: TBuildLazarusFlags;
begin
  if (APackage.Installed=pitNope) and (APackage.AutoInstall=pitNope) then exit;
  
  // check if package is required by auto install package
  DependencyPath:=PackageGraph.FindAutoInstallDependencyPath(APackage);
  if DependencyPath<>nil then begin
    DoShowPackageGraphPathList(DependencyPath);
    ParentPackage:=TLazPackage(DependencyPath[0]);
    Result:=IDEMessageDialogAb(lisPkgMangPackageIsRequired,
      Format(lisPkgMangThePackageIsRequiredByWhichIsMarkedForInstallation,
             [APackage.IDAsString, ParentPackage.IDAsString, LineEnding]),
      mtError,[mbCancel],ShowAbort);
    exit;
  end;

  // check if package is a lazarus base package
  if PackageGraph.IsCompiledInBasePackage(APackage.Name) then begin
    Result:=IDEMessageDialogAb(lisUninstallImpossible,
      Format(lisThePackageCanNotBeUninstalledBecauseItIsNeededByTh,[APackage.Name]),
      mtError,[mbCancel],ShowAbort);
    exit;
  end;

  // confirm uninstall package
  if not (puifDoNotConfirm in Flags) then begin
    Result:=IDEMessageDialogAb(lisPkgMangUninstallPackage,
      Format(lisPkgMangUninstallPackage2, [APackage.IDAsString]),
      mtConfirmation,[mbYes,mbCancel],ShowAbort);
    if Result<>mrYes then exit;
  end;
  
  PackageGraph.BeginUpdate(true);
  try
    // save package
    if APackage.IsVirtual or APackage.Modified then begin
      Result:=DoSavePackage(APackage,[]);
      if Result<>mrOk then exit;
    end;

    PackageGraph.ParseBasePackages(false);

    // remove package from auto installed packages
    if APackage.AutoInstall<>pitNope then begin
      APackage.AutoInstall:=pitNope;
      Dependency:=FindCompatibleDependencyInList(PackageGraph.FirstInstallDependency,
                                                 pddRequires,APackage);
      if Dependency<>nil then begin
        Dependency.RemoveFromList(PackageGraph.FirstInstallDependency,pddRequires);
        Dependency.Free;
        PackageGraph.SortAutoInstallDependencies;
      end;
      SaveAutoInstallDependencies;
    end;

    // save IDE build configs, so user can build IDE on command line
    BuildIDEFlags:=[blfDontClean,blfOnlyIDE];
    Result:=MainIDE.DoSaveBuildIDEConfigs(BuildIDEFlags);
    if Result<>mrOk then exit;

    if not (puifDoNotBuildIDE in Flags) then begin
      // ask user to rebuilt Lazarus now
      Result:=IDEMessageDialog(lisPkgMangRebuildLazarus,
        Format(lisPkgMangThePackageWasMarkedCurrentlyLazarus,
               [APackage.IDAsString, LineEnding, LineEnding+LineEnding]),
        mtConfirmation,[mbYes,mbNo]);
      if Result<>mrYes then begin
        Result:=mrOk;
        exit;
      end;

      // rebuild Lazarus
      Result:=MainIDE.DoBuildLazarus(BuildIDEFlags);
      if Result<>mrOk then exit;
    end;
  finally
    PackageGraph.EndUpdate;
  end;
  Result:=mrOk;
end;

function TPkgManager.UninstallPackage(APackage: TIDEPackage; ShowAbort: boolean): TModalResult;
begin
  Assert(APackage is TLazPackage, 'TPkgManager.DoUninstallPackage: APackage is not TLazPackage');
  Result := DoUninstallPackage(TLazPackage(APackage), [puifDoNotConfirm, puifDoNotBuildIDE], ShowAbort);
end;

function TPkgManager.CheckInstallPackageList(InstallPkgIDList: TObjectList;
  UninstallPkgIDList: TObjectList; Flags: TPkgInstallInIDEFlags): boolean;
var
  NewFirstAutoInstallDependency: TPkgDependency;

  procedure DeleteDependency(ADependency: TPkgDependency);
  var
    i: Integer;
    PkgID: TLazPackageID;
    PkgName: string;
  begin
    PkgName := ADependency.PackageName; // DeleteDependencyInList destroys ADependency -> don't use it anymore!
    DeleteDependencyInList(ADependency,NewFirstAutoInstallDependency,pddRequires);
    if piiifRemoveConflicts in Flags then
      for i:=InstallPkgIDList.Count-1 downto 0 do begin
        PkgID:=TLazPackageID(InstallPkgIDList[i]);
        if SysUtils.CompareText(PkgID.Name,PkgName)=0 then
          InstallPkgIDList.Delete(i); // PkgID is automatically destroyed
      end;
  end;

var
  PkgList, PkgPath, Dependencies: TFPList;
  i, j: Integer;
  APackage: TLazPackage;
  ADependency: TPkgDependency;
  NextDependency: TPkgDependency;
  SaveFlags: TPkgSaveFlags;
  ConflictDep: TPkgDependency;
  PkgID: TLazPackageID;
  s: String;
begin
  Result:=false;
  PkgList:=nil;
  Dependencies:=TFPList.Create;
  try
    // create new auto install dependency InstallPkgIDList
    ListPkgIDToDependencyList(InstallPkgIDList,NewFirstAutoInstallDependency,
                              pddRequires,Self,true);

    // load all required packages
    if LoadDependencyList(NewFirstAutoInstallDependency,piiifQuiet in Flags)<>mrOk then exit;

    // remove all top level runtime packages from the list
    // Note: it's ok if a designtime package uses a runtime package
    ADependency:=NewFirstAutoInstallDependency;
    while ADependency<>nil do begin
      NextDependency:=ADependency.NextRequiresDependency;
      if (ADependency.RequiredPackage<>nil) then begin
        if (ADependency.RequiredPackage.PackageType in [lptRunTime,lptRunTimeOnly])
        then begin
          // top level dependency on runtime package => delete
          DeleteDependency(ADependency);
        end else begin
          ConflictDep:=PackageGraph.FindRuntimePkgOnlyRecursively(
            ADependency.RequiredPackage.FirstRequiredDependency);
          //debugln(['TPkgManager.CheckInstallPackageList ',ADependency.RequiredPackage.Name,' ',ConflictDep<>nil]);
          if ConflictDep<>nil then begin
            if piiifRemoveConflicts in Flags then begin
              // can remove conflict
              if not (piiifQuiet in Flags)
              and (IDEQuestionDialog(lisNotADesigntimePackage,
                Format(lisThePackageCanNotBeInstalledBecauseItRequiresWhichI, [
                  ADependency.RequiredPackage.Name, ConflictDep.AsString]),
                mtError,
                [mrYes, Format(lisUninstall, [ADependency.RequiredPackage.Name]), mrCancel]
                )<>mrYes)
              then
                exit;
            end else begin
              // can not remove conflict
              if not (piiifQuiet in Flags) then
                IDEQuestionDialog(lisNotADesigntimePackage,
                  Format(lisThePackageCanNotBeInstalledBecauseItRequiresWhichI, [
                    ADependency.RequiredPackage.Name, ConflictDep.AsString]),
                  mtError,[mrCancel]);
              exit;
            end;
            // dependency needs a runtime only package => delete
            DeleteDependency(ADependency);
          end;
        end;
      end;
      ADependency:=NextDependency;
    end;

    PackageGraph.GetAllRequiredPackages(nil,NewFirstAutoInstallDependency,PkgList);

    // try save all modified packages
    for i:=0 to PkgList.Count-1 do begin
      APackage:=TLazPackage(PkgList[i]);
      if (not APackage.UserReadOnly)
      and (APackage.IsVirtual or APackage.Modified) then begin
        SaveFlags:=[];
        if DoSavePackage(APackage,SaveFlags)<>mrOk then exit;
      end;
    end;

    if UninstallPkgIDList<>nil then
    begin
      // check uninstall dependencies
      for i:=0 to UninstallPkgIDList.Count-1 do
      begin
        PkgID:=TLazPackageID(UninstallPkgIDList[i]);
        APackage:=PackageGraph.FindPackageWithID(PkgID);
        if APackage=nil then continue;
        if PkgList.IndexOf(APackage)<0 then continue;
        // this uninstall package is needed by the install packages
        // -> search which ones
        Dependencies.Clear;
        s:='';
        ADependency:=NewFirstAutoInstallDependency;
        while ADependency<>nil do begin
          PkgPath:=PackageGraph.FindPath(ADependency.RequiredPackage,nil,APackage.Name);
          if PkgPath<>nil then
          begin
            PkgPath.Free;
            s:=s+ADependency.PackageName+sLineBreak;
            Dependencies.Add(ADependency);
          end;
          ADependency:=ADependency.NextRequiresDependency;
        end;
        if s='' then continue;
        case IDEQuestionDialog(lisUninstallFail,
           Format(lisThePackageIsUsedBy, [APackage.IDAsString])+sLineBreak +s,
           mtConfirmation, [mrYes, lisUninstallThemToo, mrCancel]
           ) of
        mrYes:
          begin
            for j:=0 to Dependencies.Count-1 do
              DeleteDependency(TPkgDependency(Dependencies[j]));
          end;
        else
          exit(false);
        end;
      end;
    end;

    Result:=true;
  finally
    FreeDependencyList(NewFirstAutoInstallDependency,pddRequires);
    PkgList.Free;
    Dependencies.Free;
  end;
end;

function TPkgManager.InstallPackages(PkgIdList: TObjectList;
  Flags: TPkgInstallInIDEFlags): TModalResult;

  procedure CreateChangeReport(
    OldDependencyList, NewDependencyList: TPkgDependency; Report: TStrings);
  var
    CurDependency: TPkgDependency;
    OldDependency: TPkgDependency;
    NewDependency: TPkgDependency;
    s: String;
  begin
    // list all packages, that will be installed
    CurDependency:=NewDependencyList;
    while CurDependency<>nil do begin
      s:=CurDependency.AsString;
      OldDependency:=FindDependencyByNameInList(OldDependencyList,pddRequires,
                                                CurDependency.PackageName);
      if OldDependency=nil then begin
        // newly installed
        s:=s+'|'+lisPkgMgrNew;
        Report.Add(s);
      end;
      CurDependency:=CurDependency.NextRequiresDependency;
    end;

    // list all packages, that will be removed
    CurDependency:=OldDependencyList;
    while CurDependency<>nil do begin
      NewDependency:=FindDependencyByNameInList(NewDependencyList,pddRequires,
                                                CurDependency.PackageName);
      if NewDependency=nil then
        // this package will be removed
        Report.Add('|'+lisPkgMgrRemove+'|'+CurDependency.AsString);
      CurDependency:=CurDependency.NextRequiresDependency;
    end;

    // list all packages, that are kept
    CurDependency:=NewDependencyList;
    while CurDependency<>nil do begin
      s:=CurDependency.AsString;
      OldDependency:=FindDependencyByNameInList(OldDependencyList,pddRequires,
                                                CurDependency.PackageName);
      if OldDependency<>nil then begin
        // stay installed
        if CurDependency.AsString<>OldDependency.AsString then
          s:=s+'|'+lisPkgMgrKeep+'|'+OldDependency.AsString;
        Report.Add(s);
      end;
      CurDependency:=CurDependency.NextRequiresDependency;
    end;
  end;

  procedure AddPkg(Pkg: TLazPackage);
  var
    i: Integer;
    PkgID: TLazPackageID;
  begin
    if Pkg=nil then exit;
    i:=PkgIdList.Count-1;
    while (i>=0) and (TLazPackageID(PkgIdList[i]).Compare(Pkg)<>0) do
      dec(i);
    if i>=0 then exit;
    PkgID:=TLazPackageID.Create;
    PkgID.AssignID(Pkg);
    PkgIdList.Add(PkgID);
  end;

var
  NewFirstAutoInstallDependency: TPkgDependency;
  BuildIDEFlags: TBuildLazarusFlags;
  Report: TStringList;
  PkgList: TFPList;
  RequiredPackage: TLazPackage;
  i: Integer;
  CurDependency: TPkgDependency;
begin
  Result:=mrCancel;
  NewFirstAutoInstallDependency:=nil;
  PkgList:=nil;
  try
    PackageGraph.ParseBasePackages(false);

    if not (piiifClear in Flags) then
    begin
      // add existing install packages to list
      NewFirstAutoInstallDependency:=PackageGraph.FirstInstallDependency;
      while NewFirstAutoInstallDependency<>nil do begin
        AddPkg(NewFirstAutoInstallDependency.RequiredPackage);
        NewFirstAutoInstallDependency:=NewFirstAutoInstallDependency.NextRequiresDependency;
      end;
    end;

    // add base packages
    if PackageGraph.ParseBasePackages(true) then begin
      for i:=0 to PackageGraph.SrcBasePackages.Count-1 do begin
        RequiredPackage:=PackageGraph.FindPackageWithName(PackageGraph.SrcBasePackages[i],nil);
        AddPkg(RequiredPackage);
      end;
    end;

    if not (piiifSkipChecks in Flags) then
    begin
      if not CheckInstallPackageList(PkgIDList,nil,Flags*[piiifQuiet,piiifRemoveConflicts]) then
        exit(mrCancel);
    end;

    // create new auto install dependency PkgIDList
    ListPkgIDToDependencyList(PkgIDList,NewFirstAutoInstallDependency,
      pddRequires,Self,true);

    PackageGraph.SortDependencyListTopologicallyOld(NewFirstAutoInstallDependency,
                                                 false);

    if not (piiifQuiet in Flags) then
    begin
      // tell the user, which packages will stay, which will be removed and
      // which will be newly installed
      try
        Report:=TStringList.Create;
        CreateChangeReport(
          PackageGraph.FirstInstallDependency,NewFirstAutoInstallDependency,
          Report);
        if not ConfirmPackageList(Report) then exit(mrCancel);
      finally
        Report.Free;
      end;
    end;

    // try to commit changes -> replace install list
    PackageGraph.BeginUpdate(true);
    try
      // get all required packages
      //debugln('TPkgManager.MainIDEitmPkgEditInstallPkgsClick GetAllRequiredPackages for ',DependencyListAsString(NewFirstAutoInstallDependency,pddRequires));
      if LoadDependencyList(NewFirstAutoInstallDependency,false)<>mrOk then exit(mrCancel);
      PackageGraph.GetAllRequiredPackages(nil,NewFirstAutoInstallDependency,PkgList);

      // mark packages for installation
      //debugln('TPkgManager.MainIDEitmPkgEditInstallPkgsClick mark packages for installation');
      for i:=0 to PkgList.Count-1 do begin
        RequiredPackage:=TLazPackage(PkgList[i]);
        if RequiredPackage.AutoInstall=pitNope then begin
          RequiredPackage.AutoInstall:=pitStatic;
        end;
      end;

      // mark packages for uninstall
      //debugln('TPkgManager.MainIDEitmPkgEditInstallPkgsClick mark packages for uninstall');
      CurDependency:=PackageGraph.FirstInstallDependency;
      while CurDependency<>nil do begin
        if (CurDependency.RequiredPackage<>nil)
        and (not PackageGraph.IsCompiledInBasePackage(CurDependency.PackageName)) then
          CurDependency.RequiredPackage.AutoInstall:=pitNope;
        CurDependency:=CurDependency.NextRequiresDependency;
      end;

      // replace install list
      //debugln('TPkgManager.MainIDEitmPkgEditInstallPkgsClick replace install list');
      FreeDependencyList(PackageGraph.FirstInstallDependency,pddRequires);
      PackageGraph.FirstInstallDependency:=NewFirstAutoInstallDependency;
      NewFirstAutoInstallDependency:=nil;
    finally
      PackageGraph.EndUpdate;
    end;

    // save package list
    //debugln('TPkgManager.MainIDEitmPkgEditInstallPkgsClick save package list');
    PackageGraph.SortAutoInstallDependencies;
    SaveAutoInstallDependencies;

    // save IDE build configs, so user can build IDE on command line
    BuildIDEFlags:=[blfDontClean,blfOnlyIDE];
    if MainIDE.DoSaveBuildIDEConfigs(BuildIDEFlags)<>mrOk then exit(mrCancel);

    if piiifRebuildIDE in Flags then
    begin
      // rebuild Lazarus
      if MainIDE.DoBuildLazarus(BuildIDEFlags)<>mrOk then exit(mrCancel);
    end;

  finally
    FreeDependencyList(NewFirstAutoInstallDependency,pddRequires);
    PkgList.Free;
  end;
  Result:=mrOk;
end;

function TPkgManager.DoOpenPackageSource(APackage: TLazPackage): TModalResult;
var
  Filename: String;
begin
  Result:=mrCancel;
  if APackage.IsVirtual then begin
    IDEMessageDialog(lisCCOErrorCaption,
      lisPkgMangThisIsAVirtualPackageItHasNoSourceYetPleaseSaveThe,
      mtError, [mbCancel]);
    exit;
  end;
  Filename:=APackage.GetSrcFilename;
  if (not FilenameIsAbsolute(Filename)) or (not FileExistsCached(Filename)) then begin
    IDEMessageDialog(lisCCOErrorCaption, lisPkgMangPleaseCompileThePackageFirst,
      mtError,[mbCancel]);
    exit;
  end;
  Result:=MainIDE.DoOpenEditorFile(Filename,-1,-1,[ofRegularFile]);
end;

function TPkgManager.DoCompileAutoInstallPackages(Flags: TPkgCompileFlags;
  OnlyBase: boolean): TModalResult;
var
  Dependency: TPkgDependency;
  OldDependency: TPkgDependency;
  Dependencies: TPkgDependency;
  AutoRemove: Boolean;
  CompilePolicy: TPackageUpdatePolicy;
begin
  PackageGraph.BeginUpdate(false);
  Dependencies:=PackageGraph.FirstInstallDependency;
  try
    if OnlyBase then
    begin
      // create the list of base packages
      OldDependency:=PackageGraph.FirstInstallDependency;
      Dependencies:=nil;
      while OldDependency<>nil do begin
        if (OldDependency.RequiredPackage<>nil)
        and PackageGraph.IsCompiledInBasePackage(OldDependency.RequiredPackage.Name) then
        begin
          Dependency:=TPkgDependency.Create;
          Dependency.Assign(OldDependency);
          Dependency.AddToEndOfList(Dependencies,pddRequires);
        end;
        OldDependency:=OldDependency.NextRequiresDependency;
      end;
      Dependencies:=GetFirstDependency(Dependencies,pddRequires);
      PackageGraph.OpenRequiredDependencyList(Dependencies);
    end;

    // check every installed package if it was loaded correctly
    Dependency:=Dependencies;
    AutoRemove:=false;
    while Dependency<>nil do begin
      OldDependency:=Dependency;
      Dependency:=Dependency.NextRequiresDependency;
      if OldDependency.LoadPackageResult<>lprSuccess then begin
        if not AutoRemove then begin
          Result:=IDEMessageDialog(lisProjAddPackageNotFound,
            Format(lisPkgMangThePackageIsMarkedForInstallationButCanNotBeFound,
                   [OldDependency.AsString, LineEnding]),
            mtError,[mbYes,mbYesToAll,mbAbort]);
          case Result of
          mrYes: ;
          mrYesToAll: AutoRemove:=true;
          else
            SaveAutoInstallDependencies;
            exit;
          end;
        end;
        OldDependency.RemoveFromList(PackageGraph.FirstInstallDependency,pddRequires);
        OldDependency.Free;
      end;
    end;
    SaveAutoInstallDependencies;

    // check consistency
    Result:=CheckPackageGraphForCompilation(nil,Dependencies,
                            EnvironmentOptions.GetParsedLazarusDirectory,false);
    if Result<>mrOk then begin
      if ConsoleVerbosity>0 then
        debugln(['Error: (lazarus) [TPkgManager.DoCompileAutoInstallPackages] CheckPackageGraphForCompilation failed']);
      exit;
    end;
    //DebugLn(['TPkgManager.DoCompileAutoInstallPackages LCLUnitPath=',PackageGraph.LCLPackage.CompilerOptions.GetUnitPath(true)]);

    // save all open files
    if not (pcfDoNotSaveEditorFiles in Flags) then begin
      Result:=MainIDE.DoSaveForBuild(crCompile);
      if Result<>mrOk then begin
        if ConsoleVerbosity>0 then
          debugln(['Error: (lazarus) [TPkgManager.DoCompileAutoInstallPackages] MainIDE.DoSaveForBuild failed']);
        exit;
      end;
    end;
    
    // compile all auto install dependencies
    CompilePolicy:=pupAsNeeded;
    if pcfCompileDependenciesClean in Flags then
      CompilePolicy:=pupOnRebuildingAll;
    Result:=PackageGraph.CompileRequiredPackages(nil,Dependencies,false,
                                                 CompilePolicy);
    if Result<>mrOk then begin
      if ConsoleVerbosity>0 then
        debugln(['Error: (lazarus) [TPkgManager.DoCompileAutoInstallPackages] PackageGraph.CompileRequiredPackages failed']);
      exit;
    end;

  finally
    if OnlyBase then
      FreeDependencyList(Dependencies,pddRequires);
    PackageGraph.EndUpdate;
  end;
  Result:=mrOk;
end;

function TPkgManager.DoSaveAutoInstallConfig: TModalResult;
var
  TargetDir: String;
begin
  TargetDir:=MiscellaneousOptions.BuildLazProfiles.Current.TargetDirectory;
  IDEMacros.SubstituteMacros(TargetDir);
  TargetDir:=TrimFilename(TargetDir);
  if not ForceDirectory(TargetDir) then begin
    Result:=IDEMessageDialog(lisPkgMangUnableToCreateDirectory,
      Format(lisPkgMangUnableToCreateTargetDirectoryForLazarus,
             [LineEnding, TargetDir, LineEnding]),
      mtError,[mbCancel,mbAbort]);
    exit;
  end;

  Result:=PackageGraph.SaveAutoInstallConfig;
end;

function TPkgManager.DoPublishPackage(APackage: TLazPackage;
  Flags: TPkgSaveFlags; ShowDialog: boolean): TModalResult;
begin
  // show the publish dialog
  if ShowDialog then begin
    Result:=ShowPublishDialog(APackage.PublishOptions);
    if Result<>mrOk then exit;
  end;

  // save package
  Result:=DoSavePackage(APackage,Flags);
  if Result<>mrOk then exit;

  // publish package
  Result:=PublishAModule(APackage.PublishOptions);
end;

function TPkgManager.GetUsableComponentUnits(CurRoot: TPersistent): TFPList;
var
  FMainUnitInfo: TUnitInfo;
  FMainUnitInfoValid: boolean;
  FMainOwner: TObject;
  FMainOwnerValid: boolean;

  function MainUnitInfo: TUnitInfo;
  begin
    if not FMainUnitInfoValid then
    begin
      if CurRoot is TComponent then
        FMainUnitInfo := Project1.UnitWithComponent(TComponent(CurRoot));
      FMainUnitInfoValid := True;
    end;
    Result := FMainUnitInfo;
  end;

  function MainOwner: TObject;
  var
    Owners: TFPList;
  begin
    if not FMainOwnerValid then
    begin
      if MainUnitInfo <> nil then
      begin
        if MainUnitInfo.IsPartOfProject then
          FMainOwner := Project1
        else
        begin
          Owners := GetOwnersOfUnit(MainUnitInfo.Filename);
          if (Owners <> nil) and (Owners.Count > 0) then
            FMainOwner := TObject(Owners[0]);
          Owners.Free;
        end;
      end;
      FMainOwnerValid := True;
    end;
    Result := FMainOwner;
  end;

  procedure CheckUnit(AnUnitInfo: TUnitInfo);
  var
    Owners: TFPList;
    OtherOwner: TObject;
    APackage: TLazPackage;
    ConflictDependency: TPkgDependency;
    FirstDependency: TPkgDependency;
  begin
    if (AnUnitInfo.Component=nil)
    or (AnUnitInfo.Component=CurRoot) then
      exit;
    // check if the component can be used
    // A component can only be used, if it has a CreateForm statement in the lpr
    // A unit can not be used, if it has no owner (project/package).
    // And a unit can not be used, if it belongs to a higher level package.
    // For example: Package A uses Package B.
    // A can use units of B, but B can not use units of A.
    if AnUnitInfo.IsPartOfProject and MainUnitInfo.IsPartOfProject then
    begin
      // both units belong to the project => ok
    end else if AnUnitInfo.IsPartOfProject then
    begin
      // AnUnitInfo belongs to Project, but MainUnitInfo does not
      // A project unit can only be used by the project => not allowed
      exit;
    end else
    begin
      Owners:=GetOwnersOfUnit(AnUnitInfo.Filename);
      if (Owners=nil) or (Owners.Count=0) then begin
        // AnUnitInfo does not belong to a project or package
        // => this unit can not be used
        Owners.Free;
        exit;
      end;
      OtherOwner:=TObject(Owners[0]);
      Owners.Free;
      if OtherOwner=MainOwner then begin
        // both units belong to the same owner => ok
      end else if (OtherOwner is TLazPackage) then begin
        // check if MainOwner can use the package
        APackage:=TLazPackage(OtherOwner);
        if MainOwner is TProject then
          FirstDependency:=TProject(MainOwner).FirstRequiredDependency
        else if MainOwner is TLazPackage then
          FirstDependency:=TLazPackage(MainOwner).FirstRequiredDependency
        else
          exit;
        ConflictDependency:=PackageGraph.FindConflictRecursively(
          FirstDependency,APackage);
        if ConflictDependency<>nil then exit;
        if MainOwner is TLazPackage then begin
          // check if package already uses MainOwner
          ConflictDependency:=PackageGraph.FindDependencyRecursively(
            APackage.FirstRequiredDependency,TLazPackage(MainOwner).Name);
          if ConflictDependency<>nil then exit;
        end;
      end else begin
        // AnUnitInfo does not belong to a Package => can not be used
        exit;
      end;
    end;
    // this unit can be used -> add components
    if Result=nil then
      Result:=TFPList.Create;
    Result.Add(AnUnitInfo);
  end;
  
var
  AnUnitInfo: TUnitInfo;
begin
  Result:=nil;
  if not (CurRoot is TComponent) then exit;
  FMainOwner:=nil;
  FMainOwnerValid:=false;
  FMainUnitInfo:=nil;
  FMainUnitInfoValid:=false;
  if (MainOwner=nil) or (MainUnitInfo=nil) then exit;
  // search all open designer forms (can be hidden)
  AnUnitInfo:=Project1.FirstUnitWithComponent;
  while AnUnitInfo<>nil do begin
    CheckUnit(AnUnitInfo);
    AnUnitInfo:=AnUnitInfo.NextUnitWithComponent;
  end;
end;

procedure TPkgManager.IterateComponentNames(CurRoot: TPersistent;
  TypeData: PTypeData; Proc: TGetStrProc);

  procedure CheckComponent(aRoot: TComponent);
  var
    i: integer;
    CurName: String;
  begin
    if aRoot = nil then exit;
    if (aRoot <> CurRoot) and (aRoot is TypeData^.ClassType) then
      Proc(aRoot.Name);
    for i := 0 to aRoot.ComponentCount - 1 do
      if (aRoot.Components[i] is TypeData^.ClassType) then
      begin
        CurName := aRoot.Components[i].Name;
        if aRoot <> CurRoot then
          CurName := aRoot.Name + '.' + CurName;
        Proc(CurName);
      end;
  end;

var
  UnitList: TFPList;
  i: Integer;
begin
  if not (CurRoot is TComponent) then exit;
  CheckComponent(TComponent(CurRoot));
  UnitList := GetUsableComponentUnits(CurRoot);
  if UnitList = nil then exit;
  try
    for i := 0 to UnitList.Count - 1 do
      CheckComponent(TUnitInfo(UnitList[i]).Component);
  finally
    UnitList.Free;
  end;
end;

function TPkgManager.FindReferencedRootComponent(CurRoot: TPersistent;
  const ComponentName: string): TComponent;
var
  UnitList: TFPList;
  ARoot: TComponent;
  i: integer;
begin
  //DebugLn(['search ', ComponentName, ' CurRoot = ', dbgsName(CurRoot)]);
  Result := nil;
  UnitList := GetUsableComponentUnits(CurRoot);
  if UnitList = nil then 
    Exit;
  try
    for i := 0 to UnitList.Count - 1 do 
    begin
      ARoot := TUnitInfo(UnitList[i]).Component;
      DebugLn(['Hint: (lazarus) [TPkgManager.FindReferencedRootComponent] Root=',dbgsName(CurRoot),' Searched="',ComponentName,'" other root=',dbgsName(ARoot)]);
      if (ARoot <> nil) and (SysUtils.CompareText(ComponentName, ARoot.Name) = 0) then
      begin
        Result := ARoot;
        break;
      end;
    end;
  finally
    UnitList.Free;
  end;
  //DebugLn('search end');
end;

function TPkgManager.FindUsableComponent(CurRoot: TPersistent;
  const ComponentPath: string): TComponent;

  procedure CheckComponent(const RootName, SubPath: string; aRoot: TComponent);
  var
    i: integer;
  begin
    if aRoot = nil then exit;
    if (SysUtils.CompareText(RootName, aRoot.Name) <> 0) then exit;

    if SubPath = '' then
    begin
      Result := aRoot;
      Exit;
    end;

    for i := 0 to aRoot.ComponentCount - 1 do
      if SysUtils.CompareText(aRoot.Components[i].Name, SubPath) = 0 then
      begin
        Result := aRoot.Components[i];
        exit;
      end;
  end;

var
  UnitList: TFPList;
  SubPath: String;
  p: LongInt;
  RootName: String;
  i: Integer;
begin
  Result := nil;
  if not (CurRoot is TComponent) then exit;
  SubPath := ComponentPath;
  p := System.Pos('.',SubPath);
  if p < 1 then
    RootName := ''
  else begin
    RootName := copy(ComponentPath, 1, p - 1);
    SubPath := copy(SubPath, p + 1, length(SubPath));
  end;
  if (RootName = '') or (SysUtils.CompareText(RootName, TComponent(CurRoot).Name) = 0) then
    CheckComponent(TComponent(CurRoot).Name, SubPath, TComponent(CurRoot));
  if (p < 1) then
    if Result = nil then
    begin
      RootName := SubPath;
      SubPath := '';
    end
    else
      exit;
  UnitList := GetUsableComponentUnits(CurRoot);
  if UnitList = nil then exit;
  try
    for i := 0 to UnitList.Count-1 do
    begin
      CheckComponent(RootName, SubPath, TUnitInfo(UnitList[i]).Component);
      if Result <> nil then exit;
    end;
  finally
    UnitList.Free;
  end;
end;

function TPkgManager.ProjectInspectorAddDependency(Sender: TObject;
  ADependency: TPkgDependency): TModalResult;
begin
  Result:=AddProjectDependency(Project1,ADependency);
end;

function TPkgManager.ProjectInspectorRemoveDependency(Sender: TObject;
  ADependency: TPkgDependency): TModalResult;
var
  ShortUnitName: String;
  Dummy: Boolean;
begin
  Result:=mrOk;
  Project1.RemoveRequiredDependency(ADependency);
  //debugln('TPkgManager.OnProjectInspectorRemoveDependency A');
  Project1.DefineTemplates.AllChanged(false);
  if (Project1.MainUnitID>=0)
  and (pfMainUnitIsPascalSource in Project1.Flags)
  then begin
    MainIDE.SaveSourceEditorChangesToCodeCache(nil);
    ShortUnitName:=ADependency.PackageName;
    //debugln('TPkgManager.OnProjectInspectorRemoveDependency B ShortUnitName="',ShortUnitName,'"');
    if (ShortUnitName<>'') then begin
      Dummy:=CodeToolBoss.RemoveUnitFromAllUsesSections(
                                    Project1.MainUnitInfo.Source,ShortUnitName);
      if Dummy then
        Project1.MainUnitInfo.Modified:=true
      else begin
        MainIDEInterface.DoJumpToCodeToolBossError;
        Result:=mrCancel;
        exit;
      end;
    end;
  end;
end;

function TPkgManager.ProjectInspectorReAddDependency(Sender: TObject;
  ADependency: TPkgDependency): TModalResult;
begin
  Result:=mrOk;
  Project1.ReaddRemovedDependency(ADependency);
  PackageGraph.OpenDependency(ADependency,false);
  if (ADependency.RequiredPackage<>nil)
  and (not ADependency.RequiredPackage.Missing) then begin
    AddUnitToProjectMainUsesSection(Project1,ADependency.PackageName,'');
  end;
end;

procedure TPkgManager.ProjectInspectorDragDropTreeView(Sender, Source: TObject;
  X, Y: Integer);
begin
  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.OnProjectInspectorDragDropTreeView START']);
  {$ENDIF}
  FilesEditDragDrop(Sender, Source, X, Y);
  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.OnProjectInspectorDragDropTreeView END']);
  {$ENDIF}
end;

function TPkgManager.ProjectInspectorDragOverTreeView(Sender,
  Source: TObject; X, Y: Integer; out TargetTVNode: TTreeNode; out
  TargetTVType: TTreeViewInsertMarkType): boolean;
var
  SrcFilesEdit: IFilesEditorInterface;
  TargetFilesEdit: IFilesEditorInterface;
  aFileCount: integer;
  aDependencyCount: integer;
  aDirectoryCount: integer;
begin
  {$IFDEF VerbosePkgEditDrag}
  debugln(['TPkgManager.OnProjectInspectorDragOverTreeView ']);
  {$ENDIF}
  Result:=CheckDrag(Sender, Source, X, Y, SrcFilesEdit, TargetFilesEdit,
    aFileCount, aDependencyCount, aDirectoryCount, TargetTVNode, TargetTVType);
end;

procedure TPkgManager.ProjectInspectorCopyMoveFiles(Sender: TObject);
begin
  CopyMoveFiles(Sender);
end;

function TPkgManager.CanClosePackageEditor(APackage: TLazPackage): TModalResult;
begin
  Result:=APackage.Editor.CanCloseEditor;
end;

function TPkgManager.CanCloseAllPackageEditors: TModalResult;
var
  APackage: TLazPackage;
  i: Integer;
begin
  for i:=0 to PackageEditors.Count-1 do begin
    APackage:=PackageEditors.Editors[i].LazPackage;
    Result:=CanClosePackageEditor(APackage);
    if Result<>mrOk then exit;
  end;
  Result:=mrOk;
end;

function TPkgManager.CanOpenDesignerForm(AnUnitInfo: TUnitInfo;
  Interactive: boolean): TModalResult;
var
  AProject: TProject;
begin
  Result:=mrCancel;
  if AnUnitInfo=nil then exit;
  AProject:=AnUnitInfo.Project;
  if AProject=nil then exit;
  Result:=CheckProjectHasInstalledPackages(AProject,Interactive);
end;

function TPkgManager.DoClosePackageEditor(APackage: TLazPackage): TModalResult;
begin
  if APackage.Editor<>nil then
    APackage.Editor.Free;
  Result:=mrOk;
end;

function TPkgManager.DoSaveAllPackages(Flags: TPkgSaveFlags): TModalResult;
var
  AllSaved: Boolean;
  i: Integer;
  CurPackage: TLazPackage;
begin
  try
    repeat
      AllSaved:=true;
      i:=0;
      while i<PackageGraph.Count do begin
        CurPackage:=PackageGraph[i];
        if CurPackage.Modified and (not CurPackage.ReadOnly)
        and (not (lpfSkipSaving in CurPackage.Flags)) then begin
          Result:=DoSavePackage(CurPackage,Flags);
          if Result=mrIgnore then begin
            CurPackage.Flags:=CurPackage.Flags+[lpfSkipSaving];
            Result:=mrOk;
          end;
          if Result<>mrOk then exit;
          AllSaved:=false;
        end;
        inc(i);
      end;
    until AllSaved;
  finally
    // clear all lpfSkipSaving flags
    for i:=0 to PackageGraph.Count-1 do begin
      CurPackage:=PackageGraph[i];
      CurPackage.Flags:=CurPackage.Flags-[lpfSkipSaving];
    end;
  end;
  Result:=mrOk;
end;

{ TLazPackageDescriptors }

function TLazPackageDescriptors.GetItems(Index: integer): TPackageDescriptor;
begin
  Result:=TPackageDescriptor(FItems[Index]);
end;

constructor TLazPackageDescriptors.Create;
begin
  PackageDescriptors:=Self;
  FItems:=TFPList.Create;
end;

destructor TLazPackageDescriptors.Destroy;
var
  i: Integer;
begin
  fDestroying:=true;
  for i:=Count-1 downto 0 do Items[i].Release;
  FItems.Free;
  FItems:=nil;
  PackageDescriptors:=nil;
  inherited Destroy;
end;

function TLazPackageDescriptors.Count: integer;
begin
  Result:=FItems.Count;
end;

function TLazPackageDescriptors.GetUniqueName(const Name: string): string;
var
  i: Integer;
begin
  Result:=Name;
  if IndexOf(Result)<0 then exit;
  i:=0;
  repeat
    inc(i);
    Result:=Name+IntToStr(i);
  until IndexOf(Result)<0;
end;

function TLazPackageDescriptors.IndexOf(const Name: string): integer;
begin
  Result:=Count-1;
  while (Result>=0) and (AnsiCompareText(Name,Items[Result].Name)<>0) do
    dec(Result);
end;

function TLazPackageDescriptors.FindByName(const Name: string): TPackageDescriptor;
var
  i: LongInt;
begin
  i:=IndexOf(Name);
  if i>=0 then
    Result:=Items[i]
  else
    Result:=nil;
end;

procedure TLazPackageDescriptors.RegisterDescriptor(Descriptor: TPackageDescriptor);
begin
  if Descriptor.Name='' then
    raise Exception.Create('TLazPackageDescriptors.RegisterDescriptor Descriptor.Name empty');
  Descriptor.Name:=GetUniqueName(Descriptor.Name);
  FItems.Add(Descriptor);
end;

procedure TLazPackageDescriptors.UnregisterDescriptor(Descriptor: TPackageDescriptor);
var
  i: LongInt;
begin
  if fDestroying then exit;
  i:=FItems.IndexOf(Descriptor);
  if i<0 then
    raise Exception.Create('TLazPackageDescriptors.UnregisterDescriptor');
  FItems.Delete(i);
  Descriptor.Release;
end;

procedure TLazPackageDescriptors.AddDefaultPackageDescriptors;
begin
  NewIDEItems.Add(TNewLazIDEItemCategoryPackage.Create(PkgDescGroupName));
  RegisterPackageDescriptor(TPackageDescriptorStd.Create);
end;

{ TPackageDescriptorStd }

constructor TPackageDescriptorStd.Create;
begin
  inherited Create;
  Name:=PkgDescNameStandard;
end;

function TPackageDescriptorStd.GetLocalizedName: string;
begin
  Result:=lisPackage;
end;

function TPackageDescriptorStd.GetLocalizedDescription: string;
begin
  Result:=Format(lisNewDlgCreateANewStandardPackageAPackageIsACollectionOfUn,
                 [LineEnding]);
end;

end.

