{
 ***************************************************************************
 *                                                                         *
 *   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: Marius
  Modified by Juha Manninen, Balazs Szekely

  Abstract:
    A dialog to quickly find components and to add the found component
    to the designed form.
}
unit ComponentList;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils,
  // LCL
  LCLType, Forms, Controls, Graphics, StdCtrls, ExtCtrls, ComCtrls, Menus, Buttons,
  Dialogs, ImgList,
  // LazUtils
  LazLoggerBase, LazUTF8, LazFileCache, AvgLvlTree,
  // LazControls
  TreeFilterEdit,
  // BuildIntf
  ComponentReg,
  // IdeIntf
  FormEditingIntf, IDEImagesIntf, PropEdits, MenuIntf, LazIDEIntf, TextTools,
  // IDE
  LazarusIDEStrConsts, ComponentPalette, PackageDefs, IDEOptionDefs, EnvGuiOptions,
  Designer;

const
  ComponentListMenuRootName = 'ComponentList';
var
  // menu section CompListMenuSectionOpen
  CompListMenuOpenUnit: TIDEMenuCommand;
  CompListMenuOpenPackage: TIDEMenuCommand;

  // menu section CompListMenuSectionExpand
  CompListMenuExpand: TIDEMenuCommand;
  CompListMenuExpandAll: TIDEMenuCommand;

  // menu section CompListMenuSectionCollapse
  CompListMenuCollapse: TIDEMenuCommand;
  CompListMenuCollapseAll: TIDEMenuCommand;

type

  { TComponentListForm }

  TComponentListForm = class(TForm)
    chbKeepOpen: TCheckBox;
    ButtonPanel: TPanel;
    OKButton: TButton;
    LabelSearch: TLabel;
    PageControl: TPageControl;
    FilterPanel: TPanel;
    ListTree: TTreeView;
    PalletteTree: TTreeView;
    InheritanceTree: TTreeView;
    pnPaletteTree: TPanel;
    Panel6: TPanel;
    Panel7: TPanel;
    CompListPopupMenu: TPopupMenu;
    TabSheetPaletteTree: TTabSheet;
    TabSheetInheritance: TTabSheet;
    TabSheetList: TTabSheet;
    tmDeselect: TTimer;
    TreeFilterEd: TTreeFilterEdit;
    SelectionToolButton: TSpeedButton;
    procedure chbKeepOpenChange(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure TreeMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ListTreeSelectionChanged(Sender: TObject);
    procedure miCollapseAllClick(Sender: TObject);
    procedure miCollapseClick(Sender: TObject);
    procedure miExpandAllClick(Sender: TObject);
    procedure miExpandClick(Sender: TObject);
    procedure OKButtonClick(Sender: TObject);
    procedure ComponentsDblClick(Sender: TObject);    
    procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);    
    procedure CompListPopupMenuPopup(Sender: TObject);
    procedure tmDeselectTimer(Sender: TObject);
    procedure TreeFilterEdAfterFilter(Sender: TObject);
    procedure PageControlChange(Sender: TObject);
    function TreeFilterEdFilterItemEx(const ACaption: string;
      {%H-}ItemData: Pointer; out Done: Boolean): Boolean;
    procedure TreeFilterEdKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure TreeKeyPress(Sender: TObject; var Key: char);
    procedure FormKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
    procedure SelectionToolButtonClick(Sender: TObject);
  private
    FOnOpenPackage: TNotifyEvent;
    FOnOpenUnit: TNotifyEvent;
    FOnClassSelected: TNotifyEvent;
    PrevChangeStamp: Integer;
    FInitialized: Boolean;
    FIgnoreSelection: Boolean;
    FPageControlChange: Boolean;
    FActiveTree: TTreeView;
    FAddCompNewLeft, FAddCompNewTop: Integer;
    FAddCompNewParent: TComponent;
    procedure ClearSelection;
    procedure ComponentWasAdded({%H-}ALookupRoot, {%H-}AComponent: TComponent;
                                {%H-}ARegisteredComponent: TRegisteredComponent);
    procedure miOpenPackage(Sender: TObject);
    procedure miOpenUnit(Sender: TObject);
    procedure SelectionWasChanged;
    procedure AddComponentInheritanceNodes(ClassToNodeTree: TPointerToPointerTree; Comp: TRegisteredComponent);
    procedure UpdateComponents;
    procedure UpdateButtonState;
    function IsDocked: Boolean;
    procedure AddSelectedComponent(ASaveSelection: boolean = false; AAddNeighboring: boolean = false);
  protected
    procedure UpdateShowing; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetSelectedComponent: TRegisteredComponent;
    function GetSelectedPkgComp: TPkgComponent;
    property OnOpenPackage: TNotifyEvent read FOnOpenPackage write FOnOpenPackage;
    property OnOpenUnit: TNotifyEvent read FOnOpenUnit write FOnOpenUnit;
    property OnClassSelected: TNotifyEvent read FOnClassSelected write FOnClassSelected;
  end;
  
