1 {
2  /***************************************************************************
3                          installpkgsetdlg.pas
4                          --------------------
5 
6 
7  ***************************************************************************/
8 
9  ***************************************************************************
10  *                                                                         *
11  *   This source is free software; you can redistribute it and/or modify   *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  *   This code is distributed in the hope that it will be useful, but      *
17  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
19  *   General Public License for more details.                              *
20  *                                                                         *
21  *   A copy of the GNU General Public License is available on the World    *
22  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
23  *   obtain it by writing to the Free Software Foundation,                 *
24  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
25  *                                                                         *
26  ***************************************************************************
27 
28   Author: Mattias Gaertner
29 
30   Abstract:
31     Dialog to edit the package set installed in the IDE.
32 }
33 unit InstallPkgSetDlg;
34 
35 {$mode objfpc}{$H+}
36 
37 interface
38 
39 uses
40   Classes, SysUtils, contnrs, Laz_AVL_Tree,
41   // LCL
42   LCLType, LCLProc, Forms, Controls, Graphics, Dialogs, StdCtrls, Buttons,
43   ExtCtrls, ComCtrls, ImgList,
44   // LazControls
45   TreeFilterEdit,
46   // Codetools
47   BasicCodeTools,
48   // LazUtils
49   LazFileUtils, Laz2_XMLCfg, LazUTF8,
50   // IdeIntf
51   PackageDependencyIntf, PackageIntf, IDEImagesIntf, IDEHelpIntf, IDEDialogs, IDEWindowIntf, PackageLinkIntf,
52   // IDE
53   LazarusIDEStrConsts, InputHistory, LazConf, PackageDefs, PackageSystem, LPKCache, PackageLinks;
54 
55 type
56   TOnCheckInstallPackageList =
57     procedure(PkgIDs: TObjectList; RemoveConflicts: boolean; out Ok: boolean) of object;
58 
59   { TInstallPkgSetDialog }
60 
61   TInstallPkgSetDialog = class(TForm)
62     AddToInstallButton: TBitBtn;
63     AvailableTreeView: TTreeView;
64     AvailablePkgGroupBox: TGroupBox;
65     MiddleBevel: TBevel;
66     HelpButton: TBitBtn;
67     CancelButton: TBitBtn;
68     ExportButton: TButton;
69     BtnPanel: TPanel;
70     InstallTreeView: TTreeView;
71     AvailableFilterEdit: TTreeFilterEdit;
72     LPKParsingTimer: TTimer;
73     NoteLabel: TLabel;
74     Panel1: TPanel;
75     Panel2: TPanel;
76     PkgInfoMemo: TMemo;
77     PkgInfoGroupBox: TGroupBox;
78     ImportButton: TButton;
79     PkgInfoMemoLicense: TMemo;
80     SaveAndExitButton: TBitBtn;
81     InstallPkgGroupBox: TGroupBox;
82     SaveAndRebuildButton: TBitBtn;
83     InstalledFilterEdit: TTreeFilterEdit;
84     Splitter1: TSplitter;
85     Splitter2: TSplitter;
86     UninstallButton: TBitBtn;
87     procedure AddToInstallButtonClick(Sender: TObject);
FilterEditGetImageIndexnull88     function FilterEditGetImageIndex({%H-}Str: String; {%H-}Data: TObject;
89       var {%H-}AIsEnabled: Boolean): Integer;
90     procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
91     procedure InstallTreeViewKeyPress(Sender: TObject; var Key: char);
92     procedure LPKParsingTimerTimer(Sender: TObject);
93     procedure OnAllLPKParsed(Sender: TObject);
94     procedure OnIdle(Sender: TObject; var {%H-}Done: Boolean);
95     procedure TreeViewAdvancedCustomDrawItem(Sender: TCustomTreeView;
96       Node: TTreeNode; {%H-}State: TCustomDrawState; Stage: TCustomDrawStage;
97       var PaintImages, {%H-}DefaultDraw: Boolean);
98     procedure AvailableTreeViewDblClick(Sender: TObject);
99     procedure AvailableTreeViewKeyPress(Sender: TObject; var Key: char);
100     procedure AvailableTreeViewSelectionChanged(Sender: TObject);
101     procedure ExportButtonClick(Sender: TObject);
102     procedure HelpButtonClick(Sender: TObject);
103     procedure ImportButtonClick(Sender: TObject);
104     procedure SaveAndRebuildButtonClick(Sender: TObject);
105     procedure InstallTreeViewDblClick(Sender: TObject);
106     procedure InstallPkgSetDialogCreate(Sender: TObject);
107     procedure InstallPkgSetDialogDestroy(Sender: TObject);
108     procedure InstallPkgSetDialogShow(Sender: TObject);
109     procedure InstallPkgSetDialogResize(Sender: TObject);
110     procedure InstallTreeViewSelectionChanged(Sender: TObject);
111     procedure SaveAndExitButtonClick(Sender: TObject);
112     procedure UninstallButtonClick(Sender: TObject);
113   private
114     FIdleConnected: boolean;
115     FNewInstalledPackages: TObjectList; // list of TLazPackageID (not TLazPackage)
116     FOldInstalledPackages: TPkgDependency;
117     FOnCheckInstallPackageList: TOnCheckInstallPackageList;
118     FRebuildIDE: boolean;
119     FSelectedPkgState: TLPKInfoState;
120     FSelectedPkgID: string;
121     fAvailablePkgsNeedUpdate: boolean;
122     ImgIndexPackage: integer;
123     ImgIndexInstallPackage: integer;
124     ImgIndexInstalledPackage: integer;
125     ImgIndexUninstallPackage: integer;
126     ImgIndexCirclePackage: integer;
127     ImgIndexMissingPackage: integer;
128     ImgIndexAvailableOnline: integer;
129     ImgIndexOverlayUnknown: integer;
130     ImgIndexOverlayBasePackage: integer;
131     ImgIndexOverlayFPCPackage: integer;
132     ImgIndexOverlayLazarusPackage: integer;
133     ImgIndexOverlayDesigntimePackage: integer;
134     ImgIndexOverlayRuntimePackage: integer;
135     procedure SetIdleConnected(AValue: boolean);
136     procedure SetOldInstalledPackages(const AValue: TPkgDependency);
137     procedure AssignOldInstalledPackagesToList;
PackageInInstallListnull138     function PackageInInstallList(PkgName: string): boolean;
GetPkgImgIndexnull139     function GetPkgImgIndex(Installed: TPackageInstallType; InInstallList,
140       IsOnline: boolean): integer;
141     procedure UpdateAvailablePackages(Immediately: boolean = false);
142     procedure UpdateNewInstalledPackages;
DependencyToStrnull143     function DependencyToStr(Dependency: TPkgDependency): string;
144     procedure ClearNewInstalledPackages;
CheckSelectionnull145     function CheckSelection: boolean;
146     procedure UpdateButtonStates;
147     procedure UpdatePackageInfo(Tree: TTreeView);
NewInstalledPackagesContainsnull148     function NewInstalledPackagesContains(APackageID: TLazPackageID): boolean;
IndexOfNewInstalledPackageIDnull149     function IndexOfNewInstalledPackageID(APackageID: TLazPackageID): integer;
IndexOfNewInstalledPkgByNamenull150     function IndexOfNewInstalledPkgByName(const APackageName: string): integer;
151     procedure SavePackageListToFile(const AFilename: string);
152     procedure LoadPackageListFromFile(const AFilename: string);
ExtractNameFromPkgIDnull153     function ExtractNameFromPkgID(ID: string): string;
154     procedure AddToInstall;
155     procedure AddToUninstall;
156     procedure PkgInfosChanged;
157     procedure ChangePkgVersion(PkgInfo: TLPKInfo; NewVersion: TPkgVersion);
FindOnlinePackageLinknull158     function FindOnlinePackageLink(const AName: String): TPackageLink;
159   public
GetNewInstalledPackagesnull160     function GetNewInstalledPackages: TObjectList;
161     property OldInstalledPackages: TPkgDependency read FOldInstalledPackages
162                                                   write SetOldInstalledPackages;
163     property NewInstalledPackages: TObjectList read FNewInstalledPackages; // list of TLazPackageID
164     property RebuildIDE: boolean read FRebuildIDE write FRebuildIDE;
165     property OnCheckInstallPackageList: TOnCheckInstallPackageList
166                read FOnCheckInstallPackageList write FOnCheckInstallPackageList;
167     property IdleConnected: boolean read FIdleConnected write SetIdleConnected;
168   end;
169 
ShowEditInstallPkgsDialognull170 function ShowEditInstallPkgsDialog(OldInstalledPackages: TPkgDependency;
171   CheckInstallPackageList: TOnCheckInstallPackageList;
172   var NewInstalledPackages: TObjectList; // list of TLazPackageID (must be freed)
173   var RebuildIDE: boolean): TModalResult;
174 
175 implementation
176 
177 {$R *.lfm}
178 
179 procedure SetControlsWidthOnMax(AControls: array of TControl);
180 var
181   i, MaxWidth: Integer;
182 begin
183   MaxWidth:=0;
184   for i:=Low(AControls) to High(AControls) do
185     if AControls[i].Width>MaxWidth then
186       MaxWidth:=AControls[i].Width;
187   for i:=Low(AControls) to High(AControls) do
188     AControls[i].Constraints.MinWidth:=MaxWidth;  // AutoSize=True
189 end;
190 
ShowEditInstallPkgsDialognull191 function ShowEditInstallPkgsDialog(OldInstalledPackages: TPkgDependency;
192   CheckInstallPackageList: TOnCheckInstallPackageList;
193   var NewInstalledPackages: TObjectList; // list of TLazPackageID
194   var RebuildIDE: boolean): TModalResult;
195 var
196   InstallPkgSetDialog: TInstallPkgSetDialog;
197 begin
198   NewInstalledPackages:=nil;
199   InstallPkgSetDialog:=TInstallPkgSetDialog.Create(nil);
200   try
201     InstallPkgSetDialog.OldInstalledPackages:=OldInstalledPackages;
202     InstallPkgSetDialog.UpdateButtonStates;
203     InstallPkgSetDialog.OnCheckInstallPackageList:=CheckInstallPackageList;
204     Result:=InstallPkgSetDialog.ShowModal;
205     NewInstalledPackages:=InstallPkgSetDialog.GetNewInstalledPackages;
206     RebuildIDE:=InstallPkgSetDialog.RebuildIDE;
207   finally
208     InstallPkgSetDialog.Free;
209   end;
210 end;
211 
212 { TInstallPkgSetDialog }
213 
214 procedure TInstallPkgSetDialog.InstallPkgSetDialogCreate(Sender: TObject);
215 begin
216   IDEDialogLayoutList.ApplyLayout(Self);
217 
218   InstallTreeView.Images := IDEImages.Images_16;
219   AvailableTreeView.Images := IDEImages.Images_16;
220   ImgIndexPackage := IDEImages.LoadImage('item_package');
221   ImgIndexInstalledPackage := IDEImages.LoadImage('pkg_installed');
222   ImgIndexInstallPackage := IDEImages.LoadImage('pkg_package_autoinstall');
223   ImgIndexUninstallPackage := IDEImages.LoadImage('pkg_package_uninstall');
224   ImgIndexCirclePackage := IDEImages.LoadImage('pkg_package_circle');
225   ImgIndexMissingPackage := IDEImages.LoadImage('pkg_conflict');
226   ImgIndexAvailableOnline := IDEImages.LoadImage('pkg_install');
227   ImgIndexOverlayUnknown := IDEImages.LoadImage('state_unknown');
228   ImgIndexOverlayBasePackage := IDEImages.LoadImage('pkg_core_overlay');
229   ImgIndexOverlayFPCPackage := IDEImages.LoadImage('pkg_fpc_overlay');
230   ImgIndexOverlayLazarusPackage := IDEImages.LoadImage('pkg_lazarus_overlay');
231   ImgIndexOverlayDesignTimePackage := IDEImages.LoadImage('pkg_design_overlay');
232   ImgIndexOverlayRunTimePackage := IDEImages.LoadImage('pkg_runtime_overlay');
233 
234   Caption:=lisInstallUninstallPackages;
235   NoteLabel.Caption:=lisIDECompileAndRestart;
236 
237   AvailablePkgGroupBox.Caption:=lisAvailableForInstallation;
238 
239   ExportButton.Caption:=lisExportList;
240   ImportButton.Caption:=lisImportList;
241   UninstallButton.Caption:=lisUninstallSelection;
242   IDEImages.AssignImage(UninstallButton, 'arrow__darkred_right');
243   InstallPkgGroupBox.Caption:=lisPckEditInstall;
244   AddToInstallButton.Caption:=lisInstallSelection;
245   IDEImages.AssignImage(AddToInstallButton, 'arrow__darkgreen_left');
246   PkgInfoGroupBox.Caption := lisPackageInfo;
247   SaveAndRebuildButton.Caption:=lisSaveAndRebuildIDE;
248   SaveAndExitButton.Caption:=lisSaveAndExitDialog;
249   HelpButton.Caption:=lisMenuHelp;
250   CancelButton.Caption:=lisCancel;
251 
252   FNewInstalledPackages:=TObjectList.Create(true);
253   PkgInfoMemo.Clear;
254   PkgInfoMemoLicense.Clear;
255   LPKInfoCache.AddOnQueueEmpty(@OnAllLPKParsed);
256   LPKInfoCache.StartLPKReaderWithAllAvailable;
257 
258   UpdateButtonStates;
259 end;
260 
261 procedure TInstallPkgSetDialog.InstallPkgSetDialogDestroy(Sender: TObject);
262 begin
263   LPKInfoCache.EndLPKReader;
264   LPKInfoCache.RemoveOnQueueEmpty(@OnAllLPKParsed);
265   ClearNewInstalledPackages;
266   FreeAndNil(FNewInstalledPackages);
267   IdleConnected:=false;
268 end;
269 
270 procedure TInstallPkgSetDialog.InstallPkgSetDialogShow(Sender: TObject);
271 begin
272   InstalledFilterEdit.Filter:='';    // (filter) - text is shown after this.
273   AvailableFilterEdit.Filter:='';
274   SetControlsWidthOnMax([UninstallButton, AddToInstallButton]);
275   SetControlsWidthOnMax([ImportButton, ExportButton]);
276 end;
277 
278 procedure TInstallPkgSetDialog.SaveAndRebuildButtonClick(Sender: TObject);
279 begin
280   if not CheckSelection then exit;
281   RebuildIDE:=true;
282   ModalResult:=mrOk;
283 end;
284 
285 procedure TInstallPkgSetDialog.InstallTreeViewDblClick(Sender: TObject);
286 begin
287   AddToUninstall;
288 end;
289 
290 procedure TInstallPkgSetDialog.AvailableTreeViewSelectionChanged(Sender: TObject);
291 begin
292   UpdateButtonStates;
293   UpdatePackageInfo(AvailableTreeView);
294 end;
295 
296 procedure TInstallPkgSetDialog.ExportButtonClick(Sender: TObject);
297 var
298   SaveDialog: TSaveDialog;
299   AFilename: string;
300 begin
301   SaveDialog:=TSaveDialog.Create(nil);
302   try
303     InputHistories.ApplyFileDialogSettings(SaveDialog);
304     SaveDialog.InitialDir:=GetPrimaryConfigPath;
305     SaveDialog.Title:=lisExportPackageListXml;
306     SaveDialog.Options:=SaveDialog.Options+[ofPathMustExist];
307     if SaveDialog.Execute then begin
308       AFilename:=CleanAndExpandFilename(SaveDialog.Filename);
309       if ExtractFileExt(AFilename)='' then
310         AFilename:=AFilename+'.xml';
311       SavePackageListToFile(AFilename);
312     end;
313     InputHistories.StoreFileDialogSettings(SaveDialog);
314   finally
315     SaveDialog.Free;
316   end;
317 end;
318 
319 procedure TInstallPkgSetDialog.HelpButtonClick(Sender: TObject);
320 begin
321   LazarusHelp.ShowHelpForIDEControl(Self);
322 end;
323 
324 procedure TInstallPkgSetDialog.ImportButtonClick(Sender: TObject);
325 var
326   OpenDialog: TOpenDialog;
327   AFilename: string;
328 begin
329   OpenDialog:=TOpenDialog.Create(nil);
330   try
331     InputHistories.ApplyFileDialogSettings(OpenDialog);
332     OpenDialog.InitialDir:=GetPrimaryConfigPath;
333     OpenDialog.Title:=lisImportPackageListXml;
334     OpenDialog.Options:=OpenDialog.Options+[ofPathMustExist,ofFileMustExist];
335     if OpenDialog.Execute then begin
336       AFilename:=CleanAndExpandFilename(OpenDialog.Filename);
337       LoadPackageListFromFile(AFilename);
338     end;
339     InputHistories.StoreFileDialogSettings(OpenDialog);
340   finally
341     OpenDialog.Free;
342   end;
343 end;
344 
345 procedure TInstallPkgSetDialog.AddToInstallButtonClick(Sender: TObject);
346 begin
347   AddToInstall;
348 end;
349 
TInstallPkgSetDialog.FilterEditGetImageIndexnull350 function TInstallPkgSetDialog.FilterEditGetImageIndex(Str: String;
351   Data: TObject; var AIsEnabled: Boolean): Integer;
352 begin
353   Result:=0;
354 end;
355 
356 procedure TInstallPkgSetDialog.FormClose(Sender: TObject;
357   var CloseAction: TCloseAction);
358 begin
359   IDEDialogLayoutList.SaveLayout(Self);
360 end;
361 
362 procedure TInstallPkgSetDialog.InstallTreeViewKeyPress(Sender: TObject; var Key: char);
363 begin
364   if Key = char(VK_RETURN) then
365     AddToUninstall;
366 end;
367 
368 procedure TInstallPkgSetDialog.LPKParsingTimerTimer(Sender: TObject);
369 begin
370   UpdateNewInstalledPackages;
371   UpdateAvailablePackages;
372 end;
373 
374 procedure TInstallPkgSetDialog.OnAllLPKParsed(Sender: TObject);
375 begin
376   LPKParsingTimer.Enabled:=false;
377   UpdateNewInstalledPackages;
378   UpdateAvailablePackages;
379 end;
380 
381 procedure TInstallPkgSetDialog.OnIdle(Sender: TObject; var Done: Boolean);
382 begin
383   if fAvailablePkgsNeedUpdate then
384     UpdateAvailablePackages(true);
385   IdleConnected:=false;
386 end;
387 
388 procedure TInstallPkgSetDialog.TreeViewAdvancedCustomDrawItem(
389   Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState;
390   Stage: TCustomDrawStage; var PaintImages, DefaultDraw: Boolean);
391 var
392   Info: TLPKInfo;
393   NodeRect: TRect;
394   x: Integer;
395   Images: TCustomImageList;
396   CurCanvas: TCanvas;
397   y: Integer;
398   Tree: TTreeView;
399   InLazSrc: Boolean;
400   IsBase: Boolean;
401   PkgType: TLazPackageType;
402   Installed: TPackageInstallType;
403   PkgName: String;
404   ImgIndex: Integer;
405   Unknown: Boolean;
406   PackageLink: TPackageLink;
407   ImagesRes: TScaledImageListResolution;
408 begin
409   Tree:=Sender as TTreeView;
410   if Stage=cdPostPaint then begin
411     LPKInfoCache.EnterCritSection;
412     try
413       Info:=LPKInfoCache.FindPkgInfoWithIDAsString(Node.Text);
414       if Info=nil then exit;
415       PkgName:=Info.ID.Name;
416       Unknown:=not (Info.LPKParsed in [lpkiParsed,lpkiParsedError]);
417       InLazSrc:=Info.InLazSrc;
418       IsBase:=Info.Base;
419       PkgType:=Info.PkgType;
420       Installed:=Info.Installed;
421     finally
422       LPKInfoCache.LeaveCritSection;
423     end;
424     if Sender = InstallTreeView then
425       PackageLink := nil
426     else
427       PackageLink := FindOnlinePackageLink(Info.ID.Name);
428     Images:=Tree.Images;
429     if Images = nil then exit;
430     ImagesRes := Images.ResolutionForPPI[Tree.ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor];
431     CurCanvas:=Tree.Canvas;
432 
433     NodeRect:=Node.DisplayRect(False);
434     x:=Node.DisplayIconLeft+1;
435     y:=(NodeRect.Top+NodeRect.Bottom-ImagesRes.Height) div 2;
436     // draw image
437     ImgIndex:=GetPkgImgIndex(Installed,PackageInInstallList(PkgName), PackageLink <> nil);
438     ImagesRes.Draw(CurCanvas,x,y,ImgIndex);
439     // draw overlays
440     if InLazSrc then
441       ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayLazarusPackage);
442     if IsBase then
443       ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayBasePackage);
444     if PkgType=lptRunTimeOnly then
445       ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayRuntimePackage);
446     if PkgType=lptDesignTime then
447       ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayDesigntimePackage);
448     if Unknown then
449       ImagesRes.Draw(CurCanvas,x,y,ImgIndexOverlayUnknown);
450   end;
451   PaintImages:=false;
452 end;
453 
454 procedure TInstallPkgSetDialog.AvailableTreeViewDblClick(Sender: TObject);
455 begin
456   AddToInstall;
457 end;
458 
459 procedure TInstallPkgSetDialog.AvailableTreeViewKeyPress(Sender: TObject; var Key: char);
460 begin
461   if Key = char(VK_RETURN) then
462     AddToInstall;
463 end;
464 
465 procedure TInstallPkgSetDialog.InstallPkgSetDialogResize(Sender: TObject);
466 var
467   w: Integer;
468 begin
469   w:=ClientWidth div 2-InstallPkgGroupBox.BorderSpacing.Left*3;
470   if w<1 then w:=1;
471   InstallPkgGroupBox.Width:=w;
472 end;
473 
474 procedure TInstallPkgSetDialog.InstallTreeViewSelectionChanged(Sender: TObject);
475 begin
476   UpdateButtonStates;
477   UpdatePackageInfo(InstallTreeView);
478 end;
479 
480 procedure TInstallPkgSetDialog.SaveAndExitButtonClick(Sender: TObject);
481 begin
482   if not CheckSelection then exit;
483   RebuildIDE:=false;
484   ModalResult:=mrOk;
485 end;
486 
487 procedure TInstallPkgSetDialog.UninstallButtonClick(Sender: TObject);
488 begin
489   AddToUninstall;
490 end;
491 
492 procedure TInstallPkgSetDialog.SetOldInstalledPackages(const AValue: TPkgDependency);
493 begin
494   if FOldInstalledPackages=AValue then exit;
495   FOldInstalledPackages:=AValue;
496   AssignOldInstalledPackagesToList;
497 end;
498 
499 procedure TInstallPkgSetDialog.SetIdleConnected(AValue: boolean);
500 begin
501   if FIdleConnected=AValue then Exit;
502   FIdleConnected:=AValue;
503   if IdleConnected then
504     Application.AddOnIdleHandler(@OnIdle)
505   else
506     Application.RemoveOnIdleHandler(@OnIdle);
507 end;
508 
509 procedure TInstallPkgSetDialog.AssignOldInstalledPackagesToList;
510 var
511   Dependency: TPkgDependency;
512   Cnt: Integer;
513   NewPackageID: TLazPackageID;
514 begin
515   ClearNewInstalledPackages;
516   Cnt:=0;
517   Dependency:=OldInstalledPackages;
518   while Dependency<>nil do begin
519     NewPackageID:=TLazPackageID.Create;
520     if (Dependency.LoadPackageResult=lprSuccess)
521     and (Dependency.RequiredPackage<>nil) then begin
522       // packages can be freed while the dialog runs => use packageid instead
523       NewPackageID.AssignID(Dependency.RequiredPackage);
524     end else begin
525       NewPackageID.Name:=Dependency.PackageName;
526     end;
527     FNewInstalledPackages.Add(NewPackageID);
528     Dependency:=Dependency.NextRequiresDependency;
529     inc(Cnt);
530   end;
531   UpdateNewInstalledPackages;
532   UpdateAvailablePackages;
533 end;
534 
TInstallPkgSetDialog.PackageInInstallListnull535 function TInstallPkgSetDialog.PackageInInstallList(PkgName: string): boolean;
536 var
537   i: Integer;
538 begin
539   for i:=0 to NewInstalledPackages.Count-1 do
540     if SysUtils.CompareText(TLazPackageID(NewInstalledPackages[i]).Name,PkgName)=0 then
541       exit(true);
542   Result:=false;
543 end;
544 
TInstallPkgSetDialog.GetPkgImgIndexnull545 function TInstallPkgSetDialog.GetPkgImgIndex(Installed: TPackageInstallType;
546   InInstallList, IsOnline: boolean): integer;
547 begin
548   if Installed<>pitNope then begin
549     // is not currently installed
550     if InInstallList then begin
551       // is installed and will be installed
552       Result:=ImgIndexPackage;
553     end
554     else begin
555       // is installed and will be uninstalled
556       Result:=ImgIndexUninstallPackage;
557     end;
558   end else begin
559     // is currently installed
560     if InInstallList then begin
561       // is not installed and will be installed
562       Result:=ImgIndexInstallPackage;
563     end
564     else begin
565       // is not installed and will be not be installed
566       if IsOnline then
567         Result := ImgIndexAvailableOnline
568       else
569         Result:=ImgIndexPackage;
570     end;
571   end;
572 end;
573 
FindOnlinePackageLinknull574 function TInstallPkgSetDialog.FindOnlinePackageLink(const AName: String): TPackageLink;
575 var
576   PackageLink: TPackageLink;
577   PkgName: String;
578   P: Integer;
579 begin
580   Result := nil;
581   Exit;
582   if OPMInterface = nil then
583     Exit;
584   PkgName := Trim(AName);
585   P := Pos(' ', PkgName);
586   if P > 0 then
587     PkgName := Copy(PkgName, 1, P - 1);
588   PackageLink := OPMInterface.FindOnlineLink(PkgName);
589   if PackageLink <> nil then
590     Result := PackageLink;
591 end;
592 
593 
594 procedure TInstallPkgSetDialog.UpdateAvailablePackages(Immediately: boolean);
595 var
596   ANode: TAvlTreeNode;
597   FilteredBranch: TTreeFilterBranch;
598   Info: TLPKInfo;
599   List: TStringList;
600   i: Integer;
601   PackageLink: TPackageLink;
602 begin
603   if not Immediately then begin
604     fAvailablePkgsNeedUpdate:=true;
605     IdleConnected:=true;
606     exit;
607   end;
608   fAvailablePkgsNeedUpdate:=false;
609   List:=TStringList.Create;
610   try
611     // collect available packages, not yet installed
612     LPKInfoCache.EnterCritSection;
613     try
614       ANode:=LPKInfoCache.LPKByID.FindLowest;
615       while ANode<>nil do begin
616         Info:=TLPKInfo(ANode.Data);
617         ANode:=LPKInfoCache.LPKByID.FindSuccessor(ANode);
618         PackageLink := FindOnlinePackageLink(Info.ID.Name);
619         if (PackageLink <> nil) and (PackageLink.PackageType in [lptDesignTime,lptRunAndDesignTime]) then begin
620           if (not PackageInInstallList(Info.ID.Name)) then begin
621             Info.PkgType := PackageLink.PackageType;
622             Info.ID.Version.Assign(PackageLink.Version);
623             List.Add(Info.ID.IDAsString);
624             Continue;
625           end;
626         end;
627         if Info.LPKParsed=lpkiParsedError then continue;
628         if (Info.LPKParsed in [lpkiNotParsed,lpkiParsing])
629         or (Info.PkgType in [lptDesignTime,lptRunAndDesignTime])
630         then begin
631           if (not PackageInInstallList(Info.ID.Name)) then
632             List.Add(Info.ID.IDAsString);
633         end;
634       end;
635     finally
636       LPKInfoCache.LeaveCritSection;
637     end;
638     // fill tree view through FilterEdit
639     FilteredBranch := AvailableFilterEdit.GetCleanBranch(Nil); // All items are top level.
640     for i:=0 to List.Count-1 do
641       FilteredBranch.AddNodeData(List[i],nil);
642   finally
643     List.Free;
644   end;
645   AvailableFilterEdit.InvalidateFilter;
646 end;
647 
648 procedure TInstallPkgSetDialog.UpdateNewInstalledPackages;
649 var
650   NewPackageID: TLazPackageID;
651   APackage: TLazPackage;
652   FilteredBranch: TTreeFilterBranch;
653   List: TStringListUTF8Fast;
654   i: Integer;
655 begin
656   List:=TStringListUTF8Fast.Create;
657   try
658     for i:=0 to FNewInstalledPackages.Count-1 do begin
659       NewPackageID:=TLazPackageID(FNewInstalledPackages[i]);
660       APackage:=PackageGraph.FindPackageWithName(NewPackageID.Name,nil);
661       if APackage<>nil then
662         NewPackageID:=APackage;
663       List.Add(NewPackageID.IDAsString);
664     end;
665     List.Sort;
666     // fill tree view through FilterEdit
667     FilteredBranch := InstalledFilterEdit.GetCleanBranch(Nil); // All items are top level.
668     for i:=0 to List.Count-1 do
669       FilteredBranch.AddNodeData(List[i],nil);
670   finally
671     List.Free;
672   end;
673   InstalledFilterEdit.InvalidateFilter;
674 end;
675 
676 procedure TInstallPkgSetDialog.PkgInfosChanged;
677 // called in mainthread after package parser helper thread finished
678 begin
679   UpdateAvailablePackages;
680 end;
681 
682 procedure TInstallPkgSetDialog.ChangePkgVersion(PkgInfo: TLPKInfo;
683   NewVersion: TPkgVersion);
684 // called by LPKInfoCache when a lpk has a different version than the IDE list
685 var
686   OldID, NewID: String;
687 
688   procedure ChangeTV(TV: TTreeView);
689   var
690     i: Integer;
691     Node: TTreeNode;
692   begin
693     for i:=0 to TV.Items.TopLvlCount-1 do begin
694       Node:=TV.Items.TopLvlItems[i];
695       if Node.Text=OldID then
696         Node.Text:=NewID;
697     end;
698   end;
699 
700 begin
701   OldID:=PkgInfo.ID.IDAsString;
702   NewID:=PkgInfo.ID.Name+' '+NewVersion.AsString;
703   ChangeTV(AvailableTreeView);
704   ChangeTV(InstallTreeView);
705 end;
706 
TInstallPkgSetDialog.DependencyToStrnull707 function TInstallPkgSetDialog.DependencyToStr(Dependency: TPkgDependency): string;
708 begin
709   Result:='';
710   if Dependency=nil then exit;
711   if (Dependency.LoadPackageResult=lprSuccess)
712   and (Dependency.RequiredPackage<>nil) then
713     Result:=Dependency.RequiredPackage.IDAsString
714   else
715     Result:=Dependency.PackageName;
716 end;
717 
718 procedure TInstallPkgSetDialog.ClearNewInstalledPackages;
719 begin
720   FNewInstalledPackages.Clear;
721 end;
722 
TInstallPkgSetDialog.CheckSelectionnull723 function TInstallPkgSetDialog.CheckSelection: boolean;
724 begin
725   OnCheckInstallPackageList(FNewInstalledPackages,true,Result);
726 end;
727 
728 procedure TInstallPkgSetDialog.UpdateButtonStates;
729 var
730   Cnt: Integer;
731   Dependency: TPkgDependency;
732   s: String;
733   ListChanged: Boolean;
734 begin
735   UninstallButton.Enabled:=InstallTreeView.Selected<>nil;
736   AddToInstallButton.Enabled:=AvailableTreeView.Selected<>nil;
737   // check for changes
738   ListChanged:=false;
739   Cnt:=0;
740   Dependency:=OldInstalledPackages;
741   while Dependency<>nil do begin
742     s:=Dependency.PackageName;
743     if not PackageInInstallList(s) then begin
744       ListChanged:=true;
745       break;
746     end;
747     Dependency:=Dependency.NextRequiresDependency;
748     inc(Cnt);
749   end;
750   if InstallTreeView.Items.TopLvlCount<>Cnt then
751     ListChanged:=true;
752   SaveAndExitButton.Enabled:=ListChanged;
753   SaveAndRebuildButton.Enabled:=ListChanged;
754 end;
755 
756 procedure TInstallPkgSetDialog.UpdatePackageInfo(Tree: TTreeView);
757 var
758   InfoStr: string;
759 
760   procedure AddState(const NewState: string);
761   begin
762     if (InfoStr<>'') and (InfoStr[length(InfoStr)]<>' ') then
763       InfoStr:=InfoStr+', ';
764     InfoStr:=InfoStr+NewState;
765   end;
766 
767 var
768   PkgID: String;
769   Info: TLPKInfo;
770   PackageLink: TPackageLink;
771   Author, Description, License: String;
772 begin
773   if Tree = nil then Exit;
774   PkgID := '';
775   if Tree.Selected <> nil then
776     PkgID := Tree.Selected.Text;
777   if PkgID = '' then Exit;
778   if Tree = InstallTreeView then
779     PackageLink := nil
780   else
781     PackageLink := FindOnlinePackageLink(PkgID);
782   LPKInfoCache.EnterCritSection;
783   try
784     Info:=LPKInfoCache.FindPkgInfoWithIDAsString(PkgID);
785     if ((Info=nil) and (FSelectedPkgID=''))
786     or ((Info<>nil) and (Info.ID.IDAsString=FSelectedPkgID)
787                     and (Info.LPKParsed=FSelectedPkgState))
788     then
789       exit; // no change
790     PkgInfoMemo.Clear;
791     PkgInfoMemoLicense.Clear;
792     if (Info=nil) then begin
793       FSelectedPkgID:='';
794       exit;
795     end;
796     FSelectedPkgID:=PkgID;
797 
798     if Info.LPKParsed=lpkiNotParsed then begin
799       LPKInfoCache.ParseLPKInfoInMainThread(Info);
800       if FSelectedPkgID='' then begin
801         // version has changed
802         // => has already triggered an update
803         exit;
804       end;
805     end;
806 
807     if PackageLink = nil then
808     begin
809       Author := Info.Author;
810       Description := Trim(Info.Description);
811       License := Info.License;
812     end
813     else
814     begin
815       Author := PackageLink.Author;
816       Description := Trim(PackageLink.Description);
817       License := PackageLink.License;
818     end;
819 
820     if Description<>'' then         // Description is the most interesting piece.
821       PkgInfoMemo.Lines.Add(Description); // Put it first.
822     PkgInfoMemo.Lines.Add('');
823     if Author<>'' then
824       PkgInfoMemo.Lines.Add(lisPckOptsAuthor + ': ' + Author);         // Author
825     PkgInfoMemo.Lines.Add(Format(lisOIPFilename, [Info.LPKFilename])); // Pkg name
826 
827     if License<>'' then             // License has its own memo.
828       PkgInfoMemoLicense.Lines.Add(lisPckOptsLicense + ': ' + License);
829 
830     InfoStr:=lisCurrentState;
831     if Info.Installed<>pitNope then
832     begin
833       if PackageInInstallList(Info.ID.Name)=false then
834         AddState(lisSelectedForUninstallation);
835       AddState(lisInstalled);
836     end
837     else
838     begin
839       if PackageInInstallList(Info.ID.Name)=true then
840         AddState(lisSelectedForInstallation);
841       AddState(lisNotInstalled);
842       if PackageLink <> nil then
843         AddState(lisOnlinePackage);
844     end;
845     if Info.Base then
846       AddState(lisPckExplBase);
847     AddState(LazPackageTypeIdents[Info.PkgType]);
848     PkgInfoMemo.Lines.Add(InfoStr);
849     PkgInfoMemo.SelStart := 1;
850     PkgInfoMemoLicense.SelStart := 1;
851   finally
852     LPKInfoCache.LeaveCritSection;
853   end;
854 end;
855 
TInstallPkgSetDialog.NewInstalledPackagesContainsnull856 function TInstallPkgSetDialog.NewInstalledPackagesContains(
857   APackageID: TLazPackageID): boolean;
858 begin
859   Result:=IndexOfNewInstalledPackageID(APackageID)>=0;
860 end;
861 
IndexOfNewInstalledPackageIDnull862 function TInstallPkgSetDialog.IndexOfNewInstalledPackageID(
863   APackageID: TLazPackageID): integer;
864 begin
865   Result:=FNewInstalledPackages.Count-1;
866   while (Result>=0)
867   and (TLazPackageID(FNewInstalledPackages[Result]).Compare(APackageID)<>0) do
868     dec(Result);
869 end;
870 
IndexOfNewInstalledPkgByNamenull871 function TInstallPkgSetDialog.IndexOfNewInstalledPkgByName(
872   const APackageName: string): integer;
873 begin
874   Result:=FNewInstalledPackages.Count-1;
875   while (Result>=0)
876   and (CompareText(TLazPackageID(FNewInstalledPackages[Result]).Name,
877        APackageName)<>0)
878   do
879     dec(Result);
880 end;
881 
882 procedure TInstallPkgSetDialog.SavePackageListToFile(const AFilename: string);
883 var
884   XMLConfig: TXMLConfig;
885   i: Integer;
886   LazPackageID: TLazPackageID;
887 begin
888   try
889     XMLConfig:=TXMLConfig.CreateClean(AFilename);
890     try
891       XMLConfig.SetDeleteValue('Packages/Count',FNewInstalledPackages.Count,0);
892       for i:=0 to FNewInstalledPackages.Count-1 do begin
893         LazPackageID:=TLazPackageID(FNewInstalledPackages[i]);
894         XMLConfig.SetDeleteValue('Packages/Item'+IntToStr(i)+'/ID',
895                                  LazPackageID.IDAsString,'');
896       end;
897       InvalidateFileStateCache;
898       XMLConfig.Flush;
899     finally
900       XMLConfig.Free;
901     end;
902   except
903     on E: Exception do begin
904       MessageDlg(lisCodeToolsDefsWriteError,
905         Format(lisErrorWritingPackageListToFile,[LineEnding,AFilename,LineEnding,E.Message]),
906         mtError, [mbCancel], 0);
907     end;
908   end;
909 end;
910 
911 procedure TInstallPkgSetDialog.LoadPackageListFromFile(const AFilename: string);
912 
PkgNameExistsnull913   function PkgNameExists(List: TObjectList; ID: TLazPackageID): boolean;
914   var
915     i: Integer;
916     LazPackageID: TLazPackageID;
917   begin
918     if List<>nil then
919       for i:=0 to List.Count-1 do begin
920         LazPackageID:=TLazPackageID(List[i]);
921         if CompareText(LazPackageID.Name,ID.Name)=0 then begin
922           Result:=true;
923           exit;
924         end;
925       end;
926     Result:=false;
927   end;
928 
929 var
930   XMLConfig: TXMLConfig;
931   i: Integer;
932   LazPackageID: TLazPackageID;
933   NewCount: LongInt;
934   NewList: TObjectList;
935   ID: String;
936 begin
937   NewList:=nil;
938   LazPackageID:=nil;
939   try
940     XMLConfig:=TXMLConfig.Create(AFilename);
941     try
942       NewCount:=XMLConfig.GetValue('Packages/Count',0);
943       LazPackageID:=TLazPackageID.Create;
944       for i:=0 to NewCount-1 do begin
945         // get ID
946         ID:=XMLConfig.GetValue('Packages/Item'+IntToStr(i)+'/ID','');
947         if ID='' then continue;
948         // parse ID
949         if not LazPackageID.StringToID(ID) then continue;
950         // ignore doubles
951         if PkgNameExists(NewList,LazPackageID) then continue;
952         // add
953         if NewList=nil then NewList:=TObjectList.Create(true);
954         NewList.Add(LazPackageID);
955         LazPackageID:=TLazPackageID.Create;
956       end;
957       // clean up old list
958       ClearNewInstalledPackages;
959       FNewInstalledPackages.Free;
960       // assign new list
961       FNewInstalledPackages:=NewList;
962       NewList:=nil;
963       UpdateNewInstalledPackages;
964       UpdateAvailablePackages;
965       UpdateButtonStates;
966     finally
967       XMLConfig.Free;
968       LazPackageID.Free;
969       NewList.Free;
970     end;
971   except
972     on E: Exception do begin
973       MessageDlg(lisCodeToolsDefsReadError,
974         Format(lisErrorReadingPackageListFromFile,[LineEnding,AFilename,LineEnding,E.Message]),
975         mtError, [mbCancel], 0);
976     end;
977   end;
978 end;
979 
ExtractNameFromPkgIDnull980 function TInstallPkgSetDialog.ExtractNameFromPkgID(ID: string): string;
981 begin
982   if ID='' then
983     Result:=''
984   else
985     Result:=GetIdentifier(PChar(ID));
986 end;
987 
988 procedure TInstallPkgSetDialog.AddToInstall;
989 
SelectionOknull990   function SelectionOk(aPackageID: TLazPackageID): Boolean;
991   var
992     APackage: TLazPackage;
993     ConflictDep: TPkgDependency;
994     i: Integer;
995   begin
996     // check if already in list
997     if NewInstalledPackagesContains(aPackageID) then begin
998       MessageDlg(lisDuplicate,
999         Format(lisThePackageIsAlreadyInTheList, [aPackageID.Name]),
1000         mtError, [mbCancel],0);
1001       exit(false);
1002     end;
1003     // check if a package with same name is already in the list
1004     i:=IndexOfNewInstalledPkgByName(aPackageID.Name);
1005     if i>=0 then begin
1006       MessageDlg(lisConflict,
1007         Format(lisThereIsAlreadyAPackageInTheList, [aPackageID.Name]),
1008         mtError,[mbCancel],0);
1009       exit(false);
1010     end;
1011     // check if package is loaded and has some attributes that prevents
1012     // installation in the IDE
1013     APackage:=PackageGraph.FindPackageWithID(aPackageID);
1014     if APackage<>nil then begin
1015       if APackage.PackageType in [lptRunTime,lptRunTimeOnly] then begin
1016         IDEMessageDialog(lisNotADesigntimePackage,
1017           Format(lisThePackageIsNotADesignTimePackageItCanNotBeInstall,
1018                  [APackage.IDAsString]),
1019           mtError, [mbCancel]);
1020         exit(false);
1021       end;
1022       ConflictDep:=PackageGraph.FindRuntimePkgOnlyRecursively(
1023         APackage.FirstRequiredDependency);
1024       if ConflictDep<>nil then begin
1025         IDEMessageDialog(lisNotADesigntimePackage,
1026           Format(lisThePackageCanNotBeInstalledBecauseItRequiresWhichI,
1027             [APackage.Name, ConflictDep.AsString]),
1028           mtError, [mbCancel]);
1029         exit(false);
1030       end;
1031     end;
1032     Result:=true;
1033   end;
1034 
1035 var
1036   i, j: Integer;
1037   NewSelectedIndex, LastNonSelectedIndex: Integer;
1038   NewPackageID: TLazPackageID;
1039   Additions: TObjectList;
1040   AddedPkgNames: TStringList;
1041   TVNode: TTreeNode;
1042   PkgName: String;
1043   FilteredBranch: TTreeFilterBranch;
1044   PkgLinks: TList;
1045   PkgLinksStr: String;
1046   PkgLink: TPackageLink;
1047 begin
1048   NewSelectedIndex:=-1;
1049   LastNonSelectedIndex:=-1;
1050   Additions:=TObjectList.Create(false);
1051   AddedPkgNames:=TStringList.Create;
1052   PkgLinks := TList.Create;
1053   NewPackageID:=TLazPackageID.Create;
1054   FilteredBranch := AvailableFilterEdit.GetExistingBranch(Nil); // All items are top level.
1055   try
1056     for i:=0 to AvailableTreeView.Items.TopLvlCount-1 do
1057     begin
1058       TVNode:=AvailableTreeView.Items.TopLvlItems[i];
1059       if not TVNode.MultiSelected then begin
1060         LastNonSelectedIndex:=i;
1061         continue;
1062       end;
1063       NewSelectedIndex:=i+1; // Will have the next index after the selected one.
1064       PkgName:=TVNode.Text;
1065       // Convert package name to ID and check it
1066       if not NewPackageID.StringToID(PkgName) then begin
1067         TVNode.Selected:=false;
1068         DebugLn('TInstallPkgSetDialog.AddToInstall invalid ID: ', PkgName);
1069         continue;
1070       end;
1071       if not SelectionOk(NewPackageID) then begin
1072         TVNode.Selected:=false;
1073         exit;
1074       end;
1075       // ok => add to list
1076       PkgLink := FindOnlinePackageLink(NewPackageID.Name);
1077       if PkgLink <> nil then begin
1078         if not FileExists(PkgLink.OPMFileName) then
1079           PkgLinks.Add(PkgLink)
1080         else
1081         begin
1082           PkgLink := LazPackageLinks.AddUserLink(PkgLink.OPMFileName, PkgLink.Name);
1083           if PkgLink <> nil then
1084             LazPackageLinks.SaveUserLinks;
1085           Additions.Add(NewPackageID);
1086           NewPackageID:=TLazPackageID.Create;
1087           AddedPkgNames.Add(PkgName);
1088         end;
1089       end else begin
1090         Additions.Add(NewPackageID);
1091         NewPackageID:=TLazPackageID.Create;
1092         AddedPkgNames.Add(PkgName);
1093       end;
1094     end;
1095     //download online packages
1096     if (OPMInterface <> nil) and (PkgLinks.Count > 0) then
1097     begin
1098       PkgLinksStr := '';
1099       for I := 0 to PkgLinks.Count - 1 do begin
1100         if PkgLinksStr = '' then
1101           PkgLinksStr := '"' + TPackageLink(PkgLinks.Items[I]).Name + '"'
1102         else
1103           PkgLinksStr := PkgLinksStr + ', ' + '"' + TPackageLink(PkgLinks.Items[I]).Name + '"';
1104       end;
1105       if IDEMessageDialog(lisDownload, Format(lisDonwloadOnlinePackages, [PkgLinksStr]), mtConfirmation, [mbYes, mbNo]) = mrYes then begin
1106         if OPMInterface.DownloadPackages(PkgLinks) = mrOK then begin
1107           for I := PkgLinks.Count - 1 downto 0 do begin
1108             if OPMInterface.IsPackageAvailable(TPackageLink(PkgLinks.Items[I]), 1) then begin
1109               Additions.Add(NewPackageID);
1110               NewPackageID:=TLazPackageID.Create;
1111               AddedPkgNames.Add(PkgName);
1112               PkgLink := LazPackageLinks.AddUserLink(TPackageLink(PkgLinks.Items[I]).OPMFileName, TPackageLink(PkgLinks.Items[I]).Name);
1113               if PkgLink <> nil then
1114                 LazPackageLinks.SaveUserLinks;
1115             end;
1116           end;
1117         end
1118         else
1119           Dec(NewSelectedIndex);
1120       end
1121       else
1122         Dec(NewSelectedIndex);
1123     end;
1124     // all ok => add to installed packages
1125     for i:=0 to Additions.Count-1 do
1126       FNewInstalledPackages.Add(Additions[i]);
1127     for i:=0 to AddedPkgNames.Count-1 do begin
1128       j:=FilteredBranch.Items.IndexOf(AddedPkgNames[i]);
1129       Assert(j<>-1, 'TInstallPkgSetDialog.AddToInstall: '+AddedPkgNames[i]+' not found in Filter items.');
1130       FilteredBranch.Items.Delete(j);
1131     end;
1132     // Don't call UpdateAvailablePackages here, only the selected nodes were removed.
1133     UpdateNewInstalledPackages;
1134     UpdateButtonStates;
1135     if ((NewSelectedIndex=-1) or (NewSelectedIndex=AvailableTreeView.Items.TopLvlCount)) then
1136       NewSelectedIndex:=LastNonSelectedIndex;
1137     if NewSelectedIndex<>-1 then
1138       AvailableTreeView.Items.TopLvlItems[NewSelectedIndex].Selected:=True;
1139     AvailableFilterEdit.InvalidateFilter;
1140   finally
1141     NewPackageID.Free;
1142     AddedPkgNames.Free;
1143     Additions.Free;
1144     PkgLinks.Free;
1145   end;
1146 end;
1147 
1148 procedure TInstallPkgSetDialog.AddToUninstall;
1149 
1150   function SelectionOk(aPackageID: TLazPackageID): Boolean;
1151   var
1152     APackage: TLazPackage;
1153   begin
1154     APackage:=PackageGraph.FindPackageWithID(aPackageID);
1155     if APackage<>nil then begin
1156       // check if package is a base package
1157       if PackageGraph.IsStaticBasePackage(APackage.Name) then begin
1158         MessageDlg(lisUninstallImpossible,
1159           Format(lisThePackageCanNotBeUninstalledBecauseItIsNeededByTh, [
1160             APackage.Name]), mtError, [mbCancel], 0);
1161         exit(false);
1162       end;
1163     end;
1164     Result:=true;
1165   end;
1166 
1167 var
1168   i, j: Integer;
1169   NewSelectedIndex, LastNonSelectedIndex: Integer;
1170   DelPackageID, PackID: TLazPackageID;
1171   Deletions: TObjectList;
1172   DeletedPkgNames: TStringList;
1173   TVNode: TTreeNode;
1174   PkgName: String;
1175   FilteredBranch: TTreeFilterBranch;
1176 begin
1177   NewSelectedIndex:=-1;
1178   LastNonSelectedIndex:=-1;
1179   Deletions:=TObjectList.Create(true);
1180   DeletedPkgNames:=TStringList.Create;
1181   DelPackageID:=TLazPackageID.Create;
1182   FilteredBranch := InstalledFilterEdit.GetExistingBranch(Nil); // All items are top level.
1183   try
1184     for i:=0 to InstallTreeView.Items.TopLvlCount-1 do begin
1185       TVNode:=InstallTreeView.Items.TopLvlItems[i];
1186       if not TVNode.MultiSelected then begin
1187         LastNonSelectedIndex:=i;
1188         continue;
1189       end;
1190       NewSelectedIndex:=i+1; // Will have the next index after the selected one.
1191       PkgName:=TVNode.Text;
1192       if not DelPackageID.StringToID(PkgName) then begin
1193         TVNode.Selected:=false;
1194         debugln('TInstallPkgSetDialog.AddToUninstall invalid ID: ', PkgName);
1195         continue;
1196       end;
1197       if not SelectionOk(DelPackageID) then begin
1198         TVNode.Selected:=false;
1199         exit;
1200       end;
1201       // ok => add to deletions
1202       Deletions.Add(DelPackageID);
1203       DelPackageID:=TLazPackageID.Create;
1204       DeletedPkgNames.Add(PkgName);
1205     end;
1206 
1207     // ok => remove from installed packages
1208     InstallTreeView.Selected:=nil;
1209     for i:=0 to Deletions.Count-1 do begin
1210       PackID:=TLazPackageID(Deletions[i]);
1211       j:=IndexOfNewInstalledPackageID(PackID);
1212       FNewInstalledPackages.Delete(j);
1213     end;
1214     for i:=0 to DeletedPkgNames.Count-1 do begin
1215       j:=FilteredBranch.Items.IndexOf(DeletedPkgNames[i]);
1216       Assert(j<>-1, 'TInstallPkgSetDialog.AddToUninstall: '+DeletedPkgNames[i]+' not found in Filter items.');
1217       FilteredBranch.Items.Delete(j);
1218     end;
1219 
1220     // Don't call UpdateNewInstalledPackages here, only the selected nodes were removed.
1221     UpdateAvailablePackages;
1222     UpdateButtonStates;
1223     if ((NewSelectedIndex=-1) or (NewSelectedIndex=InstallTreeView.Items.TopLvlCount)) then
1224       NewSelectedIndex:=LastNonSelectedIndex;
1225     if NewSelectedIndex<>-1 then
1226       InstallTreeView.Items.TopLvlItems[NewSelectedIndex].Selected:=True;
1227     InstalledFilterEdit.InvalidateFilter;
1228   finally
1229     DelPackageID.Free;
1230     DeletedPkgNames.Free;
1231     Deletions.Free;
1232   end;
1233 end;
1234 
GetNewInstalledPackagesnull1235 function TInstallPkgSetDialog.GetNewInstalledPackages: TObjectList;
1236 var
1237   i: Integer;
1238   NewPackageID: TLazPackageID;
1239 begin
1240   Result:=TObjectList.Create(true);
1241   for i:=0 to FNewInstalledPackages.Count-1 do begin
1242     NewPackageID:=TLazPackageID.Create;
1243     NewPackageID.AssignID(TLazPackageID(FNewInstalledPackages[i]));
1244     Result.Add(NewPackageID);
1245   end;
1246 end;
1247 
1248 end.
1249 
1250