{
 ***************************************************************************
 *                                                                         *
 *   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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.        *
 *                                                                         *
 ***************************************************************************

  Author: Marius
  Modified by Juha Manninen

  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, LCLType, Forms, Controls, Graphics, StdCtrls, ExtCtrls,
  ComCtrls, ButtonPanel, Dialogs, LazarusIDEStrConsts, ComponentReg,
  PackageDefs, FormEditingIntf, PropEdits, ListFilterEdit, TreeFilterEdit, fgl;

type

  TRegisteredCompList = specialize TFPGList<TRegisteredComponent>;

  { TComponentListForm }

  TComponentListForm = class(TForm)
    ButtonPanel: TButtonPanel;
    LabelSearch: TLabel;
    ComponentsListbox: TListBox;
    ListFilterEd: TListFilterEdit;
    PageControl: TPageControl;
    Panel3: TPanel;
    Panel5: TPanel;
    Panel6: TPanel;
    Panel7: TPanel;
    TabSheetInheritance: TTabSheet;
    TabSheetListBox: TTabSheet;
    TabSheetPaletteTree: TTabSheet;
    InheritanceTree: TTreeView;
    PalletteTree: TTreeView;
    TreeFilterEd: TTreeFilterEdit;
    procedure CancelButtonClick(Sender: TObject);
    procedure OKButtonClick(Sender: TObject);
    procedure ComponentsDblClick(Sender: TObject);
    procedure ComponentsClick(Sender: TObject);
    procedure ComponentsListboxDrawItem(Control: TWinControl; Index: Integer;
      ARect: TRect; State: TOwnerDrawState);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure TreeCustomDrawItem(Sender: TCustomTreeView;
      Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure ComponentsListboxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure InheritanceTreeKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure PageControlChange(Sender: TObject);
    procedure PalletteTreeKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure UpdateComponentSelection(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  private
    PrevPageIndex: Integer;
    FComponentList: TRegisteredCompList;
    procedure FindAllLazarusComponents;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetSelectedComponent: TRegisteredComponent;
  end;
  
var
  ComponentListForm: TComponentListForm;

implementation

{$R *.lfm}

{ TComponentListForm }

constructor TComponentListForm.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FComponentList := TRegisteredCompList.Create;
  ButtonPanel.OKButton.Caption     := lisUseSelected;
  ComponentsListBox.ItemHeight     := ComponentPaletteImageHeight + 1;
  InheritanceTree.DefaultItemHeight:= ComponentPaletteImageHeight + 1;
  PalletteTree.DefaultItemHeight   := ComponentPaletteImageHeight + 1;

  //Translations..
  LabelSearch.Caption := lisMenuFind;
  Caption := lisCmpLstComponents;
  TabSheetListBox.Caption := lisCmpLstList;
  TabSheetPaletteTree.Caption := lisCmpLstPalette;
  TabSheetInheritance.Caption := lisCmpLstInheritance;

  //PLEASE add a defaultpage property in TPagecontrol
  PrevPageIndex := -1;
  PageControl.ActivePage := TabSheetListBox;
  TreeFilterEd.Visible := False;

  FindAllLazarusComponents;
  UpdateComponentSelection(nil);
  ListFilterEd.InvalidateFilter;
end;

destructor TComponentListForm.Destroy;
begin
  ComponentListForm := nil;
  FreeAndNil(FComponentList);
  inherited Destroy;
end;

function TComponentListForm.GetSelectedComponent: TRegisteredComponent;
var
  i: Integer;
begin
  Result:=nil;
  if ComponentsListbox.IsVisible then
  begin
    i := ComponentsListbox.ItemIndex;
    if i>=0 then
      Result := TRegisteredComponent(ComponentsListbox.Items.Objects[i]);
  end
  else if PalletteTree.IsVisible then
  begin
    if Assigned(PalletteTree.Selected) then
      Result := TRegisteredComponent(PalletteTree.Selected.Data);
  end
  else if InheritanceTree.IsVisible then
  begin
    if Assigned(InheritanceTree.Selected) then
      Result := TRegisteredComponent(InheritanceTree.Selected.Data);
  end;
end;

procedure TComponentListForm.FindAllLazarusComponents;
//Collect all available components (excluding hidden)
var
  AComponent: TRegisteredComponent;
  APage: TBaseComponentPage;
  i, j: Integer;
begin
  if Assigned(IDEComponentPalette) then
  begin
    for i := 0 to IDEComponentPalette.Count-1 do
    begin
      APage := IDEComponentPalette.Pages[i];
      if APage.Visible then
        for j := 0 to APage.Count-1 do
        begin
          AComponent := APage.Items[j];
          if AComponent.Visible and (AComponent.PageName<>'') then
            FComponentList.Add(AComponent);
        end;
    end;
  end;
end;

procedure TComponentListForm.UpdateComponentSelection(Sender: TObject);
//Apply the filter and fill the three tabsheets
var
  AComponent: TRegisteredComponent;
  AClassName: string;
  AClassList, List: TStringlist;
  i, j, AIndex: Integer;
  ANode: TTreeNode;
  AClass: TClass;
begin
  if [csDestroying,csLoading]*ComponentState<>[] then exit;
  Screen.Cursor := crHourGlass;
  try
    //First tabsheet (ComponentsListbox)
    ComponentsListbox.Items.BeginUpdate;
    try
      ComponentsListbox.Items.Clear;
      for i := 0 to FComponentList.Count-1 do
      begin
        AComponent := FComponentList[i];
        AClassName := AComponent.ComponentClass.ClassName;
        ListFilterEd.Items.AddObject(AClassName, AComponent);
      end;
    finally
      ComponentsListbox.Items.EndUpdate;
    end;

    //Second tabsheet (palette layout)
    PalletteTree.BeginUpdate;
    try
      PalletteTree.Items.Clear;
      for i := 0 to FComponentList.Count-1 do
      begin
        AComponent := FComponentList[i];
        AClassName := AComponent.ComponentClass.ClassName;
        //find out parent node
        ANode := PalletteTree.Items.FindTopLvlNode(AComponent.PageName);
        if ANode = nil then
          ANode := PalletteTree.Items.AddChild(nil, AComponent.PageName);
        //add the item
        ANode := PalletteTree.Items.AddChildObject(ANode, AClassName, AComponent);
      end;
      PalletteTree.FullExpand;
    finally
      PalletteTree.EndUpdate;
    end;

    //Third tabsheet (component inheritence)
    List := TStringlist.Create;
    AClassList := TStringlist.Create;
    InheritanceTree.Items.BeginUpdate;
    try
      InheritanceTree.Items.Clear;
      AClassList.Sorted := true;
      AClassList.CaseSensitive := false;
      AClassList.Duplicates := dupIgnore;
      for i := 0 to FComponentList.Count-1 do
      begin
        AComponent := FComponentList[i];
        AClassName := AComponent.ComponentClass.ClassName;
        // walk down to parent, stop on tcomponent, since components are at least
        // a tcomponent descendant
        List.Clear;
        AClass := AComponent.ComponentClass;
        while (AClass.ClassInfo <> nil) and (AClass.ClassType <> TComponent.ClassType) do
        begin
          List.AddObject(AClass.ClassName, TObject(AClass));
          AClass := AClass.ClassParent;
        end;
        //build the tree
        for j := List.Count - 1 downto 0 do
        begin
          AClass := TClass(List.Objects[j]);
          AClassName := List[j];
          if not AClassList.Find(AClassName, AIndex)
          then begin
            //find out parent position
            if Assigned(AClass.ClassParent) and AClassList.Find(AClass.ClassParent.ClassName, AIndex)
            then ANode := TTreeNode(AClassList.Objects[AIndex])
            else ANode := nil;
            //add the item
            if AClassName <> AComponent.ComponentClass.ClassName
            then ANode := InheritanceTree.Items.AddChild(ANode, AClassName)
            else ANode := InheritanceTree.Items.AddChildObject(ANode, AClassName, AComponent);
            AClassList.AddObject(AClassName, ANode);
          end;
        end;
      end;
      InheritanceTree.AlphaSort;
      InheritanceTree.FullExpand;
    finally
      List.Free;
      AClassList.Free;
      InheritanceTree.Items.EndUpdate;
    end;
    
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TComponentListForm.ComponentsListboxDrawItem(Control: TWinControl;
  Index: Integer; ARect: TRect; State: TOwnerDrawState);
var
  Comp: TRegisteredComponent;
  CurStr: string;
  CurIcon: TCustomBitmap;
  TxtH, IconWidth, IconHeight: Integer;
begin
  if (Index<0) or (Index>=ComponentsListBox.Items.Count) then exit;
  // draw registered component
  Comp:=TRegisteredComponent(ComponentsListBox.Items.Objects[Index]);
  with ComponentsListBox.Canvas do begin
    CurStr:=Comp.ComponentClass.ClassName;
//  CurStr:=Format(lisPckEditPage,[Comp.ComponentClass.ClassName,Comp.Page.PageName]);
    TxtH:=TextHeight(CurStr);
    FillRect(ARect);
    CurIcon:=nil;
    if Comp is TPkgComponent then
      CurIcon:=TPkgComponent(Comp).Icon;
    if CurIcon<>nil then
    begin
      IconWidth:=CurIcon.Width;
      IconHeight:=CurIcon.Height;
      Draw(ARect.Left+(25-IconWidth) div 2,
           ARect.Top+(ARect.Bottom-ARect.Top-IconHeight) div 2, CurIcon);
    end;
    TextOut(ARect.Left+25,
            ARect.Top+(ARect.Bottom-ARect.Top-TxtH) div 2, CurStr);
  end;
end;

procedure TComponentListForm.TreeCustomDrawItem(Sender: TCustomTreeView;
  Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
var
  Comp: TRegisteredComponent;
  ARect: TRect;
  CurIcon: TCustomBitmap;
  Indent, IconWidth, IconHeight: Integer;
begin
  DefaultDraw := False;
  Indent := (Sender as TTreeView).Indent;
  Comp := TRegisteredComponent(Node.Data);
  with Sender.Canvas do
  begin
    if cdsSelected in State then
    begin
      Brush.Color := clHighlight;   //Brush.Style := ...
      Font.Color := clHighlightText;
    end
    else begin
      Brush.Color := clDefault;
      Font.Color := clDefault;
    end;
    ARect := Node.DisplayRect(False);
    FillRect(ARect);
    //Brush.Style := bsClear;     //don't paint over the background bitmap.
    ARect.Left := ARect.Left + (Node.Level * Indent);
    // ARect.Left now points to the left of the image, or text if no image
    CurIcon := nil;
    if Comp is TPkgComponent then
      CurIcon := TPkgComponent(Comp).Icon;
    if CurIcon<>nil then
    begin
      IconWidth := CurIcon.Width;
      IconHeight := CurIcon.Height;
      ARect.Left := ARect.Left + Indent;
      //ARect.Left is now the leftmost portion of the image.
      Draw(ARect.Left+(25-IconWidth) div 2,
           ARect.Top+(ARect.Bottom-ARect.Top-IconHeight) div 2, CurIcon);
      ARect.Left := ARect.Left + IconWidth + 2;
    end;
    //Now we are finally in a position to draw the text.
    TextOut(ARect.Left, ARect.Top, Node.Text);
  end;
end;

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

procedure TComponentListForm.ComponentsClick(Sender: TObject);
// This is used for all 3 lists / treeviews
var
  AComponent: TRegisteredComponent;
begin
  AComponent:=GetSelectedComponent;
  if AComponent<>nil then
    IDEComponentPalette.Selected:=AComponent;
end;

procedure TComponentListForm.ComponentsListboxKeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
    if (ComponentsListbox.ItemIndex >= 0) then
      ComponentsDblClick(Sender);
end;

procedure TComponentListForm.PalletteTreeKeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
    ComponentsDblClick(Sender);
end;

procedure TComponentListForm.InheritanceTreeKeyDown(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
    ComponentsDblClick(Sender);
end;

procedure TComponentListForm.PageControlChange(Sender: TObject);
begin
  Assert(PageControl.PageIndex <> PrevPageIndex, Format(
    'TComponentListForm.PageControlChange: PageControl.PageIndex = PrevPageIndex = %d',
    [PrevPageIndex]));
  case PageControl.PageIndex of
    0: begin
      ListFilterEd.Visible := True;
      ListFilterEd.SetFocus;
      if PrevPageIndex > 0 then
        ListFilterEd.Text := TreeFilterEd.Text;
      ListFilterEd.InvalidateFilter;
      TreeFilterEd.Visible := False;
    end;
    1: begin
      TreeFilterEd.Visible := True;
      TreeFilterEd.FilteredTreeview := PalletteTree;
      TreeFilterEd.SetFocus;
      if PrevPageIndex <= 0 then
        TreeFilterEd.Text := ListFilterEd.Text;
      TreeFilterEd.InvalidateFilter;
      ListFilterEd.Visible := False;
    end;
    2: begin
      TreeFilterEd.Visible := True;
      TreeFilterEd.FilteredTreeview := InheritanceTree;
      TreeFilterEd.SetFocus;
      if PrevPageIndex <= 0 then
        TreeFilterEd.Text := ListFilterEd.Text;
      TreeFilterEd.InvalidateFilter;
      ListFilterEd.Visible := False;
    end;
  end;
  PrevPageIndex := PageControl.PageIndex;
end;

procedure TComponentListForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  // Using a dock manager...
  if Parent<>nil then
  begin
    CloseAction := caNone;
    //todo: helper function in DockManager or IDEDockMaster for closing forms.
    // Only close the window if it's floating.
    // AnchorDocking doesn't seem to initialize 'FloatingDockSiteClass' so we can't just check 'Floating'.
    // Also, AnchorDocking use nested forms, so the check for HostDockSite.Parent.
    if Assigned(HostDockSite) and (HostDockSite.DockClientCount <= 1)
      and (HostDockSite is TCustomForm) and (HostDockSite.Parent = nil) then
    begin
      TCustomForm(HostDockSite).Close;
    end;
  end;
end;

procedure TComponentListForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
//Close the form on escape key like every other IDE dialog does
begin
  if (Key=VK_ESCAPE) and (Parent=nil) then
    CancelButtonClick(nil);
end;

procedure TComponentListForm.OKButtonClick(Sender: TObject);
// Select component from palette and close this form. User can insert the component.
var
  AComponent: TRegisteredComponent;
begin
  AComponent := GetSelectedComponent;
  if AComponent<>nil then begin
    IDEComponentPalette.Selected := AComponent;
    Close;
  end;
end;

procedure TComponentListForm.CancelButtonClick(Sender: TObject);
begin
  IDEComponentPalette.Selected := nil; // Remove component selection from palette.
  Close;
end;

end.