var
  ComponentListForm: TComponentListForm;

procedure RegisterStandardComponentListMenuItems;

implementation

{$R *.lfm}

procedure RegisterStandardComponentListMenuItems;
var
  AParent: TIDEMenuSection;
begin
  ComponentListMenuRoot := RegisterIDEMenuRoot(ComponentListMenuRootName);

  // register the section for open
  CompListMenuSectionOpen:=RegisterIDEMenuSection(ComponentListMenuRoot,'Open');
  AParent:=CompListMenuSectionOpen;
  CompListMenuOpenUnit:=RegisterIDEMenuCommand(AParent,'Open Unit','Open Unit');// resourcestring is set later
  CompListMenuOpenPackage:=RegisterIDEMenuCommand(AParent,'Open Package','Open Package'); // resourcestring is set later

  // register the section for expand
  CompListMenuSectionExpand:=RegisterIDEMenuSection(ComponentListMenuRoot,'Expand');
  AParent:=CompListMenuSectionExpand;
  CompListMenuExpand:=RegisterIDEMenuCommand(AParent, 'Expand', lisExpand);
  CompListMenuExpandAll:=RegisterIDEMenuCommand(AParent, 'Expand All', lisExpandAll2);

  // register the section for collapse
  CompListMenuSectionCollapse:=RegisterIDEMenuSection(ComponentListMenuRoot,'Collapse');
  AParent:=CompListMenuSectionCollapse;
  CompListMenuCollapse:=RegisterIDEMenuCommand(AParent, 'Collapse', lisCollapse);
  CompListMenuCollapseAll:=RegisterIDEMenuCommand(AParent, 'Collapse All',lisCollapseAll2);
end;

function GetSelectedDesignComponent: TComponent;
var
  ASelections: TPersistentSelectionList;
begin
  Result := nil;
  ASelections := TPersistentSelectionList.Create;
  try
    GlobalDesignHook.GetSelection(ASelections);
    if (ASelections.Count > 0) and (ASelections[0] is TComponent) then
      Result := TComponent(ASelections[0])
    else if GlobalDesignHook.LookupRoot is TComponent then
      Result := TComponent(GlobalDesignHook.LookupRoot);
  finally
    ASelections.Free;
  end;
end;

function GetSelectedTreeComp(aTree: TTreeView): TRegisteredComponent;
begin
  if Assigned(aTree.Selected) then
    Result := TRegisteredComponent(aTree.Selected.Data)
  else
    Result := nil;
end;

function GetRegCompClassname(RegComp: TRegisteredComponent): string;
begin
  Result:=RegComp.ComponentClass.ClassName;
  if RegComp.HasAmbiguousClassName then
    Result:=Result+'('+RegComp.ComponentClass.UnitName+')';
end;

procedure SelectTreeComp(aTree: TTreeView);
var
  Node: TTreeNode;
begin
  with IDEComponentPalette do
    if Assigned(Selected) then
      Node := aTree.Items.FindNodeWithText(GetRegCompClassname(Selected))
    else
      Node := Nil;
  aTree.Selected := Node;
  if aTree.Selected <> nil then
    aTree.Selected.MakeVisible;
