{
 ***************************************************************************
 *                                                                         *
 *   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: Balázs Székely
 Abstract:
   Implementation of the visual tree, which displays the package sructure
   downloaded from the remote repository.
}
unit opkman_visualtree;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, contnrs, Math, dateutils, laz.VirtualTrees,
  // LCL
  Controls, Graphics, Menus, Dialogs, Forms, LCLType, Buttons,
  // LazUtils
  LazStringUtils,
  // IDEIntf
  LCLIntf, PackageIntf,
  // OpkMan
  opkman_common, opkman_serializablepackages, opkman_const,
  opkman_options, opkman_packagedetailsfrm, opkman_showhint;


type
  PData = ^TData;
  TData = record
    DataType: Integer;
    PID: Integer;
    PFID: Integer;
    Repository: String;
    PackageState: TPackageState;
    PackageName: String;
    PackageDisplayName: String;
    Category: String;
    LazarusPackageName: String;
    Version: String;
    InstalledVersion: String;
    UpdateVersion: String;
    Description: String;
    Author: String;
    LazCompatibility: String;
    FPCCompatibility: String;
    SupportedWidgetSet: String;
    PackageType: TLazPackageType;
    Dependencies: String;
    License: String;
    RepositoryFileName: String;
    RepositoryFileSize: Int64;
    RepositoryFileHash: String;
    RepositoryDate: TDateTime;
    HomePageURL: String;
    DownloadURL: String;
    DownloadZipURL: String;
    HasUpdate: Boolean;
    DisableInOPM: Boolean;
    IsUpdated: Boolean;
    SVNURL: String;
    CommunityDescription: String;
    ExternalDependencies: String;
    OrphanedPackage: Integer;
    InstallState: Integer;
    ButtonID: Integer;
    Button: TSpeedButton;
    Rating: Integer;
    IsDependencyNode: Boolean;
  end;

  TFilterBy = (fbPackageName, fbLazarusPackageName, fbPackageCategory, fbPackageState,
               fbVersion, fbDescription, fbAuthor, fbLazCompatibility, fbFPCCompatibility,
               fbSupportedWidgetsets, fbPackageType, fbDependecies, fbLicense);

  { TVisualTree }
  TOnChecking = procedure(Sender: TObject; const AIsAllChecked: Boolean) of object;
  TVisualTree = class
  private
    FVST: TLazVirtualStringTree;
    FHoverNode: PVirtualNode;
    FHoverNodeOld: PVirtualNode;
    FHoverP: TPoint;
    FHoverColumn: Integer;
    FLink: String;
    FLinkClicked: Boolean;
    FSortCol: Integer;
    FSortDir: laz.VirtualTrees.TSortDirection;
    FCheckingNodes: Boolean;
    FLeaving: Boolean;
    FOnChecking: TOnChecking;
    FOnChecked: TNotifyEvent;
    FMouseEnter: Boolean;
    FShowHintFrm: TShowHintFrm;
    FOldButtonNode: PVirtualNode;
    FStarSize: Integer;
    procedure DoOpenPackage(const APackageName: String);
    procedure VSTBeforeCellPaint(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; {%H-}Column: TColumnIndex;
      {%H-}CellPaintMode: TVTCellPaintMode; CellRect: TRect; var {%H-}ContentRect: TRect);
    procedure VSTChecking(Sender: TBaseVirtualTree; {%H-}Node: PVirtualNode;
      var NewState: TCheckState; var {%H-}Allowed: Boolean);
    procedure VSTChecked(Sender: TBaseVirtualTree; {%H-}Node: PVirtualNode);
    procedure VSTCompareNodes(Sender: TBaseVirtualTree; Node1,
      Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
    procedure VSTGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
      {%H-}Kind: TVTImageKind; Column: TColumnIndex; var {%H-}Ghosted: Boolean;
      var ImageIndex: Integer);
    procedure VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; {%H-}TextType: TVSTTextType; var CellText: String);
    procedure VSTHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
    procedure VSTPaintText(Sender: TBaseVirtualTree;
      const TargetCanvas: TCanvas; Node: PVirtualNode; {%H-}Column: TColumnIndex;
      {%H-}TextType: TVSTTextType);
    procedure VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure VSTMouseMove(Sender: TObject; {%H-}Shift: TShiftState; X, Y: Integer);
    procedure VSTMouseEnter(Sender: TObject);
    procedure VSTMouseLeave(Sender: TObject);
    procedure VSTMouseDown(Sender: TObject; Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer);
    procedure VSTKeyDown(Sender: TObject; var {%H-}Key: Word; {%H-}Shift: TShiftState);
    procedure VSTKeyUp(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
    procedure VSTGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
      var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: String);
    procedure VSTAfterCellPaint(Sender: TBaseVirtualTree;  {%H-}TargetCanvas: TCanvas;
      Node: PVirtualNode; Column: TColumnIndex; const {%H-}CellRect: TRect);
    procedure VSTDblClick(Sender: TObject);
    procedure VSTScroll(Sender: TBaseVirtualTree; {%H-}DeltaX, {%H-}DeltaY: Integer);
    procedure VSTIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: String; var Result: Integer);
    procedure VSTClick(Sender: TObject);
    procedure VSTAfterPaint(Sender: TBaseVirtualTree; {%H-}TargetCanvas: TCanvas);
    procedure VSTEnter(Sender: TObject);
    function IsAllChecked(const AChecking: PVirtualNode): Boolean;
    procedure ButtonClick(Sender: TObject);
    procedure DrawStars(ACanvas: TCanvas; AStartIndex: Integer; P: TPoint; AAvarage: Double);
    function GetColumn(const AX: Integer): Integer;
    function TranslateCategories(const AStr: String): String;
    procedure SetButtonVisibility(Node: PVirtualNode; Column: TColumnIndex);
    procedure CallBack(Sender: TBaseVirtualTree; Node: PVirtualNode; {%H-}Data: Pointer; var {%H-}Abort: Boolean);
    procedure ShowDetails(const AButtonID: Integer);
    procedure OpenPackage(const AButtonID: Integer);
    procedure ResetDependencyNodes;
    function GetPackageAbsolutePath(APackageName: String): String;
  public
    constructor Create(const AParent: TWinControl; const AImgList: TImageList;
      APopupMenu: TPopupMenu);
    destructor Destroy; override;
  public
    procedure PopulateTree;
    procedure CheckNodes(const Checked: Boolean);
    procedure FilterTree(const AFilterBy: TFilterBy; const AText: String; const AExtraParam: Integer = -1);
    procedure ResetFilter;
    procedure ExpandEx;
    procedure CollapseEx;
    procedure GetPackageList;
    procedure UpdatePackageStates;
    procedure UpdatePackageUStatus;
    function ResolveDependencies: TModalResult;
    function GetCheckedRepositoryPackages: Integer;
    procedure SetupColors;
    procedure AutoAdjustLayout(AXProportion, AYProportion: Double);
  published
    property OnChecking: TOnChecking read FOnChecking write FOnChecking;
    property OnChecked: TNotifyEvent read FOnChecked write FOnChecked;
    property VST: TLazVirtualStringTree read FVST;
    property ShowHintFrm: TShowHintFrm read FShowHintFrm;
  end;

var
  VisualTree: TVisualTree = nil;

implementation

uses
  imgList;

{ TVisualTree }

constructor TVisualTree.Create(const AParent: TWinControl; const AImgList: TImageList;
  APopupMenu: TPopupMenu);
