1{%MainUnit ../comctrls.pp}
2
3{ TToolButton
4
5 *****************************************************************************
6  This file is part of the Lazarus Component Library (LCL)
7
8  See the file COPYING.modifiedLGPL.txt, included in this distribution,
9  for details about the license.
10 *****************************************************************************
11
12}
13
14{ TToolButtonActionLink }
15
16procedure TToolButtonActionLink.AssignClient(AClient: TObject);
17begin
18  inherited AssignClient(AClient);
19  FClient := AClient as TToolButton;
20end;
21
22function TToolButtonActionLink.IsCheckedLinked: Boolean;
23begin
24  Result := inherited IsCheckedLinked and
25    (TToolButton(FClient).Down = (Action as TCustomAction).Checked);
26end;
27
28function TToolButtonActionLink.IsImageIndexLinked: Boolean;
29begin
30  Result := inherited IsImageIndexLinked and
31    (TToolButton(FClient).ImageIndex = (Action as TCustomAction).ImageIndex);
32end;
33
34procedure TToolButtonActionLink.SetChecked(Value: Boolean);
35begin
36  if IsCheckedLinked then
37    TToolButton(FClient).Down := Value;
38end;
39
40procedure TToolButtonActionLink.SetImageIndex(Value: Integer);
41begin
42  if IsImageIndexLinked then
43    TToolButton(FClient).ImageIndex := Value;
44end;
45
46{ TToolButton }
47
48constructor TToolButton.Create(TheOwner: TComponent);
49begin
50  inherited Create(TheOwner);
51  FImageIndex := -1;
52  FStyle := tbsButton;
53  FShowCaption := true;
54  ControlStyle := [csCaptureMouse, csSetCaption, csDesignNoSmoothResize];
55  with GetControlClassDefaultSize do
56    SetInitialBounds(0, 0, CX, CY);
57end;
58
59procedure TToolButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
60  X, Y: Integer);
61
62  procedure SendButtonUpMsg;
63  var
64    msg: TLMMouse;
65    pt: TPoint;
66  begin
67    FillChar({%H-}msg, SizeOf(msg), 0);
68    msg.Msg:=LM_LBUTTONUP;
69    pt := ScreenToClient(Mouse.CursorPos);
70    msg.XPos:=pt.X;
71    msg.YPos:=pt.Y;
72    WndProc(TLMessage(msg));
73  end;
74var
75  NewFlags: TToolButtonFlags;
76  APointInArrow: Boolean;
77begin
78  //debugln(['TToolButton.MouseDown ',DbgSName(Self)]);
79  SetMouseInControl(True);
80  NewFlags := FToolButtonFlags - [tbfPressed, tbfArrowPressed];
81  if (Button = mbLeft) then
82  begin
83    APointInArrow := PointInArrow(X, Y);
84    //use some threshold to decide if the DropdownMenu should be opened again.
85    //  When no DropdownMenu is assigned, FLastDropDownTick is always 0
86    //  therefore the condition is always met.
87    if Enabled and not(
88          (GetTickCount64 < FLastDropDownTick + 100)
89      and (APointInArrow or (Style<>tbsDropDown))) then
90    begin
91      if APointInArrow then
92        Include(NewFlags, tbfArrowPressed)
93      else
94        Include(NewFlags, tbfPressed);
95    end;
96    if NewFlags <> FToolButtonFlags then
97    begin
98      FToolButtonFlags := NewFlags;
99      Invalidate;
100    end;
101  end;
102
103  FLastDown := Down;
104
105  inherited MouseDown(Button, Shift, X, Y);
106
107  FLastDropDownTick := 0;
108  if (Button = mbLeft) and Enabled and
109     (Style in [tbsButton, tbsDropDown, tbsButtonDrop]) then
110  begin
111    if ((Style in [tbsButton, tbsButtonDrop]) and (tbfPressed in NewFlags) or
112        (Style = tbsDropDown) and (tbfArrowPressed in NewFlags)) and
113       CheckMenuDropdown then
114    begin
115      FLastDropDownTick := GetTickCount64;
116
117      //because we show the DropdownMenu in MouseDown, we have to send
118      //  LM_LBUTTONUP manually to make it work in all widgetsets!
119      // Some widgetsets work without it (e.g. win32) but some don't (e.g. carbon).
120      SendButtonUpMsg;
121    end else
122    begin
123      if (Style = tbsDropDown) and
124         (NewFlags * [tbfArrowPressed, tbfPressed] = [tbfPressed])
125      then
126        Down := True;
127    end;
128  end;
129end;
130
131procedure TToolButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
132  X, Y: Integer);
133var
134  ButtonPressed, ArrowPressed: Boolean;
135  Pt: TPoint;
136  NewFlags: TToolButtonFlags;
137begin
138  //DebugLn(['TToolButton.MouseUp ',Name,':',ClassName,' ',dbgs(ord(Button)),' ',X,',',Y]);
139  FLastDown := False;
140  NewFlags := FToolButtonFlags;
141  ButtonPressed := (Button = mbLeft) and (tbfPressed in NewFlags);
142  ArrowPressed := (Button = mbLeft) and (tbfArrowPressed in NewFlags);
143  if ButtonPressed then
144    Exclude(NewFlags, tbfPressed);
145  if ArrowPressed then
146    Exclude(NewFlags, tbfArrowPressed);
147  if (tbfMouseInArrow in NewFlags) and PointInArrow(X, Y) then
148    Exclude(NewFlags, tbfMouseInArrow);
149
150  if NewFlags <> FToolButtonFlags then
151  begin
152    FToolButtonFlags := NewFlags;
153    Invalidate;
154  end;
155
156  inherited MouseUp(Button, Shift, X, Y);
157
158  if (Button = mbLeft) then
159  begin
160    if FMouseInControl then
161    begin
162      Pt := Point(X, Y);
163      if not PtInRect(Rect(0,0,Width,Height), Pt) then
164        SetMouseInControl(false);
165    end;
166    if (Style in [tbsButton, tbsDropDown, tbsButtonDrop]) then
167      Down := False;
168    //button is pressed, but DropdownMenu was not shown
169    if FMouseInControl and (FLastDropDownTick = 0) then
170    begin
171      if ButtonPressed then
172      begin
173        if (Style = tbsCheck) then
174          Down := not Down;
175        Click;
176      end else
177      if ArrowPressed then
178        ArrowClick;
179      //DON'T USE the tool button (Self) after the click call because it could
180      //have been destroyed in the OnClick event handler (e.g. Lazarus IDE does it)!
181    end;
182  end;
183end;
184
185procedure TToolButton.Notification(AComponent: TComponent; Operation: TOperation);
186begin
187  inherited Notification(AComponent, Operation);
188  if Operation = opRemove then
189  begin
190    if AComponent = DropdownMenu then
191      DropdownMenu := nil
192    else
193    if AComponent = MenuItem then
194      MenuItem := nil;
195  end;
196end;
197
198procedure TToolButton.Paint;
199
200  procedure DrawDropDownArrow(OwnerDetails: TThemedElementDetails; const DropDownButtonRect: TRect);
201  var
202    Details: TThemedElementDetails;
203    ArrowState: TThemedToolBar;
204  begin
205    if Style = tbsButtonDrop then
206    begin
207      if Enabled then
208        ArrowState := ttbSplitButtonDropDownNormal
209      else
210        ArrowState := ttbSplitButtonDropDownDisabled;
211    end else
212    begin
213      ArrowState := TThemedToolBar(ord(ttbSplitButtonDropDownNormal) + OwnerDetails.State - 1);
214      if (tbfArrowPressed in FToolButtonFlags) and FMouseInControl and Enabled then
215        ArrowState := ttbSplitButtonDropDownPressed
216      else
217      if (FToolButtonFlags*[tbfMouseInArrow,tbfPressed] = [tbfPressed]) and not FLastDown then
218        ArrowState := ttbSplitButtonDropDownHot;
219    end;
220    Details := ThemeServices.GetElementDetails(ArrowState);
221    if (FToolBar <> nil) and (not FToolBar.Flat)
222    and (Style <> tbsButtonDrop) and (Details.State in [1, 4])
223    then
224      Details.State := 2;
225    ThemeServices.DrawElement(Canvas.Handle, Details, DropDownButtonRect);
226  end;
227
228  procedure DrawDivider(Details: TThemedElementDetails; ARect: TRect);
229  begin
230    // theme services have no strict rule to draw divider in the center,
231    // so we should calculate rectangle here
232    // on windows 7 divider can't be less than 4 pixels
233    if FToolBar.IsVertical then
234    begin
235      if (ARect.Bottom - ARect.Top) > 5 then
236      begin
237        ARect.Top := (ARect.Top + ARect.Bottom) div 2 - 3;
238        ARect.Bottom := ARect.Top + 5;
239      end;
240    end
241    else
242    begin
243      if (ARect.Right - ARect.Left) > 5 then
244      begin
245        ARect.Left := (ARect.Left + ARect.Right) div 2 - 3;
246        ARect.Right := ARect.Left + 5;
247      end;
248    end;
249    ThemeServices.DrawElement(Canvas.GetUpdatedHandle([csBrushValid, csPenValid]),
250       Details, ARect);
251  end;
252
253  procedure DrawSeparator(Details: TThemedElementDetails; ARect: TRect);
254  begin
255    // separator is just an empty space between buttons, so we should not draw anything,
256    // but vcl draws line when toolbar is flat, because there is no way to detect
257    // space between flat buttons. Better if we draw something too. One of suggestions
258    // was to draw 2 lines instead of one divider - this way separator and divider will differ
259    if FToolBar.Flat then // draw it only for flat Toolbar
260    begin
261      if FToolBar.IsVertical then
262      begin
263        if (ARect.Bottom - ARect.Top) >= 10 then
264        begin
265          ARect.Top := (ARect.Top + ARect.Bottom) div 2 - 5;
266          ARect.Bottom := ARect.Top + 5;
267          DrawDivider(Details, ARect);
268          OffsetRect(ARect, 0, 5);
269          DrawDivider(Details, ARect);
270        end
271        else
272          DrawDivider(Details, ARect);
273      end
274      else
275      begin
276        if (ARect.Right - ARect.Left) >= 10 then
277        begin
278          ARect.Left := (ARect.Left + ARect.Right) div 2 - 5;
279          ARect.Right := ARect.Left + 5;
280          DrawDivider(Details, ARect);
281          OffsetRect(ARect, 5, 0);
282          DrawDivider(Details, ARect);
283        end
284        else
285          DrawDivider(Details, ARect);
286      end;
287    end;
288  end;
289
290var
291  PaintRect: TRect;
292  ButtonRect: TRect;
293  MainBtnRect: TRect;
294  DropDownButtonRect: TRect;
295  TextSize: TSize;
296  TextPos: TPoint;
297  IconSize: TSize;
298  IconPos: TPoint;
299  ImgList: TCustomImageList;
300  ImgIndex: integer;
301  Details, TempDetails: TThemedElementDetails;
302  ImgEffect: TGraphicsDrawEffect;
303begin
304  if (FToolBar<>nil) and (ClientWidth>0) and (ClientHeight>0) then
305  begin
306    PaintRect := ClientRect; // the whole paint area
307
308    // calculate button area(s)
309    MainBtnRect := PaintRect;
310    ButtonRect := PaintRect;
311    Details := GetButtonDrawDetail;
312
313    // OnDrawItem
314    if Assigned(FToolBar.OnPaintButton) then
315    begin
316      if (Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck]) then
317      begin
318        TempDetails := Details;
319        if ((FToolBar <> nil) and not FToolBar.Flat) and (TempDetails.State in [1, 4]) then
320          TempDetails.State := 2;
321      end;
322
323      FToolBar.OnPaintButton(Self, TempDetails.State);
324      exit;
325    end;
326
327    if Style in [tbsDropDown, tbsButtonDrop] then
328    begin
329      DropDownButtonRect := ButtonRect;
330      if Style = tbsDropDown then
331        DropDownButtonRect.Left := DropDownButtonRect.Right-FToolBar.DropDownWidth
332      else
333      begin
334        DropDownButtonRect.Left := DropDownButtonRect.Right-FToolBar.ButtonDropWidth;
335        DropDownButtonRect.Right := DropDownButtonRect.Left + FToolBar.DropDownWidth;
336      end;
337      MainBtnRect.Right := DropDownButtonRect.Left;
338      if Style = tbsDropDown then
339        ButtonRect := MainBtnRect
340      else
341        Inc(MainBtnRect.Right, cDefButtonDropDecArrowWidth); // tbsButtonDrop ignore extra space between button and arrow
342    end
343    else
344      DropDownButtonRect := Rect(0,0,0,0);
345
346    // calculate text size
347    TextSize.cx:=0;
348    TextSize.cy:=0;
349    if (Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck]) and (FToolBar.ShowCaptions) and
350      ((FToolbar.List and ShowCaption) or not FToolBar.List) and //Allow hide caption only in list mode
351      (Caption <> '') then
352      TextSize := GetTextSize;
353
354    // calculate icon size
355    IconSize := Size(0,0);
356    GetCurrentIcon(ImgList, ImgIndex, ImgEffect);
357    if (ImgList<>nil) then
358    begin
359      IconSize := ImgList.SizeForPPI[FToolBar.ImagesWidth, Font.PixelsPerInch];
360      if IconSize.cy <= 0 then
361        IconSize.cx := 0;
362    end;
363
364    // calculate text and icon position
365    TextPos:=Point(0,0);
366    IconPos:=Point(0,0);
367    if TextSize.cx > 0 then
368    begin
369      if IconSize.cx > 0 then
370      begin
371        if FToolBar.List then
372        begin
373          // icon left of text
374          IconPos.X:=(MainBtnRect.Left+MainBtnRect.Right-IconSize.cx-TextSize.cx-2) div 2;
375          IconPos.Y:=(MainBtnRect.Top+MainBtnRect.Bottom-IconSize.cy) div 2;
376          TextPos.X:=IconPos.X+IconSize.cx+2;
377          TextPos.Y:=(MainBtnRect.Top+MainBtnRect.Bottom-TextSize.cy) div 2;
378        end else
379        begin
380          // icon above text
381          IconPos.X:=(MainBtnRect.Left+MainBtnRect.Right-IconSize.cx) div 2;
382          IconPos.Y:=(MainBtnRect.Top+MainBtnRect.Bottom-IconSize.cy-TextSize.cy-2) div 2;
383          TextPos.X:=(MainBtnRect.Left+MainBtnRect.Right-TextSize.cx) div 2;
384          TextPos.Y:=IconPos.Y+IconSize.cy+2;
385        end;
386      end else
387      begin
388        // only text
389        TextPos.X:=(MainBtnRect.Left+MainBtnRect.Right-TextSize.cx) div 2;
390        TextPos.Y:=(MainBtnRect.Top+MainBtnRect.Bottom-TextSize.cy) div 2;
391      end;
392    end else
393    if IconSize.cx>0 then
394    begin
395      // only icon
396      IconPos.X:=(MainBtnRect.Left+MainBtnRect.Right-IconSize.cx) div 2;
397      IconPos.Y:=(MainBtnRect.Top+MainBtnRect.Bottom-IconSize.cy) div 2;
398    end;
399
400    // draw button
401    if (Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck]) then
402    begin
403      // non-Flat toolbars come from old windows where you was able to set how
404      // to draw it by adjusting toolbar window options
405      // with current windows toolbars should be drawn using Theme
406      // so let's treat flat toolbars as starndard toolbars and draw them using ThemeManager
407      // and to draw a non-Flat toolbars we need to somehow mimic always raised state
408      // of their buttons - a good way is to draw them using Hot style also for
409      // normal and disables states
410      TempDetails := Details;
411      if ((FToolBar <> nil) and not FToolBar.Flat) and (TempDetails.State in [1, 4]) then
412        TempDetails.State := 2;
413
414      ThemeServices.DrawElement(Canvas.GetUpdatedHandle([csBrushValid, csPenValid]),
415         TempDetails, ButtonRect);
416      ButtonRect := ThemeServices.ContentRect(Canvas.Handle, TempDetails, ButtonRect);
417    end
418    else
419    if Style = tbsDivider then
420    begin
421      DrawDivider(Details, ButtonRect);
422      ButtonRect := Rect(0, 0, 0, 0); // nothing can be drawn on divider
423    end
424    else
425    if Style = tbsSeparator then
426    begin
427      if ThemeServices.ThemesEnabled then begin
428        Details:=ThemeServices.GetElementDetails(ttbSeparatorNormal);
429        ThemeServices.DrawElement(Canvas.Handle,Details,ClientRect)
430      end else
431        DrawSeparator(Details, ButtonRect);
432      ButtonRect := Rect(0, 0, 0, 0); // nothing can be drawn on separator
433    end;
434
435    // draw dropdown button
436    if Style in [tbsDropDown, tbsButtonDrop] then
437      DrawDropDownArrow(Details, DropDownButtonRect);
438
439    // draw icon
440    if (ImgList<>nil) then
441      ImgList.ResolutionForPPI[FToolBar.ImagesWidth, Font.PixelsPerInch, GetCanvasScaleFactor]
442        .Draw(Canvas, IconPos.X, IconPos.Y, ImgIndex, ImgEffect);
443
444    // draw text
445    if (TextSize.cx > 0) then
446    begin
447      MainBtnRect.Left := TextPos.X;
448      MainBtnRect.Top := TextPos.Y;
449      // if State is disabled then change to PushButtonDisabled since
450      // ToolButtonDisabled text looks not disabled though windows native toolbutton
451      // text drawn with disabled look. For other widgetsets there is no difference which
452      // disabled detail to use
453      TempDetails := Details;
454      if TempDetails.State = 4 then
455        TempDetails := ThemeServices.GetElementDetails(tbPushButtonDisabled);
456      ThemeServices.DrawText(Canvas, TempDetails, Caption, MainBtnRect,
457        DT_LEFT or DT_TOP, 0);
458    end;
459
460    // draw separator (at runtime: just space, at designtime: a rectangle)
461    if (Style = tbsSeparator) and (csDesigning in ComponentState) then
462    begin
463      Canvas.Brush.Color := clBackground;
464      Canvas.Pen.Color := clBlack;
465      dec(PaintRect.Right);
466      dec(PaintRect.Bottom);
467      Canvas.FrameRect(PaintRect);
468    end;
469  end;
470
471  inherited Paint;
472end;
473
474function TToolButton.PointInArrow(const X, Y: Integer): Boolean;
475begin
476  Result := (Style = tbsDropDown) and (FToolBar <> nil)
477    and (Y >= 0) and (Y <= ClientHeight)
478    and (X > ClientWidth - FToolBar.DropDownWidth) and (X <= ClientWidth);
479end;
480
481procedure TToolButton.Loaded;
482begin
483  inherited Loaded;
484  CopyPropertiesFromMenuItem(FMenuItem);
485end;
486
487procedure TToolButton.SetAutoSize(Value: Boolean);
488begin
489  if Value = AutoSize then exit;
490  inherited SetAutoSize(Value);
491  RequestAlign;
492end;
493
494procedure TToolButton.RealSetText(const AValue: TCaption);
495begin
496  if ([csLoading,csDestroying]*ComponentState=[]) then
497  begin
498    InvalidatePreferredSize;
499    inherited RealSetText(AValue);
500    AdjustSize;
501  end
502  else
503    inherited RealSetText(AValue);
504end;
505
506procedure TToolButton.SetToolBar(NewToolBar: TToolBar);
507begin
508  if FToolBar = NewToolBar then exit;
509  Parent := NewToolBar;
510end;
511
512procedure TToolButton.ActionChange(Sender: TObject; CheckDefaults: Boolean);
513var
514  NewAction: TCustomAction;
515begin
516  inherited ActionChange(Sender, CheckDefaults);
517  if Sender is TCustomAction then
518  begin
519    NewAction := TCustomAction(Sender);
520    if (not CheckDefaults) or (not Down) then
521      Down := NewAction.Checked;
522    if (not CheckDefaults) or (ImageIndex<0) then
523      ImageIndex := NewAction.ImageIndex;
524  end;
525end;
526
527procedure TToolButton.ArrowClick;
528begin
529  if Assigned(FOnArrowClick) then
530    FOnArrowClick(Self);
531end;
532
533function TToolButton.GetActionLinkClass: TControlActionLinkClass;
534begin
535  Result := TToolButtonActionLink;
536end;
537
538procedure TToolButton.CopyPropertiesFromMenuItem(const Value: TMenuItem);
539begin
540  if not Assigned(Value) then Exit;
541  BeginUpdate;
542  Action := Value.Action;
543  Caption := Value.Caption;
544  Down := Value.Checked;
545  Enabled := Value.Enabled;
546  Hint := Value.Hint;
547  ImageIndex := Value.ImageIndex;
548  Visible := Value.Visible;
549  EndUpdate;
550end;
551
552procedure TToolButton.CMHitTest(var Message: TCMHitTest);
553begin
554  if (not (Style in [tbsDivider, tbsSeparator])) or (DragKind = dkDock) then
555    Message.Result := 1
556  else
557    Message.Result := 0;
558end;
559
560class procedure TToolButton.WSRegisterClass;
561begin
562  inherited WSRegisterClass;
563  RegisterCustomToolButton;
564end;
565
566procedure TToolButton.MouseEnter;
567begin
568  // DebugLn('TToolButton.MouseEnter ',Name);
569  inherited MouseEnter;
570  SetMouseInControl(true);
571end;
572
573procedure TToolButton.MouseLeave;
574begin
575  // DebugLn('TToolButton.MouseLeave ',Name);
576  inherited MouseLeave;
577
578  if not(tbfDropDownMenuShown in FToolButtonFlags) then
579  begin
580    if (not MouseCapture)
581    and ([tbfPressed, tbfArrowPressed, tbfMouseInArrow] * FToolButtonFlags <> []) then
582    begin
583      Exclude(FToolButtonFlags, tbfPressed);
584      Exclude(FToolButtonFlags, tbfArrowPressed);
585      Exclude(FToolButtonFlags, tbfMouseInArrow);
586    end;
587    SetMouseInControl(false);
588  end;
589end;
590
591procedure TToolButton.MouseMove(Shift: TShiftState; X, Y: Integer);
592var
593  NewFlags: TToolButtonFlags;
594begin
595  inherited MouseMove(Shift, X, Y);
596
597  if (not MouseCapture) and (Style = tbsDropDown) and (FToolBar <> nil) then
598  begin
599    NewFlags := FToolButtonFlags;
600    if PointInArrow(X, Y) then
601      Include(NewFlags, tbfMouseInArrow)
602    else
603      Exclude(NewFlags, tbfMouseInArrow);
604
605    if NewFlags <> FToolButtonFlags then
606    begin
607      FToolButtonFlags := NewFlags;
608      Invalidate;
609    end;
610  end;
611end;
612
613procedure TToolButton.SetDown(Value: Boolean);
614var
615  StartIndex, EndIndex: integer;
616  i: Integer;
617  CurButton: TToolButton;
618begin
619  if Value = FDown then exit;
620  if (csLoading in ComponentState) then
621  begin
622    FDown := Value;
623    Exit;
624  end;
625
626  //DebugLn('TToolButton.SetDown ',Style=tbsCheck,',',FDown,',',GroupAllUpAllowed);
627  if (Style = tbsCheck) and FDown and (not GroupAllUpAllowed) then
628    Exit;
629
630  FDown := Value;
631
632  if (Style = tbsCheck) and FDown and Grouped then
633  begin
634    // uncheck all other in the group
635    GetGroupBounds(StartIndex, EndIndex);
636    if StartIndex >= 0 then
637    begin
638      for i := StartIndex to EndIndex do
639      begin
640        CurButton := FToolBar.Buttons[i];
641        if (CurButton <> Self) and (CurButton.FDown) then
642        begin
643          CurButton.FDown := False;
644          CurButton.Invalidate;
645        end;
646      end;
647    end;
648  end;
649
650  Invalidate;
651  if Assigned(FToolBar) then
652    FToolBar.ToolButtonDown(Self, FDown);
653end;
654
655procedure TToolButton.SetDropdownMenu(Value: TPopupMenu);
656begin
657  if Value = FDropdownMenu then exit;
658  FDropdownMenu := Value;
659  if Assigned(Value) then
660    Value.FreeNotification(Self);
661end;
662
663procedure TToolButton.SetGrouped(Value: Boolean);
664var
665  StartIndex, EndIndex: integer;
666  CheckedIndex: Integer;
667  i: Integer;
668  CurButton: TToolButton;
669begin
670  if FGrouped = Value then exit;
671  FGrouped := Value;
672  if csLoading in ComponentState then exit;
673
674  // make sure, that only one button in a group is checked
675  while FGrouped and (Style = tbsCheck) and Assigned(FToolBar) do
676  begin
677    GetGroupBounds(StartIndex, EndIndex);
678    if StartIndex >= 0 then
679    begin
680      CheckedIndex := -1;
681      i := StartIndex;
682      while i <= EndIndex do
683      begin
684        CurButton := FToolBar.Buttons[i];
685        if CurButton.Down then
686        begin
687          if CheckedIndex < 0 then
688            CheckedIndex := i
689          else
690          begin
691            CurButton.Down := False;
692            // the last operation can change everything -> restart
693            break;
694          end;
695        end;
696        inc(i);
697      end;
698      if i > EndIndex then break;
699    end;
700  end;
701end;
702
703procedure TToolButton.SetImageIndex(Value: TImageIndex);
704begin
705  if FImageIndex = Value then exit;
706  FImageIndex := Value;
707  if IsControlVisible and Assigned(FToolBar) then
708    Invalidate;
709end;
710
711procedure TToolButton.SetMarked(Value: Boolean);
712begin
713  if FMarked = Value then exit;
714  FMarked := Value;
715  if FToolBar <> nil then
716    Invalidate;
717end;
718
719procedure TToolButton.SetIndeterminate(Value: Boolean);
720begin
721  if FIndeterminate = Value then exit;
722  if Value then SetDown(False);
723  FIndeterminate := Value;
724  if FToolBar <> nil then
725    Invalidate;
726end;
727
728procedure TToolButton.SetMenuItem(Value: TMenuItem);
729begin
730  if Value = FMenuItem then Exit;
731  // copy values from menuitem
732  // is menuitem is still loading, skip this
733  if Assigned(Value) and not (csLoading in Value.ComponentState) then
734    CopyPropertiesFromMenuItem(Value);
735  FMenuItem := Value;
736  if FMenuItem <> nil then
737    FMenuItem.FreeNotification(Self);
738end;
739
740procedure TToolButton.SetShowCaption(const AValue: boolean);
741begin
742  if FShowCaption=AValue then exit;
743  FShowCaption:=AValue;
744  if IsControlVisible then
745  begin
746    InvalidatePreferredSize;
747    UpdateVisibleToolbar;
748  end;
749end;
750
751procedure TToolButton.SetStyle(Value: TToolButtonStyle);
752begin
753  if FStyle = Value then exit;
754  FStyle := Value;
755  case Value of
756    tbsSeparator: begin
757      Width := cDefSeparatorWidth;
758      Height := cDefSeparatorWidth;
759    end;
760    tbsDivider: begin
761      Width := cDefDividerWidth;
762      Height := cDefDividerWidth;
763    end;
764  end;
765  InvalidatePreferredSize;
766  if IsControlVisible then
767    UpdateVisibleToolbar;
768end;
769
770procedure TToolButton.SetWrap(Value: Boolean);
771begin
772  if FWrap = Value then exit;
773  FWrap := Value;
774  if Assigned(FToolBar) then
775    RefreshControl;
776end;
777
778procedure TToolButton.TextChanged;
779begin
780  inherited TextChanged;
781  if FToolbar = nil then Exit;
782  if FToolbar.ShowCaptions then
783    Invalidate;
784end;
785
786procedure TToolButton.SetMouseInControl(NewMouseInControl: Boolean);
787begin
788  //DebugLn('TToolButton.SetMouseInControl A ',Name,' Old=',FMouseInControl,' New=',NewMouseInControl);
789  if FMouseInControl = NewMouseInControl then exit;
790  FMouseInControl := NewMouseInControl;
791  //DebugLn('TToolButton.SetMouseInControl B ',Name,' Now=',FMouseInControl,' Down=',Down);
792  Invalidate;
793end;
794
795procedure TToolButton.CMEnabledChanged(var Message: TLMEssage);
796begin
797  inherited;
798  invalidate;
799end;
800
801procedure TToolButton.CMVisibleChanged(var Message: TLMessage);
802begin
803  if FToolBar <> nil then
804    RefreshControl;
805end;
806
807procedure TToolButton.BeginUpdate;
808begin
809  Inc(FUpdateCount);
810end;
811
812procedure TToolButton.EndUpdate;
813begin
814  Dec(FUpdateCount);
815end;
816
817{------------------------------------------------------------------------------
818  procedure TToolButton.GetGroupBounds(var StartIndex, EndIndex: integer);
819
820  Return the index of the first and the last ToolButton in the group.
821  If no ToolBar then negative values are returned.
822  If not in a group then StartIndex=EndIndex.
823------------------------------------------------------------------------------}
824procedure TToolButton.GetGroupBounds(var StartIndex, EndIndex: integer);
825var
826  CurButton: TToolButton;
827begin
828  StartIndex := Index;
829  EndIndex := StartIndex;
830  if (Style <> tbsCheck) or (not Grouped) then exit;
831  while (StartIndex>0) do
832  begin
833    CurButton:=FToolBar.Buttons[StartIndex-1];
834    if (CurButton<>nil) and CurButton.Grouped and (CurButton.Style in [tbsCheck, tbsSeparator, tbsDivider]) then
835      dec(StartIndex)
836    else
837      break;
838  end;
839  while (EndIndex < FToolBar.FButtons.Count-1) do
840  begin
841    CurButton := FToolBar.Buttons[EndIndex+1];
842    if Assigned(CurButton) and CurButton.Grouped and (CurButton.Style in [tbsCheck, tbsSeparator, tbsDivider]) then
843      inc(EndIndex)
844    else
845      break;
846  end;
847end;
848
849function TToolButton.GetIndex: Integer;
850begin
851  if Assigned(FToolBar) then
852    Result := FToolBar.FButtons.IndexOf(Self)
853  else
854    Result := -1;
855end;
856
857function TToolButton.GetTextSize: TSize;
858var
859  S: String;
860begin
861  S := Caption;
862  DeleteAmpersands(S);
863  Result := Canvas.TextExtent(S)
864end;
865
866procedure TToolButton.GetPreferredSize(
867  var PreferredWidth, PreferredHeight: integer; Raw: boolean;
868  WithThemeSpace: boolean);
869var
870  RealButtonWidth, RealButtonHeight: Integer;
871begin
872  inherited GetPreferredSize(PreferredWidth, PreferredHeight, Raw, WithThemeSpace);
873
874  if FToolbar = nil then Exit;
875  RealButtonWidth := FToolbar.ButtonWidth;
876  RealButtonHeight := FToolbar.ButtonHeight;
877  if RealButtonHeight <= 0 then Exit;
878  // buttonheight overrules in hor toolbar
879  if FToolBar.IsVertical then
880    PreferredWidth := RealButtonWidth
881  else
882    PreferredHeight := RealButtonHeight;
883end;
884
885function TToolButton.IsWidthStored: Boolean;
886begin
887  Result := Style in [tbsSeparator, tbsDivider];
888  if FToolBar<>nil then
889    Result := Result and FToolBar.IsVertical;
890end;
891
892procedure TToolButton.RefreshControl;
893begin
894  UpdateControl;
895end;
896
897procedure TToolButton.UpdateControl;
898begin
899  UpdateVisibleToolbar;
900end;
901
902function TToolButton.CheckMenuDropdown: Boolean;
903begin
904  Result := (not (csDesigning in ComponentState)) and
905    ((Assigned(DropdownMenu) and (DropdownMenu.AutoPopup)) or Assigned(MenuItem)) and Assigned(FToolBar);
906  if Result then
907  begin
908    Include(FToolButtonFlags, tbfDropDownMenuShown);
909    try
910      Result := FToolBar.CheckMenuDropdown(Self);
911    finally
912      Exclude(FToolButtonFlags, tbfDropDownMenuShown);
913    end;
914  end;
915end;
916
917procedure TToolButton.Click;
918begin
919  inherited Click;
920end;
921
922procedure TToolButton.GetCurrentIcon(var ImageList: TCustomImageList;
923  var TheIndex: integer; var TheEffect: TGraphicsDrawEffect);
924var
925  UseAutoEffects: Integer;
926begin
927  ImageList := nil;
928  TheIndex := -1;
929  TheEffect := gdeNormal;
930  UseAutoEffects := ThemeServices.GetOption(toUseGlyphEffects);
931  if (ImageIndex < 0) or (FToolBar = nil) then Exit;
932
933  if Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck] then
934  begin
935    TheIndex := ImageIndex;
936    ImageList := FToolBar.Images;
937    if (FToolButtonFlags*[tbfPressed,tbfArrowPressed] = [tbfPressed]) then
938    begin
939      // if button pressed then use PressedImages // Maybe To-Do ?
940      {if (FToolBar.PressedImages <> nil) and (ImageIndex < FToolBar.PressedImages.Count) then
941        ImageList := FToolBar.DisabledImages
942      else} if UseAutoEffects > 0 then
943        TheEffect := gdeShadowed;
944    end else
945    if Enabled and FMouseInControl then
946    begin
947      // if mouse over button then use HotImages
948      if (FToolBar.HotImages <> nil) and (ImageIndex < FToolBar.HotImages.Count) then
949        ImageList := FToolBar.HotImages
950      else if UseAutoEffects > 0 then
951        TheEffect := gdeHighlighted;
952    end else
953    if not Enabled then
954    begin
955      // if button disabled then use DisabledImages
956      if (FToolBar.DisabledImages <> nil) and (ImageIndex < FToolBar.DisabledImages.Count) then
957        ImageList := FToolBar.DisabledImages
958      else
959        TheEffect := gdeDisabled;
960    end;
961  end;
962end;
963
964function TToolButton.IsCheckedStored: Boolean;
965begin
966  Result := (ActionLink = nil) or not TToolButtonActionLink(ActionLink).IsCheckedLinked;
967end;
968
969function TToolButton.IsHeightStored: Boolean;
970begin
971  Result := Style in [tbsSeparator, tbsDivider];
972  if FToolBar<>nil then
973    Result := Result and not FToolBar.IsVertical;
974end;
975
976function TToolButton.IsImageIndexStored: Boolean;
977begin
978  Result := (ActionLink = nil) or not TToolButtonActionLink(ActionLink).IsImageIndexLinked;
979end;
980
981procedure TToolButton.AssignTo(Dest: TPersistent);
982begin
983  inherited AssignTo(Dest);
984  if Dest is TCustomAction then
985  begin
986    TCustomAction(Dest).Checked := Down;
987    TCustomAction(Dest).ImageIndex := ImageIndex;
988  end;
989end;
990
991function TToolButton.GetButtonDrawDetail: TThemedElementDetails;
992var
993  ToolDetail: TThemedToolBar;
994begin
995  if Style = tbsDropDown then
996    ToolDetail := ttbSplitButtonNormal
997  else
998  if Style in [tbsDivider, tbsSeparator] then
999    if FToolBar.IsVertical then
1000      ToolDetail := ttbSeparatorVertNormal
1001    else
1002      ToolDetail := ttbSeparatorNormal
1003  else
1004    ToolDetail := ttbButtonNormal;
1005
1006  if not Enabled then
1007    inc(ToolDetail, 3) // ttbButtonDisabled
1008  else
1009  begin
1010    if Down then
1011    begin // checked states
1012      if (tbfPressed in FToolButtonFlags) and FMouseInControl then
1013        inc(ToolDetail, 2) // ttbButtonPressed
1014      else if FMouseInControl then
1015        inc(ToolDetail, 5) // ttbButtonCheckedHot
1016      else
1017        inc(ToolDetail, 4);// ttbButtonChecked
1018    end
1019    else
1020    begin
1021      if (tbfPressed in FToolButtonFlags) and FMouseInControl then
1022        inc(ToolDetail, 2) // ttbButtonPressed
1023      else if FMouseInControl then
1024        inc(ToolDetail, 1);// ttbButtonHot
1025    end;
1026  end;
1027  Result := ThemeServices.GetElementDetails(ToolDetail);
1028end;
1029
1030procedure TToolButton.SetParent(AParent: TWinControl);
1031var
1032  i: Integer;
1033  NewWidth: Integer;
1034  NewHeight: Integer;
1035begin
1036  CheckNewParent(AParent);
1037  if AParent=Parent then exit;
1038
1039  // remove from old button list
1040  if Assigned(FToolBar) then
1041    FToolBar.RemoveButton(Self);
1042  FToolBar := nil;
1043  if AParent is TToolBar then
1044  begin
1045    if not TToolBar(AParent).IsVertical then begin
1046      if Style in [tbsButton,tbsDropDown,tbsButtonDrop,tbsCheck] then
1047        NewWidth := TToolBar(AParent).ButtonWidth
1048      else
1049        NewWidth := Width;
1050      NewHeight := TToolBar(AParent).ButtonHeight;
1051    end else begin
1052      if Style in [tbsButton,tbsDropDown,tbsButtonDrop,tbsCheck] then
1053        NewHeight := TToolBar(AParent).ButtonHeight
1054      else
1055        NewHeight := Height;
1056      NewWidth := TToolBar(AParent).ButtonWidth;
1057    end;
1058    SetBoundsKeepBase(Left, Top, NewWidth, NewHeight);
1059  end;
1060
1061  // inherited
1062  inherited SetParent(AParent);
1063
1064  // add to new button list
1065  if Parent is TToolBar then
1066  begin
1067    FToolBar := TToolBar(Parent);
1068    i := Index;
1069    if i < 0 then
1070      FToolBar.AddButton(Self);
1071    UpdateVisibleToolbar;
1072  end;
1073  //DebugLn(['TToolButton.SetParent A ',Name,' NewIndex=',Index]);
1074end;
1075
1076procedure TToolButton.UpdateVisibleToolbar;
1077begin
1078  //DebugLn('TToolButton.UpdateVisibleToolbar ',Parent is TToolBar);
1079  if Parent is TToolBar then
1080    TToolBar(Parent).UpdateVisibleBar;
1081end;
1082
1083function TToolButton.GroupAllUpAllowed: boolean;
1084var
1085  StartIndex, EndIndex: integer;
1086  i: Integer;
1087  CurButton: TToolButton;
1088begin
1089  Result := True;
1090  if (Style = tbsCheck) and Grouped then
1091  begin
1092    GetGroupBounds(StartIndex, EndIndex);
1093    if (StartIndex >= 0) then
1094    begin
1095      // allow all up, if one button has AllowAllUp
1096      Result := False;
1097      for i := StartIndex to EndIndex do
1098      begin
1099        CurButton := FToolBar.Buttons[i];
1100        if CurButton.AllowAllUp then
1101        begin
1102          Result := True;
1103          break;
1104        end;
1105      end;
1106    end;
1107  end;
1108end;
1109
1110function TToolButton.DialogChar(var Message: TLMKey): boolean;
1111begin
1112  if IsAccel(Message.CharCode, Caption) and FToolBar.ShowCaptions then
1113  begin
1114    Click;
1115    Result := true;
1116  end else
1117    Result := inherited;
1118end;
1119
1120procedure TToolButton.CalculatePreferredSize(var PreferredWidth,
1121  PreferredHeight: integer; WithThemeSpace: Boolean);
1122var
1123  IconSize: TSize;
1124  TextSize: TSize;
1125  TextPos: TPoint;
1126  IconPos: TPoint;
1127  ImgList: TCustomImageList;
1128  ImgIndex: integer;
1129  ImgEffect: TGraphicsDrawEffect;
1130begin
1131  if Assigned(FToolBar) then
1132  begin
1133    PreferredWidth := 0;
1134    PreferredHeight := 0;
1135
1136    // calculate text size
1137    TextSize.cx := 0;
1138    TextSize.cy := 0;
1139    if (Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck]) and (FToolBar.ShowCaptions) and
1140       //Allow hide caption only in list mode
1141       ((FToolBar.List and ShowCaption) or not FToolBar.List) then
1142    begin
1143      if (Caption<>'') then
1144      begin
1145        if FToolBar.HandleAllocated then
1146          TextSize := GetTextSize;
1147      end;
1148      // add space around text
1149      inc(TextSize.cx, 4);
1150      inc(TextSize.cy, 4);
1151    end;
1152
1153    // calculate icon size
1154    IconSize := Size(0, 0);
1155    if (Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck]) then
1156    begin
1157      GetCurrentIcon(ImgList, ImgIndex, ImgEffect);
1158      if Assigned(ImgList) then
1159      begin
1160        IconSize := ImgList.SizeForPPI[FToolBar.ImagesWidth, FToolBar.Font.PixelsPerInch];
1161        if IconSize.cy <= 0 then IconSize.cx := 0;
1162      end;
1163    end;
1164    // calculate text and icon position
1165    TextPos := Point(0, 0);
1166    IconPos := Point(0, 0);
1167    if TextSize.cx > 0 then
1168    begin
1169      if IconSize.cx > 0 then
1170      begin
1171        if FToolBar.List then
1172        begin
1173          // icon left of text
1174          TextPos.X := IconPos.X + IconSize.cx + 2;
1175        end
1176        else
1177        begin
1178          // icon above text
1179          TextPos.Y := IconPos.Y + IconSize.cy + 2;
1180        end;
1181      end
1182      else
1183      begin
1184        // only text
1185      end;
1186    end
1187    else
1188    if IconSize.cx > 0 then
1189    begin
1190      // only icon
1191    end;
1192
1193    PreferredWidth := Max(IconPos.X + IconSize.cx, TextPos.X + TextSize.cx);
1194    PreferredHeight := Max(IconPos.Y + IconSize.cy, TextPos.Y + TextSize.cy);
1195    //DebugLn(['TToolButton.CalculatePreferredSize Preferred=',PreferredWidth,',',PreferredHeight,' Icon=',IconPos.X,'+',IconSize.Width,' Text=',TextPos.X,'+',TextSize.cx]);
1196    //DebugLn(['TToolButton.CalculatePreferredSize Preferred=',PreferredWidth,',',PreferredHeight,' Icon=',IconPos.Y,'+',IconSize.Height,' Text=',TextPos.Y,'+',TextSize.cy]);
1197
1198    // add button frame
1199    if (Style in [tbsButton, tbsDropDown, tbsButtonDrop, tbsCheck]) then
1200    begin
1201      inc(PreferredWidth, 4);
1202      inc(PreferredHeight, 4);
1203      PreferredWidth := Max(PreferredWidth, FToolBar.ButtonWidth);
1204      PreferredHeight := Max(PreferredHeight, FToolBar.ButtonHeight);
1205      case Style of
1206        tbsDropDown: inc(PreferredWidth, FToolBar.DropDownWidth);
1207        tbsButtonDrop:  inc(PreferredWidth, FToolBar.ButtonDropWidth-cDefButtonDropDecArrowWidth);
1208      end;
1209    end
1210    else
1211    if Style = tbsDivider then
1212      if FToolBar.IsVertical then
1213        PreferredHeight := cDefDividerWidth
1214      else
1215        PreferredWidth := cDefDividerWidth
1216    else
1217    if Style = tbsSeparator then
1218      if FToolBar.IsVertical then
1219        PreferredHeight := cDefSeparatorWidth
1220      else
1221        PreferredWidth := cDefSeparatorWidth;
1222  end;
1223  //DebugLn(['TToolButton.CalculatePreferredSize ',DbgSName(Self),' ',PreferredWidth,',',PreferredHeight,' Caption=',Caption]);
1224end;
1225
1226class function TToolButton.GetControlClassDefaultSize: TSize;
1227begin
1228  Result.CX := 23;
1229  Result.CY := 22;
1230end;
1231
1232
1233// included by comctrls.pp
1234
1235