end;

{ TComponentListForm }

constructor TComponentListForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  Name:=NonModalIDEWindowNames[nmiwComponentList];
  FActiveTree := ListTree;

  IDEImages.AssignImage(SelectionToolButton, 'tmouse');
  with SelectionToolButton do begin
    ShowHint := EnvironmentGuiOpts.ShowHintsForComponentPalette;
    Width := ComponentPaletteBtnWidth;
    BorderSpacing.Around := (FilterPanel.Height - ComponentPaletteImageHeight) div 2;
  end;

  //Translations
  LabelSearch.Caption := lisMenuFind;
  Caption := lisCmpLstComponents;
  TabSheetList.Caption := lisCmpLstList;
  TabSheetPaletteTree.Caption := lisCmpLstPalette;
  TabSheetInheritance.Caption := lisCmpLstInheritance;
  OKButton.Caption := lisUse;
  chbKeepOpen.Caption := lisKeepOpen;
  SelectionToolButton.Hint := lisSelectionTool;

  ListTree.Images := TPaletteComponent.Images;
  PalletteTree.Images := TPaletteComponent.Images;
  InheritanceTree.Images := TPaletteComponent.Images;
  if Assigned(IDEComponentPalette) then
  begin
    UpdateComponents;
    TreeFilterEd.InvalidateFilter;
    IDEComponentPalette.AddHandlerComponentAdded(@ComponentWasAdded);
    IDEComponentPalette.AddHandlerSelectionChanged(@SelectionWasChanged);
    IDEComponentPalette.AddHandlerUpdated(@UpdateComponents);
  end;
  chbKeepOpen.Checked := EnvironmentGuiOpts.ComponentListKeepOpen;
  PageControl.PageIndex := EnvironmentGuiOpts.ComponentListPageIndex;
  PageControlChange(Nil);
end;

destructor TComponentListForm.Destroy;
begin
  if Assigned(IDEComponentPalette) then begin
    IDEComponentPalette.RemoveHandlerUpdated(@UpdateComponents);
    IDEComponentPalette.RemoveHandlerSelectionChanged(@SelectionWasChanged);
    IDEComponentPalette.RemoveHandlerComponentAdded(@ComponentWasAdded);
  end;
  ComponentListForm := nil;
  inherited Destroy;
end;

procedure TComponentListForm.AddSelectedComponent(ASaveSelection: boolean; AAddNeighboring: boolean);
var
  AComponent: TRegisteredComponent;
  NewParent: TComponent;
  CurDesigner: TDesigner;
  lOldEvent: TOnComponentAdded;
  TypeClass: TComponentClass;
begin
  AComponent := GetSelectedComponent;
  NewParent := GetSelectedDesignComponent;

  // get parent component of neighboring component
  if AAddNeighboring and assigned(NewParent) then
  begin
    if (not (NewParent is TCustomForm)) and
       (not (NewParent is TCustomFrame)) then
    begin
      if NewParent.HasParent then
        NewParent := NewParent.GetParentComponent;
    end;
  end;
  if NewParent = nil then
    Exit;

  // get designer
  CurDesigner := TDesigner(FindRootDesigner(NewParent));
  if CurDesigner = nil then
    Exit;

  // get component class (e.g. display dialog to select TFrame)
  TypeClass := AComponent.GetCreationClass;
  if TypeClass = nil then
    Exit;

  // check parent
  CurDesigner.AddComponentCheckParent(NewParent, NewParent, nil, TypeClass);
  if NewParent = nil then
    Exit;

  // calculate offset
  if FAddCompNewParent <> NewParent then
  begin
    FAddCompNewLeft := 0;
    FAddCompNewTop := 0;
    FAddCompNewParent := NewParent;
  end;
  Inc(FAddCompNewLeft, EnvironmentGuiOpts.GridSizeX);
  Inc(FAddCompNewTop, EnvironmentGuiOpts.GridSizeY);

  // add component
  if ASaveSelection then
  begin
    lOldEvent := CurDesigner.OnComponentAdded; // save event
    CurDesigner.OnComponentAdded := nil;       // clear event
    CurDesigner.AddComponent(AComponent, TypeClass, NewParent, FAddCompNewLeft, FAddCompNewTop, 0, 0);
    CurDesigner.OnComponentAdded := lOldEvent; // restore event
  end else begin
    CurDesigner.AddComponent(AComponent, TypeClass, NewParent, FAddCompNewLeft, FAddCompNewTop, 0, 0);
  end;