begin
  FVST := TLazVirtualStringTree.Create(nil);
  with FVST do
   begin
     Parent := AParent;
     Align := alClient;
     Anchors := [akLeft, akTop, akRight];
     Images := AImgList;
     PopupMenu := APopupMenu;
     DefaultNodeHeight := 25;
     Indent := 22;
     TabOrder := 1;
     DefaultText := '';
     Header.AutoSizeIndex := 4;
     Header.Height := 25;
     Colors.DisabledColor := clBlack;
     with Header.Columns.Add do
     begin
       Position := 0;
       Width := 270;
       Text := rsMainFrm_VSTHeaderColumn_PackageName;
     end;
     with Header.Columns.Add do
     begin
       Position := 1;
       Alignment := taCenter;
       Width := 90;
       {$IFDEF LCLCarbon}
       Options := Options - [coResizable];
       {$ENDIF}
       Text := rsMainFrm_VSTHeaderColumn_Installed;
     end;
     with Header.Columns.Add do
     begin
       Position := 2;
       Alignment := taCenter;
       Width := 110;
       {$IFDEF LCLCarbon}
       Options := Options - [coResizable];
       {$ENDIF}
       Text := rsMainFrm_VSTHeaderColumn_Repository;
       Hint := rsMainFrm_VSTHeaderColumn_Repository_Hint;
     end;
     with Header.Columns.Add do
     begin
       Position := 3;
       Alignment := taCenter;
       Width := 110;
       {$IFDEF LCLCarbon}
       Options := Options - [coResizable];
       {$ENDIF}
       Text := rsMainFrm_VSTHeaderColumn_Update;
       Hint := rsMainFrm_VSTHeaderColumn_Update_Hint;
     end;
     with Header.Columns.Add do
     begin
       Position := 4;
       Width := 280;
       {$IFDEF LCLCarbon}
       Options := Options - [coResizable];
       {$ENDIF}
       Text := rsMainFrm_VSTHeaderColumn_Data;
      end;
     with Header.Columns.Add do
     begin
       Position := 5;
       Alignment := taCenter;
       Width := 80;
       Options := Options - [coResizable];
       Text := rsMainFrm_VSTHeaderColumn_Rating;
     end;
     with Header.Columns.Add do
     begin
       Position := 6;
       Alignment := taCenter;
       Width := 20;
       Options := Options - [coResizable];
     end;
     Header.Options := [hoAutoResize, hoColumnResize, hoRestrictDrag, hoShowSortGlyphs, hoVisible, hoShowHint];
     {$IFDEF LCLCarbon}
     Header.Options := Header.Options - [hoShowSortGlyphs];
     {$ENDIF}
     Header.SortColumn := 0;
     HintMode := hmHint;
     ShowHint := True;
     TabOrder := 2;
     IncrementalSearch := isVisibleOnly;
     IncrementalSearchDirection := sdForward;
     IncrementalSearchStart := ssFocusedNode;
     IncrementalSearchTimeout := 3000;
     TreeOptions.MiscOptions := [toCheckSupport, toFullRepaintOnResize, toInitOnSave, toWheelPanning];
     TreeOptions.PaintOptions := [toHideFocusRect, toAlwaysHideSelection, toPopupMode, toShowButtons, toShowDropmark, toShowRoot, toThemeAware];
     TreeOptions.SelectionOptions := [toFullRowSelect, toRightClickSelect];
     TreeOptions.StringOptions := [toShowStaticText];
     TreeOptions.AutoOptions := [toAutoTristateTracking];
     OnBeforeCellPaint := @VSTBeforeCellPaint;
     OnChecking := @VSTChecking;
     OnChecked := @VSTChecked;
     OnCompareNodes := @VSTCompareNodes;
     OnGetText := @VSTGetText;
     OnPaintText := @VSTPaintText;
     OnGetImageIndex := @VSTGetImageIndex;
     OnHeaderClick := @VSTHeaderClick;
     OnMouseMove := @VSTMouseMove;
     OnMouseLeave := @VSTMouseLeave;
     OnMouseEnter := @VSTMouseEnter;
     OnMouseDown := @VSTMouseDown;
     OnKeyDown := @VSTKeyDown;
     OnKeyUp := @VSTKeyUp;
     OnDblClick := @VSTDblClick;
     OnClick := @VSTClick;
     OnGetHint := @VSTGetHint;
     OnAfterCellPaint := @VSTAfterCellPaint;
     OnAfterPaint := @VSTAfterPaint;
     OnEnter := @VSTEnter;
     OnFreeNode := @VSTFreeNode;
     OnScroll := @VSTScroll;
     OnIncrementalSearch := @VSTIncrementalSearch;
   end;
  FShowHintFrm := TShowHintFrm.Create(nil);
  if AImgList <> nil then
    FStarSize := AImgList.Width
  else
    FStarSize := 0;
end;

destructor TVisualTree.Destroy;
begin
  FVST.Free;
  FShowHintFrm.Free;
  inherited Destroy;
end;

procedure TVisualTree.PopulateTree;
var
  I, J: Integer;
  RootNode, Node, ChildNode, GrandChildNode: PVirtualNode;
  RootData, Data, ChildData, GrandChildData: PData;
  LazarusPkg: TLazarusPackage;
  UniqueID: Integer;
begin
  FHoverNode := nil;
  FHoverNodeOld := nil;
  FOldButtonNode := nil;
  FVST.Clear;
  FVST.NodeDataSize := SizeOf(TData);
  UniqueID := 0;
  //add repository(DataType = 0)
  FVST.BeginUpdate;
  try
    RootNode := FVST.AddChild(nil);
    RootData := FVST.GetNodeData(RootNode);
    RootData^.Repository := Options.RemoteRepository[Options.ActiveRepositoryIndex];
    RootData^.DataType := 0;
    for I := 0 to SerializablePackages.Count - 1 do
    begin
       //add package(DataType = 1)
       Node := FVST.AddChild(RootNode);
       Node^.CheckType := ctTriStateCheckBox;
       Data := FVST.GetNodeData(Node);
       Data^.PID := I;
       Data^.PackageName := SerializablePackages.Items[I].Name;
       Data^.PackageDisplayName := SerializablePackages.Items[I].DisplayName;
       Data^.PackageState := SerializablePackages.Items[I].PackageState;
       Data^.InstallState := SerializablePackages.GetPackageInstallState(SerializablePackages.Items[I]);
       Data^.HasUpdate := SerializablePackages.Items[I].HasUpdate;
       Data^.DisableInOPM := SerializablePackages.Items[I].DisableInOPM;
       Data^.Rating := SerializablePackages.Items[I].Rating;
       Data^.RepositoryDate := SerializablePackages.Items[I].RepositoryDate;
       FVST.IsDisabled[Node] := Data^.DisableInOPM;
       Data^.DataType := 1;
       for J := 0 to SerializablePackages.Items[I].LazarusPackages.Count - 1 do
       begin
         //add LazarusPackages(DataType = 2)
         LazarusPkg := TLazarusPackage(SerializablePackages.Items[I].LazarusPackages.Items[J]);
         ChildNode := FVST.AddChild(Node);
         ChildNode^.CheckType := ctTriStateCheckBox;
         FVST.IsDisabled[ChildNode] := FVST.IsDisabled[ChildNode^.Parent];
         ChildData := FVST.GetNodeData(ChildNode);
         ChildData^.PID := I;
         ChildData^.PFID := J;
         ChildData^.LazarusPackageName := LazarusPkg.Name;
         ChildData^.InstalledVersion := LazarusPkg.InstalledFileVersion;
         ChildData^.UpdateVersion := LazarusPkg.UpdateVersion;
         ChildData^.Version := LazarusPkg.VersionAsString;
         ChildData^.PackageState := LazarusPkg.PackageState;
         ChildData^.HasUpdate := LazarusPkg.HasUpdate;
         ChildData^.DataType := 2;
         Inc(UniqueID);
         ChildData^.ButtonID := -UniqueID;
         //add description(DataType = 3)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         if ChildData^.InstalledVersion <> '' then
           GrandChildData^.Description := LazarusPkg.InstalledFileDescription
         else
           GrandChildData^.Description := LazarusPkg.Description;
         GrandChildData^.DataType := 3;
         Inc(UniqueID);
         GrandChildData^.ButtonID := UniqueID;
         //add author(DataType = 4)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         GrandChildData^.Author := LazarusPkg.Author;
         GrandChildData^.DataType := 4;
         //add lazcompatibility(DataType = 5)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         GrandChildData^.LazCompatibility := LazarusPkg.LazCompatibility;
         GrandChildData^.DataType := 5;
         //add fpccompatibility(DataType = 6)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         GrandChildData^.FPCCompatibility := LazarusPkg.FPCCompatibility;
         GrandChildData^.DataType := 6;
         //add widgetset(DataType = 7)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         GrandChildData^.SupportedWidgetSet := LazarusPkg.SupportedWidgetSet;
         GrandChildData^.DataType := 7;
         //add packagetype(DataType = 8)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         GrandChildData^.PackageType := LazarusPkg.PackageType;
         GrandChildData^.DataType := 8;
         //add license(DataType = 9)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         if ChildData^.InstalledVersion <> '' then
           GrandChildData^.License := LazarusPkg.InstalledFileLincese
         else
           GrandChildData^.License := LazarusPkg.License;
         GrandChildData^.DataType := 9;
         Inc(UniqueID);
         GrandChildData^.ButtonID := UniqueID;
         //add dependencies(DataType = 10)
         GrandChildNode := FVST.AddChild(ChildNode);
         FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
         GrandChildData := FVST.GetNodeData(GrandChildNode);
         GrandChildData^.Dependencies := LazarusPkg.DependenciesAsString;
         GrandChildData^.DataType := 10;
       end;
       //add miscellaneous(DataType = 11)
       ChildNode := FVST.AddChild(Node);
       FVST.IsDisabled[ChildNode] := FVST.IsDisabled[ChildNode^.Parent];
       ChildData := FVST.GetNodeData(ChildNode);
       ChildData^.DataType := 11;
       //add category(DataType = 12)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.Category := SerializablePackages.Items[I].Category;
       GrandChildData^.DataType := 12;
       //add Repository Filename(DataType = 13)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.RepositoryFileName := SerializablePackages.Items[I].RepositoryFileName;
       GrandChildData^.DataType := 13;
       //add Repository Filesize(DataType = 14)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.RepositoryFileSize := SerializablePackages.Items[I].RepositoryFileSize;
       GrandChildData^.DataType := 14;
       //add Repository Hash(DataType = 15)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.RepositoryFileHash := SerializablePackages.Items[I].RepositoryFileHash;
       GrandChildData^.DataType := 15;
       //add Repository Date(DataType = 16)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.RepositoryDate := SerializablePackages.Items[I].RepositoryDate;
       GrandChildData^.DataType := 16;
       FVST.Expanded[ChildNode] := True;
       //add HomePageURL(DataType = 17)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.HomePageURL := SerializablePackages.Items[I].HomePageURL;
       GrandChildData^.DataType := 17;
       //add DownloadURL(DataType = 18)
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.DownloadURL := SerializablePackages.Items[I].DownloadURL;
       GrandChildData^.DataType := 18;
       //add community description(DataType = 19) - added 2018.08.21
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.CommunityDescription := SerializablePackages.Items[I].CommunityDescription;
       GrandChildData^.DataType := 19;
       Inc(UniqueID);
       GrandChildData^.ButtonID := UniqueID;
       Data^.CommunityDescription := SerializablePackages.Items[I].CommunityDescription;
       //add external dependecies(DataType = 20) - added 2020.04.14
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.ExternalDependencies := SerializablePackages.Items[I].ExternalDependecies;
       GrandChildData^.DataType := 20;
       Inc(UniqueID);
       GrandChildData^.ButtonID := UniqueID;
       Data^.ExternalDependencies := SerializablePackages.Items[I].ExternalDependecies;
       //add orphaned package(DataType = 21) - added 2020.07.23
       GrandChildNode := FVST.AddChild(ChildNode);
       FVST.IsDisabled[GrandChildNode] := FVST.IsDisabled[GrandChildNode^.Parent];
       GrandChildData := FVST.GetNodeData(GrandChildNode);
       GrandChildData^.OrphanedPackage := SerializablePackages.Items[I].OrphanedPackage;
       GrandChildData^.DataType := 21;
       Data^.OrphanedPackage := SerializablePackages.Items[I].OrphanedPackage;
       FVST.IsVisible[GrandChildNode] := False;
    end;
    FVST.SortTree(0, laz.VirtualTrees.sdAscending);
    ExpandEx;
    CollapseEx;
  finally
    FVST.EndUpdate;
  end;
  RootNode := VST.GetFirst;
  if RootNode <> nil then
  begin
    FVST.Selected[RootNode] := True;
    FVST.FocusedNode := RootNode;
  end;
