{ /*************************************************************************** projectinspector.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 . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: Mattias Gaertner Abstract: TProjectInspectorForm is the form of the project inspector. ToDo: - drop files - multi select - popup menu - delete files - delete deps - dnd move - project groups: - activate popup menu: - copy file name - save - options - activate - compile - build - view source - close - remove project - build sooner Ctrl+Up - build later Ctrl+Down - compile all from here - build all from here } unit ProjectInspector; {$mode objfpc}{$H+} interface uses Classes, SysUtils, LCLProc, LCLType, Forms, Controls, Buttons, ComCtrls, Menus, Dialogs, FileUtil, LazFileCache, ExtCtrls, Graphics, CodeToolManager, CodeCache, TreeFilterEdit, // IDEIntf IDEHelpIntf, IDECommands, IDEDialogs, IDEImagesIntf, LazIDEIntf, ProjectIntf, PackageIntf, // IDE LazarusIDEStrConsts, IDEProcs, DialogProcs, IDEOptionDefs, EnvironmentOpts, PackageDefs, Project, PackageEditor, AddToProjectDlg; type TOnAddUnitToProject = function(Sender: TObject; AnUnitInfo: TUnitInfo): TModalresult of object; TRemoveProjInspFileEvent = function(Sender: TObject; AnUnitInfo: TUnitInfo): TModalResult of object; TRemoveProjInspDepEvent = function(Sender: TObject; ADependency: TPkgDependency): TModalResult of object; TAddProjInspDepEvent = function(Sender: TObject; ADependency: TPkgDependency): TModalResult of object; TProjectInspectorFlag = ( pifNeedUpdateFiles, pifNeedUpdateDependencies, pifNeedUpdateButtons, pifNeedUpdateTitle ); TProjectInspectorFlags = set of TProjectInspectorFlag; { TProjectInspectorForm } TProjectInspectorForm = class(TForm) BtnPanel: TPanel; DirectoryHierarchyButton: TSpeedButton; FilterEdit: TTreeFilterEdit; OpenButton: TSpeedButton; ItemsTreeView: TTreeView; ItemsPopupMenu: TPopupMenu; SortAlphabeticallyButton: TSpeedButton; // toolbar ToolBar: TToolBar; // toolbuttons AddBitBtn: TToolButton; RemoveBitBtn: TToolButton; OptionsBitBtn: TToolButton; HelpBitBtn: TToolButton; procedure AddBitBtnClick(Sender: TObject); procedure DirectoryHierarchyButtonClick(Sender: TObject); procedure FormDropFiles(Sender: TObject; const FileNames: array of String); procedure ItemsPopupMenuPopup(Sender: TObject); procedure ItemsTreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages, DefaultDraw: Boolean); procedure ItemsTreeViewDblClick(Sender: TObject); procedure ItemsTreeViewDragDrop(Sender, Source: TObject; X, Y: Integer); procedure ItemsTreeViewDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure ItemsTreeViewKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure ItemsTreeViewSelectionChanged(Sender: TObject); procedure MoveDependencyUpClick(Sender: TObject); procedure MoveDependencyDownClick(Sender: TObject); procedure SetDependencyDefaultFilenameMenuItemClick(Sender: TObject); procedure SetDependencyPreferredFilenameMenuItemClick(Sender: TObject); procedure ClearDependencyFilenameMenuItemClick(Sender: TObject); procedure OpenButtonClick(Sender: TObject); procedure OptionsBitBtnClick(Sender: TObject); procedure HelpBitBtnClick(Sender: TObject); procedure ReAddMenuItemClick(Sender: TObject); procedure RemoveBitBtnClick(Sender: TObject); procedure RemoveNonExistingFilesMenuItemClick(Sender: TObject); procedure SortAlphabeticallyButtonClick(Sender: TObject); procedure EnableI18NForLFMMenuItemClick(Sender: TObject); procedure DisableI18NForLFMMenuItemClick(Sender: TObject); private FIdleConnected: boolean; FOnAddDependency: TAddProjInspDepEvent; FOnAddUnitToProject: TOnAddUnitToProject; FOnDragDropTreeView: TDragDropEvent; FOnDragOverTreeView: TOnDragOverTreeView; FOnReAddDependency: TAddProjInspDepEvent; FOnRemoveDependency: TRemoveProjInspDepEvent; FOnRemoveFile: TRemoveProjInspFileEvent; FOnShowOptions: TNotifyEvent; FShowDirectoryHierarchy: boolean; FSortAlphabetically: boolean; FUpdateLock: integer; FLazProject: TProject; FFilesNode: TTreeNode; FNextSelectedPart: TObject;// select this file/dependency on next update DependenciesNode: TTreeNode; RemovedDependenciesNode: TTreeNode; ImageIndexFiles: integer; ImageIndexRequired: integer; ImageIndexConflict: integer; ImageIndexRemovedRequired: integer; ImageIndexProject: integer; ImageIndexUnit: integer; ImageIndexRegisterUnit: integer; ImageIndexText: integer; ImageIndexBinary: integer; ImageIndexDirectory: integer; FFlags: TProjectInspectorFlags; FProjectNodeDataList : array [TPENodeType] of TPENodeData; procedure FreeNodeData(Typ: TPENodeType); function CreateNodeData(Typ: TPENodeType; aName: string; aRemoved: boolean): TPENodeData; function GetNodeData(TVNode: TTreeNode): TPENodeData; function GetNodeItem(NodeData: TPENodeData): TObject; function GetNodeDataItem(TVNode: TTreeNode; out NodeData: TPENodeData; out Item: TObject): boolean; procedure SetDependencyDefaultFilename(AsPreferred: boolean); procedure SetIdleConnected(AValue: boolean); procedure SetLazProject(const AValue: TProject); procedure SetShowDirectoryHierarchy(const AValue: boolean); procedure SetSortAlphabetically(const AValue: boolean); procedure SetupComponents; function OnTreeViewGetImageIndex(Str: String; Data: TObject; var AIsEnabled: Boolean): Integer; procedure OnProjectBeginUpdate(Sender: TObject); procedure OnProjectEndUpdate(Sender: TObject; ProjectChanged: boolean); procedure EnableI18NForSelectedLFM(TheEnable: boolean); protected procedure KeyUp(var Key: Word; Shift: TShiftState); override; procedure IdleHandler(Sender: TObject; var Done: Boolean); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure BeginUpdate; procedure EndUpdate; function IsUpdateLocked: boolean; inline; procedure UpdateAll(Immediately: boolean = false); procedure UpdateTitle(Immediately: boolean = false); procedure UpdateProjectFiles(Immediately: boolean = false); procedure UpdateRequiredPackages(Immediately: boolean = false); procedure UpdateButtons(Immediately: boolean = false); procedure UpdatePending; function CanUpdate(Flag: TProjectInspectorFlag): boolean; function GetSingleSelectedDependency: TPkgDependency; function TreeViewToInspector(TV: TTreeView): TProjectInspectorForm; public property LazProject: TProject read FLazProject write SetLazProject; property OnShowOptions: TNotifyEvent read FOnShowOptions write FOnShowOptions; property OnAddUnitToProject: TOnAddUnitToProject read FOnAddUnitToProject write FOnAddUnitToProject; property OnAddDependency: TAddProjInspDepEvent read FOnAddDependency write FOnAddDependency; property OnRemoveFile: TRemoveProjInspFileEvent read FOnRemoveFile write FOnRemoveFile; property OnRemoveDependency: TRemoveProjInspDepEvent read FOnRemoveDependency write FOnRemoveDependency; property OnReAddDependency: TAddProjInspDepEvent read FOnReAddDependency write FOnReAddDependency; property OnDragDropTreeView: TDragDropEvent read FOnDragDropTreeView write FOnDragDropTreeView; property OnDragOverTreeView: TOnDragOverTreeView read FOnDragOverTreeView write FOnDragOverTreeView; property SortAlphabetically: boolean read FSortAlphabetically write SetSortAlphabetically; property ShowDirectoryHierarchy: boolean read FShowDirectoryHierarchy write SetShowDirectoryHierarchy; property IdleConnected: boolean read FIdleConnected write SetIdleConnected; end; var ProjInspector: TProjectInspectorForm = nil; implementation {$R *.lfm} { TProjectInspectorForm } // inline function TProjectInspectorForm.IsUpdateLocked: boolean; begin Result:=FUpdateLock>0; end; procedure TProjectInspectorForm.ItemsTreeViewDblClick(Sender: TObject); begin OpenButtonClick(Self); end; procedure TProjectInspectorForm.ItemsTreeViewDragDrop(Sender, Source: TObject; X, Y: Integer); begin OnDragDropTreeView(Sender,Source,X,Y); end; procedure TProjectInspectorForm.ItemsTreeViewDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); var TargetTVNode: TTreeNode; TargetTVType: TTreeViewInsertMarkType; begin if not OnDragOverTreeView(Sender,Source,X,Y, TargetTVNode, TargetTVType) then begin ItemsTreeView.SetInsertMark(nil,tvimNone); Accept:=false; exit; end; if State=dsDragLeave then ItemsTreeView.SetInsertMark(nil,tvimNone) else ItemsTreeView.SetInsertMark(TargetTVNode,TargetTVType); Accept:=true; end; procedure TProjectInspectorForm.ItemsTreeViewKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var Handled: Boolean; begin Handled := True; try if Key = VK_ESCAPE then Close else if Key = VK_RETURN then OpenButtonClick(Nil) else if Key = VK_DELETE then RemoveBitBtnClick(Nil) else if Key = VK_INSERT then AddBitBtnClick(Nil) else Handled := False; finally if Handled then Key := VK_UNKNOWN; end; end; procedure TProjectInspectorForm.ItemsTreeViewSelectionChanged(Sender: TObject); begin UpdateButtons; end; procedure TProjectInspectorForm.MoveDependencyUpClick(Sender: TObject); var Dependency: TPkgDependency; begin Dependency:=GetSingleSelectedDependency; if SortAlphabetically or (Dependency=nil) or Dependency.Removed or (Dependency.PrevRequiresDependency=nil) then exit; LazProject.MoveRequiredDependencyUp(Dependency); end; procedure TProjectInspectorForm.MoveDependencyDownClick(Sender: TObject); var Dependency: TPkgDependency; begin Dependency:=GetSingleSelectedDependency; if SortAlphabetically or (Dependency=nil) or Dependency.Removed or (Dependency.NextRequiresDependency=nil) then exit; LazProject.MoveRequiredDependencyDown(Dependency); end; procedure TProjectInspectorForm.SetDependencyDefaultFilenameMenuItemClick(Sender: TObject); begin SetDependencyDefaultFilename(false); end; procedure TProjectInspectorForm.SetDependencyPreferredFilenameMenuItemClick(Sender: TObject); begin SetDependencyDefaultFilename(true); end; procedure TProjectInspectorForm.ClearDependencyFilenameMenuItemClick(Sender: TObject); var CurDependency: TPkgDependency; i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; begin BeginUpdate; try for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if not (Item is TPkgDependency) then continue; CurDependency:=TPkgDependency(Item); if CurDependency.DefaultFilename='' then exit; CurDependency.DefaultFilename:=''; CurDependency.PreferDefaultFilename:=false; LazProject.Modified:=true; UpdateRequiredPackages; end; finally EndUpdate; end; end; procedure TProjectInspectorForm.AddBitBtnClick(Sender: TObject); var AddResult: TAddToProjectResult; i: Integer; NewFilename: string; NewFile: TUnitInfo; begin if ShowAddToProjectDlg(LazProject,AddResult)<>mrOk then exit; case AddResult.AddType of a2pFiles: begin BeginUpdate; for i:=0 to AddResult.FileNames.Count-1 do begin NewFilename:=AddResult.FileNames[i]; NewFile:=LazProject.UnitInfoWithFilename(NewFilename); if NewFile<>nil then begin if NewFile.IsPartOfProject then continue; end else begin NewFile:=TUnitInfo.Create(nil); NewFile.Filename:=NewFilename; LazProject.AddFile(NewFile,false); end; NewFile.IsPartOfProject:=true; if Assigned(OnAddUnitToProject) then begin if OnAddUnitToProject(Self,NewFile)<>mrOk then break; end; FNextSelectedPart:=NewFile; end; UpdateAll; EndUpdate; end; a2pRequiredPkg: begin BeginUpdate; if Assigned(OnAddDependency) then OnAddDependency(Self,AddResult.Dependency); FNextSelectedPart:=AddResult.Dependency; UpdateRequiredPackages; EndUpdate; end; end; AddResult.Free; end; procedure TProjectInspectorForm.DirectoryHierarchyButtonClick(Sender: TObject); begin ShowDirectoryHierarchy:=DirectoryHierarchyButton.Down; end; procedure TProjectInspectorForm.FormDropFiles(Sender: TObject; const FileNames: array of String); var i: Integer; NewFilename: String; NewFile: TUnitInfo; begin {$IFDEF VerboseProjInspDrag} debugln(['TProjectInspectorForm.FormDropFiles ',length(FileNames)]); {$ENDIF} if length(FileNames)=0 then exit; BeginUpdate; try for i:=0 to high(Filenames) do begin NewFilename:=CleanAndExpandFilename(FileNames[i]); if not FileExistsUTF8(NewFilename) then continue; if DirPathExists(NewFilename) then continue; NewFile:=LazProject.UnitInfoWithFilename(NewFilename); if (NewFile<>nil) and (NewFile.IsPartOfProject) then continue; {$IFDEF VerboseProjInspDrag} debugln(['TProjectInspectorForm.FormDropFiles Adding files: ',NewFilename]); {$ENDIF} if NewFile=nil then begin NewFile:=TUnitInfo.Create(nil); NewFile.Filename:=NewFilename; LazProject.AddFile(NewFile,false); end; NewFile.IsPartOfProject:=true; if Assigned(OnAddUnitToProject) then begin if OnAddUnitToProject(Self,NewFile)<>mrOk then break; end; UpdateAll; end; finally EndUpdate; end; end; procedure TProjectInspectorForm.ItemsPopupMenuPopup(Sender: TObject); var ItemCnt: integer; function AddPopupMenuItem(const ACaption: string; AnEvent: TNotifyEvent; EnabledFlag: boolean): TMenuItem; begin if ItemsPopupMenu.Items.Count<=ItemCnt then begin Result:=TMenuItem.Create(Self); ItemsPopupMenu.Items.Add(Result); end else Result:=ItemsPopupMenu.Items[ItemCnt]; Result.Caption:=ACaption; Result.OnClick:=AnEvent; Result.Enabled:=EnabledFlag; Result.Checked:=false; Result.ShowAlwaysCheckable:=false; Result.Visible:=true; Result.RadioItem:=false; Result.ImageIndex:=-1; inc(ItemCnt); end; var i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; CanRemoveCount: Integer; CanOpenCount: Integer; HasLFMCount: Integer; CurUnitInfo: TUnitInfo; DisabledI18NForLFMCount: Integer; CanReAddCount: Integer; SingleSelectedDep: TPkgDependency; DepCount: Integer; Dependency: TPkgDependency; HasValidDep: Integer; CanClearDep: Integer; begin ItemCnt:=0; CanRemoveCount:=0; CanOpenCount:=0; HasLFMCount:=0; DisabledI18NForLFMCount:=0; CanReAddCount:=0; SingleSelectedDep:=nil; DepCount:=0; HasValidDep:=0; CanClearDep:=0; for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if Item is TUnitInfo then begin CurUnitInfo:=TUnitInfo(Item); inc(CanOpenCount); if CurUnitInfo<>LazProject.MainUnitInfo then inc(CanRemoveCount); if FilenameIsPascalSource(CurUnitInfo.Filename) and FileExistsCached(ChangeFileExt(CurUnitInfo.Filename,'.lfm')) then begin inc(HasLFMCount); if CurUnitInfo.DisableI18NForLFM then inc(DisabledI18NForLFMCount); end; end else if Item is TPkgDependency then begin Dependency:=TPkgDependency(Item); if NodeData.Removed then begin inc(CanReAddCount); end else begin inc(DepCount); if DepCount=1 then SingleSelectedDep:=Dependency else SingleSelectedDep:=nil; inc(CanRemoveCount); inc(CanOpenCount); if Dependency.RequiredPackage<>nil then inc(HasValidDep); if (Dependency.DefaultFilename<>'') then inc(CanClearDep); end; end; end; // Section headers AddPopupMenuItem(lisOpen, @OpenButtonClick, CanOpenCount>0); AddPopupMenuItem(lisBtnDlgAdd, @AddBitBtnClick, AddBitBtn.Enabled); AddPopupMenuItem(lisRemove, @RemoveBitBtnClick, CanRemoveCount>0); AddPopupMenuItem(lisRemoveNonExistingFiles,@RemoveNonExistingFilesMenuItemClick, not LazProject.IsVirtual); // files section if LazProject.EnableI18N and LazProject.EnableI18NForLFM and (HasLFMCount>0) then begin AddPopupMenuItem(lisEnableI18NForLFM, @EnableI18NForLFMMenuItemClick, DisabledI18NForLFMCount0); end; // Required packages section if CanReAddCount>0 then AddPopupMenuItem(lisPckEditReAddDependency, @ReAddMenuItemClick, true); if SingleSelectedDep<>nil then begin AddPopupMenuItem(lisPckEditMoveDependencyUp, @MoveDependencyUpClick, (SingleSelectedDep.PrevRequiresDependency<>nil)); AddPopupMenuItem(lisPckEditMoveDependencyDown, @MoveDependencyDownClick, (SingleSelectedDep.NextRequiresDependency<>nil)); end; if HasValidDep>0 then begin AddPopupMenuItem(lisPckEditStoreFileNameAsDefaultForThisDependency, @SetDependencyDefaultFilenameMenuItemClick, true); AddPopupMenuItem(lisPckEditStoreFileNameAsPreferredForThisDependency, @SetDependencyPreferredFilenameMenuItemClick, true); end; if CanClearDep>0 then begin AddPopupMenuItem(lisPckEditClearDefaultPreferredFilenameOfDependency, @ClearDependencyFilenameMenuItemClick, true); end; while ItemsPopupMenu.Items.Count>ItemCnt do ItemsPopupMenu.Items.Delete(ItemsPopupMenu.Items.Count-1); end; procedure TProjectInspectorForm.ItemsTreeViewAdvancedCustomDrawItem( Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages, DefaultDraw: Boolean); var NodeData: TPENodeData; r: TRect; y: Integer; begin if Stage=cdPostPaint then begin NodeData:=GetNodeData(Node); if (NodeData<>nil) then begin if (NodeData.Typ=penFile) and (not NodeData.Removed) and FilenameIsAbsolute(NodeData.Name) and (not FileExistsCached(NodeData.Name)) then begin r:=Node.DisplayRect(true); ItemsTreeView.Canvas.Pen.Color:=clRed; y:=(r.Top+r.Bottom) div 2; ItemsTreeView.Canvas.Line(r.Left,y,r.Right,y); end; end; end; end; procedure TProjectInspectorForm.OpenButtonClick(Sender: TObject); var i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; CurFile: TUnitInfo; CurDependency: TPkgDependency; begin BeginUpdate; try for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if Item is TUnitInfo then begin CurFile:=TUnitInfo(Item); if LazarusIDE.DoOpenEditorFile(CurFile.Filename,-1,-1,[ofAddToRecent])<>mrOk then exit; end else if Item is TPkgDependency then begin CurDependency:=TPkgDependency(Item); if PackageEditingInterface.DoOpenPackageWithName( CurDependency.PackageName,[],false)<>mrOk then exit; end; end; finally EndUpdate; end; end; procedure TProjectInspectorForm.OptionsBitBtnClick(Sender: TObject); begin if Assigned(OnShowOptions) then OnShowOptions(Self); end; procedure TProjectInspectorForm.HelpBitBtnClick(Sender: TObject); begin LazarusHelp.ShowHelpForIDEControl(Self); end; procedure TProjectInspectorForm.ReAddMenuItemClick(Sender: TObject); var Dependency: TPkgDependency; i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; begin BeginUpdate; try for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if not NodeData.Removed then continue; if not (Item is TPkgDependency) then continue; Dependency:=TPkgDependency(Item); if not CheckAddingDependency(LazProject,Dependency) then exit; if Assigned(OnReAddDependency) then OnReAddDependency(Self,Dependency); end; finally EndUpdate; end; end; procedure TProjectInspectorForm.RemoveBitBtnClick(Sender: TObject); var CurDependency: TPkgDependency; i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; Msg: String; DeleteCount: Integer; CurFile: TUnitInfo; begin BeginUpdate; try // check selection Msg:=''; DeleteCount:=0; for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if Item is TUnitInfo then begin CurFile:=TUnitInfo(Item); if CurFile=LazProject.MainUnitInfo then continue; // remove file inc(DeleteCount); Msg:=Format(lisProjInspRemoveFileFromProject, [CurFile.Filename]); end else if Item is TPkgDependency then begin CurDependency:=TPkgDependency(item); if NodeData.Removed then continue; // remove dependency inc(DeleteCount); Msg:=Format(lisProjInspDeleteDependencyFor, [CurDependency.AsString]); end; end; // ask for confirmation if DeleteCount=0 then exit; if DeleteCount>1 then Msg:='Remove '+IntToStr(DeleteCount)+' items from project?'; if IDEMessageDialog(lisProjInspConfirmDeletingDependency, Msg, mtConfirmation,[mbYes,mbNo])<>mrYes then exit; // delete for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if Item is TUnitInfo then begin CurFile:=TUnitInfo(Item); if CurFile=LazProject.MainUnitInfo then continue; // remove file if Assigned(OnRemoveFile) then OnRemoveFile(Self,CurFile); end else if Item is TPkgDependency then begin CurDependency:=TPkgDependency(item); if NodeData.Removed then continue; // remove dependency if Assigned(OnRemoveDependency) then OnRemoveDependency(Self,CurDependency); end; end; finally EndUpdate; end; end; procedure TProjectInspectorForm.RemoveNonExistingFilesMenuItemClick(Sender: TObject); var AnUnitInfo: TUnitInfo; NextUnitInfo: TUnitInfo; HasChanged: Boolean; begin if LazProject.IsVirtual then exit; BeginUpdate; try HasChanged:=false; AnUnitInfo:=LazProject.FirstPartOfProject; while AnUnitInfo<>nil do begin NextUnitInfo:=AnUnitInfo.NextPartOfProject; if not (AnUnitInfo.IsVirtual or FileExistsUTF8(AnUnitInfo.Filename)) then begin AnUnitInfo.IsPartOfProject:=false; HasChanged:=true; end; AnUnitInfo:=NextUnitInfo; end; if HasChanged then begin LazProject.Modified:=true; UpdateProjectFiles; end; finally EndUpdate; end; end; procedure TProjectInspectorForm.SortAlphabeticallyButtonClick(Sender: TObject); begin SortAlphabetically:=SortAlphabeticallyButton.Down; end; procedure TProjectInspectorForm.EnableI18NForLFMMenuItemClick(Sender: TObject); begin EnableI18NForSelectedLFM(true); end; procedure TProjectInspectorForm.DisableI18NForLFMMenuItemClick(Sender: TObject); begin EnableI18NForSelectedLFM(false); end; procedure TProjectInspectorForm.SetLazProject(const AValue: TProject); begin if FLazProject=AValue then exit; if FLazProject<>nil then begin dec(FUpdateLock,LazProject.UpdateLock); FLazProject.OnBeginUpdate:=nil; FLazProject.OnEndUpdate:=nil; end; FLazProject:=AValue; if FLazProject<>nil then begin inc(FUpdateLock,LazProject.UpdateLock); FLazProject.OnBeginUpdate:=@OnProjectBeginUpdate; FLazProject.OnEndUpdate:=@OnProjectEndUpdate; end; UpdateAll; end; procedure TProjectInspectorForm.SetShowDirectoryHierarchy(const AValue: boolean); begin if FShowDirectoryHierarchy=AValue then exit; FShowDirectoryHierarchy:=AValue; DirectoryHierarchyButton.Down:=FShowDirectoryHierarchy; FilterEdit.ShowDirHierarchy:=FShowDirectoryHierarchy; FilterEdit.InvalidateFilter; EnvironmentOptions.ProjInspShowDirHierarchy := ShowDirectoryHierarchy; end; procedure TProjectInspectorForm.SetSortAlphabetically(const AValue: boolean); begin if FSortAlphabetically=AValue then exit; FSortAlphabetically:=AValue; SortAlphabeticallyButton.Down:=FSortAlphabetically; FilterEdit.SortData:=FSortAlphabetically; FilterEdit.InvalidateFilter; EnvironmentOptions.ProjInspSortAlphabetically := SortAlphabetically; end; procedure TProjectInspectorForm.SetDependencyDefaultFilename(AsPreferred: boolean); var NewFilename: String; CurDependency: TPkgDependency; i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; begin BeginUpdate; try for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if NodeData.Removed then continue; if not (Item is TPkgDependency) then continue; CurDependency:=TPkgDependency(Item); if CurDependency.RequiredPackage=nil then continue; NewFilename:=CurDependency.RequiredPackage.Filename; if (NewFilename=CurDependency.DefaultFilename) // do not use CompareFilenames and (CurDependency.PreferDefaultFilename=AsPreferred) then continue; CurDependency.DefaultFilename:=NewFilename; CurDependency.PreferDefaultFilename:=AsPreferred; LazProject.Modified:=true; UpdateRequiredPackages; end; finally EndUpdate; end; end; procedure TProjectInspectorForm.SetIdleConnected(AValue: boolean); begin if csDestroying in ComponentState then AValue:=false; if FIdleConnected=AValue then exit; FIdleConnected:=AValue; if FIdleConnected then Application.AddOnIdleHandler(@IdleHandler) else Application.RemoveOnIdleHandler(@IdleHandler); end; procedure TProjectInspectorForm.SetupComponents; function CreateToolButton(AName, ACaption, AHint, AImageName: String; AOnClick: TNotifyEvent): TToolButton; begin Result := TToolButton.Create(Self); Result.Name := AName; Result.Caption := ACaption; Result.Hint := AHint; if AImageName <> '' then Result.ImageIndex := IDEImages.LoadImage(16, AImageName); Result.ShowHint := True; Result.OnClick := AOnClick; Result.AutoSize := True; Result.Parent := ToolBar; end; function CreateDivider: TToolButton; begin Result := TToolButton.Create(Self); Result.Style := tbsDivider; Result.AutoSize := True; Result.Parent := ToolBar; end; begin ImageIndexFiles := IDEImages.LoadImage(16, 'pkg_files'); ImageIndexRequired := IDEImages.LoadImage(16, 'pkg_required'); ImageIndexConflict := IDEImages.LoadImage(16, 'pkg_conflict'); ImageIndexRemovedRequired := IDEImages.LoadImage(16, 'pkg_removedrequired'); ImageIndexProject := IDEImages.LoadImage(16, 'item_project'); ImageIndexUnit := IDEImages.LoadImage(16, 'item_unit'); ImageIndexRegisterUnit := IDEImages.LoadImage(16, 'pkg_registerunit'); ImageIndexText := IDEImages.LoadImage(16, 'pkg_text'); ImageIndexBinary := IDEImages.LoadImage(16, 'pkg_binary'); ImageIndexDirectory := IDEImages.LoadImage(16, 'pkg_files'); ItemsTreeView.Images := IDEImages.Images_16; ToolBar.Images := IDEImages.Images_16; FilterEdit.OnGetImageIndex:=@OnTreeViewGetImageIndex; AddBitBtn := CreateToolButton('AddBitBtn', lisAdd, lisPckEditAddAnItem, 'laz_add', @AddBitBtnClick); RemoveBitBtn := CreateToolButton('RemoveBitBtn', lisRemove, lisPckEditRemoveSelectedItem, 'laz_delete', @RemoveBitBtnClick); CreateDivider; OptionsBitBtn := CreateToolButton('OptionsBitBtn', dlgFROpts, lisPckEditEditGeneralOptions, 'menu_environment_options', @OptionsBitBtnClick); HelpBitBtn := CreateToolButton('HelpBitBtn', GetButtonCaption(idButtonHelp), lisPkgEdThereAreMoreFunctionsInThePopupmenu, 'menu_help', @HelpBitBtnClick); OpenButton.LoadGlyphFromResourceName(HInstance, 'laz_open'); OpenButton.Caption:=''; OpenButton.Hint:=lisOpenFile2; SortAlphabeticallyButton.Hint:=lisPESortFilesAlphabetically; SortAlphabeticallyButton.LoadGlyphFromResourceName(HInstance, 'pkg_sortalphabetically'); DirectoryHierarchyButton.Hint:=lisPEShowDirectoryHierarchy; DirectoryHierarchyButton.LoadGlyphFromResourceName(HInstance, 'pkg_hierarchical'); with ItemsTreeView do begin FFilesNode:=Items.Add(nil, dlgEnvFiles); FFilesNode.ImageIndex:=ImageIndexFiles; FFilesNode.SelectedIndex:=FFilesNode.ImageIndex; DependenciesNode:=Items.Add(nil, lisPckEditRequiredPackages); DependenciesNode.ImageIndex:=ImageIndexRequired; DependenciesNode.SelectedIndex:=DependenciesNode.ImageIndex; end; end; function TProjectInspectorForm.OnTreeViewGetImageIndex(Str: String; Data: TObject; var AIsEnabled: Boolean): Integer; var NodeData: TPENodeData; Item: TObject; begin Result := -1; if not (Data is TPENodeData) then exit; NodeData:=TPENodeData(Data); Item:=GetNodeItem(NodeData); if Item=nil then exit; if Item is TUnitInfo then begin if FilenameIsPascalUnit(TUnitInfo(Item).Filename) then Result:=ImageIndexUnit else if (LazProject<>nil) and (LazProject.MainUnitinfo=Item) then Result:=ImageIndexProject else Result:=ImageIndexText; end else if Item is TPkgDependency then begin if TPkgDependency(Item).Removed then Result:=ImageIndexRemovedRequired else if TPkgDependency(Item).LoadPackageResult=lprSuccess then Result:=ImageIndexRequired else Result:=ImageIndexConflict; end; end; procedure TProjectInspectorForm.UpdateProjectFiles(Immediately: boolean); var CurFile: TUnitInfo; FilesBranch: TTreeFilterBranch; Filename: String; ANodeData : TPENodeData; begin if not CanUpdate(pifNeedUpdateFiles) then exit; ItemsTreeView.BeginUpdate; try FilesBranch:=FilterEdit.GetBranch(FFilesNode); FilesBranch.Clear; FreeNodeData(penFile); if LazProject<>nil then begin FilterEdit.SelectedPart:=FNextSelectedPart; FilterEdit.ShowDirHierarchy:=ShowDirectoryHierarchy; FilterEdit.SortData:=SortAlphabetically; FilterEdit.ImageIndexDirectory:=ImageIndexDirectory; // collect and sort files CurFile:=LazProject.FirstPartOfProject; while CurFile<>nil do begin Filename:=CurFile.GetShortFilename(true); if Filename<>'' then Begin ANodeData := CreateNodeData(penFile, CurFile.Filename, False); FilesBranch.AddNodeData(Filename, ANodeData, CurFile.Filename); end; CurFile:=CurFile.NextPartOfProject; end; end; FilterEdit.InvalidateFilter; // Data is shown by FilterEdit. finally ItemsTreeView.EndUpdate; end; UpdateButtons; end; procedure TProjectInspectorForm.UpdateRequiredPackages(Immediately: boolean); var Dependency: TPkgDependency; RequiredBranch, RemovedBranch: TTreeFilterBranch; NodeText, AFilename: String; ANodeData : TPENodeData; begin if not CanUpdate(pifNeedUpdateDependencies) then exit; ItemsTreeView.BeginUpdate; try RequiredBranch:=FilterEdit.GetBranch(DependenciesNode); RequiredBranch.Clear; FreeNodeData(penDependency); Dependency:=Nil; if LazProject<>nil then begin // required packages Dependency:=LazProject.FirstRequiredDependency; while Dependency<>nil do begin // Figure out the item's caption NodeText:=Dependency.AsString; if Dependency.DefaultFilename<>'' then begin AFilename:=Dependency.MakeFilenameRelativeToOwner(Dependency.DefaultFilename); if Dependency.PreferDefaultFilename then NodeText:=Format(lisCEIn, [NodeText,AFilename]) // like the 'in' keyword in the uses section else NodeText:=Format(lisPckEditDefault, [NodeText, AFilename]); end; // Add the required package under the branch ANodeData := CreateNodeData(penDependency, Dependency.PackageName, False); RequiredBranch.AddNodeData(NodeText, ANodeData); Dependency:=Dependency.NextRequiresDependency; end; // removed required packages Dependency:=LazProject.FirstRemovedDependency; if Dependency<>nil then begin // Create root node for removed dependencies if not done yet. if RemovedDependenciesNode=nil then begin RemovedDependenciesNode:=ItemsTreeView.Items.Add(DependenciesNode, lisProjInspRemovedRequiredPackages); RemovedDependenciesNode.ImageIndex:=ImageIndexRemovedRequired; RemovedDependenciesNode.SelectedIndex:=RemovedDependenciesNode.ImageIndex; end; RemovedBranch:=FilterEdit.GetBranch(RemovedDependenciesNode); // Add all removed dependencies under the branch while Dependency<>nil do begin ANodeData := CreateNodeData(penDependency, Dependency.PackageName, True); RemovedBranch.AddNodeData(Dependency.AsString, ANodeData); Dependency:=Dependency.NextRequiresDependency; end; end; end; // Dependency is set to removed required packages if there is active project if (Dependency=nil) and (RemovedDependenciesNode<>nil) then begin // No removed dependencies -> delete the root node FilterEdit.DeleteBranch(RemovedDependenciesNode); FreeThenNil(RemovedDependenciesNode); end; FilterEdit.InvalidateFilter; finally ItemsTreeView.EndUpdate; end; UpdateButtons; end; procedure TProjectInspectorForm.OnProjectBeginUpdate(Sender: TObject); begin BeginUpdate; end; procedure TProjectInspectorForm.OnProjectEndUpdate(Sender: TObject; ProjectChanged: boolean); begin UpdateAll; EndUpdate; end; procedure TProjectInspectorForm.EnableI18NForSelectedLFM(TheEnable: boolean); var i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; CurUnitInfo: TUnitInfo; begin for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if not (Item is TUnitInfo) then continue; CurUnitInfo:=TUnitInfo(Item); if not FilenameIsPascalSource(CurUnitInfo.Filename) then continue; CurUnitInfo.DisableI18NForLFM:=not TheEnable; end; end; procedure TProjectInspectorForm.KeyUp(var Key: Word; Shift: TShiftState); begin inherited KeyDown(Key, Shift); ExecuteIDEShortCut(Self,Key,Shift,nil); end; procedure TProjectInspectorForm.IdleHandler(Sender: TObject; var Done: Boolean); begin if IsUpdateLocked then begin IdleConnected:=false; exit; end; UpdatePending; end; function TProjectInspectorForm.GetSingleSelectedDependency: TPkgDependency; var Item: TObject; NodeData: TPENodeData; begin Result:=nil; if not GetNodeDataItem(ItemsTreeView.Selected,NodeData,Item) then exit; if Item is TPkgDependency then Result:=TPkgDependency(Item); end; function TProjectInspectorForm.TreeViewToInspector(TV: TTreeView ): TProjectInspectorForm; begin if TV=ItemsTreeView then Result:=Self else Result:=nil; end; constructor TProjectInspectorForm.Create(TheOwner: TComponent); begin inherited Create(TheOwner); Name:=NonModalIDEWindowNames[nmiwProjectInspector]; Caption:=lisMenuProjectInspector; KeyPreview:=true; SetupComponents; KeyPreview:=true; SortAlphabetically := EnvironmentOptions.ProjInspSortAlphabetically; ShowDirectoryHierarchy := EnvironmentOptions.ProjInspShowDirHierarchy; end; destructor TProjectInspectorForm.Destroy; var nt: TPENodeType; begin IdleConnected:=false; LazProject:=nil; inherited Destroy; for nt:=Low(TPENodeType) to High(TPENodeType) do FreeNodeData(nt); if ProjInspector=Self then ProjInspector:=nil; end; procedure TProjectInspectorForm.BeginUpdate; begin inc(FUpdateLock); end; procedure TProjectInspectorForm.EndUpdate; begin if FUpdateLock=0 then RaiseException('TProjectInspectorForm.EndUpdate'); dec(FUpdateLock); if FUpdateLock=0 then IdleConnected:=true; end; procedure TProjectInspectorForm.UpdateAll(Immediately: boolean); begin ItemsTreeView.BeginUpdate; try UpdateTitle; UpdateProjectFiles; UpdateRequiredPackages; UpdateButtons; if Immediately then UpdatePending; finally ItemsTreeView.EndUpdate; end; end; procedure TProjectInspectorForm.UpdateTitle(Immediately: boolean); var NewCaption: String; begin if not CanUpdate(pifNeedUpdateTitle) then exit; if LazProject=nil then Caption:=lisMenuProjectInspector else begin NewCaption:=LazProject.GetTitle; if NewCaption='' then NewCaption:=ExtractFilenameOnly(LazProject.ProjectInfoFile); Caption:=Format(lisProjInspProjectInspector, [NewCaption]); end; end; procedure TProjectInspectorForm.UpdateButtons(Immediately: boolean); var i: Integer; TVNode: TTreeNode; NodeData: TPENodeData; Item: TObject; CanRemoveCount: Integer; CurUnitInfo: TUnitInfo; CanOpenCount: Integer; begin if not CanUpdate(pifNeedUpdateButtons) then exit; if LazProject<>nil then begin AddBitBtn.Enabled:=true; CanRemoveCount:=0; CanOpenCount:=0; for i:=0 to ItemsTreeView.SelectionCount-1 do begin TVNode:=ItemsTreeView.Selections[i]; if not GetNodeDataItem(TVNode,NodeData,Item) then continue; if Item is TUnitInfo then begin CurUnitInfo:=TUnitInfo(Item); inc(CanOpenCount); if CurUnitInfo<>LazProject.MainUnitInfo then inc(CanRemoveCount); end else if Item is TPkgDependency then begin if not NodeData.Removed then begin inc(CanRemoveCount); inc(CanOpenCount); end; end; end; RemoveBitBtn.Enabled:=(CanRemoveCount>0); OpenButton.Enabled:=(CanOpenCount>0); OptionsBitBtn.Enabled:=true; end else begin AddBitBtn.Enabled:=false; RemoveBitBtn.Enabled:=false; OpenButton.Enabled:=false; OptionsBitBtn.Enabled:=false; end; end; procedure TProjectInspectorForm.UpdatePending; begin ItemsTreeView.BeginUpdate; try if pifNeedUpdateFiles in FFlags then UpdateProjectFiles(true); if pifNeedUpdateDependencies in FFlags then UpdateRequiredPackages(true); if pifNeedUpdateTitle in FFlags then UpdateTitle(true); if pifNeedUpdateButtons in FFlags then UpdateButtons(true); IdleConnected:=false; finally ItemsTreeView.EndUpdate; end; end; function TProjectInspectorForm.CanUpdate(Flag: TProjectInspectorFlag): boolean; begin Result:=false; if csDestroying in ComponentState then exit; if LazProject=nil then exit; if IsUpdateLocked then begin Include(fFlags,Flag); IdleConnected:=true; Result:=false; end else begin Exclude(fFlags,Flag); Result:=true; end; end; procedure TProjectInspectorForm.FreeNodeData(Typ: TPENodeType); var NodeData, n: TPENodeData; begin NodeData:=FProjectNodeDataList[Typ]; while NodeData<>nil do begin n:=NodeData; NodeData:=NodeData.Next; n.Free; end; FProjectNodeDataList[Typ]:=nil; End; function TProjectInspectorForm.CreateNodeData(Typ: TPENodeType; aName: string; aRemoved: boolean): TPENodeData; Begin Result := TPENodeData.Create(Typ,aName,aRemoved); Result.Next := FProjectNodeDataList[Typ]; FProjectNodeDataList[Typ] := Result; end; function TProjectInspectorForm.GetNodeData(TVNode: TTreeNode): TPENodeData; var o: TObject; begin Result:=nil; if (TVNode=nil) then exit; o:=TObject(TVNode.Data); if o is TFileNameItem then o:=TObject(TFileNameItem(o).Data); if o is TPENodeData then Result:=TPENodeData(o); end; function TProjectInspectorForm.GetNodeItem(NodeData: TPENodeData): TObject; begin Result:=nil; if (LazProject=nil) or (NodeData=nil) then exit; case NodeData.Typ of penFile: if NodeData.Removed then Result:=nil else Result:=LazProject.UnitInfoWithFilename(NodeData.Name,[pfsfOnlyProjectFiles]); penDependency: if NodeData.Removed then Result:=LazProject.FindRemovedDependencyByName(NodeData.Name) else Result:=LazProject.FindDependencyByName(NodeData.Name); end; end; function TProjectInspectorForm.GetNodeDataItem(TVNode: TTreeNode; out NodeData: TPENodeData; out Item: TObject): boolean; begin Result:=false; Item:=nil; NodeData:=GetNodeData(TVNode); Item:=GetNodeItem(NodeData); Result:=Item<>nil; end; end.