end;

procedure TComponentListForm.chbKeepOpenChange(Sender: TObject);
begin
  EnvironmentGuiOpts.ComponentListKeepOpen := chbKeepOpen.Checked;
end;

procedure TComponentListForm.FormShow(Sender: TObject);
begin
  //DebugLn(['*** TComponentListForm.FormShow, Parent=', Parent, ', Parent.Parent=', ParentParent]);
  ButtonPanel.Visible := not IsDocked;
  if ButtonPanel.Visible then
  begin                              // ComponentList is undocked
    PageControl.AnchorSideBottom.Side := asrTop;
    UpdateButtonState;
    if TreeFilterEd.CanSetFocus then // Focus filter if window is undocked and top parent can focus
      TreeFilterEd.SetFocus;
    TreeFilterEd.SelectAll;
  end
  else                               // ComponentList is docked
    PageControl.AnchorSideBottom.Side := asrBottom;
end;

procedure TComponentListForm.ClearSelection;
begin
  ListTree.Selected := Nil;
  PalletteTree.Selected := Nil;
  InheritanceTree.Selected := Nil;
end;

function TComponentListForm.GetSelectedComponent: TRegisteredComponent;
begin
  Result := nil;
  if ListTree.IsVisible then
    Result := GetSelectedTreeComp(ListTree)
  else if PalletteTree.IsVisible then
    Result := GetSelectedTreeComp(PalletteTree)
  else if InheritanceTree.IsVisible then
    Result := GetSelectedTreeComp(InheritanceTree)
end;

function TComponentListForm.GetSelectedPkgComp: TPkgComponent;
begin
  Result:=GetSelectedComponent as TPkgComponent;
end;

function TComponentListForm.IsDocked: Boolean;
begin
  Result := (HostDockSite<>Nil) and (HostDockSite.Parent<>Nil);
end;

procedure TComponentListForm.ComponentWasAdded(ALookupRoot, AComponent: TComponent;
  ARegisteredComponent: TRegisteredComponent);
begin
  ClearSelection;
  UpdateButtonState;
end;

procedure TComponentListForm.miOpenPackage(Sender: TObject);
var
  PkgComponent: TPkgComponent;
begin
  PkgComponent:=GetSelectedPkgComp;
  if (PkgComponent=nil) or (PkgComponent.PkgFile=nil)
      or (PkgComponent.PkgFile.LazPackage=nil) then exit;
  if Assigned(OnOpenPackage) then
    OnOpenPackage(PkgComponent.PkgFile.LazPackage);
end;

procedure TComponentListForm.miOpenUnit(Sender: TObject);
var
  PkgComponent: TPkgComponent;
begin
  PkgComponent:=GetSelectedPkgComp;
  if (PkgComponent=nil) or (PkgComponent.PkgFile=nil)
      or (PkgComponent.PkgFile.LazPackage=nil) then exit;
  if Assigned(OnOpenUnit) then
    OnOpenUnit(PkgComponent);
end;

procedure TComponentListForm.SelectionWasChanged;
begin
  SelectionToolButton.Down := (IDEComponentPalette.Selected = nil);
  if FIgnoreSelection then
    Exit;
  if ListTree.IsVisible then
    SelectTreeComp(ListTree)
  else if PalletteTree.IsVisible then
    SelectTreeComp(PalletteTree)
  else if InheritanceTree.IsVisible then
    SelectTreeComp(InheritanceTree)
end;