end;

function TVisualTree.IsAllChecked(const AChecking: PVirtualNode): Boolean;
var
  Node: PVirtualNode;
begin
  Result := True;
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    if (FVST.CheckState[Node] = csUncheckedNormal) and (Node <> AChecking) then
    begin
      Result := False;
      Break;
    end;
    Node := FVST.GetNext(Node);
  end;
end;

procedure TVisualTree.ShowDetails(const AButtonID: Integer);
var
  Node, ParentNode, MetaPackageNode: PVirtualNode;
  Data, ParentData, MetaPackageData: PData;
  Text: String;
  FrmCaption: String;
begin
  if Assigned(PackageDetailsFrm) then
    Exit;

  Node := VST.GetFirst;
  while Assigned(Node) do
  begin
    Data := VST.GetNodeData(Node);
    if Data^.ButtonID = AButtonID then
    begin
      ParentNode := Node^.Parent;
      ParentData := VST.GetNodeData(ParentNode);
      case Data^.DataType of
        3: begin
             Text := Data^.Description;
             FrmCaption := rsMainFrm_VSTText_Desc + ' "' + ParentData^.LazarusPackageName  + '"';
           end;
        9: begin
             Text := Data^.License;
             FrmCaption := rsMainFrm_VSTText_Lic  + ' "' + ParentData^.LazarusPackageName  + '"';
           end;
       19: begin
             MetaPackageNode := ParentNode^.Parent;
             if MetaPackageNode <> nil then
             begin
               MetaPackageData := VST.GetNodeData(MetaPackageNode);
               Text := Data^.CommunityDescription;
               FrmCaption := rsMainFrm_VSTText_ComDesc  + ' "' + MetaPackageData^.PackageDisplayName + '"';
             end;
           end;
       20: begin
             MetaPackageNode := ParentNode^.Parent;
             if MetaPackageNode <> nil then
             begin
               MetaPackageData := VST.GetNodeData(MetaPackageNode);
               Text := Data^.ExternalDependencies;
               FrmCaption := rsMainFrm_VSTText_ExternalMetaPackageDeps  + ' "' + MetaPackageData^.PackageDisplayName + '"';
             end;
           end;
      end;
      Break;
    end;
    Node := VST.GetNext(Node);
  end;

  PackageDetailsFrm := TPackageDetailsFrm.Create(TForm(FVST.Parent.Parent));
  try
    PackageDetailsFrm.Caption := FrmCaption;
    PackageDetailsFrm.mDetails.Text := Text;
    PackageDetailsFrm.ShowModal;
  finally
    FreeAndNil(PackageDetailsFrm);
  end;
end;

procedure TVisualTree.DoOpenPackage(const APackageName: String);
var
  PackageAbsolutePath: String;
begin
  PackageAbsolutePath  := GetPackageAbsolutePath(APackageName);
  if PackageEditingInterface.DoOpenPackageFile(PackageAbsolutePath, [], True) <> mrOk then
      MessageDlgEx(rsMainFrm_VSTText_Open_Error, mtError, [mbOk], TForm(FVST.Parent.Parent));
end;

procedure TVisualTree.OpenPackage(const AButtonID: Integer);
var
  Node: PVirtualNode;
  Data: PData;
begin
  Node := VST.GetFirst;
  while Assigned(Node) do
  begin
    Data := VST.GetNodeData(Node);
    if Data^.ButtonID = AButtonID then
    begin
      DoOpenPackage(Data^.LazarusPackageName);
      Break;
    end;
    Node := VST.GetNext(Node);
  end;
end;

procedure TVisualTree.ButtonClick(Sender: TObject);
var
  Tag: Integer;
begin
  Tag := (Sender as TSpeedButton).Tag;
  if Tag < 0 then
    OpenPackage(Tag)
  else
    ShowDetails(Tag);
end;

function TVisualTree.TranslateCategories(const AStr: String): String;
var
  SL: TStringList;
  I, J: Integer;
  Str: String;
begin
  if Categories[0] = CategoriesEng[0] then
  begin
    Result := AStr;
    Exit;
  end;
  Result := '';
  SL := TStringList.Create;
  try
    SL.Delimiter := ',';
    SL.StrictDelimiter := True;
    SL.DelimitedText := AStr;
    for I := 0 to SL.Count - 1 do
    begin
      Str := Trim(SL.Strings[I]);
      for J := 0 to MaxCategories - 1 do
      begin
        if Str = CategoriesEng[J] then
        begin
          if Result = '' then
            Result := Categories[J]
          else
            Result := Result + ', ' + Categories[J];
          Break;
        end;
      end;
    end;
  finally
    SL.Free;
  end;
  if Result = '' then
    Result := AStr;
end;

procedure TVisualTree.SetButtonVisibility(Node: PVirtualNode;
  Column: TColumnIndex);
var
  Data: PData;
  R: TRect;
begin
  Data := FVST.GetNodeData(Node);
  if Assigned(Data^.Button) then
  begin
    R := FVST.GetDisplayRect(Node, Column, False);
    Data^.Button.Left   := R.Right - Data^.Button.Width - 1;
    Data^.Button.Top    := R.Top + 1;
    Data^.Button.Height := R.Bottom - R.Top - 1;
    Data^.Button.Visible := FVST.IsVisible[Node] and IntersectRect(R, FVST.GetDisplayRect(Node, Column, False), FVST.ClientRect);
    Data^.Button.Enabled := not FVST.IsDisabled[Node];
  end;
end;

procedure TVisualTree.CallBack(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Data: Pointer; var Abort: Boolean);
begin
  SetButtonVisibility(Node, 4);
end;

procedure TVisualTree.VSTAfterPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas);
begin
  Sender.IterateSubtree(nil, @CallBack, nil);
end;

procedure TVisualTree.VSTEnter(Sender: TObject);
var
  Node: PVirtualNode;
begin
  if FMouseEnter then
  begin
    FMouseEnter := False;
    Exit;
  end;
  if FVST.SelectedCount = 0 then
  begin
    Node := FVST.GetFirst;
    if Node <> nil then
    begin
      FVST.Selected[Node] := True;
      FVST.FocusedNode := Node;
    end;
  end;
end;

procedure TVisualTree.DrawStars(ACanvas: TCanvas; AStartIndex: Integer;
  P: TPoint; AAvarage: Double);
var
  imgres: TScaledImageListResolution;

  procedure Draw(const AX, AY: Integer; ATyp, ACnt, AWidth: Integer);
  var
    I: Integer;
  begin
    for I := 0 to ACnt - 1 do
      imgres.Draw(ACanvas, AX + I*AWidth, AY, AStartIndex + ATyp);
  end;

var
  F: Double;
  I, X, Y: Integer;
  Stars, NoStars: Integer;
  HalfStar: Boolean;
