1 {
2  *****************************************************************************
3   This file is part of the Lazarus Component Library (LCL)
4 
5   See the file COPYING.modifiedLGPL.txt, included in this distribution,
6   for details about the license.
7  *****************************************************************************
8 }
9 unit ButtonPanel;
10 
11 {$mode objfpc}{$h+}
12 
13 interface
14 
15 uses
16   Math, Types, SysUtils, Classes,
17   // LCL
18   LCLType, LMessages, Controls, ExtCtrls, StdCtrls, Buttons, Forms, Graphics, Themes,
19   // LazUtils
20   GraphType;
21 
22 type
23   TButtonOrder  = (boDefault, boCloseCancelOK, boCloseOKCancel);
24   TPanelButton  = (pbOK, pbCancel, pbClose, pbHelp);
25   TPanelButtons = set of TPanelButton;
26 
27 const
28   DefShowButtons = [pbOK, pbCancel, pbClose, pbHelp];
29   DefShowGlyphs = [pbOK, pbCancel, pbClose, pbHelp];
30 
31 type
32 
33   { TPanelBitBtn }
34 
35   TPanelBitBtn = class(TCustomBitBtn)
36   public
37     constructor Create(AOwner: TComponent); override;
38   published
39     // Caption is stored only if DefaultCaption = false
40     property Caption stored IsCaptionStored;
41     property DefaultCaption stored True;
42     property Left stored False;
43     property Top stored False;
44     property Width stored False;
45     property Height stored False;
46     property Enabled;
47     property Font;
48     property Glyph;
49     property Name stored True;
50     property PopupMenu;
51     property ShowHint;
52     property OnClick;
53   end;
54 
55   { TCustomButtonPanel }
56 
57   TCustomButtonPanel = class(TCustomPanel)
58   private
59     FShowBevel: Boolean;
60     FShowButtons: TPanelButtons;
61     FShowGlyphs: TPanelButtons;
62     FBevel: TBevel;
63     FGlyphs: array[TPanelButton] of TBitmap;
64     FButtons: array[TPanelButton] of TPanelBitBtn;
65     FButtonsWidth: Integer;
66     FButtonsHeight: Integer;
67     FButtonOrder: TButtonOrder;
68     FDefaultButton: TPanelButton;
69     FSpacing: TSpacingSize;
70     procedure CreateButton(AButton: TPanelButton);
71     procedure DoDefaultButton;
72     procedure DoShowButtons;
73     procedure DoShowGlyphs;
74     procedure SetButtonOrder(Value: TButtonOrder);
75     procedure SetDefaultButton(Value: TPanelButton);
76     procedure SetShowBevel(AValue: Boolean);
77     procedure SetShowButtons(Value: TPanelButtons);
78     procedure SetShowGlyphs(Value: TPanelButtons);
79     procedure SetSpacing(AValue: TSpacingSize);
80     procedure UpdateBevel;
81     procedure UpdateButtonOrder;
82     procedure UpdateSizes;
83     procedure UpdateButtonLayout;
84     procedure UpdateButtonSize;
IsLastButtonnull85     function IsLastButton(AControl: TControl): boolean;
86   protected
CreateControlBorderSpacingnull87     function CreateControlBorderSpacing: TControlBorderSpacing; override;
CustomAlignInsertBeforenull88     function CustomAlignInsertBefore(AControl1, AControl2: TControl): Boolean; override;
89     procedure CustomAlignPosition(AControl: TControl; var ANewLeft, ANewTop,
90       ANewWidth, ANewHeight: Integer; var AlignRect: TRect;
91       AlignInfo: TAlignInfo); override;
92     procedure CalculatePreferredSize(var PreferredWidth,
93       PreferredHeight: integer; WithThemeSpace: Boolean); override;
94     procedure Loaded; override;
95     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
96     procedure SetAlign(Value: TAlign); override;
97     procedure CMAppShowBtnGlyphChanged(var Message: TLMessage); message CM_APPSHOWBTNGLYPHCHANGED;
98     procedure CMShowingChanged(var Message: TLMessage); message CM_SHOWINGCHANGED;
99   public
100     constructor Create(AOwner: TComponent); override;
101     destructor Destroy; override;
102 
103     property Align default alBottom;
104     property AutoSize default True;
105 
106     property OKButton: TPanelBitBtn read FButtons[pbOK] default nil;
107     property HelpButton: TPanelBitBtn read FButtons[pbHelp] default nil;
108     property CloseButton: TPanelBitBtn read FButtons[pbClose] default nil;
109     property CancelButton: TPanelBitBtn read FButtons[pbCancel] default nil;
110     property ButtonOrder: TButtonOrder read FButtonOrder write SetButtonOrder default boDefault;
111 
112     property DefaultButton: TPanelButton read FDefaultButton write SetDefaultButton default pbOK;
113     property ShowButtons: TPanelButtons read FShowButtons write SetShowButtons default DefShowButtons;
114     property ShowGlyphs: TPanelButtons read FShowGlyphs write SetShowGlyphs default DefShowGlyphs;
115     property ShowBevel: Boolean read FShowBevel write SetShowBevel default True;
116     property Spacing: TSpacingSize read FSpacing write SetSpacing default 6;
117   published
118   end;
119 
120   { TButtonPanel }
121 
122   TButtonPanel = class(TCustomButtonPanel)
123   published
124     property Align;
125     property Anchors;
126     property AutoSize;
127     property BorderSpacing;
128     property Constraints;
129     property Enabled;
130     property OKButton;
131     property HelpButton;
132     property CloseButton;
133     property CancelButton;
134     property Color;
135     property ButtonOrder;
136     property TabOrder;
137     property DefaultButton;
138     property Spacing;
139     property OnClick;
140     property OnDblClick;
141     property OnDragDrop;
142     property OnEnter;
143     property OnExit;
144     property OnKeyDown;
145     property OnKeyPress;
146     property OnKeyUp;
147     property OnMouseDown;
148     property OnMouseEnter;
149     property OnMouseLeave;
150     property OnMouseMove;
151     property OnMouseUp;
152     property OnMouseWheel;
153     property OnMouseWheelDown;
154     property OnMouseWheelUp;
155     property OnResize;
156     property OnUTF8KeyPress;
157     property ShowButtons;
158     property ShowGlyphs;
159     property ShowBevel;
160     property Visible;
161   end;
162 
163 procedure Register;
164 
165 implementation
166 
167 const
168   DEFAULT_BUTTONPANEL_BORDERSPACING: TControlBorderSpacingDefault = (
169     Left:0; Top:0; Right:0; Bottom:0; Around:6;
170   );
171 
172 procedure Register;
173 begin
174   RegisterComponents('Misc', [TButtonPanel]);
175 end;
176 
177 { TPanelBitBtn }
178 
179 constructor TPanelBitBtn.Create(AOwner: TComponent);
180 begin
181   inherited;
182 
183   SetSubComponent(True);
184 end;
185 
186 { TCustomButtonPanel }
187 
188 procedure TCustomButtonPanel.DoShowButtons;
189 var
190   btn: TPanelButton;
191   aButton: TPanelBitBtn;
192 begin
193   DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.DoShowButtons'){$ENDIF};
194 
195   for btn := Low(btn) to High(btn) do
196   begin
197     if FButtons[btn] = nil
198     then CreateButton(btn);
199     aButton:=FButtons[btn];
200 
201     if btn in FShowButtons
202     then begin
203       if csDesigning in ComponentState then
204         aButton.ControlStyle:=aButton.ControlStyle-[csNoDesignVisible];
205       aButton.Visible := True;
206     end
207     else begin
208       if csDesigning in ComponentState then
209         aButton.ControlStyle:=aButton.ControlStyle+[csNoDesignVisible];
210       aButton.Visible := False;
211     end;
212   end;
213 
214   UpdateButtonOrder;
215   UpdateButtonLayout;
216   EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.DoShowButtons'){$ENDIF};
217 end;
218 
219 procedure TCustomButtonPanel.SetShowButtons(Value: TPanelButtons);
220 begin
221   if FShowButtons = Value then
222     Exit;
223 
224   FShowButtons := Value;
225   InvalidatePreferredSize;
226   DoShowButtons;
227 end;
228 
229 procedure TCustomButtonPanel.DoShowGlyphs;
230 var
231   btn: TPanelButton;
232 begin
233   DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.DoShowGlyphs'){$ENDIF};
234   for btn := Low(btn) to High(btn) do
235   begin
236     if FButtons[btn] = nil then Continue;
237 
238     if btn in FShowGlyphs
239     then begin
240       FButtons[btn].Glyph.Assign(FGlyphs[btn]);
241     end
242     else begin
243       FButtons[btn].Glyph.Assign(nil);
244     end;
245   end;
246   EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.DoShowGlyphs'){$ENDIF};
247 end;
248 
249 procedure TCustomButtonPanel.SetShowGlyphs(Value: TPanelButtons);
250 begin
251   if FShowGlyphs = Value then Exit;
252   FShowGlyphs := Value;
253   InvalidatePreferredSize;
254   DoShowGlyphs;
255 end;
256 
257 procedure TCustomButtonPanel.SetSpacing(AValue: TSpacingSize);
258 begin
259   if FSpacing = AValue then Exit;
260   FSpacing := AValue;
261   InvalidatePreferredSize;
262   ReAlign;
263 end;
264 
265 procedure TCustomButtonPanel.UpdateBevel;
266 begin
267   if FBevel = nil then Exit;
268 
269   case Align of
270     alTop:
271       begin
272         FBevel.Shape := bsBottomLine;
273         FBevel.Align := alBottom;
274       end;
275     alLeft:
276       begin
277         FBevel.Shape := bsRightLine;
278         FBevel.Align := alRight;
279       end;
280     alRight:
281       begin
282         FBevel.Shape := bsLeftLine;
283         FBevel.Align := alLeft;
284       end
285   else
286     // default to bottom
287     FBevel.Shape := bsTopLine;
288     FBevel.Align := alTop;
289   end;
290 
291   if Align in [alLeft, alRight]
292   then FBevel.Width := 2
293   else FBevel.Height := 2;
294 end;
295 
296 procedure TCustomButtonPanel.UpdateSizes;
297 var
298   i: Integer;
299   BtnWidth, BtnHeight: Integer;
300   Details: TThemedElementDetails;
301   DefButtonSize: TSize;
302 begin
303   if csDestroying in ComponentState then
304     Exit;
305 
306   Details := ThemeServices.GetElementDetails(tbPushButtonNormal);
307   DefButtonSize := ThemeServices.GetDetailSize(Details);
308   FButtonsWidth := DefButtonSize.cx;
309   FButtonsHeight := DefButtonSize.cy;
310 
311   for i := 0 to ControlCount - 1 do
312   begin
313     if not (Controls[i] is TCustomButton) then Continue;
314     Controls[i].GetPreferredSize(BtnWidth, BtnHeight, True);
315     if Align in [alTop, alBottom] then
316       Controls[i].Width := BtnWidth;
317     if Align in [alLeft, alRight] then
318       Controls[i].Height := BtnHeight;
319     if BtnWidth > FButtonsWidth then
320       FButtonsWidth := BtnWidth;
321     if BtnHeight > FButtonsHeight then
322       FButtonsHeight := BtnHeight;
323   end;
324 end;
325 
326 procedure TCustomButtonPanel.UpdateButtonLayout;
327 var
328   aButton: TPanelBitBtn;
329   btn: TPanelButton;
330 begin
331   for btn := Low(TPanelButton) to High(TPanelButton) do
332   begin
333     aButton:=FButtons[btn];
334     if aButton = nil then Continue;
335     aButton.Align := alCustom;
336     aButton.Default := FDefaultButton = btn;
337   end;
338 end;
339 
IsLastButtonnull340 function TCustomButtonPanel.IsLastButton(AControl: TControl): boolean;
341 // returns true if AControl is the right/bottommost of the TPanelBitBtn
342 // Note: pbHelp could be the only button, then it is the last button
343 var
344   i: TPanelButton;
345 begin
346   if not AControl.IsControlVisible then exit(false);
347   if not (AControl is TPanelBitBtn) then exit(false);
348   for i:=low(FButtons) to pred(high(FButtons)) do
349     if (FButtons[i]<>nil) and FButtons[i].IsControlVisible
350     and (FButtons[i].TabOrder>TPanelBitBtn(AControl).TabOrder) then
351       exit(false); // there is a higher one
352   Result:=true;
353 end;
354 
355 procedure TCustomButtonPanel.UpdateButtonOrder;
356 const
357   TabOrders: array[TButtonOrder, 0..3] of TPanelButton = (
358     {$IFDEF UNIX}
359     {boDefault      } (pbOK, pbCancel, pbClose, pbHelp),
360     {$ELSE}
361     {boDefault      } (pbCancel, pbOK, pbClose, pbHelp),
362     {$ENDIF}
363     {boCloseCancelOK} (pbOK, pbCancel, pbClose, pbHelp),
364     {boCloseOKCancel} (pbCancel, pbOK, pbClose, pbHelp)
365   );
366 var
367   i: Integer;
368 begin
369   //set taborder
370   for i := Low(TabOrders[FButtonOrder]) to High(TabOrders[FButtonOrder]) do
371   begin
372     if FButtons[TabOrders[FButtonOrder, i]] = nil then Continue;
373     FButtons[TabOrders[FButtonOrder, i]].TabOrder := High(TabOrders[FButtonOrder]) - i;
374   end;
375   AdjustSize;
376 end;
377 
378 procedure TCustomButtonPanel.UpdateButtonSize;
379 var
380   AParent: TCustomDesignControl;
381   Details: TThemedElementDetails;
382   DefButtonSize: TSize;
383   btn: TPanelBitBtn;
384 begin
385   AParent := GetParentDesignControl(Self);
386   if AParent=nil then
387     Exit;
388 
389   Details := ThemeServices.GetElementDetails(tbPushButtonNormal);
390   DefButtonSize := ThemeServices.GetDetailSize(Details);
391 
392   DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.UpdateButtonSize'){$ENDIF};
393   try
394     for btn in FButtons do
395     begin
396       if btn = nil then Continue;
397       if Application.Scaled and AParent.Scaled then
398       begin
399         btn.Constraints.MinWidth := MulDiv(DefButtonSize.cx, AParent.PixelsPerInch, ScreenInfo.PixelsPerInchX);
400         btn.Constraints.MinHeight := MulDiv(DefButtonSize.cy, AParent.PixelsPerInch, ScreenInfo.PixelsPerInchY);
401       end else
402       begin
403         btn.Constraints.MinWidth := DefButtonSize.cx;
404         btn.Constraints.MinHeight := DefButtonSize.cy;
405       end;
406     end;
407   finally
408     EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.UpdateButtonSize'){$ENDIF};
409   end;
410 end;
411 
412 procedure TCustomButtonPanel.SetAlign(Value: TAlign);
413 begin
414   DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.SetAlign'){$ENDIF};
415   try
416     inherited SetAlign(Value);
417     UpdateButtonLayout;
418     UpdateBevel;
419     UpdateSizes;
420   finally
421     EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.SetAlign'){$ENDIF};
422   end;
423 end;
424 
425 procedure TCustomButtonPanel.CMAppShowBtnGlyphChanged(var Message: TLMessage);
426 begin
427   NotifyControls(Message.msg);
428 end;
429 
430 procedure TCustomButtonPanel.CMShowingChanged(var Message: TLMessage);
431 begin
432   inherited;
433 
434   UpdateButtonSize;
435 end;
436 
437 procedure TCustomButtonPanel.SetButtonOrder(Value: TButtonOrder);
438 begin
439   if FButtonOrder = Value then Exit;
440   FButtonOrder := Value;
441   UpdateButtonOrder;
442 end;
443 
444 procedure TCustomButtonPanel.DoDefaultButton;
445 var
446   btn: TPanelButton;
447 begin
448   for btn := Low(btn) to High(btn) do
449   begin
450     if FButtons[btn] = nil then Continue;
451     FButtons[btn].Default := FDefaultButton = btn;
452   end;
453 end;
454 
455 procedure TCustomButtonPanel.SetDefaultButton(Value: TPanelButton);
456 begin
457   if FDefaultButton = Value then
458     Exit;
459 
460   FDefaultButton := Value;
461 
462   DoDefaultButton;
463 end;
464 
465 procedure TCustomButtonPanel.SetShowBevel(AValue: Boolean);
466 begin
467   if FShowBevel = AValue then exit;
468   FShowBevel := AValue;
469 
470   if not FShowBevel
471   then begin
472     FreeAndNil(FBevel);
473     Exit;
474   end;
475 
476   DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.SetShowBevel'){$ENDIF};
477   try
478     FBevel := TBevel.Create(Self);
479     FBevel.Parent := Self;
480     FBevel.Name   := 'Bevel';
481 
482     UpdateBevel;
483   finally
484     EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TCustomButtonPanel.SetShowBevel'){$ENDIF};
485   end;
486 end;
487 
488 procedure TCustomButtonPanel.Loaded;
489 begin
490   inherited;
491   DoShowGlyphs;
492 end;
493 
494 procedure TCustomButtonPanel.Notification(AComponent: TComponent;
495   Operation: TOperation);
496 var
497   btn: TPanelButton;
498 begin
499   if Operation=opRemove then
500   begin
501     for btn := Low(btn) to High(btn) do
502     begin
503       if FButtons[btn] <> AComponent then Continue;
504       FButtons[btn] := nil;
505       Exclude(FShowButtons, btn);
506     end;
507   end;
508   inherited Notification(AComponent, Operation);
509   if AComponent is TPanelBitBtn then
510     UpdateSizes;
511 end;
512 
513 constructor TCustomButtonPanel.Create(AOwner: TComponent);
514 begin
515   inherited Create(AOwner);
516 
517   ControlStyle := ControlStyle + [csOwnedChildrenNotSelectable];
518 
519   Align      := alBottom;
520   BevelInner := bvNone;
521   BevelOuter := bvNone;
522   Caption    := '';
523   ControlStyle := ControlStyle - [csSetCaption];
524   AutoSize   := True;
525   FSpacing   := 6;
526   ShowBevel  := True;
527 
528 
529   FDefaultButton := pbOK;
530   FButtonOrder   := boDefault;
531   FShowButtons   := DefShowButtons;
532   FShowGlyphs    := DefShowGlyphs;
533 
534   // create the buttons
535   DoShowButtons;
536 end;
537 
538 procedure TCustomButtonPanel.CreateButton(AButton: TPanelButton);
539 const
540   NAMES: array[TPanelButton] of String = (
541     'OKButton', 'CancelButton', 'CloseButton', 'HelpButton'
542   );
543   KINDS: array[TPanelButton] of TBitBtnKind = (
544     bkOK, bkCancel, bkClose, bkHelp
545   );
546 begin
547   if FButtons[AButton] <> nil then Exit;
548 
549   FButtons[AButton] := TPanelBitBtn.Create(Self);
550   with FButtons[AButton] do
551   begin
552     Name     := NAMES[AButton];
553     Kind     := KINDS[AButton];
554     AutoSize := true;
555     TabOrder := Ord(AButton); //initial order
556     Align    := alCustom;
557     if FGlyphs[AButton] = nil
558     then begin
559       // first time
560       FGlyphs[AButton] := TBitmap.Create;
561       FGlyphs[AButton].Assign(Glyph);
562     end;
563     // (re)set the glyph if needed
564     if (AButton in FShowGlyphs)
565     then Glyph.Assign(FGlyphs[AButton])
566     else Glyph.Assign(nil);
567     // set default
568     Default  := AButton = FDefaultButton;
569 
570     Parent   := Self;
571   end;
572 end;
573 
CreateControlBorderSpacingnull574 function TCustomButtonPanel.CreateControlBorderSpacing: TControlBorderSpacing;
575 begin
576   Result := TControlBorderSpacing.Create(Self, @DEFAULT_BUTTONPANEL_BORDERSPACING);
577 end;
578 
CustomAlignInsertBeforenull579 function TCustomButtonPanel.CustomAlignInsertBefore(AControl1, AControl2: TControl): Boolean;
580 begin
581   // bevel is always the very first
582   if AControl1 = FBevel then Exit(True);
583   if AControl2 = FBevel then Exit(False);
584   // the help button is the second
585   if AControl1 = FButtons[pbHelp] then Exit(True);
586   if AControl2 = FButtons[pbHelp] then Exit(False);
587   // user defined controls comes before the normal buttons
588   if (not (AControl1 is TPanelBitBtn)) and (AControl2 is TPanelBitBtn) then
589     Exit(True)
590   else if (AControl1 is TPanelBitBtn) and (not (AControl2 is TPanelBitBtn)) then
591     Exit(False);
592   // sort for taborder
593   Result := TWinControl(AControl2).TabOrder > TWinControl(AControl1).TabOrder;
594 end;
595 
596 procedure TCustomButtonPanel.CustomAlignPosition(AControl: TControl;
597   var ANewLeft, ANewTop, ANewWidth, ANewHeight: Integer; var AlignRect: TRect;
598   AlignInfo: TAlignInfo);
599 var
600   BevelSpacing: TSpacingSize;
601 begin
602   //debugln(['TCustomButtonPanel.CustomAlignPosition ',DbgSName(Self),' AControl=',DbgSName(AControl),' AlignRect=',dbgs(AlignRect),' New=',ANewLeft,',',ANewTop,',',ANewWidth,'x',ANewHeight]);
603   inherited CustomAlignPosition(AControl, ANewLeft, ANewTop, ANewWidth,
604     ANewHeight, AlignRect, AlignInfo);
605 
606   if Assigned(FBevel) and FBevel.IsControlVisible then
607     BevelSpacing := Spacing
608   else
609     BevelSpacing := 0;
610 
611   if Align in [alLeft,alRight] then
612   begin
613     // put top or bottom
614     ANewLeft:=AlignRect.Left;
615     ANewWidth:=AControl.Constraints.MinMaxWidth(AlignRect.Right-ANewLeft-BevelSpacing);
616     if Align=alRight then
617       inc(ANewLeft,BevelSpacing);
618     if AControl=FButtons[pbHelp] then
619     begin
620       ANewTop:=AlignRect.Top; // no Spacing in front of the first button
621       AlignRect.Top:=Min(AlignRect.Bottom,ANewTop+ANewHeight);
622     end else begin
623       ANewTop:=AlignRect.Bottom-ANewHeight;
624       if not IsLastButton(AControl) then
625         dec(ANewTop,Spacing);
626       AlignRect.Bottom:=Max(AlignRect.Top,ANewTop);
627     end;
628   end else
629   begin
630     // put left or right
631     ANewTop:=AlignRect.Top;
632     ANewHeight:=AControl.Constraints.MinMaxHeight(AlignRect.Bottom-ANewTop-BevelSpacing);
633     if Align=alBottom then
634       inc(ANewTop,BevelSpacing);
635     if AControl=FButtons[pbHelp] then
636     begin
637       // put left
638       ANewLeft:=AlignRect.Left; // no Spacing in front of the first button
639       AlignRect.Left:=Min(AlignRect.Right,ANewLeft+ANewWidth);
640     end else begin
641       // put right
642       ANewLeft:=AlignRect.Right-ANewWidth;
643       if not IsLastButton(AControl) then
644         dec(ANewLeft,Spacing);
645       AlignRect.Right:=Max(AlignRect.Left,ANewLeft);
646     end;
647   end;
648   //debugln(['TCustomButtonPanel.CustomAlignPosition END ',DbgSName(Self),' AControl=',DbgSName(AControl),' AlignRect=',dbgs(AlignRect),' New=',ANewLeft,',',ANewTop,',',ANewWidth,'x',ANewHeight]);
649 end;
650 
651 procedure TCustomButtonPanel.CalculatePreferredSize(var PreferredWidth,
652   PreferredHeight: integer; WithThemeSpace: Boolean);
653 var
654   i: Integer;
655   AControl: TControl;
656   MinWidth: Integer;
657   MinHeight: Integer;
658   CtrlPrefWidth, CtrlPrefHeight: integer;
659 begin
660   MinWidth:=0;
661   MinHeight:=0;
662   // add buttons
663   for i:=0 to ControlCount-1 do
664   begin
665     AControl:=Controls[i];
666     if (AControl.Align<>alCustom) or (not AControl.IsControlVisible) then continue;
667     if AControl=FBevel then continue;
668     CtrlPrefWidth:=0;
669     CtrlPrefHeight:=0;
670     AControl.GetPreferredSize(CtrlPrefWidth,CtrlPrefHeight);
671     //debugln(['TCustomButtonPanel.CalculatePreferredSize ',DbgSName(AControl),' ',CtrlPrefHeight]);
672     if Align in [alLeft,alRight] then
673     begin
674       inc(MinHeight,CtrlPrefHeight);
675       if not IsLastButton(AControl) then
676         inc(MinHeight,Spacing);
677       MinWidth:=Max(MinWidth,CtrlPrefWidth);
678     end
679     else begin
680       inc(MinWidth,CtrlPrefWidth);
681       if not IsLastButton(AControl) then
682         inc(MinWidth,Spacing);
683       MinHeight:=Max(MinHeight,CtrlPrefHeight);
684     end;
685   end;
686   // bevel
687   if (FBevel<>nil) and FBevel.IsControlVisible then
688   begin
689     if Align in [alLeft,alRight] then
690       inc(MinWidth,FBevel.Width+Spacing)
691     else
692       inc(MinHeight,FBevel.Height+Spacing);
693   end;
694   PreferredWidth:=MinWidth;
695   PreferredHeight:=MinHeight;
696   //debugln(['TCustomButtonPanel.CalculatePreferredSize ',DbgSName(Self),' ',PreferredWidth,'x',PreferredHeight]);
697 end;
698 
699 destructor TCustomButtonPanel.Destroy;
700 var
701   btn: TPanelButton;
702 begin
703   for btn := Low(btn) to High(btn) do
704     FreeAndNil(FGlyphs[btn]);
705   inherited Destroy;
706 end;
707 
708 end.
709