procedure TComponentListForm.UpdateButtonState;
begin
  OKButton.Enabled := Assigned(GetSelectedComponent);
end;

procedure TComponentListForm.UpdateShowing;
begin
  if (ButtonPanel<>nil) and ButtonPanel.Visible then
    UpdateButtonState;
  inherited UpdateShowing;
end;

procedure TComponentListForm.AddComponentInheritanceNodes(
  ClassToNodeTree: TPointerToPointerTree; Comp: TRegisteredComponent);
// Walk down to parent, stop on TComponent,
//  since components are at least TComponent descendants.
var
  InheritanceList: TFPList;
  aClass: TClass;
  Node, ParentNode: TTreeNode;
  aClassName: string;
  i: Integer;
  II: TImageIndex;
  CurRegComp: TRegisteredComponent;
begin
  InheritanceList := TFPList.Create;
  try
    aClass := Comp.ComponentClass;
    while (aClass.ClassInfo <> nil) and (aClass.ClassType <> TComponent.ClassType) do
    begin
      InheritanceList.Add(TObject(aClass));
      aClass := aClass.ClassParent;
    end;
    // Build the tree
    ParentNode:=nil;
    for i := InheritanceList.Count - 1 downto 0 do
    begin
      aClass := TClass(InheritanceList[i]);
      Node:=TTreeNode(ClassToNodeTree[aClass]);
      if Node=nil then
      begin
        if aClass=Comp.ComponentClass then
          CurRegComp:=Comp
        else
          CurRegComp:=IDEComponentPalette.FindRegComponent(aClass);
        if CurRegComp<>nil then
          aClassName:=GetRegCompClassname(CurRegComp)
        else
          aClassName:=aClass.ClassName;

        // Add the node
        Node := InheritanceTree.Items.AddChildObject(ParentNode, aClassName, CurRegComp);
        ClassToNodeTree[aClass]:=Node;
        if CurRegComp is TPaletteComponent then
          II := TPaletteComponent(CurRegComp).ImageIndex
        else
          II := -1;
        if II>=0 then
        begin
          Node.ImageIndex := II;
          Node.SelectedIndex := II;
        end;
      end;
      ParentNode:=Node;
    end;
  finally
    InheritanceList.Free;
  end;
end;

procedure TComponentListForm.UpdateComponents;
// Fill all three tabsheets: Flat list, Palette layout and Component inheritence.
var
  Pg: TBaseComponentPage;
  Comps: TRegisteredCompList;
  Comp: TRegisteredComponent;
  ParentNode: TTreeNode;
  AListNode: TTreeNode;
  APaletteNode: TTreeNode;
  i, j: Integer;
  CurIcon: TImageIndex;
  aClassName: String;
  aClassToTreeNode: TPointerToPointerTree;
begin
  if [csDestroying,csLoading]*ComponentState<>[] then exit;
  Screen.BeginWaitCursor;
  ListTree.BeginUpdate;
  PalletteTree.BeginUpdate;
  InheritanceTree.Items.BeginUpdate;
  aClassToTreeNode := TPointerToPointerTree.Create;
  try
    ListTree.Items.Clear;
    PalletteTree.Items.Clear;
    InheritanceTree.Items.Clear;
 //   ParentInheritence := InheritanceTree.Items.Add(nil, 'TComponent');
