Setup.SetupForm.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. unit Setup.SetupForm;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. TSetupForm
  8. }
  9. interface
  10. uses
  11. Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs,
  12. UIStateForm;
  13. type
  14. TSetupForm = class(TUIStateForm)
  15. private
  16. FBaseUnitX, FBaseUnitY: Integer;
  17. FRightToLeft: Boolean;
  18. FFlipControlsOnShow: Boolean;
  19. FSizeAndCenterOnShow: Boolean;
  20. FControlsFlipped: Boolean;
  21. FKeepSizeY: Boolean;
  22. procedure WMQueryEndSession(var Message: TWMQueryEndSession); message WM_QUERYENDSESSION;
  23. protected
  24. procedure Center;
  25. procedure CenterInsideControl(const Ctl: TWinControl;
  26. const InsideClientArea: Boolean);
  27. procedure CenterInsideRect(const InsideRect: TRect);
  28. procedure CreateParams(var Params: TCreateParams); override;
  29. procedure CreateWnd; override;
  30. procedure FlipControlsIfNeeded;
  31. procedure SizeAndCenterIfNeeded(const ACenterInsideControl: Boolean;
  32. const CenterInsideControlCtl: TWinControl;
  33. const CenterInsideControlInsideClientArea: Boolean);
  34. procedure VisibleChanging; override;
  35. procedure WndProc(var Message: TMessage); override;
  36. public
  37. constructor Create(AOwner: TComponent); override;
  38. constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
  39. function CalculateButtonWidth(const ButtonCaptions: array of String): Integer;
  40. procedure InitializeFont;
  41. function ScalePixelsX(const N: Integer): Integer;
  42. function ScalePixelsY(const N: Integer): Integer;
  43. function ShouldSizeX: Boolean;
  44. function ShouldSizeY: Boolean;
  45. function ShowModal: Integer; override;
  46. procedure FlipSizeAndCenterIfNeeded(const ACenterInsideControl: Boolean = False;
  47. const CenterInsideControlCtl: TWinControl = nil;
  48. const CenterInsideControlInsideClientArea: Boolean = False); virtual;
  49. property BaseUnitX: Integer read FBaseUnitX;
  50. property BaseUnitY: Integer read FBaseUnitY;
  51. published
  52. property ControlsFlipped: Boolean read FControlsFlipped;
  53. property FlipControlsOnShow: Boolean read FFlipControlsOnShow write FFlipControlsOnShow;
  54. property KeepSizeY: Boolean read FKeepSizeY write FKeepSizeY;
  55. property RightToLeft: Boolean read FRightToLeft;
  56. property SizeAndCenterOnShow: Boolean read FSizeAndCenterOnShow write FSizeAndCenterOnShow;
  57. end;
  58. procedure CalculateBaseUnitsFromFont(const Font: TFont; var X, Y: Integer);
  59. function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
  60. function SetFontNameSize(const AFont: TFont; const AName: String;
  61. const ASize: Integer; const AFallbackName: String;
  62. const AFallbackSize: Integer): Boolean;
  63. const
  64. OrigBaseUnitX = 6;
  65. OrigBaseUnitY = 13;
  66. implementation
  67. uses
  68. Generics.Collections, UITypes,
  69. Shared.CommonFunc, Shared.CommonFunc.Vcl, Setup.MainFunc, BidiUtils;
  70. var
  71. WM_QueryCancelAutoPlay: UINT;
  72. function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
  73. begin
  74. if not WorkArea or
  75. not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
  76. Result := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
  77. end;
  78. function GetRectOfMonitorContainingRect(const R: TRect): TRect;
  79. { Returns bounding rectangle of monitor containing or nearest to R }
  80. type
  81. HMONITOR = type THandle;
  82. TMonitorInfo = record
  83. cbSize: DWORD;
  84. rcMonitor: TRect;
  85. rcWork: TRect;
  86. dwFlags: DWORD;
  87. end;
  88. const
  89. MONITOR_DEFAULTTONEAREST = $00000002;
  90. var
  91. Module: HMODULE;
  92. MonitorFromRect: function(const lprc: TRect; dwFlags: DWORD): HMONITOR; stdcall;
  93. GetMonitorInfo: function(hMonitor: HMONITOR; var lpmi: TMonitorInfo): BOOL; stdcall;
  94. M: HMONITOR;
  95. Info: TMonitorInfo;
  96. begin
  97. Module := GetModuleHandle(user32);
  98. MonitorFromRect := GetProcAddress(Module, 'MonitorFromRect');
  99. GetMonitorInfo := GetProcAddress(Module, 'GetMonitorInfoA');
  100. if Assigned(MonitorFromRect) and Assigned(GetMonitorInfo) then begin
  101. M := MonitorFromRect(R, MONITOR_DEFAULTTONEAREST);
  102. Info.cbSize := SizeOf(Info);
  103. if GetMonitorInfo(M, Info) then begin
  104. Result := Info.rcWork;
  105. Exit;
  106. end;
  107. end;
  108. Result := GetRectOfPrimaryMonitor(True);
  109. end;
  110. function SetFontNameSize(const AFont: TFont; const AName: String;
  111. const ASize: Integer; const AFallbackName: String;
  112. const AFallbackSize: Integer): Boolean;
  113. { Returns True if AName <> '' and it used AName as the font name,
  114. False otherwise. }
  115. function SizeToHeight(const S: Integer): Integer;
  116. begin
  117. Result := MulDiv(-S, Screen.PixelsPerInch, 72);
  118. end;
  119. begin
  120. Result := False;
  121. if AName <> '' then begin
  122. if FontExists(AName) then begin
  123. AFont.Name := AName;
  124. AFont.Height := SizeToHeight(ASize);
  125. Result := True;
  126. Exit;
  127. end;
  128. { Note: AFallbackName is not used if the user specified an empty string for
  129. AName because in that case they want the default font used always }
  130. if (AFallbackName <> '') and FontExists(AFallbackName) then begin
  131. AFont.Name := AFallbackName;
  132. AFont.Height := SizeToHeight(AFallbackSize);
  133. Exit;
  134. end;
  135. end;
  136. AFont.Name := GetPreferredUIFont;
  137. AFont.Height := SizeToHeight(AFallbackSize);
  138. end;
  139. procedure CalculateBaseUnitsFromFont(const Font: TFont; var X, Y: Integer);
  140. var
  141. DC: HDC;
  142. Size: TSize;
  143. TM: TTextMetric;
  144. begin
  145. DC := GetDC(0);
  146. try
  147. SelectObject(DC, Font.Handle);
  148. { Based on code from Q145994: }
  149. GetTextExtentPoint(DC,
  150. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 52, Size);
  151. X := (Size.cx div 26 + 1) div 2;
  152. GetTextMetrics(DC, TM);
  153. Y := TM.tmHeight;
  154. finally
  155. ReleaseDC(0, DC);
  156. end;
  157. end;
  158. procedure NewChangeScale(const Ctl: TControl; const XM, XD, YM, YD: Integer);
  159. var
  160. X, Y, W, H: Integer;
  161. begin
  162. X := MulDiv(Ctl.Left, XM, XD);
  163. Y := MulDiv(Ctl.Top, YM, YD);
  164. if not(csFixedWidth in Ctl.ControlStyle) then
  165. W := MulDiv(Ctl.Width, XM, XD)
  166. else
  167. W := Ctl.Width;
  168. if not(csFixedHeight in Ctl.ControlStyle) then
  169. H := MulDiv(Ctl.Height, YM, YD)
  170. else
  171. H := Ctl.Height;
  172. Ctl.SetBounds(X, Y, W, H);
  173. end;
  174. procedure NewScaleControls(const Ctl: TWinControl; const XM, XD, YM, YD: Integer);
  175. { This is like TControl.ScaleControls, except it allows the width and height
  176. to be scaled independently }
  177. var
  178. I: Integer;
  179. C: TControl;
  180. begin
  181. for I := 0 to Ctl.ControlCount-1 do begin
  182. C := Ctl.Controls[I];
  183. if C is TWinControl then begin
  184. TWinControl(C).DisableAlign;
  185. try
  186. NewScaleControls(TWinControl(C), XM, XD, YM, YD);
  187. NewChangeScale(C, XM, XD, YM, YD);
  188. finally
  189. TWinControl(C).EnableAlign;
  190. end;
  191. end
  192. else
  193. NewChangeScale(C, XM, XD, YM, YD);
  194. end;
  195. end;
  196. function GetParentSetupForm(AControl: TControl): TSetupForm;
  197. begin
  198. { Note: Unlike GetParentForm, this checks all levels, not just the top }
  199. repeat
  200. if AControl is TSetupForm then begin
  201. Result := TSetupForm(AControl);
  202. Exit;
  203. end;
  204. AControl := AControl.Parent;
  205. until AControl = nil;
  206. Result := nil;
  207. end;
  208. function IsParentSetupFormFlipped(AControl: TControl): Boolean;
  209. var
  210. ParentForm: TSetupForm;
  211. begin
  212. ParentForm := GetParentSetupForm(AControl);
  213. if Assigned(ParentForm) then
  214. Result := ParentForm.ControlsFlipped
  215. else
  216. Result := False;
  217. end;
  218. function IsParentSetupFormRightToLeft(AControl: TControl): Boolean;
  219. var
  220. ParentForm: TSetupForm;
  221. begin
  222. ParentForm := GetParentSetupForm(AControl);
  223. if Assigned(ParentForm) then
  224. Result := ParentForm.RightToLeft
  225. else
  226. Result := False;
  227. end;
  228. type
  229. TControlAnchorsList = TDictionary<TControl, TAnchors>;
  230. TControlAccess = class(TControl);
  231. procedure StripAndStoreCustomAnchors(const Ctl: TControl; const AnchorsList: TControlAnchorsList);
  232. var
  233. I: Integer;
  234. begin
  235. if Ctl.Anchors <> [akLeft, akTop] then begin
  236. AnchorsList.Add(Ctl, Ctl.Anchors);
  237. { Before we can set Anchors to [akLeft, akTop] (which has a special
  238. 'no anchors' meaning to VCL), we first need to update the Explicit*
  239. properties so the control doesn't get moved back to an old position. }
  240. TControlAccess(Ctl).UpdateExplicitBounds;
  241. Ctl.Anchors := [akLeft, akTop];
  242. end;
  243. if Ctl is TWinControl then
  244. for I := 0 to TWinControl(Ctl).ControlCount-1 do
  245. StripAndStoreCustomAnchors(TWinControl(Ctl).Controls[I], AnchorsList);
  246. end;
  247. procedure RestoreAnchors(const Ctl: TControl; const AnchorsList: TControlAnchorsList);
  248. begin
  249. { The order in which we restore the anchors shouldn't matter, so just
  250. enumerate the list. }
  251. for var Item in AnchorsList do
  252. Item.Key.Anchors := Item.Value;
  253. end;
  254. { TSetupForm }
  255. constructor TSetupForm.Create(AOwner: TComponent);
  256. begin
  257. { Must initialize FRightToLeft here in addition to CreateNew because
  258. CreateNew isn't virtual on Delphi 2 and 3 }
  259. FRightToLeft := LangOptions.RightToLeft;
  260. FFlipControlsOnShow := FRightToLeft;
  261. FSizeAndCenterOnShow := True;
  262. inherited;
  263. { In Delphi 2005 and later, Position defaults to poDefaultPosOnly, but we
  264. don't want the form to be changing positions whenever its handle is
  265. recreated, so change it to the D7 and earlier default of poDesigned. }
  266. if Position = poDefaultPosOnly then
  267. Position := poDesigned;
  268. end;
  269. constructor TSetupForm.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
  270. begin
  271. { Note: On Delphi 2 and 3, CreateNew isn't virtual, so this is only reached
  272. when TSetupForm.CreateNew is called explicitly }
  273. FRightToLeft := LangOptions.RightToLeft;
  274. FFlipControlsOnShow := FRightToLeft;
  275. FSizeAndCenterOnShow := True;
  276. inherited;
  277. end;
  278. function TSetupForm.CalculateButtonWidth(const ButtonCaptions: array of String): Integer;
  279. var
  280. DC: HDC;
  281. I, W: Integer;
  282. begin
  283. Result := ScalePixelsX(75);
  284. { Increase the button size if there are unusually long button captions }
  285. DC := GetDC(0);
  286. try
  287. SelectObject(DC, Font.Handle);
  288. for I := Low(ButtonCaptions) to High(ButtonCaptions) do begin
  289. W := GetTextWidth(DC, ButtonCaptions[I], True) + ScalePixelsX(20);
  290. if Result < W then
  291. Result := W;
  292. end;
  293. finally
  294. ReleaseDC(0, DC);
  295. end;
  296. end;
  297. procedure TSetupForm.CenterInsideControl(const Ctl: TWinControl;
  298. const InsideClientArea: Boolean);
  299. var
  300. R: TRect;
  301. begin
  302. const CtlForm = GetParentForm(Ctl);
  303. if (CtlForm = nil) or not IsWindowVisible(CtlForm.Handle) or
  304. IsIconic(CtlForm.Handle) then begin
  305. Center;
  306. Exit;
  307. end;
  308. if not InsideClientArea then begin
  309. if GetWindowRect(Ctl.Handle, R) then
  310. CenterInsideRect(R);
  311. end
  312. else begin
  313. R := Ctl.ClientRect;
  314. MapWindowPoints(Ctl.Handle, 0, R, 2);
  315. CenterInsideRect(R);
  316. end;
  317. end;
  318. procedure TSetupForm.CenterInsideRect(const InsideRect: TRect);
  319. var
  320. R, MR: TRect;
  321. begin
  322. R := Bounds(InsideRect.Left + ((InsideRect.Right - InsideRect.Left) - Width) div 2,
  323. InsideRect.Top + ((InsideRect.Bottom - InsideRect.Top) - Height) div 2,
  324. Width, Height);
  325. { Clip to nearest monitor }
  326. MR := GetRectOfMonitorContainingRect(R);
  327. if R.Right > MR.Right then
  328. OffsetRect(R, MR.Right - R.Right, 0);
  329. if R.Bottom > MR.Bottom then
  330. OffsetRect(R, 0, MR.Bottom - R.Bottom);
  331. if R.Left < MR.Left then
  332. OffsetRect(R, MR.Left - R.Left, 0);
  333. if R.Top < MR.Top then
  334. OffsetRect(R, 0, MR.Top - R.Top);
  335. BoundsRect := R;
  336. end;
  337. procedure TSetupForm.Center;
  338. begin
  339. CenterInsideRect(GetRectOfPrimaryMonitor(True));
  340. end;
  341. procedure TSetupForm.CreateParams(var Params: TCreateParams);
  342. begin
  343. inherited;
  344. { With Application.MainFormOnTaskBar=True, by default, a form won't get a
  345. taskbar button if the main form hasn't been created yet (due to the owner
  346. being an invisible Application.Handle), or if the main form exists but
  347. isn't visible (e.g., because it's a silent install). Force it to have a
  348. taskbar button in those cases by specifying no owner for the window.
  349. (Another method is to set WS_EX_APPWINDOW and leave WndParent set to
  350. Application.Handle, but it doesn't quite work correctly: if the form
  351. displays a message box, and you activate another app's window, clicking on
  352. the form's taskbar button activates the message box again, but the taskbar
  353. button doesn't change to a "selected" state.) }
  354. if (Params.WndParent <> 0) and
  355. (Application.MainFormOnTaskBar or (Params.WndParent <> Application.Handle)) and
  356. not IsWindowOnTaskbar(Params.WndParent) then
  357. Params.WndParent := 0;
  358. if FRightToLeft then
  359. Params.ExStyle := Params.ExStyle or (WS_EX_RTLREADING or WS_EX_LEFTSCROLLBAR or WS_EX_RIGHT);
  360. end;
  361. procedure TSetupForm.CreateWnd;
  362. begin
  363. inherited;
  364. if WM_QueryCancelAutoPlay <> 0 then
  365. AddToWindowMessageFilterEx(Handle, WM_QueryCancelAutoPlay);
  366. end;
  367. procedure TSetupForm.FlipControlsIfNeeded;
  368. begin
  369. if FFlipControlsOnShow then begin
  370. FFlipControlsOnShow := False;
  371. FControlsFlipped := not FControlsFlipped;
  372. FlipControls(Self);
  373. end;
  374. end;
  375. procedure TSetupForm.SizeAndCenterIfNeeded(const ACenterInsideControl: Boolean; const CenterInsideControlCtl: TWinControl; const CenterInsideControlInsideClientArea: Boolean);
  376. begin
  377. if FSizeAndCenterOnShow then begin
  378. FSizeAndCenterOnShow := False;
  379. { Apply custom initial size from script - depends on Anchors being set on all the controls }
  380. if ShouldSizeX then
  381. ClientWidth := MulDiv(ClientWidth, SetupHeader.WizardSizePercentX, 100);
  382. if ShouldSizeY then
  383. ClientHeight := MulDiv(ClientHeight, SetupHeader.WizardSizePercentY, 100);
  384. { Center }
  385. if ACenterInsideControl then
  386. CenterInsideControl(CenterInsideControlCtl, CenterInsideControlInsideClientArea)
  387. else
  388. Center;
  389. end;
  390. end;
  391. function TSetupForm.ShouldSizeX: Boolean;
  392. begin
  393. Result := SetupHeader.WizardSizePercentX > 100;
  394. end;
  395. function TSetupForm.ShouldSizeY: Boolean;
  396. begin
  397. Result := not FKeepSizeY and (SetupHeader.WizardSizePercentY > 100);
  398. end;
  399. procedure TSetupForm.FlipSizeAndCenterIfNeeded(const ACenterInsideControl: Boolean;
  400. const CenterInsideControlCtl: TWinControl; const CenterInsideControlInsideClientArea: Boolean);
  401. begin
  402. { 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. }
  403. FlipControlsIfNeeded;
  404. SizeAndCenterIfNeeded(ACenterInsideControl, CenterInsideControlCtl, CenterInsideControlInsideClientArea);
  405. end;
  406. procedure TSetupForm.InitializeFont;
  407. var
  408. ControlAnchorsList: TControlAnchorsList;
  409. W, H: Integer;
  410. R: TRect;
  411. begin
  412. { Note: Must keep the following lines in synch with Setup.ScriptFunc.pas's
  413. InitializeScaleBaseUnits }
  414. SetFontNameSize(Font, LangOptions.DialogFontName, LangOptions.DialogFontSize,
  415. '', 8);
  416. CalculateBaseUnitsFromFont(Font, FBaseUnitX, FBaseUnitY);
  417. if (FBaseUnitX <> OrigBaseUnitX) or (FBaseUnitY <> OrigBaseUnitY) then begin
  418. ControlAnchorsList := TControlAnchorsList.Create;
  419. try
  420. { Custom anchors interfere with our scaling code, so strip them and restore
  421. afterwards. }
  422. StripAndStoreCustomAnchors(Self, ControlAnchorsList);
  423. { Loosely based on scaling code from TForm.ReadState: }
  424. NewScaleControls(Self, BaseUnitX, OrigBaseUnitX, BaseUnitY, OrigBaseUnitY);
  425. R := ClientRect;
  426. W := MulDiv(R.Right, FBaseUnitX, OrigBaseUnitX);
  427. H := MulDiv(R.Bottom, FBaseUnitY, OrigBaseUnitY);
  428. SetBounds(Left, Top, W + (Width - R.Right), H + (Height - R.Bottom));
  429. finally
  430. RestoreAnchors(Self, ControlAnchorsList);
  431. end;
  432. end;
  433. end;
  434. function TSetupForm.ScalePixelsX(const N: Integer): Integer;
  435. begin
  436. Result := MulDiv(N, BaseUnitX, OrigBaseUnitX);
  437. end;
  438. function TSetupForm.ScalePixelsY(const N: Integer): Integer;
  439. begin
  440. Result := MulDiv(N, BaseUnitY, OrigBaseUnitY);
  441. end;
  442. function TSetupForm.ShowModal: Integer;
  443. begin
  444. { Work around VCL issue (Delphi 11.3): ShowModal calls DisableTaskWindows
  445. without ensuring the form's handle has been created first. If the handle
  446. is created after DisableTaskWindows, PopupMode=pmAuto breaks;
  447. TCustomForm.CreateParams finds that the active window is disabled, and
  448. doesn't use it as the owner. It then falls back to pmNone behavior, which
  449. is to use the main form or application window as the owner. }
  450. HandleNeeded;
  451. Result := inherited;
  452. end;
  453. procedure TSetupForm.VisibleChanging;
  454. begin
  455. inherited;
  456. { Note: Unlike DoShow, any exceptions raised in VisibleChanging will be
  457. propagated out, which is what we want }
  458. if not Visible then
  459. FlipSizeAndCenterIfNeeded;
  460. end;
  461. procedure TSetupForm.WMQueryEndSession(var Message: TWMQueryEndSession);
  462. begin
  463. { TDummyClass.AntiShutdownHook in Setup.dpr already denies shutdown attempts
  464. but we also need to catch WM_QUERYENDSESSION here to suppress the VCL's
  465. default handling which calls CloseQuery. We do not want to let TMainForm &
  466. TNewDiskForm display any 'Exit Setup?' message boxes since we're already
  467. denying shutdown attempts, and also we can't allow them to potentially be
  468. displayed on top of another dialog box that's already displayed. }
  469. { Return zero, except if RestartInitiatedByThisProcess is set (which means
  470. we called RestartComputer previously) }
  471. if RestartInitiatedByThisProcess then
  472. Message.Result := 1;
  473. end;
  474. procedure TSetupForm.WndProc(var Message: TMessage);
  475. begin
  476. { When we receive a 'QueryCancelAutoPlay' message as a result of a new CD
  477. being inserted, return 1 to prevent it from being 'autoplayed'.
  478. Note: According to the docs, this message is only sent on Shell version
  479. 4.70 and later. }
  480. if (WM_QueryCancelAutoPlay <> 0) and (Message.Msg = WM_QueryCancelAutoPlay) then
  481. Message.Result := 1
  482. else
  483. inherited;
  484. end;
  485. initialization
  486. BidiUtils.IsParentFlippedFunc := IsParentSetupFormFlipped;
  487. BidiUtils.IsParentRightToLeftFunc := IsParentSetupFormRightToLeft;
  488. WM_QueryCancelAutoPlay := RegisterWindowMessage('QueryCancelAutoPlay');
  489. end.