begin
  imgres := FVST.Images.ResolutionForPPI[FVST.ImagesWidth, FVST.Font.PixelsPerInch, FVST.GetCanvasScaleFactor];

  HalfStar := False;
  F := Frac(AAvarage);
  I := Trunc(AAvarage);
  case CompareValue(F, 0.25, 0.005) of
      -1:
        begin
            Stars := I;
            NoStars := 5 - Stars;
        end;
    0, 1:
        begin
          if CompareValue(F, 0.75, 0.005) = -1 then
          begin
            Stars := I;
            NoStars := 5 - Stars - 1;
            HalfStar := True;
          end
          else
          begin
            Stars := I + 1;
            NoStars := 5 - Stars;
          end;
        end;
  end;
  X := P.X;
  Y := P.Y;
  Draw(X, Y, 0, Stars, FStarSize);
  Inc(X, Stars*FStarSize);
  if HalfStar then
  begin
    Draw(X, Y, 2, 1, FStarSize);
    Inc(X, FStarSize);
  end;
  Draw(X, Y, 1, NoStars, FStarSize);
end;

procedure TVisualTree.VSTAfterCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  const CellRect: TRect);
var
  Data: PData;
  R: TRect;
  P: TPoint;
  Stars: Integer;
begin
  if Column = 5 then
  begin
    Data := FVST.GetNodeData(Node);
    if Data^.DataType = 1 then
    begin
      R := FVST.GetDisplayRect(Node, Column, False);
      P.X := R.Left + 1;
      P.Y := ((R.Bottom - R.Top - FStarSize) div 2) + 1;
      if (Node = FHoverNode) and (not FLeaving) and (FHoverP.X >= P.X + 1) and (Abs(FHoverP.X - P.X) <= R.Right - R.Bottom) then
      begin
        Stars := Trunc((FHoverP.X - P.X)/FStarSize) + 1;
        if Stars > 5 then
          Stars := 5;
        DrawStars(TargetCanvas, 23, P, Stars)
      end
      else
        DrawStars(TargetCanvas, 20, P, Data^.Rating);
    end
  end;
end;

procedure TVisualTree.CheckNodes(const Checked: Boolean);
var
  Node: PVirtualNode;
begin
  FCheckingNodes := True;
  try
    Node := FVST.GetFirst;
    while Assigned(Node) do
    begin
      if Checked then
        FVST.CheckState[Node] := csCheckedNormal
      else
        FVST.CheckState[Node] := csUncheckedNormal;
      Node := FVST.GetNext(Node);
    end;
  finally
    FCheckingNodes := False;
  end;
end;

procedure TVisualTree.FilterTree(const AFilterBy: TFilterBy; const AText:
  String; const AExtraParam: Integer = -1);

  function IsAtLeastOneChildVisible(const ANode: PVirtualNode): Boolean;
  var
    Level: Integer;
    Node: PVirtualNode;
    Data: PData;
  begin
    Result := False;
    Level := FVST.GetNodeLevel(ANode);
    Node := FVST.GetFirstChild(ANode);
    while Assigned(Node) do
    begin
      Data := FVST.GetNodeData(Node);
      case Level of
        0: if (vsVisible in Node^.States) then
           begin
             Result := True;
             Break;
           end;
        1: if (vsVisible in Node^.States) and (Data^.DataType = 2) then
           begin
             Result := True;
             Break;
           end;
      end;
      Node := FVST.GetNextSibling(Node);
    end;
  end;

  procedure HideShowParentNodes(const ANode: PVirtualNode; AShow: Boolean);
  var
    Level: Integer;
    RepositoryNode, PackageNode, LazarusPkgNode: PVirtualNode;
  begin
    RepositoryNode := nil;
    PackageNode := nil;
    LazarusPkgNode := nil;
    Level := FVST.GetNodeLevel(ANode);
    case Level of
      1: begin
           RepositoryNode := ANode^.Parent;
           PackageNode := ANode;
         end;
      2: begin
           RepositoryNode := ANode^.Parent^.Parent;
           PackageNode := ANode^.Parent;
           LazarusPkgNode := ANode;
         end;
      3: begin
           RepositoryNode := ANode^.Parent^.Parent^.Parent;
           PackageNode := ANode^.Parent^.Parent;
           LazarusPkgNode := ANode^.Parent;
         end;
    end;
    if Level = 1 then
    begin
      if AShow then
        FVST.IsVisible[RepositoryNode] := True
      else
        if not IsAtLeastOneChildVisible(RepositoryNode) then
          FVST.IsVisible[RepositoryNode] := False;
    end
    else if Level = 2 then
    begin
      if AShow then
      begin
        FVST.IsVisible[PackageNode] := True;
        FVST.IsVisible[RepositoryNode] := True;
      end
      else
      begin
        if not IsAtLeastOneChildVisible(PackageNode) then
        begin
          FVST.IsVisible[PackageNode] := False;
          HideShowParentNodes(PackageNode, AShow);
        end;
      end;
    end
    else if Level = 3 then
    begin
      if AShow then
      begin
        FVST.IsVisible[LazarusPkgNode] := True;
        FVST.IsVisible[PackageNode] := True;
        FVST.IsVisible[RepositoryNode] := True;
      end
      else
      begin
        FVST.IsVisible[LazarusPkgNode] := False;
        HideShowParentNodes(LazarusPkgNode, AShow);
      end;
    end;
  end;

  procedure FilterNode(Node: PVirtualNode; ADataText: String);
  var
    P: Integer;
  begin
    P := PosI(AText, ADataText);
    if P > 0 then
      FVST.IsVisible[Node] := True
    else
      FVST.IsVisible[Node] := False;
    if AText = 'PackageCategory' then //special case for categories
    begin
      if (P > 0) then
      begin
        FVST.IsVisible[Node^.Parent^.Parent] := True;
        FVST.IsVisible[Node^.Parent^.Parent^.Parent] := True;
      end
      else
      begin
        FVST.IsVisible[Node^.Parent^.Parent] := False;
        if not IsAtLeastOneChildVisible(Node^.Parent^.Parent^.Parent) then
          FVST.IsVisible[Node^.Parent^.Parent^.Parent] := False
      end;
    end
    else
      HideShowParentNodes(Node, P > 0)
  end;

var
  Node: PVirtualNode;
  Data: PData;
begin
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    case AFilterBy of
      fbPackageName:
        begin
          if (Data^.DataType = 1) then
            FilterNode(Node, Data^.PackageDisplayName);
        end;
      fbLazarusPackageName:
        begin
          if (Data^.DataType = 2) then
            FilterNode(Node, Data^.LazarusPackageName);
        end;
      fbPackageCategory:
        begin
          if Data^.DataType = 12 then
          begin
            if PosI(CategoriesEng[AExtraParam], Data^.Category) > 0 then
              FilterNode(Node, 'PackageCategory')
            else
              FilterNode(Node, '')
          end;
        end;
     fbPackageState:
       begin
         if Data^.DataType = 2 then
         begin
           if Data^.PackageState = TPackageState(AExtraParam) then
             FilterNode(Node, 'PackageState')
           else
             FilterNode(Node, '');
         end;
       end;
     fbVersion:
       begin
         if Data^.DataType = 2 then
           FilterNode(Node, Data^.Version);
       end;
     fbDescription:
       begin
         if Data^.DataType = 3 then
           FilterNode(Node, Data^.Description);
       end;
     fbAuthor:
       begin
         if Data^.DataType = 4 then
           FilterNode(Node, Data^.Author);
       end;
     fbLazCompatibility:
       begin
         if Data^.DataType = 5 then
           FilterNode(Node, Data^.LazCompatibility);
       end;
     fbFPCCompatibility:
       begin
         if Data^.DataType = 6 then
           FilterNode(Node, Data^.FPCCompatibility);
       end;
     fbSupportedWidgetsets:
       begin
         if Data^.DataType = 7 then
           FilterNode(Node, Data^.SupportedWidgetSet);
       end;
     fbPackageType:
       begin
         if Data^.DataType = 8 then
         begin
           if Data^.PackageType = TLazPackageType(AExtraParam) then
             FilterNode(Node, 'PackageType')
           else
             FilterNode(Node, '');
         end;
       end;
     fbLicense:
       begin
          if Data^.DataType = 9 then
           FilterNode(Node, Data^.License);
       end;
     fbDependecies:
       begin
          if Data^.DataType = 10 then
           FilterNode(Node, Data^.Dependencies);
       end;
   end;
   Node := FVST.GetNext(Node);
  end;
  Node := FVST.GetFirst;
  if Node <> nil then
    FVST.TopNode := Node;
end;

procedure TVisualTree.ResetFilter;
var
  Node: PVirtualNode;
  Data: PData;
begin
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if Data^.DataType <> 21 then
      FVST.IsVisible[Node] := True;
    Node := FVST.GetNext(Node);
  end;
  Node := FVST.GetFirst;
  CollapseEx;
  if Node <> nil then
    FVST.TopNode := Node;
end;

procedure TVisualTree.ExpandEx;
var
  Node: PVirtualNode;
  Data: PData;
begin
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if (Data^.DataType = 0) or (Data^.DataType = 1) or (Data^.DataType = 11) then
      VST.Expanded[Node] := True;
    Node := FVST.GetNext(Node);
  end;
  Node := FVST.GetFirst;
  if Node <> nil then
    FVST.TopNode := Node;
