lazarus/lcl/include/toolbar.inc

1363 lines
40 KiB
PHP

{%MainUnit ../comctrls.pp}
{******************************************************************************
TToolbar
******************************************************************************
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
type
TToolBarSpeedButton = class(TSpeedButton)
private
FMenuItemIndex: integer;
end;
{ TToolBarMenuItem }
TToolBarMenuItem = class(TMenuItem)
private
FOrigItem: TMenuItem;
protected
procedure DoClicked(var msg); message LM_ACTIVATE;
end;
{ TToolBarMenuItem }
procedure TToolBarMenuItem.DoClicked(var msg);
begin
if FOrigItem <> nil then
FOrigItem.Dispatch(msg);
end;
function CompareToolBarControlHorz(Control1, Control2: TControl): integer;
var
ToolBar: TToolBar;
Row1: Integer;
Row2: Integer;
HalfBtnHeight, BtnHeight: Integer;
begin
Result := 0;
if not (Control1.Parent is TToolBar) then Exit;
if Control1 is TToolBarSpeedButton then begin
if Control2 is TToolBarSpeedButton then
exit(TToolBarSpeedButton(Control1).FMenuItemIndex - TToolBarSpeedButton(Control2).FMenuItemIndex)
else
exit(-1);
end
else
if Control2 is TToolBarSpeedButton then
exit(1);
ToolBar := TToolBar(Control1.Parent);
BtnHeight := ToolBar.ButtonHeight;
if BtnHeight <= 0 then BtnHeight := 1;
HalfBtnHeight := BtnHeight div 2;
Row1 := (Control1.Top + HalfBtnHeight) div BtnHeight;
Row2 := (Control2.Top + HalfBtnHeight) div BtnHeight;
Result := CompareValue(Row1, Row2);
if Result = 0 then
begin
Result := CompareValue(Control1.Left, Control2.Left);
if ToolBar.UseRightToLeftAlignment then
Result:=-Result;
end;
if Result = 0 then
begin
Row1 := ToolBar.GetControlIndex(Control1);
Row2 := ToolBar.GetControlIndex(Control2);
Result := CompareValue(Row1, Row2);
end;
end;
function CompareToolBarControlVert(Control1, Control2: TControl): integer;
var
ToolBar: TToolBar;
Col1: Integer;
Col2: Integer;
HalfBtnWidth, BtnWidth: Integer;
begin
Result := 0;
if not (Control1.Parent is TToolBar) then Exit;
if Control1 is TToolBarSpeedButton then begin
if Control2 is TToolBarSpeedButton then
exit(TToolBarSpeedButton(Control1).FMenuItemIndex - TToolBarSpeedButton(Control2).FMenuItemIndex)
else
exit(-1);
end
else
if Control2 is TToolBarSpeedButton then
exit(1);
ToolBar := TToolBar(Control1.Parent);
BtnWidth := ToolBar.ButtonWidth;
if BtnWidth <= 0 then BtnWidth := 1;
HalfBtnWidth := BtnWidth div 2;
Col1 := (Control1.Left + HalfBtnWidth) div BtnWidth;
Col2 := (Control2.Left + HalfBtnWidth) div BtnWidth;
Result := CompareValue(Col1, Col2);
if Result = 0 then
Result := CompareValue(Control1.Top, Control2.Top);
if Result = 0 then
begin
Col1 := ToolBar.GetControlIndex(Control1);
Col2 := ToolBar.GetControlIndex(Control2);
Result := CompareValue(Col1, Col2);
end;
end;
{------------------------------------------------------------------------------
Method: TToolbar.Create
Params: AOwner: the owner of the class
Returns: Nothing
Constructor for the class.
------------------------------------------------------------------------------}
constructor TToolBar.Create(TheOwner: TComponent);
var
Details: TThemedElementDetails;
begin
inherited Create(TheOwner);
ControlStyle := [csAcceptsControls, csCaptureMouse, csClickEvents,
csDoubleClicks, csMenuEvents, csSetCaption, csParentBackground, csOpaque];
FFlat := True;
Height := 32;
// ToDo: Test the scaling code. Widths are scaled in many places.
Details := ThemeServices.GetElementDetails(ttbSplitButtonDropDownNormal);
FThemeDropDownWidth := ThemeServices.GetDetailSize(Details).cx;
Details := ThemeServices.GetElementDetails(ttbDropDownButtonNormal);
FThemeButtonDropWidth := ThemeServices.GetDetailSize(Details).cx;
FButtonHeight := -1;
FButtonWidth := -1;
FDropDownWidth := -1;
FNewStyle := True;
FWrapable := True;
FButtons := TList.Create;
FMenuButtons := TList.Create;
FSubMenuItems := TList.Create;
FSubMenu := TPopupMenu.Create(Self);
FMenuObserver := TTMPObserver.Create;
FMenuObserver.DoOnChange := @UpdateMenuItem;
FMenuObserver.DoOnFree := @RemoveMenu;
FIndent := 1;
FList := False;
FImageChangeLink := TChangeLink.Create;
FImageChangeLink.OnChange := @ImageListChange;
FDisabledImageChangeLink := TChangeLink.Create;
FDisabledImageChangeLink.OnChange := @DisabledImageListChange;
FHotImageChangeLink := TChangeLink.Create;
FHotImageChangeLink.OnChange := @HotImageListChange;
EdgeBorders := [ebTop];
with GetControlClassDefaultSize do
SetInitialBounds(0, 0, CX, CY);
Align := alTop;
end;
destructor TToolBar.Destroy;
var
I: Integer;
begin
for I := 0 to FButtons.Count - 1 do
if TControl(FButtons[I]) is TToolButton then
TToolButton(FButtons[I]).FToolBar := nil;
for I := 0 to FMenuButtons.Count - 1 do
if TControl(FMenuButtons[I]) is TToolBarSpeedButton then
TToolBarSpeedButton(FMenuButtons[I]).Free;
if Assigned(FMenu) then FMenu.FPODetachObserver(FMenuObserver);
FreeThenNil(FButtons);
FreeThenNil(FMenuButtons);
FreeAndNil(FSubMenu);
FreeThenNil(FSubMenuItems);
FreeAndNil(FMenuObserver);
FreeThenNil(FHotImageChangeLink);
FreeThenNil(FImageChangeLink);
FreeThenNil(FDisabledImageChangeLink);
inherited Destroy;
end;
procedure TToolBar.FlipChildren(AllLevels: Boolean);
begin
if AllLevels then ;
// no flipping
end;
procedure TToolBar.CreateWnd;
begin
BeginUpdate;
try
DisableAutoSizing{$IFDEF DebugDisableAutoSizing}('TToolBar.CreateWnd'){$ENDIF};
try
inherited CreateWnd;
UpdateVisibleBar;
finally
EnableAutoSizing{$IFDEF DebugDisableAutoSizing}('TToolBar.CreateWnd'){$ENDIF};
end;
finally
EndUpdate;
end;
end;
procedure TToolBar.AlignControls(AControl: TControl;
var RemainingClientRect: TRect);
var
NewWidth, NewHeight: integer;
begin
if tbfPlacingControls in FToolBarFlags then exit;
Include(FToolBarFlags, tbfPlacingControls);
DisableAlign;
try
AdjustClientRect(RemainingClientRect);
if IsVertical then
WrapButtons(Height, NewWidth, NewHeight, False)
else
WrapButtons(Width, NewWidth, NewHeight, False);
finally
Exclude(FToolBarFlags, tbfPlacingControls);
EnableAlign;
end;
end;
procedure TToolBar.RepositionButton(Index: Integer);
begin
if ([csLoading,csDestroying]*ComponentState<>[]) then exit;
UpdateVisibleBar;
end;
procedure TToolBar.RepositionButtons(Index: Integer);
begin
UpdateVisibleBar;
end;
procedure TToolBar.SetButtonHeight(const AValue: Integer);
begin
SetButtonSize(ButtonWidth,AValue);
end;
procedure TToolBar.SetButtonWidth(const AValue: Integer);
begin
SetButtonSize(AValue,ButtonHeight);
end;
procedure TToolBar.ImageListChange(Sender: TObject);
begin
if (Sender = Images) then UpdateVisibleBar;
end;
procedure TToolBar.SetShowCaptions(const AValue: Boolean);
begin
if FShowCaptions = AValue then exit;
FShowCaptions := AValue;
UpdateVisibleBar;
end;
procedure TToolBar.CloseCurrentMenu;
begin
FCurrentMenu.Close;
// move menu items back
if Assigned(FSrcMenuItem) then
begin
MoveSubMenuItems(FCurrentMenu.Items, FSrcMenuItem);
if Assigned(FDropDownButton) then
FDropDownButton.Down := False;
end;
end;
procedure TToolBar.MoveSubMenuItems(SrcMenuItem, DestMenuItem: TMenuItem);
var
i: Integer;
MovingMenuItem: TMenuItem;
begin
if (SrcMenuItem = nil) or (DestMenuItem = nil) or (SrcMenuItem = DestMenuItem) then
Exit;
for i := SrcMenuItem.Count - 1 downto 0 do
begin
MovingMenuItem := SrcMenuItem.Items[i];
SrcMenuItem.Delete(i);
DestMenuItem.Insert(0, MovingMenuItem);
end;
end;
procedure TToolBar.AddButton(Button: TToolButton);
begin
FButtons.Add(Button);
end;
procedure TToolBar.RemoveButton(Button: TToolButton);
begin
if FDropDownButton=Button then FDropDownButton:=nil;
FButtons.Remove(Button);
end;
procedure TToolBar.UpdateMenuItem(Sender: TObject);
var
i, xpos, wglyph: Integer;
tmpMenuButton: TToolBarSpeedButton;
begin
xpos := 0;
while (FMenuButtons.Count > 0) do
begin
i := FMenuButtons.Count - 1;
tmpMenuButton := TToolBarSpeedButton(FMenuButtons.Items[i]);
FMenuButtons.Delete(i);
FreeAndNil(tmpMenuButton);
end;
if not Assigned(FMenu) then Exit;
for i := 0 to FMenu.Items.Count-1 do
if FMenu.Items[i].Visible then
begin
tmpMenuButton := TToolBarSpeedButton.Create(Self);
tmpMenuButton.AutoSize := False;
tmpMenuButton.Align := alNone;
tmpMenuButton.Alignment := taCenter;
tmpMenuButton.Height := FMenu.Height;
tmpMenuButton.Left := xpos;
tmpMenuButton.Flat := True;
tmpMenuButton.ParentFont := True;
tmpMenuButton.Color := Self.Color;
tmpMenuButton.ShowCaption := True;
tmpMenuButton.Caption := FMenu.Items[i].Caption;
tmpMenuButton.Enabled := FMenu.Items[i].Enabled;
tmpMenuButton.Visible := FMenu.Items[i].Visible;
tmpMenuButton.Margin := -1;
tmpMenuButton.Spacing := 7;
tmpMenuButton.FMenuItemIndex:= i;
wglyph := 0;
tmpMenuButton.Images := nil;
if Assigned(FMenu.Images) and (FMenu.Items[i].ImageIndex >= 0) then begin
if FMenu.Items[i].ImageIndex < FMenu.Images.Count then
begin
FMenu.Images.GetBitmap(FMenu.Items[i].ImageIndex, tmpMenuButton.Glyph);
wglyph := tmpMenuButton.Glyph.Width + 5;
end;
end
else
if Assigned(FMenu.Items[i].Bitmap) then begin
tmpMenuButton.Glyph := FMenu.Items[i].Bitmap;
wglyph := tmpMenuButton.Glyph.Width + 5;
end;
tmpMenuButton.OnClick := @MenuButtonClick;
tmpMenuButton.Parent := Self;
if FMenu.Items[i].Default then tmpMenuButton.Font.Style := [fsBold];
tmpMenuButton.Width := Self.Canvas.TextWidth(FMenu.Items[i].Caption) + wglyph + 14;
tmpMenuButton.Constraints.MinWidth := tmpMenuButton.Width;
tmpMenuButton.Constraints.MaxWidth := tmpMenuButton.Width;
tmpMenuButton.Update;
Inc(xpos, tmpMenuButton.Width);
FMenuButtons.Add(tmpMenuButton);
end;
end;
procedure TToolBar.RemoveMenu(Sender: TObject);
begin
FMenu.FPODetachObserver(FMenuObserver);
FMenu := nil;
end;
procedure TToolBar.MenuButtonClick(Sender: TObject);
var
MenuButton: TToolBarSpeedButton;
SubMenuPos: TPoint;
tmpMI: TMenuItem;
function NewItem(const ACaption: string; AShortCut: TShortCut; AChecked,
TheEnabled: Boolean; hCtx: THelpContext;
const AName: string): TMenuItem;
begin
Result:=TToolBarMenuItem.Create(nil);
with Result do begin
Caption:=ACaption;
ShortCut:=AShortCut;
HelpContext:=hCtx;
Checked:=AChecked;
Enabled:=TheEnabled;
Name:=AName;
end;
end;
procedure PrepSubMenuItem(SubMenuItemIn, SubMenuItemOut: TMenuItem);
var
i: Integer;
begin
for i := 0 to SubMenuItemIn.Count - 1 do
begin
SubMenuItemOut.Add(NewItem(SubMenuItemIn.Items[i].Caption,
SubMenuItemIn.Items[i].ShortCut,
SubMenuItemIn.Items[i].Checked,
SubMenuItemIn.Items[i].Visible,
SubMenuItemIn.Items[i].HelpContext,
''));
SubMenuItemOut.Items[i].AutoCheck := SubMenuItemIn.Items[i].AutoCheck;
SubMenuItemOut.Items[i].AutoLineReduction := SubMenuItemIn.Items[i].AutoLineReduction;
SubMenuItemOut.Items[i].Default := SubMenuItemIn.Items[i].Default;
SubMenuItemOut.Items[i].GlyphShowMode := SubMenuItemIn.Items[i].GlyphShowMode;
SubMenuItemOut.Items[i].GroupIndex := SubMenuItemIn.Items[i].GroupIndex;
SubMenuItemOut.Items[i].Hint := SubMenuItemIn.Items[i].Hint;
SubMenuItemOut.Items[i].Bitmap := SubMenuItemIn.Items[i].Bitmap;
SubMenuItemOut.Items[i].ImageIndex := SubMenuItemIn.Items[i].ImageIndex;
SubMenuItemOut.Items[i].RadioItem := SubMenuItemIn.Items[i].RadioItem;
SubMenuItemOut.Items[i].RightJustify := SubMenuItemIn.Items[i].RightJustify;
SubMenuItemOut.Items[i].ShortCutKey2 := SubMenuItemIn.Items[i].ShortCutKey2;
SubMenuItemOut.Items[i].ShowAlwaysCheckable := SubMenuItemIn.Items[i].ShowAlwaysCheckable;
SubMenuItemOut.Items[i].SubMenuImages := SubMenuItemIn.Items[i].SubMenuImages;
SubMenuItemOut.Items[i].SubMenuImagesWidth := SubMenuItemIn.Items[i].SubMenuImagesWidth;
TToolBarMenuItem(SubMenuItemOut.Items[i]).FOrigItem := SubMenuItemIn.Items[i];
if SubMenuItemIn.Items[i].Count > 0 then PrepSubMenuItem(SubMenuItemIn.Items[i], SubMenuItemOut.Items[i]);
end;
end;
begin
MenuButton := TToolBarSpeedButton(Sender);
tmpMI := FMenu.Items[MenuButton.FMenuItemIndex];
if not Assigned(tmpMI) then Exit;
tmpMI.Click;
FSubMenu.Items.Clear;
if tmpMI.Count = 0 then Exit;
FSubMenu.Images := tmpMI.GetImageList;
PrepSubMenuItem(tmpMI, FSubMenu.Items);
SubMenuPos := ClientToScreen(TPoint.Create(MenuButton.Left, MenuButton.Top + MenuButton.Height));
FSubMenu.PopUp(SubMenuPos.X, SubMenuPos.Y);
end;
function TToolBar.IsVertical: Boolean;
begin
case FOrientation of
tboHorizontal: exit(False);
tboVertical: exit(True);
end;
if (Parent is TCoolBar) then
Exit(TCoolBar(Parent).Vertical);
if Align in [alNone, alClient, alCustom] then
Result := Height > Width
else
Result := Align in [alLeft, alRight];
end;
class procedure TToolBar.WSRegisterClass;
begin
inherited WSRegisterClass;
RegisterToolBar;
end;
procedure TToolBar.ApplyFontForButtons;
var
i: integer;
begin
for i := 0 to ButtonCount - 1 do
Buttons[i].Font := Font;
end;
function TToolBar.ButtonHeightIsStored: Boolean;
begin
Result := FButtonHeight >= 0;
end;
function TToolBar.ButtonWidthIsStored: Boolean;
begin
Result := FButtonWidth >= 0;
end;
function TToolBar.GetButton(Index: Integer): TToolButton;
begin
Result := TToolButton(FButtons[Index]);
end;
function TToolBar.GetButtonCount: Integer;
begin
Result := FButtons.Count;
end;
function TToolBar.GetTransparent: Boolean;
begin
Result := not (csOpaque in ControlStyle);
end;
procedure TToolBar.SetList(const AValue: Boolean);
begin
if FList = AValue then exit;
FList := AValue;
UpdateVisibleBar;
end;
procedure TToolBar.SetMenu(AValue: TMainMenu);
begin
if FMenu = AValue then Exit;
if Assigned(FMenu) then
begin
FMenu.FPODetachObserver(FMenuObserver);
end;
FMenu := AValue;
if Assigned(FMenu) then FMenu.FPOAttachObserver(FMenuObserver);
UpdateVisibleBar;
end;
procedure TToolBar.SetOrientation(AValue: TToolBarOrientation);
begin
if FOrientation = AValue then Exit;
FOrientation := AValue;
invalidate;
end;
procedure TToolBar.SetFlat(const AValue: Boolean);
begin
if FFlat = AValue then exit;
FFlat := AValue;
Invalidate;
end;
procedure TToolBar.SetTransparent(const AValue: Boolean);
begin
if GetTransparent = AValue then exit;
if AValue then
ControlStyle := ControlStyle - [csOpaque]
else
ControlStyle := ControlStyle + [csOpaque];
Invalidate;
end;
procedure TToolBar.SetWrapable(const AValue: Boolean);
begin
if FWrapable = AValue then exit;
FWrapable := AValue;
ReAlign;
end;
procedure TToolBar.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if Operation = opRemove then
begin
if AComponent = FMenu then Menu := nil;
if AComponent = FImages then Images := nil;
if AComponent = FHotImages then HotImages := nil;
if AComponent = FDisabledImages then DisabledImages := nil;
end;
end;
procedure TToolBar.SetImages(const AValue: TCustomImageList);
begin
if FImages = AValue then Exit;
if FImages <> nil then
FImages.UnRegisterChanges(FImageChangeLink);
FImages := AValue;
if FImages <> nil then
begin
FImages.RegisterChanges(FImageChangeLink);
FImages.FreeNotification(Self);
end;
UpdateVisibleBar;
end;
procedure TToolBar.SetImagesWidth(const aImagesWidth: Integer);
begin
if FImagesWidth = aImagesWidth then Exit;
FImagesWidth := aImagesWidth;
UpdateVisibleBar;
end;
procedure TToolBar.DisabledImageListChange(Sender: TObject);
begin
if (Sender = DisabledImages) then UpdateVisibleBar;
end;
procedure TToolBar.DoAutoAdjustLayout(const AMode: TLayoutAdjustmentPolicy;
const AXProportion, AYProportion: Double);
begin
inherited;
if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
begin
BeginUpdate;
try
if ButtonWidthIsStored then
ButtonWidth := Round(ButtonWidth * AXProportion);
if ButtonHeightIsStored then
ButtonHeight := Round(ButtonHeight * AYProportion);
if DropDownWidthIsStored then
DropDownWidth := Round(DropDownWidth * AXProportion);
FRealizedButtonHeight := 0;
FRealizedButtonWidth := 0;
FRealizedDropDownWidth := 0;
FRealizedButtonDropWidth := 0;
FToolBarFlags := FToolBarFlags + [tbfUpdateVisibleBarNeeded];
finally
EndUpdate;
end;
end;
end;
procedure TToolBar.SetDisabledImages(const AValue: TCustomImageList);
begin
if FDisabledImages = AValue then Exit;
if FDisabledImages <> nil then
FDisabledImages.UnRegisterChanges(FDisabledImageChangeLink);
FDisabledImages := AValue;
if FDisabledImages <> nil then
begin
FDisabledImages.RegisterChanges(FDisabledImageChangeLink);
FDisabledImages.FreeNotification(Self);
end;
UpdateVisibleBar;
end;
procedure TToolBar.SetDropDownWidth(const ADropDownWidth: Integer);
begin
if FDropDownWidth = ADropDownWidth then Exit;
FDropDownWidth := ADropDownWidth;
UpdateVisibleBar;
end;
procedure TToolBar.HotImageListChange(Sender: TObject);
begin
if (Sender = HotImages) then UpdateVisibleBar;
end;
procedure TToolBar.UpdateVisibleBar;
var
i: Integer;
begin
if ([csLoading,csDestroying]*ComponentState<>[]) or (not HandleAllocated) then
begin
Include(FToolBarFlags,tbfUpdateVisibleBarNeeded);
Exit;
end;
UpdateMenuItem(Self);
for i := 0 to FButtons.Count - 1 do
begin
TControl(FButtons[i]).InvalidatePreferredSize;
TControl(FButtons[i]).AdjustSize;
end;
AdjustSize;
Invalidate;
Exclude(FToolBarFlags,tbfUpdateVisibleBarNeeded);
end;
procedure TToolBar.SetHotImages(const AValue: TCustomImageList);
begin
if FHotImages = AValue then Exit;
if FHotImages <> nil then
FHotImages.UnRegisterChanges(FHotImageChangeLink);
FHotImages := AValue;
if FHotImages <> nil then
begin
FHotImages.RegisterChanges(FHotImageChangeLink);
FHotImages.FreeNotification(Self);
end;
UpdateVisibleBar;
end;
procedure TToolBar.SetIndent(const AValue: Integer);
begin
if FIndent = AValue then exit;
FIndent := AValue;
UpdateVisibleBar;
end;
procedure TToolBar.Loaded;
begin
inherited Loaded;
UpdateVisibleBar;
end;
procedure TToolBar.EndUpdate;
var
i: Integer;
tmpMenuItem: TMenuItem;
begin
inherited EndUpdate;
if FUpdateCount=0 then begin
if tbfUpdateVisibleBarNeeded in FToolBarFlags then
UpdateVisibleBar;
end;
end;
function TToolBar.GetEnumerator: TToolBarEnumerator;
begin
Result := TToolBarEnumerator.Create(Self);
end;
function TToolBar.GetDropDownWidth: Integer;
begin
if FDropDownWidth < 0 then
begin
if FRealizedDropDownWidth = 0 then
FRealizedDropDownWidth := ScaleScreenToFont(FThemeDropDownWidth);
Result := FRealizedDropDownWidth;
end else
Result := FDropDownWidth;
end;
function TToolBar.GetButtonDropWidth: Integer;
begin
if FDropDownWidth < 0 then
begin
if FRealizedButtonDropWidth = 0 then
FRealizedButtonDropWidth := ScaleScreenToFont(FThemeButtonDropWidth);
Result := FRealizedButtonDropWidth;
end else
Result := FDropDownWidth+FThemeButtonDropWidth-FThemeDropDownWidth;
end;
function TToolBar.GetButtonHeight: Integer;
begin
if FButtonHeight < 0 then
begin
if FRealizedButtonHeight = 0 then
FRealizedButtonHeight := Scale96ToFont(cDefButtonHeight);
Result := FRealizedButtonHeight;
end else
Result := FButtonHeight;
end;
function TToolBar.GetButtonWidth: Integer;
begin
if FButtonWidth < 0 then
begin
if FRealizedButtonWidth = 0 then
FRealizedButtonWidth := Scale96ToFont(cDefButtonWidth);
Result := FRealizedButtonWidth;
end else
Result := FButtonWidth;
end;
procedure TToolBar.Paint;
begin
if csDesigning in ComponentState then
begin
Canvas.Pen.Color:=clRed;
Canvas.FrameRect(Clientrect);
end;
inherited Paint;
if Assigned(OnPaint) then
OnPaint(Self);
end;
procedure TToolBar.SetButtonSize(NewButtonWidth, NewButtonHeight: integer);
var
CurControl: TControl;
NewWidth: Integer;
NewHeight: Integer;
i, RealButtonWidth, RealButtonHeight: Integer;
ChangeW, ChangeH: Boolean;
begin
ChangeW := FButtonWidth <> NewButtonWidth;
ChangeH := FButtonHeight <> NewButtonHeight;
if not (ChangeW or ChangeH) then Exit;
FButtonWidth:=NewButtonWidth;
FButtonHeight:=NewButtonHeight;
RealButtonWidth := ButtonWidth;
RealButtonHeight := ButtonHeight;
if FUpdateCount > 0 then Exit;
if [csLoading, csDestroying] * ComponentState <> [] then Exit;
// set all children to ButtonWidth ButtonHeight
BeginUpdate;
try
for i:=ControlCount-1 downto 0 do
begin
CurControl := Controls[i];
CurControl.InvalidatePreferredSize;
NewWidth := CurControl.Width;
NewHeight := CurControl.Height;
// width
if ChangeW
and (RealButtonWidth > 0)
and not CurControl.AutoSize
and (CurControl is TToolButton)
and (CurControl.Align in [alNone, alLeft, alRight])
then begin
if TToolButton(CurControl).Style in [tbsButton,tbsCheck,tbsDropDown]
then begin
CurControl.GetPreferredSize(NewWidth,NewHeight);
if NewWidth < RealButtonWidth then
NewWidth := RealButtonWidth;
end;
end;
// height
// in horizontal toolbars the height is set by the toolbar independent of autosize
if ChangeH
and (RealButtonHeight > 0)
and ((Align in [alTop, alBottom]) or not CurControl.AutoSize)
then NewHeight := RealButtonHeight;
CurControl.SetBounds(CurControl.Left, CurControl.Top, NewWidth, NewHeight);
end;
finally
EndUpdate;
end;
end;
function TToolBar.CanFocus: Boolean;
begin
Result := False;
end;
procedure TToolBar.DoAutoSize;
begin
// children are moved in ControlsAligned independent of AutoSize=true
end;
function TToolBar.DropDownWidthIsStored: Boolean;
begin
Result := FDropDownWidth >= 0;
end;
procedure TToolBar.CalculatePreferredSize(var PreferredWidth,
PreferredHeight: integer; WithThemeSpace: Boolean);
var
AWidth: Integer;
NewWidth, NewHeight: Integer;
begin
NewWidth:=0;
NewHeight:=0;
if (Parent<>nil)
and (not Parent.AutoSize)
and AnchorSideLeft.IsAnchoredToParent(akLeft)
and AnchorSideRight.IsAnchoredToParent(akRight) then begin
// the width depends on the parent
// the width is fixed
AWidth:=Constraints.MinMaxWidth(Width);
end
else begin
AWidth:=Constraints.MinMaxWidth(Screen.Width);
end;
WrapButtons(AWidth,NewWidth,NewHeight,true);
PreferredWidth := NewWidth;
PreferredHeight := NewHeight;
//DebugLn(['TToolBar.CalculatePreferredSize ',DbgSName(Self),' ',PreferredWidth,'x',PreferredHeight,' Count=',ControlCount]);
end;
{------------------------------------------------------------------------------
function TToolBar.WrapButtons(var NewWidth, NewHeight: Integer): Boolean;
Position all controls, that have Align=alNone.
The controls are put from left to right.
If the controls don't fit in a row and Wrapable=true, then the next row is
started.
If Wrapable=false, then the row is wrapped after the first button with
Wrap=true.
------------------------------------------------------------------------------}
function TToolBar.WrapButtons(UseSize: integer;
out NewWidth, NewHeight: Integer; Simulate: boolean): Boolean;
var
ARect: TRect;
x, y, w, h: Integer;
NewControlWidth, NewControlHeight: Integer;
CurControl: TControl;
ObstacleControls: TFPList;
FullSizeObstacleControls: TFPList;
StartX, StartY: Integer;
Vertical: Boolean; // true = ToolBar is vertical, controls are put in rows
RowsLeftToRight: Boolean; // rows are left to right
RealButtonWidth, RealButtonHeight: Integer;
procedure CalculatePosition;
var
AlignedControl: TControl;
NewBounds: TRect;
SiblingBounds: TRect;
j: Integer;
PreferredBtnWidth, PreferredBtnHeight: Integer;
Intersects: Boolean;
IntersectsWithLimitedHeightControl: Boolean;
StartedAtRowStart: Boolean;
begin
// compute the size
if (CurControl is TToolButton) and (not CurControl.AutoSize) then
begin
PreferredBtnWidth := 0;
PreferredBtnHeight := 0;
CurControl.GetPreferredSize(PreferredBtnWidth, PreferredBtnHeight);
if Vertical then
begin
// column layout
NewControlHeight := PreferredBtnHeight;
NewControlWidth := RealButtonWidth;
end
else
begin
// row layout
NewControlHeight := RealButtonHeight;
NewControlWidth := PreferredBtnWidth;
end;
if (TToolButton(CurControl).Style in [tbsButton, tbsDropDown, tbsCheck]) then
begin
if Vertical then
begin
// column layout
if (NewControlHeight < RealButtonHeight) then
NewControlHeight := RealButtonHeight;
end
else begin
// row layout
if (NewControlWidth < RealButtonWidth) then
NewControlWidth := RealButtonWidth;
end;
end;
//debugln(['CalculatePosition preferred toolbutton size ',DbgSName(CurControl),' ',NewControlWidth,' ',NewControlHeight]);
end
else
if Vertical then
begin
// column layout
NewControlWidth := RealButtonWidth;
NewControlHeight := CurControl.Height;
end
else
begin
// row layout
NewControlWidth := CurControl.Width;
NewControlHeight := RealButtonHeight;
end;
if Vertical or RowsLeftToRight then
NewBounds := Bounds(x, y, NewControlWidth, NewControlHeight)
else
NewBounds := Bounds(x-NewControlWidth, y, NewControlWidth, NewControlHeight);
//DebugLn(['CalculatePosition ',DbgSName(CurControl),' NewBounds=',dbgs(NewBounds),' x=',x,' y=',y]);
if Vertical then
StartedAtRowStart := y = StartY
else
StartedAtRowStart := x = StartX;
repeat
// move control until it does not overlap
IntersectsWithLimitedHeightControl := False;
j := 0;
while j < ObstacleControls.Count do
begin
AlignedControl := TControl(ObstacleControls[j]);
SiblingBounds := AlignedControl.BoundsRect;
Intersects:=(SiblingBounds.Right > NewBounds.Left) and
(SiblingBounds.Left < NewBounds.Right) and
(SiblingBounds.Bottom > NewBounds.Top) and
(SiblingBounds.Top < NewBounds.Bottom);
if Intersects then
begin
//DebugLn(['CalculatePosition Move ',NewBounds.Left,'->',SiblingBounds.Right]);
if Vertical then
begin
// column layout
NewBounds.Top := SiblingBounds.Bottom;
NewBounds.Bottom := NewBounds.Top + NewControlHeight;
end
else
begin
// row layout
if RowsLeftToRight then
begin
NewBounds.Left := SiblingBounds.Right;
NewBounds.Right := NewBounds.Left + NewControlWidth;
end else begin
NewBounds.Right := SiblingBounds.Left;
NewBounds.Left := NewBounds.Right - NewControlWidth;
end;
end;
j := 0; // check again, needed, because ObstacleControls are not sorted
// (and can not be sorted, because they can overlap)
if FullSizeObstacleControls.IndexOf(AlignedControl) < 0 then
IntersectsWithLimitedHeightControl := True;
end
else
inc(j);
end;
if Vertical then
begin
// column layout
if (not Wrapable) or
(NewBounds.Bottom <= ARect.Bottom) or (NewBounds.Top = StartY) or
(StartedAtRowStart and not IntersectsWithLimitedHeightControl) then
begin
// control fits into the row
//DebugLn(['CalculatePosition fits: ',DbgSName(CurControl),' ',dbgs(NewBounds)]);
x := NewBounds.Left;
y := NewBounds.Top;
break;
end;
// try next row
NewBounds.Top := StartY;
NewBounds.Bottom := NewBounds.Top + NewControlHeight;
inc(NewBounds.Left, RealButtonWidth);
inc(NewBounds.Right, RealButtonWidth);
end
else
begin
// row layout
if (not Wrapable)
or (StartedAtRowStart and not IntersectsWithLimitedHeightControl)
or (RowsLeftToRight and ((NewBounds.Left = StartX) or (NewBounds.Right <= ARect.Right)))
or ((not RowsLeftToRight) and ((NewBounds.Right = StartX) or (NewBounds.Left >= ARect.Left)))
then begin
// control fits into the row
//DebugLn(['CalculatePosition fits: ',DbgSName(CurControl),' ',dbgs(NewBounds)]);
x := NewBounds.Left;
y := NewBounds.Top;
break;
end;
//debugln(['CalculatePosition overlaps: ',DbgSName(CurControl),' ',dbgs(NewBounds),' ARect=',DbgS(ARect),' StartX=',StartX]);
// try next row
inc(NewBounds.Top, RealButtonHeight);
inc(NewBounds.Bottom, RealButtonHeight);
if RowsLeftToRight then
begin
NewBounds.Left := StartX;
NewBounds.Right := NewBounds.Left + NewControlWidth;
end else begin
NewBounds.Right := StartX;
NewBounds.Left := NewBounds.Right - NewControlWidth;
end;
end;
StartedAtRowStart := True;
//DebugLn('CalculatePosition Next Row ',DbgSName(CurControl),' ',dbgs(NewBounds));
until false;
end;
function AnchoredToParent(AControl: TControl; Side: TAnchorKind): boolean;
var
AnchorControl: TControl;
AnchorSide: TAnchorSideReference;
p: integer;
begin
if not (Side in CurControl.Anchors) then exit(false);
AnchorControl:=nil;
CurControl.AnchorSide[Side].GetSidePosition(AnchorControl,AnchorSide,P);
if AnchorControl=nil then
AnchorControl:=CurControl;
Result:=(Side in AnchorControl.Anchors);
end;
var
OrderedControls: TFPList;
CurClientRect: TRect;
AdjustClientFrame: TRect;
i: Integer;
GrowSide: TAnchorKind; // when a line is full, grow the TToolBar in this direction
SeparatorWidthChange: Boolean;
begin
//DebugLn(['WrapButtons ',DbgSName(Self),' Wrapable=',Wrapable,' ',dbgs(BoundsRect),' Vertical=',IsVertical,' RTL=',UseRightToLeftAlignment,' Simulate=',Simulate]);
Result := True;
RealButtonWidth := ButtonWidth;
RealButtonHeight := ButtonHeight;
Vertical := IsVertical;
NewWidth := 0;
NewHeight := 0;
ObstacleControls := TFPList.Create;
FullSizeObstacleControls := TFPList.Create;
OrderedControls := TFPList.Create;
if not Simulate then
FRowCount := 0;
DisableAlign;
BeginUpdate;
try
if Vertical then
begin
GrowSide := akRight;
RowsLeftToRight := true;
end
else begin
GrowSide := akBottom;
RowsLeftToRight:=not UseRightToLeftAlignment;
end;
for i:=0 to ControlCount-1 do
begin
CurControl := Controls[i];
if CurControl.Align = alNone then begin
// this control will be auto positioned and auto sized by this function
// => set to Left,Top anchoring
CurControl.Anchors:=[akLeft,akTop];
CurControl.AnchorSide[akLeft].Control:=nil;
CurControl.AnchorSide[akTop].Control:=nil;
OrderedControls.Add(CurControl);
end else begin
// this control will be positioned/sized by the default LCL functions
// the OrderedControls will be positioned around them (without overlapping)
ObstacleControls.Add(CurControl);
// check if this obstacle auto grows, for example if this toolbar is
// aligned to the top, check if the obstacle grows downwards (Align=alLeft)
if AnchoredToParent(CurControl,GrowSide) then begin
// this obstacle auto grows (important for the wrap algorithm)
FullSizeObstacleControls.Add(CurControl);
end;
end;
end;
// sort OrderedControls
if Vertical then
OrderedControls.Sort(TListSortCompare(@CompareToolBarControlVert))
else
OrderedControls.Sort(TListSortCompare(@CompareToolBarControlHorz));
// position OrderedControls
CurClientRect := ClientRect;
if Vertical then
inc(CurClientRect.Bottom, UseSize - Height)
else
inc(CurClientRect.Right, UseSize - Width);
ARect := CurClientRect;
AdjustClientRect(ARect);
AdjustClientFrame.Left := ARect.Left - CurClientRect.Left;
AdjustClientFrame.Top := ARect.Top - CurClientRect.Top;
AdjustClientFrame.Right := CurClientRect.Right - ARect.Right;
AdjustClientFrame.Bottom := CurClientRect.Bottom - ARect.Bottom;
//DebugLn(['TToolBar.WrapButtons ',DbgSName(Self),' ARect=',dbgs(ARect)]);
// important: top, left button must start in the corner of AdjustClientRect
// otherwise Toolbar.AutoSize=true will create an endless loop
if Vertical or RowsLeftToRight then
StartX := ARect.Left
else
StartX := ARect.Right;
StartY := ARect.Top;
x := StartX;
y := StartY;
//debugln(['TToolBar.WrapButtons Start=',StartX,' ',StartY]);
for i := 0 to OrderedControls.Count - 1 do
begin
CurControl := TControl(OrderedControls[i]);
if not CurControl.IsControlVisible then
Continue;
CalculatePosition;
//DebugLn(['WrapButtons ',DbgSName(CurControl),' ',x,',',y,',',CurControl.Width,'x',CurControl.Height]);
if CurControl.AutoSize then
begin
w := CurControl.Width;
h := CurControl.Height;
end
else
begin
w := NewControlWidth;
h := NewControlHeight;
end;
w := CurControl.Constraints.MinMaxWidth(w);
h := CurControl.Constraints.MinMaxHeight(h);
SeparatorWidthChange := (CurControl is TToolButton) and
(TToolButton(CurControl).Style in [tbsSeparator, tbsDivider]);
if SeparatorWidthChange then begin
if not Vertical then begin
SeparatorWidthChange := (w <> CurControl.Width);
w := CurControl.Width;
end else begin
SeparatorWidthChange := (h <> CurControl.Height);
h := CurControl.Height;
end;
end;
if Vertical <> FPrevVertical then //swap h/w when orientation changed
begin
if (CurControl is TToolButton) and
(TToolButton(CurControl).Style in [tbsSeparator, tbsDivider]) then
begin
if not Vertical then
w := CurControl.Height
else
h := CurControl.Width;
end;
end;
if (CurControl.Left <> x) or (CurControl.Top <> y) or
(CurControl.Width <> w) or (CurControl.Height <> h) then
begin
//DebugLn(['TToolBar.WrapButtons moving child: ',DbgSName(CurControl),' Old=',dbgs(CurControl.BoundsRect),' New=',dbgs(Bounds(x,y,w,h))]);
if not Simulate then
begin
//DebugLn(['TToolBar.WrapButtons moving child: ',DbgSName(CurControl),' Old=',dbgs(CurControl.BoundsRect),' New=',dbgs(Bounds(x,y,w,h))]);
if SeparatorWidthChange then
CurControl.SetBoundsKeepBase(x,y,w,h)
else
CurControl.SetBounds(x,y,w,h);
//DebugLn(['TToolBar.WrapButtons moved child: ',DbgSName(CurControl),' ',dbgs(CurControl.BoundsRect)]);
end;
end;
// adjust NewWidth, NewHeight
if Vertical or RowsLeftToRight then
NewWidth := Max(NewWidth, x + w + AdjustClientFrame.Right)
else
NewWidth := Max(NewWidth, ARect.Right - x + ARect.Left + AdjustClientFrame.Right);
NewHeight := Max(NewHeight, y + h + AdjustClientFrame.Bottom);
// step to next position
if Vertical then
begin
inc(y, h);
if not Wrapable and
(CurControl is TToolButton) and
(TToolButton(CurControl).Wrap) then
begin
// user forced wrap -> start new line
y := StartY;
inc(x, RealButtonWidth);
if not Simulate then
inc(FRowCount);
end;
end
else
begin
if RowsLeftToRight then
inc(x, w);
if not Wrapable and
(CurControl is TToolButton) and
(TToolButton(CurControl).Wrap) then
begin
// user forced wrap -> start new line
x := StartX;
inc(y, RealButtonHeight);
if not Simulate then
inc(FRowCount);
end;
end;
end;
finally
ObstacleControls.Free;
OrderedControls.Free;
FullSizeObstacleControls.Free;
EndUpdate;
EnableAlign;
FPrevVertical := Vertical;
end;
end;
procedure TToolBar.CNDropDownClosed(var Message: TLMessage);
begin
CloseCurrentMenu;
end;
procedure TToolBar.AdjustClientRect(var ARect: TRect);
begin
inherited AdjustClientRect(ARect);
inc(ARect.Left, Indent);
end;
class function TToolBar.GetControlClassDefaultSize: TSize;
begin
Result.CX := 150;
Result.CY := 26;
end;
function TToolBar.FindButtonFromAccel(Accel: Word): TToolButton;
var
i: Integer;
begin
for i := 0 to FButtons.Count - 1 do
if TControl(FButtons[i]) is TToolButton then
begin
Result := Buttons[i];
if Result.Visible and Result.Enabled and IsAccel(Accel, Result.Caption) then
Exit;
end;
Result := nil;
end;
procedure TToolBar.FontChanged(Sender: TObject);
begin
inherited FontChanged(Sender);
ApplyFontForButtons;
FRealizedButtonWidth := 0;
FRealizedButtonHeight := 0;
FRealizedDropDownWidth := 0;
FRealizedButtonDropWidth := 0;
end;
function TToolBar.CheckMenuDropdown(Button: TToolButton): Boolean;
var
APoint: TPoint;
begin
Result := False;
if Button = nil then
Exit;
if Assigned(FCurrentMenu) then
begin
CloseCurrentMenu;
if FCurrentMenuAutoFree then
begin
FreeAndNil(FCurrentMenu);
FCurrentMenuAutoFree := False;
end;
end;
FSrcMenuItem := nil;
FSrcMenu := nil;
FDropDownButton := Button;
if Assigned(Button.DropdownMenu) then
// the button has a popupenu
FCurrentMenu := Button.DropdownMenu
else
if Assigned(Button.MenuItem) then
begin
// the button has a menuitem
// since the button is clicked - menu item must be clicked too
Button.MenuItem.Click;
// -> create a temporary TPopupMenu and move all child menuitems
FCurrentMenuAutoFree := True;
FCurrentMenu := TPopupMenu.Create(Self);
FSrcMenuItem := Button.MenuItem;
FSrcMenu := FSrcMenuItem.GetParentMenu;
FCurrentMenu.Items.HelpContext := FSrcMenuItem.HelpContext;
if Assigned(FSrcMenu) then
FCurrentMenu.Images := FSrcMenu.Images;
MoveSubMenuItems(FSrcMenuItem, FCurrentMenu.Items);
end
else
Exit;
FCurrentMenu.PopupComponent := Self;
APoint := Button.ClientToScreen(Point(0, Button.ClientHeight));
if FCurrentMenu.IsRightToLeft then Inc(APoint.X, Button.Width);
FCurrentMenu.Popup(APoint.X, APoint.Y);
// The next command will be executed after popup menu close because Popup is a
// syncronous method. We can't send this message on Menu.Close event because
// Click happen after the Close event and if we remove all the menu items there
// we will not be able to handle the Click event
// we also need to postpone this message to allow after Popup cleanup and click happen
PostMessage(Handle, CN_DROPDOWNCLOSED, 0, 0);
Result := True;
end;
procedure TToolBar.ClickButton(Button: TToolButton);
begin
Button.Click;
end;
{ TToolBar.TTMPObserver }
procedure TToolBar.TTMPObserver.FPOObservedChanged(ASender: TObject;
Operation: TFPObservedOperation; Data: Pointer);
begin
case Operation of
ooFree: if Assigned(DoOnFree) then DoOnFree(ASender);
ooChange: if Assigned(DoOnChange) then DoOnChange(ASender);
end;
end;
procedure TToolBar.TTMPObserver.SetDoOnChange(AValue: TNotifyEvent);
begin
if FDoOnChange = AValue then Exit;
FDoOnChange := AValue;
end;
procedure TToolBar.TTMPObserver.SetDoOnFree(AValue: TNotifyEvent);
begin
if FDoOnFree = AValue then Exit;
FDoOnFree := AValue;
end;