//    FClassList.AddObject('TComponent', ParentInheritence);
    // Iterate all pages
    for i := 0 to IDEComponentPalette.Pages.Count-1 do
    begin
      Pg := IDEComponentPalette.Pages[i];
      if not Pg.Visible then Continue;
      Comps := IDEComponentPalette.RefUserCompsForPage(Pg.PageName);
      // Palette layout Page header
      ParentNode := PalletteTree.Items.AddChild(nil, Pg.PageName);
      // Iterate components of one page
      for j := 0 to Comps.Count-1 do begin
        Comp := Comps[j];
        if not Comp.Visible then Continue;
        aClassName := GetRegCompClassname(Comp);
        // Flat list item
        AListNode := ListTree.Items.AddChildObject(Nil, aClassName, Comp);
        // Palette layout item
        APaletteNode := PalletteTree.Items.AddChildObject(ParentNode, aClassName, Comp);
        if Comp is TPaletteComponent then
          CurIcon := TPaletteComponent(Comp).ImageIndex
        else
          CurIcon := -1;
        if CurIcon>=0 then
        begin
          AListNode.ImageIndex := CurIcon;
          AListNode.SelectedIndex := AListNode.ImageIndex;
          APaletteNode.ImageIndex := AListNode.ImageIndex;
          APaletteNode.SelectedIndex := AListNode.ImageIndex;
        end;
        // Component inheritence item
        AddComponentInheritanceNodes(aClassToTreeNode,Comp);
      end;
    end;
    InheritanceTree.AlphaSort;
    {$IFnDEF NoComponentListTreeExpand}
    InheritanceTree.FullExpand;    // Some users may not want the trees expanded.
    PalletteTree.FullExpand;
    {$ENDIF}
    PrevChangeStamp := IDEComponentPalette.ChangeStamp;
  finally
    aClassToTreeNode.Free;
    InheritanceTree.Items.EndUpdate;
    PalletteTree.EndUpdate;
    ListTree.EndUpdate;
    Screen.EndWaitCursor;
  end;
end;

procedure TComponentListForm.TreeFilterEdAfterFilter(Sender: TObject);
begin
  if TreeFilterEd.Filter = '' then
    IDEComponentPalette.SetSelectedComp(nil, False);
  UpdateButtonState;
end;

procedure TComponentListForm.ComponentsDblClick(Sender: TObject);
// This is used for all 3 treeviews
begin
  OKButtonClick(nil);       // Select and close this form
end;

procedure TComponentListForm.ListTreeSelectionChanged(Sender: TObject);
var
  AComponent: TRegisteredComponent;
begin
  UpdateButtonState;
  if FInitialized then
  begin
    if FPageControlChange then
      Exit;
    AComponent:=GetSelectedComponent;
    if AComponent<>nil then
      IDEComponentPalette.SetSelectedComp(AComponent, ssShift in GetKeyShiftState)
    else
    begin
      FIgnoreSelection := True;
      IDEComponentPalette.SetSelectedComp(nil, False);
      FIgnoreSelection := False;
    end;
  end
  else begin
    // Only run once when the IDE starts.
    FInitialized := True;
    IDEComponentPalette.SetSelectedComp(nil, False);
    ListTree.Selected := Nil;
    PalletteTree.Selected := Nil;
    InheritanceTree.Selected := Nil;
  end
end;

procedure TComponentListForm.TreeMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  // ignore right click to bring up context menu
  if (Button <> mbLeft) or IsDocked or chbKeepOpen.Checked then Exit;
  // show form editor
  if assigned(OnClassSelected) then
    OnClassSelected(self);
end;

procedure TComponentListForm.TreeKeyPress(Sender: TObject; var Key: char);
// This is used for all 3 treeviews
begin
  if Key = Char(VK_RETURN) then
    ComponentsDblClick(Sender);
end;

procedure TComponentListForm.PageControlChange(Sender: TObject);
begin
  //DebugLn(['TComponentListForm.PageControlChange: Start']);
  FPageControlChange := True;
  case PageControl.PageIndex of
    0: begin
         TreeFilterEd.FilteredTreeview := ListTree;
         FActiveTree := ListTree;
        end;
    1: begin
         TreeFilterEd.FilteredTreeview := PalletteTree;
         FActiveTree := PalletteTree;
       end;
    2: begin
         TreeFilterEd.FilteredTreeview := InheritanceTree;
         FActiveTree := InheritanceTree;
        end;
  end;
  EnvironmentGuiOpts.ComponentListPageIndex := PageControl.PageIndex;
  FActiveTree.BeginUpdate;
  tmDeselect.Enabled := True;
end;

function TComponentListForm.TreeFilterEdFilterItemEx(const ACaption: string;
  ItemData: Pointer; out Done: Boolean): Boolean;
