{ *************************************************************************** * * * This source is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This code is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * A copy of the GNU General Public License is available on the World * * Wide Web at . You can also * * obtain it by writing to the Free Software Foundation, * * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * * *************************************************************************** Author: 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; { 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.