1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Marius
22   Modified by Juha Manninen, Balazs Szekely
23 
24   Abstract:
25     A dialog to quickly find components and to add the found component
26     to the designed form.
27 }
28 unit ComponentList;
29 
30 {$mode objfpc}{$H+}
31 
32 interface
33 
34 uses
35   Classes, SysUtils,
36   // LCL
37   LCLType, Forms, Controls, Graphics, StdCtrls, ExtCtrls, ComCtrls, Menus, Buttons,
38   Dialogs, ImgList,
39   // LazUtils
40   LazLoggerBase, LazUTF8,
41   // LazControls
42   TreeFilterEdit,
43   // IdeIntf
44   FormEditingIntf, IDEImagesIntf, PropEdits, ComponentReg,
45   // IDE
46   LazarusIDEStrConsts, PackageDefs, IDEOptionDefs, EnvironmentOpts, Designer;
47 
48 type
49 
50   { TComponentListForm }
51 
52   TComponentListForm = class(TForm)
53     chbKeepOpen: TCheckBox;
54     ButtonPanel: TPanel;
55     miCollapse: TMenuItem;
56     miCollapseAll: TMenuItem;
57     miExpand: TMenuItem;
58     miExpandAll: TMenuItem;
59     OKButton: TButton;
60     LabelSearch: TLabel;
61     PageControl: TPageControl;
62     FilterPanel: TPanel;
63     ListTree: TTreeView;
64     PalletteTree: TTreeView;
65     InheritanceTree: TTreeView;
66     pnPaletteTree: TPanel;
67     Panel6: TPanel;
68     Panel7: TPanel;
69     pmCollapseExpand: TPopupMenu;
70     TabSheetPaletteTree: TTabSheet;
71     TabSheetInheritance: TTabSheet;
72     TabSheetList: TTabSheet;
73     tmDeselect: TTimer;
74     TreeFilterEd: TTreeFilterEdit;
75     SelectionToolButton: TSpeedButton;
76     procedure chbKeepOpenChange(Sender: TObject);
77     procedure FormActivate(Sender: TObject);
78     procedure FormShow(Sender: TObject);
79     procedure ListTreeSelectionChanged(Sender: TObject);
80     procedure miCollapseAllClick(Sender: TObject);
81     procedure miCollapseClick(Sender: TObject);
82     procedure miExpandAllClick(Sender: TObject);
83     procedure miExpandClick(Sender: TObject);
84     procedure OKButtonClick(Sender: TObject);
85     procedure ComponentsDblClick(Sender: TObject);
86     procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
87     procedure pmCollapseExpandPopup(Sender: TObject);
88     procedure tmDeselectTimer(Sender: TObject);
89     procedure TreeFilterEdAfterFilter(Sender: TObject);
90     procedure PageControlChange(Sender: TObject);
91     procedure TreeKeyPress(Sender: TObject; var Key: char);
92     procedure FormKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
93     procedure SelectionToolButtonClick(Sender: TObject);
94   private
95     PrevChangeStamp: Integer;
96     // List for Component inheritence view
97     FClassList: TStringListUTF8Fast;
98     FInitialized: Boolean;
99     FIgnoreSelection: Boolean;
100     FPageControlChange: Boolean;
101     FActiveTree: TTreeView;
102     FAddCompNewLeft, FAddCompNewTop: Integer;
103     FAddCompNewParent: TComponent;
104     procedure ClearSelection;
105     procedure SelectionWasChanged;
106     procedure ComponentWasAdded({%H-}ALookupRoot, {%H-}AComponent: TComponent;
107                                 {%H-}ARegisteredComponent: TRegisteredComponent);
108     procedure DoComponentInheritence(Comp: TRegisteredComponent);
109     procedure UpdateComponents;
110     procedure UpdateButtonState;
IsDockednull111     function IsDocked: Boolean;
112     procedure AddSelectedComponent;
113   protected
114     procedure UpdateShowing; override;
115   public
116     constructor Create(AOwner: TComponent); override;
117     destructor Destroy; override;
GetSelectedComponentnull118     function GetSelectedComponent: TRegisteredComponent;
119   end;
120 
121 var
122   ComponentListForm: TComponentListForm;
123 
124 implementation
125 
126 {$R *.lfm}
127 
128 { TComponentListForm }
129 
130 constructor TComponentListForm.Create(AOwner: TComponent);
131 begin
132   inherited Create(AOwner);
133 
134   Name:=NonModalIDEWindowNames[nmiwComponentList];
135   FActiveTree := ListTree;
136 
137   IDEImages.AssignImage(SelectionToolButton, 'tmouse');
138   with SelectionToolButton do begin
139     ShowHint := EnvironmentOptions.ShowHintsForComponentPalette;
140     Width := ComponentPaletteBtnWidth;
141     BorderSpacing.Around := (FilterPanel.Height - ComponentPaletteImageHeight) div 2;
142   end;
143 
144   //Translations
145   LabelSearch.Caption := lisMenuFind;
146   Caption := lisCmpLstComponents;
147   TabSheetList.Caption := lisCmpLstList;
148   TabSheetPaletteTree.Caption := lisCmpLstPalette;
149   TabSheetInheritance.Caption := lisCmpLstInheritance;
150   OKButton.Caption := lisUse;
151   chbKeepOpen.Caption := lisKeepOpen;
152   SelectionToolButton.Hint := lisSelectionTool;
153 
154   ListTree.Images := TPkgComponent.Images;
155   PalletteTree.Images := TPkgComponent.Images;
156   InheritanceTree.Images := TPkgComponent.Images;
157   if Assigned(IDEComponentPalette) then
158   begin
159     UpdateComponents;
160     TreeFilterEd.InvalidateFilter;
161     IDEComponentPalette.AddHandlerSelectionChanged(@SelectionWasChanged);
162     IDEComponentPalette.AddHandlerComponentAdded(@ComponentWasAdded);
163   end;
164   chbKeepOpen.Checked := EnvironmentOptions.ComponentListKeepOpen;
165   PageControl.PageIndex := EnvironmentOptions.ComponentListPageIndex;
166   PageControlChange(Nil);
167 end;
168 
169 procedure TComponentListForm.AddSelectedComponent;
170 var
171   AComponent: TRegisteredComponent;
172   ASelections: TPersistentSelectionList;
173   NewParent: TComponent;
174   CurDesigner: TDesigner;
175 begin
176   AComponent := GetSelectedComponent;
177   ASelections := TPersistentSelectionList.Create;
178   try
179     GlobalDesignHook.GetSelection(ASelections);
180     if (ASelections.Count>0) and (ASelections[0] is TComponent) then
181       NewParent := TComponent(ASelections[0])
182     else if GlobalDesignHook.LookupRoot is TComponent then
183       NewParent := TComponent(GlobalDesignHook.LookupRoot)
184     else
185       NewParent := nil;
186   finally
187     ASelections.Free;
188   end;
189 
190   if NewParent=nil then
191     Exit;
192 
193   CurDesigner:=TDesigner(FindRootDesigner(NewParent));
194   if CurDesigner=nil then
195     Exit;
196 
197   CurDesigner.AddComponentCheckParent(NewParent, NewParent, nil, AComponent.ComponentClass);
198   if NewParent=nil then
199     Exit;
200 
201   if FAddCompNewParent<>NewParent then
202   begin
203     FAddCompNewLeft := 0;
204     FAddCompNewTop := 0;
205     FAddCompNewParent := NewParent;
206   end;
207   Inc(FAddCompNewLeft, 8);
208   Inc(FAddCompNewTop, 8);
209   CurDesigner.AddComponent(AComponent, AComponent.ComponentClass, NewParent, FAddCompNewLeft, FAddCompNewTop, 0, 0);
210 end;
211 
212 procedure TComponentListForm.chbKeepOpenChange(Sender: TObject);
213 begin
214   EnvironmentOptions.ComponentListKeepOpen := chbKeepOpen.Checked;
215 end;
216 
217 destructor TComponentListForm.Destroy;
218 begin
219   if Assigned(IDEComponentPalette) then
220     IDEComponentPalette.RemoveHandlerComponentAdded(@ComponentWasAdded);
221   ComponentListForm := nil;
222   inherited Destroy;
223 end;
224 
225 procedure TComponentListForm.FormShow(Sender: TObject);
226 begin
227   //DebugLn(['*** TComponentListForm.FormShow, Parent=', Parent, ', Parent.Parent=', ParentParent]);
228   ButtonPanel.Visible := not IsDocked;
229   if ButtonPanel.Visible then
230   begin                              // ComponentList is undocked
231     PageControl.AnchorSideBottom.Side := asrTop;
232     UpdateButtonState;
233     if TreeFilterEd.CanFocus then    // Focus filter if window is undocked
234       TreeFilterEd.SetFocus;
235     TreeFilterEd.SelectAll;
236   end
237   else                               // ComponentList is docked
238     PageControl.AnchorSideBottom.Side := asrBottom;
239 end;
240 
241 procedure TComponentListForm.FormActivate(Sender: TObject);
242 begin
243   if Assigned(IDEComponentPalette) and (IDEComponentPalette.ChangeStamp<>PrevChangeStamp) then
244     UpdateComponents;
245 end;
246 
247 procedure TComponentListForm.ClearSelection;
248 begin
249   ListTree.Selected := Nil;
250   PalletteTree.Selected := Nil;
251   InheritanceTree.Selected := Nil;
252 end;
253 
254 procedure SelectTreeComp(aTree: TTreeView);
255 var
256   Node: TTreeNode;
257 begin
258   with IDEComponentPalette do
259     if Assigned(Selected) then
260       Node := aTree.Items.FindNodeWithText(Selected.ComponentClass.ClassName)
261     else
262       Node := Nil;
263   aTree.Selected := Node;
264   if aTree.Selected <> nil then
265     aTree.Selected.MakeVisible;
266 end;
267 
268 procedure TComponentListForm.SelectionWasChanged;
269 begin
270   SelectionToolButton.Down := (IDEComponentPalette.Selected = nil);
271 
272   // ToDo: Select the component in active treeview.
273   if FIgnoreSelection then
274     Exit;
275 
276   if ListTree.IsVisible then
277     SelectTreeComp(ListTree)
278   else if PalletteTree.IsVisible then
279     SelectTreeComp(PalletteTree)
280   else if InheritanceTree.IsVisible then
281     SelectTreeComp(InheritanceTree)
282 end;
283 
GetSelectedTreeCompnull284 function GetSelectedTreeComp(aTree: TTreeView): TRegisteredComponent;
285 begin
286   if Assigned(aTree.Selected) then
287     Result := TRegisteredComponent(aTree.Selected.Data)
288   else
289     Result := nil;
290 end;
291 
GetSelectedComponentnull292 function TComponentListForm.GetSelectedComponent: TRegisteredComponent;
293 begin
294   Result := nil;
295   if ListTree.IsVisible then
296     Result := GetSelectedTreeComp(ListTree)
297   else if PalletteTree.IsVisible then
298     Result := GetSelectedTreeComp(PalletteTree)
299   else if InheritanceTree.IsVisible then
300     Result := GetSelectedTreeComp(InheritanceTree)
301 end;
302 
IsDockednull303 function TComponentListForm.IsDocked: Boolean;
304 begin
305   Result := (HostDockSite<>Nil) and (HostDockSite.Parent<>Nil);
306 end;
307 
308 procedure TComponentListForm.ComponentWasAdded(ALookupRoot, AComponent: TComponent;
309   ARegisteredComponent: TRegisteredComponent);
310 begin
311   ClearSelection;
312   UpdateButtonState;
313 end;
314 
315 procedure TComponentListForm.UpdateButtonState;
316 begin
317   OKButton.Enabled := Assigned(GetSelectedComponent);
318 end;
319 
320 procedure TComponentListForm.UpdateShowing;
321 begin
322   if (ButtonPanel<>nil) and ButtonPanel.Visible then
323     UpdateButtonState;
324   inherited UpdateShowing;
325 end;
326 
327 procedure TComponentListForm.DoComponentInheritence(Comp: TRegisteredComponent);
328 // Walk down to parent, stop on TComponent,
329 //  since components are at least TComponent descendants.
330 var
331   PalList: TStringList;
332   AClass: TClass;
333   Node: TTreeNode;
334   ClssName: string;
335   i, Ind: Integer;
336   II: TImageIndex;
337 begin
338   PalList := TStringList.Create;
339   try
340     AClass := Comp.ComponentClass;
341     while (AClass.ClassInfo <> nil) and (AClass.ClassType <> TComponent.ClassType) do
342     begin
343       PalList.AddObject(AClass.ClassName, TObject(AClass));
344       AClass := AClass.ClassParent;
345     end;
346     // Build the tree
347     for i := PalList.Count - 1 downto 0 do
348     begin
349       AClass := TClass(PalList.Objects[i]);
350       ClssName := PalList[i];
351       if not FClassList.Find(ClssName, Ind) then
352       begin
353         // Find out parent position
354         if Assigned(AClass.ClassParent)
355         and FClassList.Find(AClass.ClassParent.ClassName, Ind) then
356           Node := TTreeNode(FClassList.Objects[Ind])
357         else
358           Node := nil;
359         // Add the item
360         if ClssName <> Comp.ComponentClass.ClassName then
361           Node := InheritanceTree.Items.AddChild(Node, ClssName)
362         else
363         begin
364           Node := InheritanceTree.Items.AddChildObject(Node, ClssName, Comp);
365           if Comp is TPkgComponent then
366             II := TPkgComponent(Comp).ImageIndex
367           else
368             II := -1;
369           if II>=0 then
370           begin
371             Node.ImageIndex := II;
372             Node.SelectedIndex := Node.ImageIndex;
373           end;
374         end;
375         FClassList.AddObject(ClssName, Node);
376       end;
377     end;
378   finally
379     PalList.Free;
380   end;
381 end;
382 
383 procedure TComponentListForm.UpdateComponents;
384 // Fill all three tabsheets: Flat list, Palette layout and Component inheritence.
385 var
386   Pg: TBaseComponentPage;
387   Comps: TRegisteredCompList;
388   Comp: TRegisteredComponent;
389   ParentNode: TTreeNode;
390   AListNode: TTreeNode;
391   APaletteNode: TTreeNode;
392   i, j: Integer;
393   CurIcon: TImageIndex;
394 begin
395   if [csDestroying,csLoading]*ComponentState<>[] then exit;
396   Screen.BeginWaitCursor;
397   ListTree.BeginUpdate;
398   PalletteTree.BeginUpdate;
399   InheritanceTree.Items.BeginUpdate;
400   FClassList := TStringListUTF8Fast.Create;
401   try
402     ListTree.Items.Clear;
403     PalletteTree.Items.Clear;
404     InheritanceTree.Items.Clear;
405     FClassList.Sorted := true;
406     FClassList.Duplicates := dupIgnore;
407  //   ParentInheritence := InheritanceTree.Items.Add(nil, 'TComponent');
408 //    FClassList.AddObject('TComponent', ParentInheritence);
409     // Iterate all pages
410     for i := 0 to IDEComponentPalette.Pages.Count-1 do
411     begin
412       Pg := IDEComponentPalette.Pages[i];
413       Comps := IDEComponentPalette.RefUserCompsForPage(Pg.PageName);
414       // Palette layout Page header
415       ParentNode := PalletteTree.Items.AddChild(nil, Pg.PageName);
416       // Iterate components of one page
417       for j := 0 to Comps.Count-1 do begin
418         Comp := Comps[j];
419         // Flat list item
420         AListNode := ListTree.Items.AddChildObject(Nil, Comp.ComponentClass.ClassName, Comp);
421         // Palette layout item
422         APaletteNode := PalletteTree.Items.AddChildObject(ParentNode, Comp.ComponentClass.ClassName, Comp);
423         if Comp is TPkgComponent then
424           CurIcon := TPkgComponent(Comp).ImageIndex
425         else
426           CurIcon := -1;
427         if CurIcon>=0 then
428         begin
429           AListNode.ImageIndex := CurIcon;
430           AListNode.SelectedIndex := AListNode.ImageIndex;
431           APaletteNode.ImageIndex := AListNode.ImageIndex;
432           APaletteNode.SelectedIndex := AListNode.ImageIndex;
433         end;
434         // Component inheritence item
435         DoComponentInheritence(Comp);
436       end;
437     end;
438     InheritanceTree.AlphaSort;
439     {$IFnDEF NoComponentListTreeExpand}
440     InheritanceTree.FullExpand;    // Some users may not want the trees expanded.
441     PalletteTree.FullExpand;
442     {$ENDIF}
443     PrevChangeStamp := IDEComponentPalette.ChangeStamp;
444   finally
445     FClassList.Free;
446     InheritanceTree.Items.EndUpdate;
447     PalletteTree.EndUpdate;
448     ListTree.EndUpdate;
449     Screen.EndWaitCursor;
450   end;
451 end;
452 
453 procedure TComponentListForm.TreeFilterEdAfterFilter(Sender: TObject);
454 begin
455   if TreeFilterEd.Filter = '' then
456     IDEComponentPalette.SetSelectedComp(nil, False);
457   UpdateButtonState;
458 end;
459 
460 procedure TComponentListForm.ComponentsDblClick(Sender: TObject);
461 // This is used for all 3 treeviews
462 begin
463   OKButtonClick(nil);       // Select and close this form
464 end;
465 
466 procedure TComponentListForm.ListTreeSelectionChanged(Sender: TObject);
467 var
468   AComponent: TRegisteredComponent;
469 begin
470   UpdateButtonState;
471   if FInitialized then
472   begin
473     if FPageControlChange then
474       Exit;
475     AComponent:=GetSelectedComponent;
476     if AComponent<>nil then
477       IDEComponentPalette.SetSelectedComp(AComponent, ssShift in GetKeyShiftState)
478     else
479     begin
480       FIgnoreSelection := True;
481       IDEComponentPalette.SetSelectedComp(nil, False);
482       FIgnoreSelection := False;
483     end;
484   end
485   else begin
486     // Only run once when the IDE starts.
487     FInitialized := True;
488     IDEComponentPalette.SetSelectedComp(nil, False);
489     ListTree.Selected := Nil;
490     PalletteTree.Selected := Nil;
491     InheritanceTree.Selected := Nil;
492   end
493 end;
494 
495 procedure TComponentListForm.TreeKeyPress(Sender: TObject; var Key: char);
496 // This is used for all 3 treeviews
497 begin
498   if Key = Char(VK_RETURN) then
499     ComponentsDblClick(Sender);
500 end;
501 
502 procedure TComponentListForm.PageControlChange(Sender: TObject);
503 begin
504   //DebugLn(['TComponentListForm.PageControlChange: Start']);
505   FPageControlChange := True;
506   case PageControl.PageIndex of
507     0: begin
508          TreeFilterEd.FilteredTreeview := ListTree;
509          FActiveTree := ListTree;
510         end;
511     1: begin
512          TreeFilterEd.FilteredTreeview := PalletteTree;
513          FActiveTree := PalletteTree;
514        end;
515     2: begin
516          TreeFilterEd.FilteredTreeview := InheritanceTree;
517          FActiveTree := InheritanceTree;
518         end;
519   end;
520   TreeFilterEd.InvalidateFilter;
521   EnvironmentOptions.ComponentListPageIndex := PageControl.PageIndex;
522   FActiveTree.BeginUpdate;
523   tmDeselect.Enabled := True;
524 end;
525 
526 procedure TComponentListForm.tmDeselectTimer(Sender: TObject);
527 begin
528   tmDeselect.Enabled := False;
529   FActiveTree.Selected := nil;
530   SelectionWasChanged;
531   FActiveTree.EndUpdate;
532   FPageControlChange := False;
533 end;
534 
535 procedure TComponentListForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
536 begin
537   ClearSelection;
538   IDEComponentPalette.Selected := Nil;
539 end;
540 
541 procedure TComponentListForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
542 begin
543   if Key=VK_ESCAPE then
544   begin
545     if (IDEComponentPalette.Selected = nil) and not IsDocked  then //close only if no component is selected
546       Close
547     else
548       ClearSelection; //unselect if component is selected
549   end;
550 end;
551 
552 procedure TComponentListForm.OKButtonClick(Sender: TObject);
553 // Select component from palette and close this form. User can insert the component.
554 var
555   AComponent: TRegisteredComponent;
556   OldFocusedControl: TWinControl;
557 begin
558   AComponent := GetSelectedComponent;
559   if AComponent=nil then
560     Exit;
561 
562   OldFocusedControl := Screen.ActiveControl;
563   AddSelectedComponent;
564   if (OldFocusedControl<>nil) and OldFocusedControl.CanSetFocus then // AddComponent in docked mode steals focus to designer, get it back
565     OldFocusedControl.SetFocus;
566 
567   if not IsDocked and not chbKeepOpen.Checked then
568     Close;
569 end;
570 
571 procedure TComponentListForm.miCollapseAllClick(Sender: TObject);
572 begin
573   TreeFilterEd.FilteredTreeview.FullCollapse;
574 end;
575 
576 procedure TComponentListForm.miCollapseClick(Sender: TObject);
577 var
578   Node: TTreeNode;
579 begin
580   Node := TreeFilterEd.FilteredTreeview.Selected;
581   if Node = nil then
582     Exit;
583   if (Node.Level > 0) and (Node.HasChildren = False) then
584     Node := Node.Parent;
585   Node.Collapse(True);
586 end;
587 
588 procedure TComponentListForm.miExpandAllClick(Sender: TObject);
589 begin
590   TreeFilterEd.FilteredTreeview.FullExpand;
591 end;
592 
593 procedure TComponentListForm.miExpandClick(Sender: TObject);
594 var
595   Node: TTreeNode;
596 begin
597   Node := TreeFilterEd.FilteredTreeview.Selected;
598   if Node = nil then
599     Exit;
600   if (Node.Level > 0) and (Node.HasChildren = False) then
601     Node := Node.Parent;
602   Node.Expand(True);
603 end;
604 
605 procedure TComponentListForm.pmCollapseExpandPopup(Sender: TObject);
606 var
607   Node: TTreeNode;
608 begin
609   Node := TreeFilterEd.FilteredTreeview.Selected;
610   if Node = nil then
611   begin
612     miExpand.Enabled := False;
613     miCollapse.Enabled := False;
614   end
615   else
616   begin
617     miExpand.Enabled := (Node.HasChildren) and (not Node.Expanded);
618     miCollapse.Enabled := (Node.HasChildren) and (Node.Expanded);
619   end;
620 end;
621 
622 procedure TComponentListForm.SelectionToolButtonClick(Sender: TObject);
623 begin
624   SelectionToolButton.Down := True;
625   IDEComponentPalette.SetSelectedComp(nil, False);
626 end;
627 
628 end.
629 
630