begin
  Done := true;
  result := MultiWordSearch(TreeFilterEd.Text, ACaption);
end;

procedure TComponentListForm.TreeFilterEdKeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
var
  c: char;
begin
  if KeyToQWERTY(Key, Shift, c) then
    TreeFilterEd.SelText := c;
end;

procedure TComponentListForm.tmDeselectTimer(Sender: TObject);
begin
  tmDeselect.Enabled := False;
  FActiveTree.Selected := nil;
  SelectionWasChanged;
  FActiveTree.EndUpdate;
  FPageControlChange := False;
end;

procedure TComponentListForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  ClearSelection;
  IDEComponentPalette.Selected := Nil;
end;

procedure TComponentListForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
  AComponent: TRegisteredComponent;
begin
  // add a neighboring component and leave focus on the selected component
  if (Key = VK_RETURN) and (Shift = [ssCtrl]) then
  begin
    AComponent := GetSelectedComponent;
    if AComponent <> nil then
      AddSelectedComponent(true, true);
    if TreeFilterEd.CanSetFocus then // not necessarily here?
      TreeFilterEd.SetFocus;
    Key := 0;
    exit;
  end;

  // add a child component and leave focus on the selected component
  if (Key = VK_RETURN) and (Shift = [ssShift]) then
  begin
    AComponent := GetSelectedComponent;
    if AComponent <> nil then
      AddSelectedComponent(true);
    if TreeFilterEd.CanSetFocus then // not necessarily here?
      TreeFilterEd.SetFocus;
    Key := 0;
    exit;
  end;

  // close
  if (Key = VK_ESCAPE) and (Shift = []) then
  begin
    if (IDEComponentPalette.Selected = nil) and not IsDocked then
      Close                            // close only if no component is selected
    else
      ClearSelection; // unselect if component is selected
    Key := 0;
    exit;
  end;

  // set focus on filter
  if (Key = VK_F) and (Shift = [ssCtrl]) then
  begin
    if TreeFilterEd.CanSetFocus then // not necessarily here?
      TreeFilterEd.SetFocus;
    Key := 0;
    exit;
  end;

  // select tab - [Ctrl+Tab] and [Ctrl+Shift+Tab]
  if (Key = VK_TAB) and ((Shift = [ssCtrl]) or (Shift = [ssCtrl, ssShift])) then
  begin
    with PageControl do
      if Shift = [ssCtrl]
        then PageIndex := (            PageIndex + 1) mod PageCount
        else PageIndex := (PageCount + PageIndex - 1) mod PageCount; // add "PageCount" - so that there is no negative number
    PageControlChange(Sender);
    Key := 0;
    exit;
  end;
end;

procedure TComponentListForm.OKButtonClick(Sender: TObject);
// Select component from palette and close this form. User can insert the component.
var
  AComponent: TRegisteredComponent;
  OldFocusedControl: TWinControl;
begin
  AComponent := GetSelectedComponent;
  if AComponent=nil then
    Exit;

  OldFocusedControl := Screen.ActiveControl;
  AddSelectedComponent;
  if (OldFocusedControl<>nil) and OldFocusedControl.CanSetFocus then
    OldFocusedControl.SetFocus; // AddComponent in docked mode steals focus to designer, get it back

  if IsDocked or chbKeepOpen.Checked then Exit;
  Close;
end;

procedure TComponentListForm.miCollapseAllClick(Sender: TObject);
begin
  TreeFilterEd.FilteredTreeview.FullCollapse;
end;

procedure TComponentListForm.miCollapseClick(Sender: TObject);
var
  Node: TTreeNode;
begin
  Node := TreeFilterEd.FilteredTreeview.Selected;
  if Node = nil then
    Exit;
  if (Node.Level > 0) and (Node.HasChildren = False) then
    Node := Node.Parent;
  Node.Collapse(True);
end;

procedure TComponentListForm.miExpandAllClick(Sender: TObject);
begin
  TreeFilterEd.FilteredTreeview.FullExpand;
