123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- unit Setup.SetupForm;
- {
- Inno Setup
- Copyright (C) 1997-2025 Jordan Russell
- Portions by Martijn Laan
- For conditions of distribution and use, see LICENSE.TXT.
- TSetupForm
- }
- interface
- uses
- Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs,
- UIStateForm;
- type
- TSetupForm = class(TUIStateForm)
- private
- FBaseUnitX, FBaseUnitY: Integer;
- FRightToLeft: Boolean;
- FFlipControlsOnShow: Boolean;
- FSizeAndCenterOnShow: Boolean;
- FControlsFlipped: Boolean;
- FKeepSizeY: Boolean;
- procedure WMQueryEndSession(var Message: TWMQueryEndSession); message WM_QUERYENDSESSION;
- protected
- procedure Center;
- procedure CenterInsideControl(const Ctl: TWinControl;
- const InsideClientArea: Boolean);
- procedure CenterInsideRect(const InsideRect: TRect);
- procedure CreateParams(var Params: TCreateParams); override;
- procedure CreateWnd; override;
- procedure FlipControlsIfNeeded;
- procedure SizeAndCenterIfNeeded(const ACenterInsideControl: Boolean;
- const CenterInsideControlCtl: TWinControl;
- const CenterInsideControlInsideClientArea: Boolean);
- procedure VisibleChanging; override;
- procedure WndProc(var Message: TMessage); override;
- public
- constructor Create(AOwner: TComponent); override;
- constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
- function CalculateButtonWidth(const ButtonCaptions: array of String): Integer;
- procedure InitializeFont;
- function ScalePixelsX(const N: Integer): Integer;
- function ScalePixelsY(const N: Integer): Integer;
- function ShouldSizeX: Boolean;
- function ShouldSizeY: Boolean;
- function ShowModal: Integer; override;
- procedure FlipSizeAndCenterIfNeeded(const ACenterInsideControl: Boolean = False;
- const CenterInsideControlCtl: TWinControl = nil;
- const CenterInsideControlInsideClientArea: Boolean = False); virtual;
- property BaseUnitX: Integer read FBaseUnitX;
- property BaseUnitY: Integer read FBaseUnitY;
- published
- property ControlsFlipped: Boolean read FControlsFlipped;
- property FlipControlsOnShow: Boolean read FFlipControlsOnShow write FFlipControlsOnShow;
- property KeepSizeY: Boolean read FKeepSizeY write FKeepSizeY;
- property RightToLeft: Boolean read FRightToLeft;
- property SizeAndCenterOnShow: Boolean read FSizeAndCenterOnShow write FSizeAndCenterOnShow;
- end;
- procedure CalculateBaseUnitsFromFont(const Font: TFont; var X, Y: Integer);
- function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
- function SetFontNameSize(const AFont: TFont; const AName: String;
- const ASize: Integer; const AFallbackName: String;
- const AFallbackSize: Integer): Boolean;
- const
- OrigBaseUnitX = 6;
- OrigBaseUnitY = 13;
- implementation
- uses
- Generics.Collections, UITypes,
- Shared.CommonFunc, Shared.CommonFunc.Vcl, Setup.MainFunc, BidiUtils;
- var
- WM_QueryCancelAutoPlay: UINT;
- function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
- begin
- if not WorkArea or
- not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
- Result := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
- end;
- function GetRectOfMonitorContainingRect(const R: TRect): TRect;
- { Returns bounding rectangle of monitor containing or nearest to R }
- type
- HMONITOR = type THandle;
- TMonitorInfo = record
- cbSize: DWORD;
- rcMonitor: TRect;
- rcWork: TRect;
- dwFlags: DWORD;
- end;
- const
- MONITOR_DEFAULTTONEAREST = $00000002;
- var
- Module: HMODULE;
- MonitorFromRect: function(const lprc: TRect; dwFlags: DWORD): HMONITOR; stdcall;
- GetMonitorInfo: function(hMonitor: HMONITOR; var lpmi: TMonitorInfo): BOOL; stdcall;
- M: HMONITOR;
- Info: TMonitorInfo;
- begin
- Module := GetModuleHandle(user32);
- MonitorFromRect := GetProcAddress(Module, 'MonitorFromRect');
- GetMonitorInfo := GetProcAddress(Module, 'GetMonitorInfoA');
- if Assigned(MonitorFromRect) and Assigned(GetMonitorInfo) then begin
- M := MonitorFromRect(R, MONITOR_DEFAULTTONEAREST);
- Info.cbSize := SizeOf(Info);
- if GetMonitorInfo(M, Info) then begin
- Result := Info.rcWork;
- Exit;
- end;
- end;
- Result := GetRectOfPrimaryMonitor(True);
- end;
- function SetFontNameSize(const AFont: TFont; const AName: String;
- const ASize: Integer; const AFallbackName: String;
- const AFallbackSize: Integer): Boolean;
- { Returns True if AName <> '' and it used AName as the font name,
- False otherwise. }
- function SizeToHeight(const S: Integer): Integer;
- begin
- Result := MulDiv(-S, Screen.PixelsPerInch, 72);
- end;
- begin
- Result := False;
- if AName <> '' then begin
- if FontExists(AName) then begin
- AFont.Name := AName;
- AFont.Height := SizeToHeight(ASize);
- Result := True;
- Exit;
- end;
- { Note: AFallbackName is not used if the user specified an empty string for
- AName because in that case they want the default font used always }
- if (AFallbackName <> '') and FontExists(AFallbackName) then begin
- AFont.Name := AFallbackName;
- AFont.Height := SizeToHeight(AFallbackSize);
- Exit;
- end;
- end;
- AFont.Name := GetPreferredUIFont;
- AFont.Height := SizeToHeight(AFallbackSize);
- end;
- procedure CalculateBaseUnitsFromFont(const Font: TFont; var X, Y: Integer);
- var
- DC: HDC;
- Size: TSize;
- TM: TTextMetric;
- begin
- DC := GetDC(0);
- try
- SelectObject(DC, Font.Handle);
- { Based on code from Q145994: }
- GetTextExtentPoint(DC,
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 52, Size);
- X := (Size.cx div 26 + 1) div 2;
- GetTextMetrics(DC, TM);
- Y := TM.tmHeight;
- finally
- ReleaseDC(0, DC);
- end;
- end;
- procedure NewChangeScale(const Ctl: TControl; const XM, XD, YM, YD: Integer);
- var
- X, Y, W, H: Integer;
- begin
- X := MulDiv(Ctl.Left, XM, XD);
- Y := MulDiv(Ctl.Top, YM, YD);
- if not(csFixedWidth in Ctl.ControlStyle) then
- W := MulDiv(Ctl.Width, XM, XD)
- else
- W := Ctl.Width;
- if not(csFixedHeight in Ctl.ControlStyle) then
- H := MulDiv(Ctl.Height, YM, YD)
- else
- H := Ctl.Height;
- Ctl.SetBounds(X, Y, W, H);
- end;
- procedure NewScaleControls(const Ctl: TWinControl; const XM, XD, YM, YD: Integer);
- { This is like TControl.ScaleControls, except it allows the width and height
- to be scaled independently }
- var
- I: Integer;
- C: TControl;
- begin
- for I := 0 to Ctl.ControlCount-1 do begin
- C := Ctl.Controls[I];
- if C is TWinControl then begin
- TWinControl(C).DisableAlign;
- try
- NewScaleControls(TWinControl(C), XM, XD, YM, YD);
- NewChangeScale(C, XM, XD, YM, YD);
- finally
- TWinControl(C).EnableAlign;
- end;
- end
- else
- NewChangeScale(C, XM, XD, YM, YD);
- end;
- end;
- function GetParentSetupForm(AControl: TControl): TSetupForm;
- begin
- { Note: Unlike GetParentForm, this checks all levels, not just the top }
- repeat
- if AControl is TSetupForm then begin
- Result := TSetupForm(AControl);
- Exit;
- end;
- AControl := AControl.Parent;
- until AControl = nil;
- Result := nil;
- end;
- function IsParentSetupFormFlipped(AControl: TControl): Boolean;
- var
- ParentForm: TSetupForm;
- begin
- ParentForm := GetParentSetupForm(AControl);
- if Assigned(ParentForm) then
- Result := ParentForm.ControlsFlipped
- else
- Result := False;
- end;
- function IsParentSetupFormRightToLeft(AControl: TControl): Boolean;
- var
- ParentForm: TSetupForm;
- begin
- ParentForm := GetParentSetupForm(AControl);
- if Assigned(ParentForm) then
- Result := ParentForm.RightToLeft
- else
- Result := False;
- end;
- type
- TControlAnchorsList = TDictionary<TControl, TAnchors>;
- TControlAccess = class(TControl);
- procedure StripAndStoreCustomAnchors(const Ctl: TControl; const AnchorsList: TControlAnchorsList);
- var
- I: Integer;
- begin
- if Ctl.Anchors <> [akLeft, akTop] then begin
- AnchorsList.Add(Ctl, Ctl.Anchors);
- { Before we can set Anchors to [akLeft, akTop] (which has a special
- 'no anchors' meaning to VCL), we first need to update the Explicit*
- properties so the control doesn't get moved back to an old position. }
- TControlAccess(Ctl).UpdateExplicitBounds;
- Ctl.Anchors := [akLeft, akTop];
- end;
- if Ctl is TWinControl then
- for I := 0 to TWinControl(Ctl).ControlCount-1 do
- StripAndStoreCustomAnchors(TWinControl(Ctl).Controls[I], AnchorsList);
- end;
- procedure RestoreAnchors(const Ctl: TControl; const AnchorsList: TControlAnchorsList);
- begin
- { The order in which we restore the anchors shouldn't matter, so just
- enumerate the list. }
- for var Item in AnchorsList do
- Item.Key.Anchors := Item.Value;
- end;
- { TSetupForm }
- constructor TSetupForm.Create(AOwner: TComponent);
- begin
- { Must initialize FRightToLeft here in addition to CreateNew because
- CreateNew isn't virtual on Delphi 2 and 3 }
- FRightToLeft := LangOptions.RightToLeft;
- FFlipControlsOnShow := FRightToLeft;
- FSizeAndCenterOnShow := True;
- inherited;
- { In Delphi 2005 and later, Position defaults to poDefaultPosOnly, but we
- don't want the form to be changing positions whenever its handle is
- recreated, so change it to the D7 and earlier default of poDesigned. }
- if Position = poDefaultPosOnly then
- Position := poDesigned;
- end;
- constructor TSetupForm.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
- begin
- { Note: On Delphi 2 and 3, CreateNew isn't virtual, so this is only reached
- when TSetupForm.CreateNew is called explicitly }
- FRightToLeft := LangOptions.RightToLeft;
- FFlipControlsOnShow := FRightToLeft;
- FSizeAndCenterOnShow := True;
- inherited;
- end;
- function TSetupForm.CalculateButtonWidth(const ButtonCaptions: array of String): Integer;
- var
- DC: HDC;
- I, W: Integer;
- begin
- Result := ScalePixelsX(75);
- { Increase the button size if there are unusually long button captions }
- DC := GetDC(0);
- try
- SelectObject(DC, Font.Handle);
- for I := Low(ButtonCaptions) to High(ButtonCaptions) do begin
- W := GetTextWidth(DC, ButtonCaptions[I], True) + ScalePixelsX(20);
- if Result < W then
- Result := W;
- end;
- finally
- ReleaseDC(0, DC);
- end;
- end;
- procedure TSetupForm.CenterInsideControl(const Ctl: TWinControl;
- const InsideClientArea: Boolean);
- var
- R: TRect;
- begin
- const CtlForm = GetParentForm(Ctl);
- if (CtlForm = nil) or not IsWindowVisible(CtlForm.Handle) or
- IsIconic(CtlForm.Handle) then begin
- Center;
- Exit;
- end;
- if not InsideClientArea then begin
- if GetWindowRect(Ctl.Handle, R) then
- CenterInsideRect(R);
- end
- else begin
- R := Ctl.ClientRect;
- MapWindowPoints(Ctl.Handle, 0, R, 2);
- CenterInsideRect(R);
- end;
- end;
- procedure TSetupForm.CenterInsideRect(const InsideRect: TRect);
- var
- R, MR: TRect;
- begin
- R := Bounds(InsideRect.Left + ((InsideRect.Right - InsideRect.Left) - Width) div 2,
- InsideRect.Top + ((InsideRect.Bottom - InsideRect.Top) - Height) div 2,
- Width, Height);
- { Clip to nearest monitor }
- MR := GetRectOfMonitorContainingRect(R);
- if R.Right > MR.Right then
- OffsetRect(R, MR.Right - R.Right, 0);
- if R.Bottom > MR.Bottom then
- OffsetRect(R, 0, MR.Bottom - R.Bottom);
- if R.Left < MR.Left then
- OffsetRect(R, MR.Left - R.Left, 0);
- if R.Top < MR.Top then
- OffsetRect(R, 0, MR.Top - R.Top);
- BoundsRect := R;
- end;
- procedure TSetupForm.Center;
- begin
- CenterInsideRect(GetRectOfPrimaryMonitor(True));
- end;
- procedure TSetupForm.CreateParams(var Params: TCreateParams);
- begin
- inherited;
- { With Application.MainFormOnTaskBar=True, by default, a form won't get a
- taskbar button if the main form hasn't been created yet (due to the owner
- being an invisible Application.Handle), or if the main form exists but
- isn't visible (e.g., because it's a silent install). Force it to have a
- taskbar button in those cases by specifying no owner for the window.
- (Another method is to set WS_EX_APPWINDOW and leave WndParent set to
- Application.Handle, but it doesn't quite work correctly: if the form
- displays a message box, and you activate another app's window, clicking on
- the form's taskbar button activates the message box again, but the taskbar
- button doesn't change to a "selected" state.) }
- if (Params.WndParent <> 0) and
- (Application.MainFormOnTaskBar or (Params.WndParent <> Application.Handle)) and
- not IsWindowOnTaskbar(Params.WndParent) then
- Params.WndParent := 0;
- if FRightToLeft then
- Params.ExStyle := Params.ExStyle or (WS_EX_RTLREADING or WS_EX_LEFTSCROLLBAR or WS_EX_RIGHT);
- end;
- procedure TSetupForm.CreateWnd;
- begin
- inherited;
- if WM_QueryCancelAutoPlay <> 0 then
- AddToWindowMessageFilterEx(Handle, WM_QueryCancelAutoPlay);
- end;
- procedure TSetupForm.FlipControlsIfNeeded;
- begin
- if FFlipControlsOnShow then begin
- FFlipControlsOnShow := False;
- FControlsFlipped := not FControlsFlipped;
- FlipControls(Self);
- end;
- end;
- procedure TSetupForm.SizeAndCenterIfNeeded(const ACenterInsideControl: Boolean; const CenterInsideControlCtl: TWinControl; const CenterInsideControlInsideClientArea: Boolean);
- begin
- if FSizeAndCenterOnShow then begin
- FSizeAndCenterOnShow := False;
- { Apply custom initial size from script - depends on Anchors being set on all the controls }
- if ShouldSizeX then
- ClientWidth := MulDiv(ClientWidth, SetupHeader.WizardSizePercentX, 100);
- if ShouldSizeY then
- ClientHeight := MulDiv(ClientHeight, SetupHeader.WizardSizePercentY, 100);
- { Center }
- if ACenterInsideControl then
- CenterInsideControl(CenterInsideControlCtl, CenterInsideControlInsideClientArea)
- else
- Center;
- end;
- end;
- function TSetupForm.ShouldSizeX: Boolean;
- begin
- Result := SetupHeader.WizardSizePercentX > 100;
- end;
- function TSetupForm.ShouldSizeY: Boolean;
- begin
- Result := not FKeepSizeY and (SetupHeader.WizardSizePercentY > 100);
- end;
- procedure TSetupForm.FlipSizeAndCenterIfNeeded(const ACenterInsideControl: Boolean;
- const CenterInsideControlCtl: TWinControl; const CenterInsideControlInsideClientArea: Boolean);
- begin
- { Flipping must be done first because when flipping after sizing the flipping might get old info for anchors that didn't do their work yet. }
- FlipControlsIfNeeded;
- SizeAndCenterIfNeeded(ACenterInsideControl, CenterInsideControlCtl, CenterInsideControlInsideClientArea);
- end;
- procedure TSetupForm.InitializeFont;
- var
- ControlAnchorsList: TControlAnchorsList;
- W, H: Integer;
- R: TRect;
- begin
- { Note: Must keep the following lines in synch with Setup.ScriptFunc.pas's
- InitializeScaleBaseUnits }
- SetFontNameSize(Font, LangOptions.DialogFontName, LangOptions.DialogFontSize,
- '', 8);
- CalculateBaseUnitsFromFont(Font, FBaseUnitX, FBaseUnitY);
- if (FBaseUnitX <> OrigBaseUnitX) or (FBaseUnitY <> OrigBaseUnitY) then begin
- ControlAnchorsList := TControlAnchorsList.Create;
- try
- { Custom anchors interfere with our scaling code, so strip them and restore
- afterwards. }
- StripAndStoreCustomAnchors(Self, ControlAnchorsList);
- { Loosely based on scaling code from TForm.ReadState: }
- NewScaleControls(Self, BaseUnitX, OrigBaseUnitX, BaseUnitY, OrigBaseUnitY);
- R := ClientRect;
- W := MulDiv(R.Right, FBaseUnitX, OrigBaseUnitX);
- H := MulDiv(R.Bottom, FBaseUnitY, OrigBaseUnitY);
- SetBounds(Left, Top, W + (Width - R.Right), H + (Height - R.Bottom));
- finally
- RestoreAnchors(Self, ControlAnchorsList);
- end;
- end;
- end;
- function TSetupForm.ScalePixelsX(const N: Integer): Integer;
- begin
- Result := MulDiv(N, BaseUnitX, OrigBaseUnitX);
- end;
- function TSetupForm.ScalePixelsY(const N: Integer): Integer;
- begin
- Result := MulDiv(N, BaseUnitY, OrigBaseUnitY);
- end;
- function TSetupForm.ShowModal: Integer;
- begin
- { Work around VCL issue (Delphi 11.3): ShowModal calls DisableTaskWindows
- without ensuring the form's handle has been created first. If the handle
- is created after DisableTaskWindows, PopupMode=pmAuto breaks;
- TCustomForm.CreateParams finds that the active window is disabled, and
- doesn't use it as the owner. It then falls back to pmNone behavior, which
- is to use the main form or application window as the owner. }
- HandleNeeded;
- Result := inherited;
- end;
- procedure TSetupForm.VisibleChanging;
- begin
- inherited;
- { Note: Unlike DoShow, any exceptions raised in VisibleChanging will be
- propagated out, which is what we want }
- if not Visible then
- FlipSizeAndCenterIfNeeded;
- end;
- procedure TSetupForm.WMQueryEndSession(var Message: TWMQueryEndSession);
- begin
- { TDummyClass.AntiShutdownHook in Setup.dpr already denies shutdown attempts
- but we also need to catch WM_QUERYENDSESSION here to suppress the VCL's
- default handling which calls CloseQuery. We do not want to let TMainForm &
- TNewDiskForm display any 'Exit Setup?' message boxes since we're already
- denying shutdown attempts, and also we can't allow them to potentially be
- displayed on top of another dialog box that's already displayed. }
- { Return zero, except if RestartInitiatedByThisProcess is set (which means
- we called RestartComputer previously) }
- if RestartInitiatedByThisProcess then
- Message.Result := 1;
- end;
- procedure TSetupForm.WndProc(var Message: TMessage);
- begin
- { When we receive a 'QueryCancelAutoPlay' message as a result of a new CD
- being inserted, return 1 to prevent it from being 'autoplayed'.
- Note: According to the docs, this message is only sent on Shell version
- 4.70 and later. }
- if (WM_QueryCancelAutoPlay <> 0) and (Message.Msg = WM_QueryCancelAutoPlay) then
- Message.Result := 1
- else
- inherited;
- end;
- initialization
- BidiUtils.IsParentFlippedFunc := IsParentSetupFormFlipped;
- BidiUtils.IsParentRightToLeftFunc := IsParentSetupFormRightToLeft;
- WM_QueryCancelAutoPlay := RegisterWindowMessage('QueryCancelAutoPlay');
- end.
|