end;

procedure TVisualTree.CollapseEx;
var
  Node: PVirtualNode;
  Data: PData;
begin
  FVST.FullCollapse;
  Node := FVST.GetFirst(True);
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if (Data^.DataType = 0) or (Data^.DataType = 11) then
      VST.Expanded[Node] := True;
    Node := FVST.GetNext(Node);
  end;
end;

procedure TVisualTree.GetPackageList;
var
  Node: PVirtualNode;
  Data: PData;
  MetaPkg: TMetaPackage;
  LazarusPkg: TLazarusPackage;
begin
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if Data^.DataType = 1 then
    begin
      MetaPkg := SerializablePackages.Items[Data^.PID];
      if MetaPkg <> nil then
      begin
        if (FVST.CheckState[Node] = csCheckedNormal) or (FVST.CheckState[Node] = csMixedNormal) then
        begin
          if (FVST.IsVisible[Node]) or (Data^.IsDependencyNode) then
          begin
            MetaPkg.Checked := True;
            if Data^.IsDependencyNode then
              MetaPkg.IsDependencyPackage := True;
          end
          else
            MetaPkg.Checked := False;
        end
        else if FVST.CheckState[Node] = csUncheckedNormal then
          MetaPkg.Checked := False
      end;
    end;
    if Data^.DataType = 2 then
    begin
      LazarusPkg := TLazarusPackage(SerializablePackages.Items[Data^.PID].LazarusPackages.Items[Data^.PFID]);
      if LazarusPkg <> nil then
      begin
        if FVST.CheckState[Node] = csCheckedNormal then
        begin
          if (FVST.IsVisible[Node]) or (Data^.IsDependencyNode) then
          begin
            LazarusPkg.Checked := True;
            if Data^.IsDependencyNode then
              LazarusPkg.IsDependencyPackage:= True
          end
          else
            LazarusPkg.Checked := False;
        end
        else if FVST.CheckState[Node] = csUncheckedNormal then
          LazarusPkg.Checked := False
      end;
    end;
    Node := FVST.GetNext(Node);
  end;
end;

procedure TVisualTree.UpdatePackageStates;
var
  Node: PVirtualNode;
  Data: PData;
  MetaPkg: TMetaPackage;
  LazarusPkg: TLazarusPackage;
begin
  SerializablePackages.GetPackageStates;
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if (Data^.DataType = 1) then
    begin
      MetaPkg := SerializablePackages.Items[Data^.PID];
      if MetaPkg <> nil then
      begin
        Data^.PackageState := MetaPkg.PackageState;
        Data^.InstallState := SerializablePackages.GetPackageInstallState(MetaPkg);
        FVST.ReinitNode(Node, False);
        FVST.RepaintNode(Node);
      end;
    end;
    if Data^.DataType = 2 then
    begin
      LazarusPkg := TLazarusPackage(SerializablePackages.Items[Data^.PID].LazarusPackages.Items[Data^.PFID]);
      if LazarusPkg <> nil then
      begin
        Data^.InstalledVersion := LazarusPkg.InstalledFileVersion;
        Data^.PackageState := LazarusPkg.PackageState;
        FVST.ReinitNode(Node, False);
        FVST.RepaintNode(Node);
      end;
    end;
    Node := FVST.GetNext(Node);
  end;
end;

procedure TVisualTree.UpdatePackageUStatus;
var
  Node: PVirtualNode;
  Data, ParentData: PData;
  MetaPkg: TMetaPackage;
  LazarusPkg: TLazarusPackage;
begin
  if VisualTree = nil then
    exit;

  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if (Data^.DataType = 1) then
    begin
      MetaPkg := SerializablePackages.Items[Data^.PID];
      if MetaPkg <> nil then
      begin
        Data^.DownloadZipURL := MetaPkg.DownloadZipURL;
        Data^.HasUpdate := MetaPkg.HasUpdate;
        Data^.DisableInOPM := MetaPkg.DisableInOPM;
        Data^.Rating := MetaPkg.Rating;
        FVST.IsDisabled[Node] := Data^.DisableInOPM;
        FVST.ReinitNode(Node, False);
        FVST.RepaintNode(Node);
      end;
    end;
    if Data^.DataType = 2 then
    begin
      LazarusPkg := TLazarusPackage(SerializablePackages.Items[Data^.PID].LazarusPackages.Items[Data^.PFID]);
      if LazarusPkg <> nil then
      begin
        Data^.UpdateVersion := LazarusPkg.UpdateVersion;
        Data^.HasUpdate := LazarusPkg.HasUpdate;
        FVST.IsDisabled[Node] := FVST.IsDisabled[Node^.Parent];
        FVST.ReinitNode(Node, False);
        FVST.RepaintNode(Node);
      end;
    end;
    if Data^.DataType in [3..20] then
    begin
      FVST.IsDisabled[Node] := FVST.IsDisabled[Node^.Parent];
      ParentData := FVST.GetNodeData(Node^.Parent);
      if (Data^.DataType = 3) or (Data^.DataType = 9) then
      begin
        case Data^.DataType of
          3: if ParentData^.InstalledVersion <> '' then
               Data^.Description := LazarusPkg.InstalledFileDescription
             else
               Data^.Description := LazarusPkg.Description;
          9: if ParentData^.InstalledVersion <> '' then
               Data^.License := LazarusPkg.InstalledFileLincese
             else
               Data^.License := LazarusPkg.License;
        end;
        if Assigned(Data^.Button) then
          Data^.Button.Enabled := not FVST.IsDisabled[Node];
      end;
      FVST.ReinitNode(Node, False);
      FVST.RepaintNode(Node);
    end;
    Node := FVST.GetNext(Node);
  end;
end;

procedure TVisualTree.VSTBeforeCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
  Data: PData;
begin
  if Options.UseDefaultTheme then
    Exit;
  if CellPaintMode = cpmPaint then
  begin
    Data := Sender.GetNodeData(Node);

    if (Data^.DataType = 0) or (Data^.DataType = 1) or (Data^.DataType = 2) then
      TargetCanvas.Brush.Color := $00E5E5E5
    else
      TargetCanvas.Brush.Color := clBtnFace;
    TargetCanvas.FillRect(CellRect);
    if (Node = Sender.FocusedNode) then
    begin
      TargetCanvas.Brush.Color := FVST.Colors.FocusedSelectionColor;
      if Column = 0 then
        TargetCanvas.FillRect(ContentRect)
     else
       TargetCanvas.FillRect(CellRect);
    end
  end;
end;

procedure TVisualTree.VSTChecking(Sender: TBaseVirtualTree; Node: PVirtualNode;
  var NewState: TCheckState; var Allowed: Boolean);
begin
  if FCheckingNodes then
    Exit;
  if NewState = csUncheckedNormal then
  begin
    if Assigned(FOnChecking) then
      FOnChecking(Self, False);
  end
  else if NewState = csCheckedNormal then
  begin
    if IsAllChecked(Node) then
      FOnChecking(Self, True);
  end;
end;

procedure TVisualTree.VSTChecked(Sender: TBaseVirtualTree; Node: PVirtualNode);
begin
  if FCheckingNodes then
    Exit;
  if Assigned(FOnChecked) then
    FOnChecked(Self);
end;

procedure TVisualTree.ResetDependencyNodes;
var
  Node: PVirtualNode;
  Data: PData;
begin
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    Data^.IsDependencyNode := False;
    Node := FVST.GetNext(Node);
  end;
end;

function TVisualTree.ResolveDependencies: TModalResult;
var
  Parent, Node, NodeSearch: PVirtualNode;
  ParentData, Data, DataSearch: PData;
  Msg: String;
  PackageList: TObjectList;
  PkgFileName: String;
  DependencyPkg: TLazarusPackage;
  I: Integer;