end;

procedure TComponentListForm.miExpandClick(Sender: TObject);
var
  Node: TTreeNode;
begin
  Node := TreeFilterEd.FilteredTreeview.Selected;
  if Node = nil then
    Exit;
  if (Node.Level > 0) and (Node.HasChildren = False) then
    Node := Node.Parent;
  Node.Expand(True);
end;

procedure TComponentListForm.CompListPopupMenuPopup(Sender: TObject);
var
  Node: TTreeNode;
  PkgComponent: TPkgComponent;
  APackage: TLazPackage;
  PkgFile: TPkgFile;
  ShownFilename, UnitFilename: String;
begin
  //debugln(['TComponentListForm.CompListPopupMenuPopup ']);
  ComponentListMenuRoot.MenuItem:=CompListPopupMenu.Items;

  // expand/collapse
  if (PageControl.ActivePage=TabSheetPaletteTree)
      or (PageControl.ActivePage=TabSheetInheritance) then
  begin
    Node := TreeFilterEd.FilteredTreeview.Selected;
    if Node = nil then
    begin
      CompListMenuExpand.Enabled := False;
      CompListMenuCollapse.Enabled := False;
    end
    else
    begin
      CompListMenuExpand.Enabled := (Node.HasChildren) and (not Node.Expanded);
      CompListMenuCollapse.Enabled := (Node.HasChildren) and (Node.Expanded);
    end;
    CompListMenuExpand.Visible := true;
    CompListMenuExpandAll.Visible := true;
    CompListMenuCollapse.Visible := true;
    CompListMenuCollapseAll.Visible := true;
    CompListMenuExpand.OnClick:=@miExpandClick;
    CompListMenuExpandAll.OnClick:=@miExpandAllClick;
    CompListMenuCollapse.OnClick:=@miCollapseClick;
    CompListMenuCollapseAll.OnClick:=@miCollapseAllClick;
  end else begin
    CompListMenuExpand.Visible := false;
    CompListMenuExpandAll.Visible := false;
    CompListMenuCollapse.Visible := false;
    CompListMenuCollapseAll.Visible := false;
  end;

  // open unit/package
  PkgComponent:=GetSelectedPkgComp;
  if (PkgComponent=nil) or (PkgComponent.PkgFile=nil) then
  begin
    CompListMenuOpenUnit.Caption:=lisOpenUnit;
    CompListMenuOpenUnit.Visible:=false;
    CompListMenuOpenPackage.Caption:=lisOpenPackage3;
    CompListMenuOpenPackage.Visible:=false;
  end else begin
    PkgFile:=PkgComponent.PkgFile;
    APackage:=PkgFile.LazPackage;
    CompListMenuOpenPackage.Caption:=Format(lisCPOpenPackage, [APackage.IDAsString]);
    CompListMenuOpenPackage.Visible:=true;
    CompListMenuOpenPackage.OnClick:=@miOpenPackage;

    ShownFilename:=PkgFile.Filename;
    UnitFilename:=PkgFile.GetFullFilename;
    if not FileExistsCached(UnitFilename) then begin
      UnitFilename:=LazarusIDE.FindSourceFile(ExtractFilename(UnitFilename),
                                              APackage.Directory,[]);
      if FileExists(UnitFilename) then
        UnitFilename:=ShownFilename;
    end;
    CompListMenuOpenUnit.Caption:=Format(lisCPOpenUnit, [ShownFilename]);
    CompListMenuOpenUnit.Enabled:=FileExistsCached(UnitFilename);
    CompListMenuOpenUnit.Visible:=true;
    CompListMenuOpenUnit.OnClick:=@miOpenUnit;
  end;
end;

procedure TComponentListForm.SelectionToolButtonClick(Sender: TObject);
begin
  SelectionToolButton.Down := True;
  IDEComponentPalette.SetSelectedComp(nil, False);
  if IsDocked or chbKeepOpen.Checked then Exit;
  // show form editor
  if assigned(OnClassSelected) then
    OnClassSelected(self);
end;

end.

