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