begin
  Result := mrNone;
  ResetDependencyNodes;
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    if VST.CheckState[Node] = csCheckedNormal then
    begin
      Data := FVST.GetNodeData(Node);
      if Data^.DataType = 2 then
      begin
        PackageList := TObjectList.Create(True);
        try
          SerializablePackages.GetPackageDependencies(Data^.LazarusPackageName, PackageList, True, True);
          for I := 0 to PackageList.Count - 1 do
          begin
            PkgFileName := TPackageDependency(PackageList.Items[I]).PkgFileName + '.lpk';
            NodeSearch := VST.GetFirst;
            while Assigned(NodeSearch) do
            begin
              if NodeSearch <> Node then
              begin
                DataSearch := FVST.GetNodeData(NodeSearch);
                if DataSearch^.DataType = 2 then
                begin
                  DependencyPkg := TLazarusPackage(SerializablePackages.Items[DataSearch^.PID].LazarusPackages.Items[DataSearch^.PFID]);
                  if (FVST.CheckState[NodeSearch] <> csCheckedNormal) and
                       (CompareText(DataSearch^.LazarusPackageName, PkgFileName) = 0) and
                         ((SerializablePackages.IsDependencyOk(TPackageDependency(PackageList.Items[I]), DependencyPkg)) and
                           ((not (DependencyPkg.PackageState = psInstalled)) or ((DependencyPkg.PackageState = psInstalled) and (not (SerializablePackages.IsInstalledVersionOk(TPackageDependency(PackageList.Items[I]), DataSearch^.InstalledVersion)))))) then
                  begin
                    if (Result = mrNone) or (Result = mrYes) then
                    begin
                      Msg := Format(rsMainFrm_rsPackageDependency0, [Data^.LazarusPackageName, DataSearch^.LazarusPackageName]);
                      Result := MessageDlgEx(Msg, mtConfirmation, [mbYes, mbYesToAll, mbNo, mbNoToAll, mbCancel], TForm(FVST.Parent.Parent));
                      if Result in [mrNo, mrNoToAll] then
                        if MessageDlgEx(rsMainFrm_rsPackageDependency1, mtInformation, [mbYes, mbNo], TForm(FVST.Parent.Parent)) <> mrYes then
                          Exit(mrCancel);
                      if (Result = mrNoToAll) or (Result = mrCancel) then
                        Exit(mrCancel);
                    end;
                    if Result in [mrYes, mrYesToAll] then
                    begin
                      FVST.CheckState[NodeSearch] := csCheckedNormal;
                      FVST.ReinitNode(NodeSearch, False);
                      FVST.RepaintNode(NodeSearch);
                      DataSearch^.IsDependencyNode := True;
                      Parent := NodeSearch^.Parent;
                      if Parent <> nil then
                      begin
                        ParentData := FVST.GetNodeData(Parent);
                        ParentData^.IsDependencyNode := True;
                      end;
                    end;
                  end;
                end;
              end;
              NodeSearch := FVST.GetNext(NodeSearch);
            end;
          end;
        finally
          PackageList.Free;
        end;
      end;
    end;
    Node := FVST.GetNext(Node);
  end;
end;

function TVisualTree.GetCheckedRepositoryPackages: Integer;
var
  Node: PVirtualNode;
  Data: PData;
begin
  Result := 0;
  Node := FVST.GetFirst;
  while Assigned(Node) do
  begin
    Data := FVST.GetNodeData(Node);
    if (Data^.DataType = 1) and ((FVST.CheckState[Node] = csCheckedNormal) or (FVST.CheckState[Node] = csMixedNormal)) then
      Inc(Result);
    if Result > 1 then
      Break;
    Node := FVST.GetNext(Node);
  end;
end;

procedure TVisualTree.SetupColors;
begin
  if not Options.UseDefaultTheme then
  begin
    FVST.Color := clBtnFace;
    FVST.TreeOptions.PaintOptions := FVST.TreeOptions.PaintOptions + [toAlwaysHideSelection];
  end else
  begin
    FVST.Color := clDefault;
    FVST.TreeOptions.PaintOptions := FVST.TreeOptions.PaintOptions - [toAlwaysHideSelection];
  end;
end;

procedure TVisualTree.AutoAdjustLayout(AXProportion, AYProportion: Double);
begin
  FStarSize := round(FStarSize * AXProportion);
end;

