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