procedure TVisualTree.VSTCompareNodes(Sender: TBaseVirtualTree; Node1,
  Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
  Data1: PData;
  Data2: PData;
begin
  Data1 := Sender.GetNodeData(Node1);
  Data2 := Sender.GetNodeData(Node2);
  case Column of
    0: begin
         if (Data1^.DataType = 1) and (Data1^.DataType = 1) then
         begin
           if (Trim(Data1^.PackageDisplayName) <> '') and (Trim(Data2^.PackageDisplayName) <> '') then
             Result := CompareText(Data1^.PackageDisplayName, Data2^.PackageDisplayName)
           else
             Result := CompareText(Data1^.PackageName, Data2^.PackageName);
         end;
         if (Data1^.DataType < Data2^.DataType) then
           Result := 0
         else if (Data1^.DataType > Data2^.DataType) then
           Result := 1
         else if (Data1^.DataType = 2) and (Data1^.DataType = 2) then
           Result := CompareText(Data1^.LazarusPackageName, Data2^.LazarusPackageName);
       end;
    1: if (Data1^.DataType = 1) and (Data1^.DataType = 1) then
         Result := Data2^.InstallState - Data1^.InstallState;
    3: if (Data1^.DataType = 1) and (Data1^.DataType = 1) then
         Result := Ord(Data2^.HasUpdate) - Ord(Data1^.HasUpdate);
  end;
end;

procedure TVisualTree.VSTGetImageIndex(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
  var Ghosted: Boolean; var ImageIndex: Integer);
var
  Data: PData;
begin
  Data := FVST.GetNodeData(Node);
  if Column = 0 then
  begin
    case Data^.DataType of
      1: if ((Options.DaysToShowNewPackages > 0) and (DaysBetween(Now, Data^.RepositoryDate) <= Options.DaysToShowNewPackages)) and
            ((not Options.ShowRegularIcons) or ((Options.ShowRegularIcons) and (Data^.InstallState = 0))) then
           ImageIndex := 25
         else
         begin
           if Data^.OrphanedPackage = 0 then
             ImageIndex := 1
           else
             ImageIndex := 36;
         end;
     20: ImageIndex := 10;
     21: ImageIndex := 36;
     else
        ImageIndex := Data^.DataType
    end;
  end;
end;

function GetDisplayString(const AStr: String): String;
var
  SL: TStringList;
  I: Integer;
begin
  Result := '';
  SL := TStringList.Create;
  try
    SL.Text := AStr;
    for I := 0 to SL.Count - 1 do
      if Result = '' then
        Result := SL.Strings[I]
      else
        Result := Result + ' ' + SL.Strings[I];
  finally
    SL.Free;
  end;
end;

procedure TVisualTree.VSTGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
  Data: PData;
begin
  Data := FVST.GetNodeData(Node);
  if TextType = ttStatic then
  begin
    if Column = 0 then
    begin
      if ((Options.DaysToShowNewPackages > 0) and (DaysBetween(Now, Data^.RepositoryDate) <= Options.DaysToShowNewPackages)) and
         ((not Options.ShowRegularIcons) or ((Options.ShowRegularIcons) and (Data^.InstallState = 0))) and (Data^.DataType = 1) then
        CellText := '- ' + FormatDateTime('YYYY.MM.DD', Data^.RepositoryDate)
      else
        CellText := '';
    end
    else
      CellText := '';
  end
  else if TextType = ttNormal then
  begin
    if Column = 0 then
    begin
      case Data^.DataType of
        0: CellText := Data^.Repository;
        1: if Trim(Data^.PackageDisplayName) = '' then
             CellText := Data^.PackageName
           else
             CellText := Data^.PackageDisplayName;
        2: CellText := Data^.LazarusPackageName;
        3: CellText := rsMainFrm_VSTText_Description;
        4: CellText := rsMainFrm_VSTText_Author;
        5: CellText := rsMainFrm_VSTText_LazCompatibility;
        6: CellText := rsMainFrm_VSTText_FPCCompatibility;
        7: CellText := rsMainFrm_VSTText_SupportedWidgetsets;
        8: CellText := rsMainFrm_VSTText_Packagetype;
        9: CellText := rsMainFrm_VSTText_License;
       10: CellText := rsMainFrm_VSTText_Dependecies;
       11: CellText := rsMainFrm_VSTText_PackageInfo;
       12: CellText := rsMainFrm_VSTText_Category;
       13: CellText := rsMainFrm_VSTText_RepositoryFilename;
       14: CellText := rsMainFrm_VSTText_RepositoryFileSize;
       15: CellText := rsMainFrm_VSTText_RepositoryFileHash;
       16: CellText := rsMainFrm_VSTText_RepositoryFileDate;
       17: CellText := rsMainFrm_VSTText_HomePageURL;
       18: CellText := rsMainFrm_VSTText_DownloadURL;
       19: CellText := rsMainFrm_VSTText_CommunityDescription;
       20: CellText := rsMainFrm_VSTText_ExternalDeps;
       21: CellText := rsMainFrm_VSTText_OrphanedPackage1;
      end;
    end
    else if Column = 1 then
    begin
      case Data^.DataType of
        1: case Data^.InstallState of
            //0: CellText := rsMainFrm_VSTText_Install0;
            1: CellText := rsMainFrm_VSTText_Install1;
            2: CellText := rsMainFrm_VSTText_Install2;
        end;
        2: begin
             if Data^.InstalledVersion <> '' then
               CellText := Data^.InstalledVersion
             else
               CellText := '-';
           end
        else
          CellText := '';
      end
    end
    else if Column = 2 then
    begin
      if Data^.DataType = 2 then
        CellText := Data^.Version
      else
        CellText := '';
    end
    else if Column = 3 then
    begin
      case Data^.DataType of
        1: if Data^.HasUpdate then
             CellText := 'NEW';
        2: begin
             if (Data^.UpdateVersion <> '') then
               CellText := Data^.UpdateVersion
             else
               CellText := '-';
           end
        else
          CellText := '';
      end
    end
    else if Column = 4 then
    begin
      case Data^.DataType of
        0: CellText := '';
        1: CellText := '';
        2: case Ord(Data^.PackageState) of
             0: CellText := rsMainFrm_VSTText_PackageState0;
             1: CellText := rsMainFrm_VSTText_PackageState1;
             2: CellText := rsMainFrm_VSTText_PackageState2;
             3: begin
                  if not Data^.HasUpdate then
                  begin
                    if (Data^.UpdateVersion = '') then
                    begin
                      if Data^.InstalledVersion < Data^.Version then
                        CellText := rsMainFrm_VSTText_PackageState6
                      else if Data^.InstalledVersion = Data^.Version then
                        CellText := rsMainFrm_VSTText_PackageState4
                      else if Data^.InstalledVersion > Data^.Version then
                        CellText := rsMainFrm_VSTText_PackageState7
                    end
                    else
                    begin
                      if Data^.InstalledVersion < Data^.UpdateVersion then
                        CellText := rsMainFrm_VSTText_PackageState6
                      else if (Data^.InstalledVersion = Data^.UpdateVersion) then
                        CellText := rsMainFrm_VSTText_PackageState4
                      else if (Data^.InstalledVersion > Data^.UpdateVersion) then
                        CellText := rsMainFrm_VSTText_PackageState7
                    end;
                  end
                  else
                  begin
                    if Data^.InstalledVersion < Data^.UpdateVersion then
                      CellText := rsMainFrm_VSTText_PackageState6
                    else if Data^.InstalledVersion = Data^.UpdateVersion then
                      CellText := rsMainFrm_VSTText_PackageState4
                    else if Data^.InstalledVersion > Data^.UpdateVersion then
                      CellText := rsMainFrm_VSTText_PackageState7
                  end;
                  Data^.IsUpdated := CellText = rsMainFrm_VSTText_PackageState4;
                end;
           end;
        3: CellText := GetDisplayString(Data^.Description);
        4: CellText := Data^.Author;
        5: CellText := Data^.LazCompatibility;
        6: CellText := Data^.FPCCompatibility;
        7: CellText := Data^.SupportedWidgetSet;
        8: CellText := GetPackageTypeString(Data^.PackageType);
        9: CellText := GetDisplayString(Data^.License);
       10: CellText := Data^.Dependencies;
       11: CellText := '';
       12: CellText := TranslateCategories(Data^.Category);
       13: CellText := Data^.RepositoryFileName;
       14: CellText := FormatSize(Data^.RepositoryFileSize);
       15: CellText := Data^.RepositoryFileHash;
       16: CellText := FormatDateTime('YYYY.MM.DD', Data^.RepositoryDate);
       17: CellText := Data^.HomePageURL;
       18: CellText := Data^.DownloadURL;
       19: CellText := GetDisplayString(Data^.CommunityDescription);
       20: CellText := GetDisplayString(Data^.ExternalDependencies);
       21: case Data^.OrphanedPackage of
             0: CellText := rsMainFrm_VSTText_Install0;
             1: CellText := rsMainFrm_VSTText_Install1;
           end;
      end;
    end
    else if Column = 5 then
    begin
      CellText := '';
    end
  end;
end;

procedure TVisualTree.VSTHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
begin
  if (HitInfo.Column <> 0) and (HitInfo.Column <> 1) and (HitInfo.Column <> 3) then
    Exit;
  if HitInfo.Button = mbLeft then
  begin
    with Sender, Treeview do
    begin
      if (SortColumn = NoColumn) or (SortColumn <> HitInfo.Column) then
      begin
        SortColumn    := HitInfo.Column;
        SortDirection := laz.VirtualTrees.sdAscending;
      end
      else
      begin
        if SortDirection = laz.VirtualTrees.sdAscending then
          SortDirection := laz.VirtualTrees.sdDescending
        else
          SortDirection := laz.VirtualTrees.sdAscending;
        FSortDir := SortDirection;
      end;
      SortTree(SortColumn, SortDirection, False);
      FSortCol := Sender.SortColumn;
    end;
  end;
end;

procedure TVisualTree.VSTPaintText(Sender: TBaseVirtualTree;
  const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType);

 function GetTextColor(const ADefColor: TColor; AIsFocusedNode: Boolean): TColor;
 begin
   Result := ADefColor;
   if Result = clDefault then
     Result := Sender.GetDefaultColor(dctFont);
   if AIsFocusedNode then
   {$IFDEF WINDOWS}
     Result := clWhite;
   {$ENDIF}
 end;

var
  Data: PData;
begin
  Data := FVST.GetNodeData(Node);
  if TextType = ttStatic then
  begin
    if Column = 0 then
    begin
      if ((Options.DaysToShowNewPackages > 0) and (DaysBetween(Now, Data^.RepositoryDate) <= Options.DaysToShowNewPackages)) and
          ((not Options.ShowRegularIcons) or ((Options.ShowRegularIcons) and (Data^.InstallState = 0))) then
        TargetCanvas.Font.Style := TargetCanvas.Font.Style - [fsBold];
      TargetCanvas.Font.Color := GetTextColor(FVST.Font.Color, Node = Sender.FocusedNode);
    end
  end
  else if TextType = ttNormal then
  begin
    case column of
      2: begin
           if Data^.DataType = 2 then
           begin
             if (Data^.InstalledVersion = '') or ((Data^.InstalledVersion <> '') and (Data^.InstalledVersion < Data^.Version)) then
               TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]
             else
               TargetCanvas.Font.Style := TargetCanvas.Font.Style - [fsBold];
           end;
           TargetCanvas.Font.Color := GetTextColor(FVST.Font.Color, Node = Sender.FocusedNode);
         end;
      3: begin
           case Data^.DataType of
             1: TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold];
             2: if (Data^.HasUpdate) and (Data^.UpdateVersion > Data^.InstalledVersion) then
                  TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]
                else
                  TargetCanvas.Font.Style := TargetCanvas.Font.Style - [fsBold];
           end;
           TargetCanvas.Font.Color := GetTextColor(FVST.Font.Color, Node = Sender.FocusedNode);
         end;
      4: begin
           if (FHoverNode = Node) and (FHoverColumn = Column) and ((Data^.DataType = 17) or (Data^.DataType = 18)) then
           begin
             TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsUnderline];
             TargetCanvas.Font.Color := GetTextColor(clBlue, Node = Sender.FocusedNode);
           end
           else if (Data^.DataType = 2) and (Data^.IsUpdated) then
           begin
             TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold];
             TargetCanvas.Font.Color := GetTextColor(clGreen, Node = Sender.FocusedNode);
           end
           else
             TargetCanvas.Font.Color := GetTextColor(FVST.Font.Color, Node = Sender.FocusedNode);
         end
      else
        TargetCanvas.Font.Color := GetTextColor(FVST.Font.Color, Node = Sender.FocusedNode);
    end;
  end
end;

procedure TVisualTree.VSTFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  Data: PData;
begin
  Data := FVST.GetNodeData(Node);
  if Assigned(Data^.Button) then
    Data^.Button.Visible := False;
  Finalize(Data^);
end;

function TVisualTree.GetColumn(const AX: Integer): Integer;
var
  I: Integer;
  L, R: Integer;
begin
  Result := -1;
  for I := 0 to VST.Header.Columns.Count - 1 do
  begin
    VST.Header.Columns.GetColumnBounds(I, L, R);
    if (AX >= L) and (AX <= R) then
    begin
      Result := I;
      Break;
    end;
  end;
end;

function TVisualTree.GetPackageAbsolutePath(APackageName: String): String;
var
  LazarusPkg: TLazarusPackage;
begin
  Result := '';
  LazarusPkg := SerializablePackages.FindLazarusPackage(APackageName);
  if (LazarusPkg <> nil) and (FileExists(LazarusPkg.PackageAbsolutePath)) then
    Result := LazarusPkg.PackageAbsolutePath;
end;

procedure TVisualTree.VSTMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  Data: PData;
  ContRect: TRect;
  P: TPoint;
  Level: Integer;
begin
  FHoverColumn := -1;
  FHoverP.X := X;
  FHoverP.Y := Y;
  FHoverNode:= VST.GetNodeAt(X, Y);
  FHoverColumn := GetColumn(X);

  if (FOldButtonNode <> nil) and (FOldButtonNode <> FHoverNode) then
  begin
    Data := FVST.GetNodeData(FOldButtonNode);
    if Data^.Button <> nil then
    begin
      Data^.Button.Free;
      Data^.Button := nil;
    end;
  end;

  if ((FHoverColumn = 0) or (FShowHintFrm.Visible)) and (FHoverNode <> nil) then
  begin
    case Options.HintFormOption of
      0: begin
           if FShowHintFrm.Visible then
             FShowHintFrm.SetupTimer(300);
           Exit;
         end;
      1: begin
            P.X := X;
            P.Y := Y;
            ContRect := FVST.GetDisplayRect(FHoverNode, FHoverColumn, False);
            Level := FVST.GetNodeLevel(FHoverNode);
            if (ssShift in Shift) and PtInRect(ContRect, P) and (Level > 0) then
            begin
              P := FVST.ClientToScreen(P);
              if FShowHintFrm.Visible then
                FShowHintFrm.MoveFormTo(P.X, P.Y)
              else
                FShowHintFrm.ShowFormAt(P.X, P.Y);
              case Level of
                 2: FHoverNode := FHoverNode^.Parent;
                 3: FHoverNode := FHoverNode^.Parent^.Parent;
              end;
              if FHoverNode <> FHoverNodeOld then
              begin
                FShowHintFrm.UpdateInfo(FHoverNode);
                FHoverNodeOld := FHoverNode;
              end;
            end
        end;
      2: Exit;
    end;
  end;
  if (FHoverColumn = 5) and (FHoverNode <> nil) then
  begin
    FVST.ReinitNode(FHoverNode, False);
    FVST.RepaintNode(FHoverNode);
  end
  else if (FHoverColumn = 4) and (FHoverNode <> nil) then
  begin
    Data := VST.GetNodeData(FHoverNode);
    if ((Data^.DataType = 17) and (Trim(Data^.HomePageURL) <> '')) or
       ((Data^.DataType = 18) and (Trim(Data^.DownloadURL) <> '')) then
      FVST.Cursor := crHandPoint;

    if (Data^.Button = nil) and
       (
         ((Data^.DataType = 2) and (Data^.PackageState in [psExtracted, psInstalled]) and (GetPackageAbsolutePath(Data^.LazarusPackageName) <> '')) or
         ((Data^.DataType = 3) and (Trim(Data^.Description) <> '')) or
         ((Data^.DataType = 9) and (Trim(Data^.License) <> '')) or
         ((Data^.DataType = 19) and (Trim(Data^.CommunityDescription) <> '')) or
         ((Data^.DataType = 20) and (Trim(Data^.ExternalDependencies) <> ''))
        ) then
    begin
      Data := FVST.GetNodeData(FHoverNode);
      Data^.Button := TSpeedButton.Create(VST);
      with Data^.Button do
      begin
        if Data^.DataType = 2 then
        begin
          Width := 75;
          Caption := rsMainFrm_VSTText_Open;
        end
        else
          Caption := '...';
        Parent := FVST;
        Tag := Data^.ButtonID;
        Visible := True;
        OnClick := @ButtonClick;
      end;
      FOldButtonNode := FHoverNode;
    end;
  end
end;

procedure TVisualTree.VSTMouseEnter(Sender: TObject);
begin
  FLeaving := False;
end;

procedure TVisualTree.VSTMouseLeave(Sender: TObject);
begin
  if Assigned(FHoverNode) then
  begin
    FLeaving := True;
    FVST.ReinitNode(FHoverNode, False);
    FVST.RepaintNode(FHoverNode)
  end;
end;

procedure TVisualTree.VSTMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
 Node: PVirtualNode;
 Data: PData;
 MenuItem: TMenuItem;
 DownColumn: Integer;
 R: TRect;
 PackageName: String;
 MetaPkg: TMetaPackage;
begin
  FMouseEnter := True;
  Node := FVST.GetNodeAt(X, Y);
  if Node <> nil then
  begin
    DownColumn := GetColumn(X);
    Data := FVST.GetNodeData(Node);
    if Button = mbLeft then
    begin
      case DownColumn of
        4: if (Data^.DataType = 17) or (Data^.DataType = 18) and (DownColumn = 4) then
           begin
             FLinkClicked := True;
             if (Data^.DataType = 17) and (Trim(Data^.HomePageURL) <> '') then
               FLink := Data^.HomePageURL
             else if (Data^.DataType = 18) and (Trim(Data^.DownloadURL) <> '') then
               FLink := Data^.DownloadURL;
           end;
        5: begin
             if Data^.DataType = 1 then
             begin
               R := FVST.GetDisplayRect(Node, DownColumn, False);
               Data^.Rating := Trunc((FHoverP.X - R.Left - 1)/FStarSize) + 1;
               if Data^.Rating > 5 then
                 Data^.Rating := 5;
               MetaPkg := SerializablePackages.Items[Data^.PID];
               if MetaPkg <> nil then
                 MetaPkg.Rating := Data^.Rating;
               if Data^.PackageDisplayName <> '' then
                 PackageName := Data^.PackageDisplayName
               else
                 PackageName := Data^.PackageName;
               MessageDlgEx(Format(rsMainFrm_rsPackageRating, [PackageName, InttoStr(Data^.Rating)]), mtInformation, [mbOk],  TForm(FVST.Parent.Parent));
             end;
           end;
      end;
    end
    else if Button = mbRight then
    begin
      MenuItem := FVST.PopupMenu.Items.Find(rsMainFrm_miCopyToClpBrd);
      if MenuItem <> nil then
        MenuItem.Enabled := ((Data^.DataType = 17) and (Trim(Data^.HomePageURL) <> '')) or
                            ((Data^.DataType = 18) and (Trim(Data^.DownloadURL) <> ''));
      MenuItem := FVST.PopupMenu.Items.Find(rsMainFrm_miResetRating);
      if MenuItem <> nil then
        MenuItem.Enabled := (DownColumn = 5) and (Data^.Rating <> 0);
    end;
  end
end;

procedure TVisualTree.VSTKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
 //
end;

procedure TVisualTree.VSTKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if Key = VK_SHIFT then
    if Assigned(FShowHintFrm) and FShowHintFrm.Visible and (Options.HintFormOption = 1) then
      FShowHintFrm.SetupTimer(300);
end;

procedure TVisualTree.VSTGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle;
  var HintText: String);
var
  Data: PData;
  P1, P2: TPoint;
  Level: Integer;
begin
  Data := FVST.GetNodeData(Node);
  if (Column <> 4) and (Column <> 0) then
    Exit;
  LineBreakStyle := hlbForceSingleLine;
  case Data^.DataType of
    1: if Options.HintFormOption = 0 then
       begin
         HintText := '';
         P1.X := 0;
         P1.Y := 0;
         GetCursorPos(P1);
         P2 := FVST.ScreenToClient(P1);
         Level := FVST.GetNodeLevel(FHoverNode);
         if PtInRect(FVST.GetDisplayRect(Node, Column, True), P2) and (Level > 0) then
         begin
           FShowHintFrm.ShowFormAt(P1.X, P1.Y);
           FShowHintFrm.SetupTimer(Application.HintHidePause);
           case Level of
              2: FHoverNode := FHoverNode^.Parent;
              3: FHoverNode := FHoverNode^.Parent^.Parent;
           end;
           if FHoverNode <> FHoverNodeOld then
           begin
             FShowHintFrm.UpdateInfo(FHoverNode);
             FHoverNodeOld := FHoverNode;
           end;
         end;
       end;
    3: HintText := Data^.Description;
    4: HintText := Data^.Author;
    5: HintText := Data^.LazCompatibility;
    6: HintText := Data^.FPCCompatibility;
    7: HintText := Data^.SupportedWidgetSet;
    8: HintText := GetPackageTypeString(Data^.PackageType);
    9: HintText := GetDisplayString(Data^.License);
    10: HintText := Data^.Dependencies;
    11: HintText := '';
    12: HintText := TranslateCategories(Data^.Category);
    13: HintText := Data^.RepositoryFileName;
    14: HintText := FormatSize(Data^.RepositoryFileSize);
    15: HintText := Data^.RepositoryFileHash;
    16: HintText := FormatDateTime('YYYY.MM.DD', Data^.RepositoryDate);
    17: HintText := Data^.HomePageURL;
    18: HintText := Data^.DownloadURL;
    19: HintText := Data^.CommunityDescription;
    20: HintText := Data^.ExternalDependencies;
   else
       HintText := '';
  end;
end;

procedure TVisualTree.VSTDblClick(Sender: TObject);
var
  Data: PData;
  Node: PVirtualNode;
begin
  Node := FVST.GetFirstSelected;
  if (Node <> nil) and (FVST.GetNodeLevel(Node) = 2) then
  begin
    Data := FVST.GetNodeData(Node);
    if (Data^.DataType = 2) and (Data^.PackageState in [psExtracted, psInstalled]) then
      DoOpenPackage(Data^.LazarusPackageName);
  end;
end;

procedure TVisualTree.VSTScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
begin
  if FShowHintFrm.Visible then
    FShowHintFrm.Hide;
end;

procedure TVisualTree.VSTIncrementalSearch(Sender: TBaseVirtualTree;
  Node: PVirtualNode; const SearchText: String; var Result: Integer);
var
  Data: PData;
begin
  Data := FVST.GetNodeData(Node);
  if FVST.GetNodeLevel(Node) = 1 then
    Result := StrLIComp(PChar(SearchText), PChar(Data^.PackageDisplayName), Min(Length(SearchText), Length(Data^.PackageDisplayName)))
  else
    Result := -1;
end;

procedure TVisualTree.VSTClick(Sender: TObject);
begin
  if FLinkClicked then
  begin
    FLinkClicked := False;
    FHoverColumn := -1;
    FHoverNode := nil;
    if Trim(FLink) <> '' then
      OpenURL(FLink);
    FLink := '';
  end;
end;


end.

