Setup.SetupForm.pas 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. unit Setup.SetupForm;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. TSetupForm
  8. Also used by UninstallProgressForm and UninstallSharedFileForm!
  9. Requires following globals to be set:
  10. -IsDarkInstallMode
  11. -LangOptions.RightToLeft
  12. -LangOptions.DialogFontName
  13. -LangOptions.DialogFontSize
  14. -LangOptions.DialogFontBaseScaleWidth
  15. -LangOptions.DialogFontBaseScaleHeight
  16. -SetupHeader.WizardLightControlStyling
  17. -shWizardKeepAspectRatio in SetupHeader.Options
  18. Also requires following globals to be set, but 0 is allowed:
  19. -SetupHeader.WizardSizePercentX
  20. -SetupHeader.WizardSizePercentY
  21. }
  22. interface
  23. uses
  24. Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs,
  25. UIStateForm,
  26. Setup.MainFunc;
  27. type
  28. TSetupForm = class(TUIStateForm)
  29. private
  30. FOrigBaseUnitX, FOrigBaseUnitY: Integer;
  31. FBaseUnitX, FBaseUnitY: Integer;
  32. FRightToLeft: Boolean;
  33. FFlipControlsOnShow: Boolean;
  34. FCenterOnShow: Boolean;
  35. FControlsFlipped: Boolean;
  36. FKeepSizeX, FKeepSizeY: Boolean;
  37. FOrigClientWidthAfterScale, FOrigClientHeightAfterScale: Integer;
  38. FSetForeground: Boolean;
  39. FDidDisableChildControlsStylesAsNeeded: Boolean;
  40. class function ShouldDisableControlStylesAsNeeded: Boolean; static;
  41. class procedure DisableControlStyleAsNeeded(const Ctl: TControl); static;
  42. procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
  43. procedure WMQueryEndSession(var Message: TWMQueryEndSession); message WM_QUERYENDSESSION;
  44. protected
  45. procedure Center;
  46. procedure CenterInsideControl(const Ctl: TWinControl;
  47. const InsideClientArea: Boolean);
  48. procedure CenterInsideRect(const InsideRect: TRect);
  49. procedure CreateParams(var Params: TCreateParams); override;
  50. procedure CreateWnd; override;
  51. function GetExtraClientWidth: Integer;
  52. function GetExtraClientHeight: Integer;
  53. procedure FlipControlsIfNeeded;
  54. procedure CenterIfNeeded(const ACenterInsideControl: Boolean;
  55. const CenterInsideControlCtl: TWinControl;
  56. const CenterInsideControlInsideClientArea: Boolean);
  57. procedure VisibleChanging; override;
  58. procedure WndProc(var Message: TMessage); override;
  59. public
  60. constructor Create(AOwner: TComponent); override;
  61. constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); override;
  62. function CalculateButtonWidth(const ButtonCaptions: array of String): Integer;
  63. procedure InitializeFont(const KeepSizeX: Boolean = False; const KeepSizeY: Boolean = False);
  64. class function ScalePixelsX(const OrigBaseUnitX, BaseUnitX, N: Integer): Integer; overload; static;
  65. class function ScalePixelsY(const OrigBaseUnitY, BaseUnitY, N: Integer): Integer; overload; static;
  66. class procedure SetCtlParent(const AControl: TControl; const AParent: TWinControl); static;
  67. function ScalePixelsX(const N: Integer): Integer; overload;
  68. function ScalePixelsY(const N: Integer): Integer; overload;
  69. procedure SetBackImage(const BackImages: TWizardImages; const Stretch, Center: Boolean; const Opacity: Byte; const Redraw: Boolean); overload;
  70. function ShouldSizeX: Boolean;
  71. function ShouldSizeY: Boolean;
  72. function ShowModal: Integer; override;
  73. procedure FlipAndCenterIfNeeded(const ACenterInsideControl: Boolean = False;
  74. const CenterInsideControlCtl: TWinControl = nil;
  75. const CenterInsideControlInsideClientArea: Boolean = False); virtual;
  76. property BaseUnitX: Integer read FBaseUnitX;
  77. published
  78. property CenterOnShow: Boolean read FCenterOnShow write FCenterOnShow;
  79. property ControlsFlipped: Boolean read FControlsFlipped;
  80. property ExtraClientWidth: Integer read GetExtraClientWidth;
  81. property ExtraClientHeight: Integer read GetExtraClientHeight;
  82. property FlipControlsOnShow: Boolean read FFlipControlsOnShow write FFlipControlsOnShow;
  83. property KeepSizeX: Boolean read FKeepSizeX;
  84. property KeepSizeY: Boolean read FKeepSizeY;
  85. property RightToLeft: Boolean read FRightToLeft;
  86. property SetForeground: Boolean read FSetForeground write FSetForeground;
  87. end;
  88. procedure CalculateBaseUnitsFromFont(const Font: TFont; var X, Y: Integer);
  89. function SetFontNameSize(const AFont: TFont; const AName: String;
  90. const ASize: Integer; const AFallbackName: String;
  91. const AFallbackSize: Integer): Boolean;
  92. implementation
  93. uses
  94. Generics.Collections, UITypes, WinXPanels, Themes, StdCtrls, ExtCtrls,
  95. BidiUtils, BitmapButton, BitmapImage, NewNotebook, NewStaticText, NewCheckListBox, FormBackgroundStyleHook,
  96. Shared.Struct, Shared.CommonFunc, Shared.CommonFunc.Vcl,
  97. Setup.InstFunc;
  98. var
  99. WM_QueryCancelAutoPlay: UINT;
  100. function GetRectOfPrimaryMonitor(const WorkArea: Boolean): TRect;
  101. begin
  102. if not WorkArea or
  103. not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
  104. Result := Rect(0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));
  105. end;
  106. function SetFontNameSize(const AFont: TFont; const AName: String;
  107. const ASize: Integer; const AFallbackName: String;
  108. const AFallbackSize: Integer): Boolean;
  109. { Returns True if AName <> '' and it used AName as the font name,
  110. False otherwise. }
  111. function SizeToHeight(const S: Integer): Integer;
  112. begin
  113. Result := MulDiv(-S, Screen.PixelsPerInch, 72);
  114. end;
  115. begin
  116. Result := False;
  117. if AName <> '' then begin
  118. if FontExists(AName) then begin
  119. AFont.Name := AName;
  120. AFont.Height := SizeToHeight(ASize);
  121. Result := True;
  122. Exit;
  123. end;
  124. { Note: AFallbackName is not used if the user specified an empty string for
  125. AName because in that case they want the default font used always }
  126. if (AFallbackName <> '') and FontExists(AFallbackName) then begin
  127. AFont.Name := AFallbackName;
  128. AFont.Height := SizeToHeight(AFallbackSize);
  129. Exit;
  130. end;
  131. end;
  132. AFont.Name := 'Segoe UI';
  133. AFont.Height := SizeToHeight(AFallbackSize);
  134. end;
  135. procedure CalculateBaseUnitsFromFont(const Font: TFont; var X, Y: Integer);
  136. var
  137. DC: HDC;
  138. Size: TSize;
  139. TM: TTextMetric;
  140. begin
  141. DC := GetDC(0);
  142. try
  143. SelectObject(DC, Font.Handle);
  144. { Based on code from Q145994: }
  145. GetTextExtentPoint(DC,
  146. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 52, Size);
  147. X := (Size.cx div 26 + 1) div 2;
  148. GetTextMetrics(DC, TM);
  149. Y := TM.tmHeight;
  150. finally
  151. ReleaseDC(0, DC);
  152. end;
  153. end;
  154. function IsParentSetupFormFlipped(AControl: TControl): Boolean;
  155. function GetParentSetupForm(AControl: TControl): TSetupForm;
  156. begin
  157. { Note: Unlike GetParentForm, this checks all levels, not just the top }
  158. repeat
  159. if AControl is TSetupForm then begin
  160. Result := TSetupForm(AControl);
  161. Exit;
  162. end;
  163. AControl := AControl.Parent;
  164. until AControl = nil;
  165. Result := nil;
  166. end;
  167. var
  168. ParentForm: TSetupForm;
  169. begin
  170. ParentForm := GetParentSetupForm(AControl);
  171. if Assigned(ParentForm) then
  172. Result := ParentForm.ControlsFlipped
  173. else
  174. Result := False;
  175. end;
  176. function GetPPI(const Wnd: HWND): Integer;
  177. begin
  178. { Based on TSysStyleHook.GetCurrentPPI }
  179. if CheckPerMonitorV2SupportForWindow(Wnd) then begin { Currently always False in Setup }
  180. { GetDPIForWindow requires Windows 10 version 1607. However, because it is delay-loaded and it's
  181. never executed on older versions of Windows, it does not cause entry point not found errors. }
  182. Result := Integer(GetDPIForWindow(Wnd))
  183. end else
  184. Result := Screen.PixelsPerInch;
  185. end;
  186. { TSetupForm }
  187. constructor TSetupForm.Create(AOwner: TComponent);
  188. begin
  189. { Must initialize FRightToLeft here in addition to CreateNew because
  190. CreateNew isn't virtual on Delphi 2 and 3 }
  191. FRightToLeft := LangOptions.RightToLeft;
  192. FFlipControlsOnShow := FRightToLeft;
  193. FCenterOnShow := True;
  194. inherited;
  195. { Setting BidiMode before inherited causes an AV when TControl tries to
  196. send CM_BIDIMODECHANGED. This is why we have additonal RTL code in
  197. CreateParams below. }
  198. if FRightToLeft then
  199. BiDiMode := bdRightToLeft;
  200. { In Delphi 2005 and later, Position defaults to poDefaultPosOnly, but we
  201. don't want the form to be changing positions whenever its handle is
  202. recreated, so change it to the D7 and earlier default of poDesigned. }
  203. if Position = poDefaultPosOnly then
  204. Position := poDesigned;
  205. end;
  206. constructor TSetupForm.CreateNew(AOwner: TComponent; Dummy: Integer = 0);
  207. begin
  208. { Note: On Delphi 2 and 3, CreateNew isn't virtual, so this is only reached
  209. when TSetupForm.CreateNew is called explicitly }
  210. FRightToLeft := LangOptions.RightToLeft;
  211. FFlipControlsOnShow := FRightToLeft;
  212. FCenterOnShow := True;
  213. inherited;
  214. if FRightToLeft then
  215. BiDiMode := bdRightToLeft;
  216. end;
  217. function TSetupForm.CalculateButtonWidth(const ButtonCaptions: array of String): Integer;
  218. var
  219. DC: HDC;
  220. W: Integer;
  221. begin
  222. Result := ScalePixelsX(75);
  223. { Increase the button size if there are unusually long button captions }
  224. DC := GetDC(0);
  225. try
  226. SelectObject(DC, Font.Handle);
  227. for var I := Low(ButtonCaptions) to High(ButtonCaptions) do begin
  228. W := GetTextWidth(DC, ButtonCaptions[I], True) + ScalePixelsX(20);
  229. if Result < W then
  230. Result := W;
  231. end;
  232. finally
  233. ReleaseDC(0, DC);
  234. end;
  235. end;
  236. procedure TSetupForm.CenterInsideControl(const Ctl: TWinControl;
  237. const InsideClientArea: Boolean);
  238. var
  239. R: TRect;
  240. begin
  241. const CtlForm = GetParentForm(Ctl);
  242. if (CtlForm = nil) or not IsWindowVisible(CtlForm.Handle) or
  243. IsIconic(CtlForm.Handle) then begin
  244. Center;
  245. Exit;
  246. end;
  247. if not InsideClientArea then begin
  248. if GetWindowRect(Ctl.Handle, R) then
  249. CenterInsideRect(R);
  250. end
  251. else begin
  252. R := Ctl.ClientRect;
  253. MapWindowPoints(Ctl.Handle, 0, R, 2);
  254. CenterInsideRect(R);
  255. end;
  256. end;
  257. procedure TSetupForm.CenterInsideRect(const InsideRect: TRect);
  258. function GetRectOfMonitorContainingRect(const R: TRect): TRect;
  259. { Returns bounding rectangle of monitor containing or nearest to R }
  260. type
  261. HMONITOR = type THandle;
  262. TMonitorInfo = record
  263. cbSize: DWORD;
  264. rcMonitor: TRect;
  265. rcWork: TRect;
  266. dwFlags: DWORD;
  267. end;
  268. const
  269. MONITOR_DEFAULTTONEAREST = $00000002;
  270. var
  271. Module: HMODULE;
  272. MonitorFromRect: function(const lprc: TRect; dwFlags: DWORD): HMONITOR; stdcall;
  273. GetMonitorInfo: function(hMonitor: HMONITOR; var lpmi: TMonitorInfo): BOOL; stdcall;
  274. M: HMONITOR;
  275. Info: TMonitorInfo;
  276. begin
  277. Module := GetModuleHandle(user32);
  278. MonitorFromRect := GetProcAddress(Module, 'MonitorFromRect');
  279. GetMonitorInfo := GetProcAddress(Module, 'GetMonitorInfoA');
  280. if Assigned(MonitorFromRect) and Assigned(GetMonitorInfo) then begin
  281. M := MonitorFromRect(R, MONITOR_DEFAULTTONEAREST);
  282. Info.cbSize := SizeOf(Info);
  283. if GetMonitorInfo(M, Info) then begin
  284. Result := Info.rcWork;
  285. Exit;
  286. end;
  287. end;
  288. Result := GetRectOfPrimaryMonitor(True);
  289. end;
  290. var
  291. R, MR: TRect;
  292. begin
  293. R := Bounds(InsideRect.Left + ((InsideRect.Right - InsideRect.Left) - Width) div 2,
  294. InsideRect.Top + ((InsideRect.Bottom - InsideRect.Top) - Height) div 2,
  295. Width, Height);
  296. { Clip to nearest monitor }
  297. MR := GetRectOfMonitorContainingRect(R);
  298. if R.Right > MR.Right then
  299. OffsetRect(R, MR.Right - R.Right, 0);
  300. if R.Bottom > MR.Bottom then
  301. OffsetRect(R, 0, MR.Bottom - R.Bottom);
  302. if R.Left < MR.Left then
  303. OffsetRect(R, MR.Left - R.Left, 0);
  304. if R.Top < MR.Top then
  305. OffsetRect(R, 0, MR.Top - R.Top);
  306. BoundsRect := R;
  307. end;
  308. procedure TSetupForm.Center;
  309. begin
  310. CenterInsideRect(GetRectOfPrimaryMonitor(True));
  311. end;
  312. procedure TSetupForm.CreateParams(var Params: TCreateParams);
  313. begin
  314. inherited;
  315. { With Application.MainFormOnTaskBar=True, by default, a form won't get a
  316. taskbar button if the main form hasn't been created yet (due to the owner
  317. being an invisible Application.Handle), or if the main form exists but
  318. isn't visible (e.g., because it's a silent install). Force it to have a
  319. taskbar button in those cases by specifying no owner for the window.
  320. (Another method is to set WS_EX_APPWINDOW and leave WndParent set to
  321. Application.Handle, but it doesn't quite work correctly: if the form
  322. displays a message box, and you activate another app's window, clicking on
  323. the form's taskbar button activates the message box again, but the taskbar
  324. button doesn't change to a "selected" state.) }
  325. if (Params.WndParent <> 0) and
  326. (Application.MainFormOnTaskBar or (Params.WndParent <> Application.Handle)) and
  327. not IsWindowOnTaskbar(Params.WndParent) then
  328. Params.WndParent := 0;
  329. { See comment in Create. Also: The following does not make the title bar RTL.
  330. Achieving this requires adding WS_EX_LAYOUTRTL, which VCL does not support. }
  331. if FRightToLeft then
  332. Params.ExStyle := Params.ExStyle or (WS_EX_RTLREADING or WS_EX_LEFTSCROLLBAR or WS_EX_RIGHT);
  333. end;
  334. class function TSetupForm.ShouldDisableControlStylesAsNeeded: Boolean;
  335. begin
  336. Result := not IsDarkInstallMode and (SetupHeader.WizardLightControlStyling <> wcsAll);
  337. if Result then begin
  338. const LStyle = StyleServices;
  339. Result := LStyle.Enabled and not LStyle.IsSystemStyle;
  340. end;
  341. end;
  342. class procedure TSetupForm.DisableControlStyleAsNeeded(const Ctl: TControl);
  343. { Call ShouldDisableControlStylesAsNeeded first }
  344. begin
  345. { SetupHeader.WizardLightControlStyling is either wcsAllButButtons or wcsOnlyRequired,
  346. so for buttons the style must always be disabled. }
  347. if Ctl is TCustomButton then
  348. Ctl.StyleName := TStyleManager.SystemStyleName
  349. else if SetupHeader.WizardLightControlStyling = wcsOnlyRequired then begin
  350. if (Ctl is TNewCheckListBox) and TNewCheckListBox(Ctl).TransparentIfStyled then begin
  351. { Requires VCL Styles for transparency, but can be told to use native checkboxes and radiobuttons }
  352. TNewCheckListBox(Ctl).DisableStyledButtons := True;
  353. end else begin
  354. const KeepStyle =
  355. (Ctl is TCustomPanel) or
  356. (Ctl is TBitmapButton) or (Ctl is TBitmapImage) or { Don't use VCL Styles }
  357. (Ctl is TNewNotebook) or (Ctl is TNewNotebookPage) or { Don't use VCL Styles }
  358. ((Ctl is TNewStaticText) and TNewStaticText(Ctl).Transparent); { Requires VCL Styles for transparency }
  359. if not KeepStyle then
  360. Ctl.StyleName := TStyleManager.SystemStyleName;
  361. end;
  362. end;
  363. end;
  364. procedure TSetupForm.CreateWnd;
  365. procedure DisableChildControlsStylesAsNeeded(const ParentCtl: TWinControl);
  366. { Call ShouldDisableControlStylesAsNeeded first }
  367. begin
  368. for var I := 0 to ParentCtl.ControlCount-1 do begin
  369. const Ctl = ParentCtl.Controls[I];
  370. if Ctl is TWinControl then begin
  371. const WinCtl = Ctl as TWinControl;
  372. { Sanity check that the control's handle isn't already allocated,
  373. because otherwise it would run TWinControl.UpdateStyleElements
  374. which does a RecreateWnd. Might work but isn't efficient. }
  375. if WinCtl.HandleAllocated then
  376. InternalError('Unexpected HandleAllocated');
  377. { Update children }
  378. DisableChildControlsStylesAsNeeded(WinCtl);
  379. end;
  380. { Update self }
  381. DisableControlStyleAsNeeded(Ctl);
  382. end;
  383. end;
  384. procedure SetControlsCurrentPPI(const Ctl: TWinControl; const PPI: Integer);
  385. begin
  386. for var I := 0 to Ctl.ControlCount-1 do begin
  387. const C = Ctl.Controls[I];
  388. if C is TWinControl then begin
  389. SetControlsCurrentPPI(TWinControl(C), PPI);
  390. C.SetCurrentPPI(PPI);
  391. end else
  392. C.SetCurrentPPI(PPI)
  393. end;
  394. end;
  395. begin
  396. { DisableChildControlsStylesAsNeeded works both before and after
  397. calling inherited. But it does require the child controls to have
  398. no handle allocated, which is why it's in CreateWnd and not in
  399. Create: in Create it can't be before inherited since it wouldn't
  400. yet know about the children, and also not after since the
  401. handles might be allocated. }
  402. if ShouldDisableControlStylesAsNeeded and not FDidDisableChildControlsStylesAsNeeded then begin
  403. DisableChildControlsStylesAsNeeded(Self);
  404. { Don't need to disable again if the window is recreated }
  405. FDidDisableChildControlsStylesAsNeeded := True;
  406. end;
  407. inherited;
  408. if WM_QueryCancelAutoPlay <> 0 then
  409. AddToWindowMessageFilterEx(Handle, WM_QueryCancelAutoPlay);
  410. { SetDarkTitleBar requires Handle to be allocated, which is why we
  411. we call it after calling inherited. }
  412. if (TStyleManager.FormBorderStyle = fbsSystemStyle) or not (seBorder in StyleElements) then
  413. SetDarkTitleBar(Self, IsDarkInstallMode);
  414. { We don't use the Scaled property for scaling and this means the CurrentPPI property will not be
  415. set correctly. This causes problems when VCL code inspects it, for example in THintWindow.CalcHintRect
  416. and FormStyleHook.GetBorderSize. So we should update it ourselves by directly writing to the
  417. FCurrentPPI private variable of the form and all controls on it, which we can do using a class
  418. helper. Note: Doing it later for the form causes issues with incorrect non-client vs. client size
  419. when styled title bars are enabled. }
  420. const PPI = GetPPI(Handle);
  421. SetCurrentPPI(PPI);
  422. SetControlsCurrentPPI(Self, PPI);
  423. { Now that CurrentPPI of the form is set you must make sure that any controls you later parent to
  424. the form already have the same CurrentPPI, otherwise VCL will scale the controls. Currently this
  425. is done in:
  426. -Setup.ScriptClasses's TControlParentW and TNewNotebookPageNotebook_W
  427. -Setup.WizardForm.CustomPages's SetCtlParent
  428. -TWizardForm.AddPage
  429. To debug/detect scaling add a breakpoint in Vcl.Controls' TWinControl.ChangeScale and set project
  430. option Building->Delphi Compiler->Compiling->Debugging->Use debug .dcus. }
  431. end;
  432. procedure TSetupForm.FlipControlsIfNeeded;
  433. begin
  434. if FFlipControlsOnShow then begin
  435. FFlipControlsOnShow := False;
  436. FControlsFlipped := not FControlsFlipped;
  437. FlipControls(Self);
  438. end;
  439. end;
  440. procedure TSetupForm.CenterIfNeeded(const ACenterInsideControl: Boolean; const CenterInsideControlCtl: TWinControl; const CenterInsideControlInsideClientArea: Boolean);
  441. begin
  442. if FCenterOnShow then begin
  443. FCenterOnShow := False;
  444. { Center }
  445. if ACenterInsideControl and (CenterInsideControlCtl <> nil) then
  446. CenterInsideControl(CenterInsideControlCtl, CenterInsideControlInsideClientArea)
  447. else
  448. Center;
  449. end;
  450. end;
  451. function TSetupForm.ShouldSizeX: Boolean;
  452. begin
  453. Result := not FKeepSizeX and (SetupHeader.WizardSizePercentX > 100);
  454. end;
  455. function TSetupForm.ShouldSizeY: Boolean;
  456. begin
  457. Result := not FKeepSizeY and (SetupHeader.WizardSizePercentY > 100);
  458. end;
  459. procedure TSetupForm.FlipAndCenterIfNeeded(const ACenterInsideControl: Boolean;
  460. const CenterInsideControlCtl: TWinControl; const CenterInsideControlInsideClientArea: Boolean);
  461. begin
  462. FlipControlsIfNeeded;
  463. CenterIfNeeded(ACenterInsideControl, CenterInsideControlCtl, CenterInsideControlInsideClientArea);
  464. end;
  465. type
  466. TControlAccess = class(TControl);
  467. procedure TSetupForm.InitializeFont(const KeepSizeX, KeepSizeY: Boolean);
  468. procedure NewChangeScale(const Ctl: TControl; const XM, XD, YM, YD: Integer);
  469. var
  470. X, Y, W, H: Integer;
  471. begin
  472. X := MulDiv(Ctl.Left, XM, XD);
  473. Y := MulDiv(Ctl.Top, YM, YD);
  474. if not(csFixedWidth in Ctl.ControlStyle) then
  475. W := MulDiv(Ctl.Width, XM, XD)
  476. else
  477. W := Ctl.Width;
  478. if not(csFixedHeight in Ctl.ControlStyle) then
  479. H := MulDiv(Ctl.Height, YM, YD)
  480. else
  481. H := Ctl.Height;
  482. Ctl.SetBounds(X, Y, W, H);
  483. end;
  484. procedure NewScaleControls(const Ctl: TWinControl; const XM, XD, YM, YD: Integer);
  485. { This is like TControl.ScaleControls, except it allows the width and height
  486. to be scaled independently }
  487. var
  488. I: Integer;
  489. C: TControl;
  490. begin
  491. for I := 0 to Ctl.ControlCount-1 do begin
  492. C := Ctl.Controls[I];
  493. if C is TWinControl then begin
  494. TWinControl(C).DisableAlign;
  495. try
  496. NewScaleControls(TWinControl(C), XM, XD, YM, YD);
  497. NewChangeScale(C, XM, XD, YM, YD);
  498. finally
  499. TWinControl(C).EnableAlign;
  500. end;
  501. end
  502. else
  503. NewChangeScale(C, XM, XD, YM, YD);
  504. end;
  505. end;
  506. type
  507. TControlAnchorsList = TDictionary<TControl, TAnchors>;
  508. procedure StripAndStoreChildControlCustomAnchors(const ParentCtl: TControl; const AnchorsList: TControlAnchorsList);
  509. begin
  510. if ParentCtl is TWinControl then begin
  511. const ParentWinCtl = TWinControl(ParentCtl);
  512. for var I := 0 to ParentWinCtl.ControlCount-1 do begin
  513. const Ctl = ParentWinCtl.Controls[I];
  514. if Ctl.Anchors <> [akLeft, akTop] then begin
  515. AnchorsList.Add(Ctl, Ctl.Anchors);
  516. { Before we can set Anchors to [akLeft, akTop] (which has a special
  517. 'no anchors' meaning to VCL), we first need to update the Explicit*
  518. properties so the control doesn't get moved back to an old position }
  519. TControlAccess(Ctl).UpdateExplicitBounds;
  520. Ctl.Anchors := [akLeft, akTop];
  521. end;
  522. StripAndStoreChildControlCustomAnchors(Ctl, AnchorsList);
  523. end;
  524. end;
  525. end;
  526. procedure RestoreAnchors(const AnchorsList: TControlAnchorsList);
  527. begin
  528. { The order in which we restore the anchors shouldn't matter, so just
  529. enumerate the list }
  530. for var Item in AnchorsList do
  531. Item.Key.Anchors := Item.Value;
  532. end;
  533. function ExcludeFromParentHandlesNeeded(const ParentCtl: TWinControl): Boolean;
  534. begin
  535. { Right-aligned TStackPanels are excluded. For example, calling
  536. HandleNeeded on TaskDialogForm's BottomStackPanel causes it to become
  537. left-aligned instead of right-aligned. This occurs regardless of the
  538. timing of the HandleNeeded call, such as before or after sizing. }
  539. Result := (ParentCtl is TStackPanel) and (TStackPanel(ParentCtl).Align = alRight);
  540. end;
  541. procedure ParentHandlesNeeded(const ParentCtl: TControl);
  542. begin
  543. if ParentCtl is TWinControl then begin
  544. const ParentWinCtl = TWinControl(ParentCtl);
  545. if (ParentWinCtl.ControlCount > 0) and not ExcludeFromParentHandlesNeeded(ParentWinCtl) then begin
  546. if not (ParentWinCtl is TNewNotebook) then { For notebooks: only need handles on pages }
  547. ParentWinCtl.HandleNeeded;
  548. for var I := 0 to ParentWinCtl.ControlCount-1 do
  549. ParentHandlesNeeded(ParentWinCtl.Controls[I]);
  550. end;
  551. end;
  552. end;
  553. begin
  554. { Create parent handles.
  555. Various things related to positioning and anchoring don't work without this:
  556. you get positions of child controls back as if there was no anchoring until
  557. handles are automatically created.
  558. Initially we did this only when sizing the form (for WizardForm it worked if
  559. done after sizing but for UninstallProgressForm it had be done before sizing,
  560. for unknown reasons).
  561. For WizardForm's BeveledLabel though, it needs it before the font name/size
  562. change (again for unknown reasons), otherwise the label will end up in the
  563. wrong position, even if all we do is changing the font. Setting AutoSize to
  564. False also causes it to stay in the correct position. (To see the bad
  565. positioning for WizardForm.BeveledLabel you would first have to disable next
  566. ParentHandlesNeeded call and then also the automatic vertical recentering in
  567. WizardForm.)
  568. Doing it always, instead of only before or after sizing, also helps
  569. TaskDialogForm which does its own sizing (so KeepSizeX and KeepSizeY are
  570. both True), but still needs parent handles to be created to avoid the issue.
  571. Note: Caller should make sure the created handles do not get lost again. For
  572. example, setting StyleElements to [] on a parent would cause the handle to
  573. get deallocated again, and would reintroduce the issue. So this must be done
  574. before calling us, and not after. }
  575. ParentHandlesNeeded(Self); { Also see ShowModal }
  576. { Set font. Note: Must keep the following lines in synch with Setup.ScriptFunc.pas's
  577. InitializeScaleBaseUnits }
  578. SetFontNameSize(Font, LangOptions.DialogFontName, LangOptions.DialogFontSize, '', 9);
  579. CalculateBaseUnitsFromFont(Font, FBaseUnitX, FBaseUnitY);
  580. FOrigBaseUnitX := LangOptions.DialogFontBaseScaleWidth;
  581. FOrigBaseUnitY := LangOptions.DialogFontBaseScaleHeight;
  582. if shWizardKeepAspectRatio in SetupHeader.Options then begin
  583. if FBaseUnitX * FOrigBaseUnitY > FBaseUnitY * FOrigBaseUnitX then begin
  584. FBaseUnitY := FBaseUnitX;
  585. FOrigBaseUnitY := FOrigBaseUnitX;
  586. end else begin
  587. FBaseUnitX := FBaseUnitY;
  588. FOrigBaseUnitX := FOrigBaseUnitY;
  589. end;
  590. end;
  591. { Scale }
  592. if (FBaseUnitX <> FOrigBaseUnitX) or (FBaseUnitY <> FOrigBaseUnitY) then begin
  593. const ControlAnchorsList = TControlAnchorsList.Create;
  594. try
  595. { Custom anchors interfere with our scaling code, so strip them and restore
  596. afterward }
  597. StripAndStoreChildControlCustomAnchors(Self, ControlAnchorsList);
  598. { Loosely based on scaling code from TForm.ReadState: }
  599. NewScaleControls(Self, FBaseUnitX, FOrigBaseUnitX, FBaseUnitY, FOrigBaseUnitY);
  600. const R = ClientRect;
  601. const W = MulDiv(R.Right, FBaseUnitX, FOrigBaseUnitX);
  602. const H = MulDiv(R.Bottom, FBaseUnitY, FOrigBaseUnitY);
  603. SetBounds(Left, Top, W + (Width - R.Right), H + (Height - R.Bottom));
  604. finally
  605. RestoreAnchors(ControlAnchorsList);
  606. ControlAnchorsList.Free;
  607. end;
  608. end;
  609. { Size }
  610. FKeepSizeX := KeepSizeX;
  611. FKeepSizeY := KeepSizeY;
  612. FOrigClientWidthAfterScale := ClientWidth;
  613. FOrigClientHeightAfterScale := ClientHeight;
  614. const LShouldSizeX = ShouldSizeX;
  615. const LShouldSizeY = ShouldSizeY;
  616. if LShouldSizeX then
  617. ClientWidth := MulDiv(ClientWidth, SetupHeader.WizardSizePercentX, 100);
  618. if LShouldSizeY then
  619. ClientHeight := MulDiv(ClientHeight, SetupHeader.WizardSizePercentY, 100);
  620. end;
  621. function TSetupForm.GetExtraClientWidth: Integer;
  622. begin
  623. Result := ClientWidth - FOrigClientWidthAfterScale;
  624. end;
  625. function TSetupForm.GetExtraClientHeight: Integer;
  626. begin
  627. Result := ClientHeight - FOrigClientHeightAfterScale;
  628. end;
  629. class function TSetupForm.ScalePixelsX(const OrigBaseUnitX, BaseUnitX, N: Integer): Integer;
  630. begin
  631. Result := MulDiv(N, BaseUnitX, OrigBaseUnitX);
  632. end;
  633. function TSetupForm.ScalePixelsX(const N: Integer): Integer;
  634. begin
  635. Result := ScalePixelsX(FOrigBaseUnitX, FBaseUnitX, N);
  636. end;
  637. class function TSetupForm.ScalePixelsY(const OrigBaseUnitY, BaseUnitY, N: Integer): Integer;
  638. begin
  639. Result := MulDiv(N, BaseUnitY, OrigBaseUnitY);
  640. end;
  641. function TSetupForm.ScalePixelsY(const N: Integer): Integer;
  642. begin
  643. Result := ScalePixelsY(FOrigBaseUnitY, FBaseUnitY, N);
  644. end;
  645. procedure TSetupForm.SetBackImage(const BackImages: TWizardImages; const Stretch, Center: Boolean;
  646. const Opacity: Byte; const Redraw: Boolean);
  647. begin
  648. if not CustomWizardBackground then
  649. InternalError('Cannot set a background image at this time: custom wizard background not active');
  650. const Graphic = SelectBestImage(BackImages, ClientWidth, ClientHeight);
  651. TFormBackgroundStyleHook.Graphic := Graphic;
  652. TFormBackgroundStyleHook.GraphicTarget := Self;
  653. TFormBackgroundStyleHook.Stretch := Stretch;
  654. TFormBackgroundStyleHook.Center := Center;
  655. TFormBackgroundStyleHook.Opacity := Opacity;
  656. TNewCheckListBox.ComplexParentBackground := Graphic <> nil;
  657. if Redraw and HandleAllocated then
  658. RedrawWindow(Handle, nil, 0, RDW_INVALIDATE or RDW_ERASE or RDW_UPDATENOW or RDW_ALLCHILDREN);
  659. end;
  660. class procedure TSetupForm.SetCtlParent(const AControl: TControl; const AParent: TWinControl);
  661. { To be called when a control is added after the form has already been created }
  662. begin
  663. { Disable style if needed }
  664. if ShouldDisableControlStylesAsNeeded then
  665. DisableControlStyleAsNeeded(AControl);
  666. { Set CurrentPPI of the control to be parented to the CurrentPPI of the parent, preventing VCL
  667. from scaling the control. Also see TSetupForm.CreateWnd. }
  668. AControl.SetCurrentPPI(AParent.CurrentPPI);
  669. AControl.Parent := AParent;
  670. end;
  671. function TSetupForm.ShowModal: Integer;
  672. begin
  673. { Work around VCL issue (Delphi 11.3): ShowModal calls DisableTaskWindows
  674. without ensuring the form's handle has been created first. If the handle
  675. is created after DisableTaskWindows, PopupMode=pmAuto breaks;
  676. TCustomForm.CreateParams finds that the active window is disabled, and
  677. doesn't use it as the owner. It then falls back to pmNone behavior, which
  678. is to use the main form or application window as the owner. }
  679. HandleNeeded; { Also see InitializeFont }
  680. Result := inherited;
  681. end;
  682. procedure TSetupForm.VisibleChanging;
  683. begin
  684. inherited;
  685. { Note: Unlike DoShow, any exceptions raised in VisibleChanging will be
  686. propagated out, which is what we want }
  687. if not Visible then
  688. FlipAndCenterIfNeeded;
  689. end;
  690. procedure TSetupForm.CMShowingChanged(var Message: TMessage);
  691. begin
  692. inherited;
  693. { This usually just makes the taskbar button flash }
  694. if FSetForeground and Showing then
  695. SetForegroundWindow(Handle);
  696. end;
  697. procedure TSetupForm.WMQueryEndSession(var Message: TWMQueryEndSession);
  698. begin
  699. { TDummyClass.AntiShutdownHook in Setup.dpr already denies shutdown attempts
  700. but we also need to catch WM_QUERYENDSESSION here to suppress the VCL's
  701. default handling which calls CloseQuery. We do not want to let TMainForm &
  702. TNewDiskForm display any 'Exit Setup?' message boxes since we're already
  703. denying shutdown attempts, and also we can't allow them to potentially be
  704. displayed on top of another dialog box that's already displayed. }
  705. { Return zero, except if RestartInitiatedByThisProcess is set (which means
  706. we called RestartComputer previously) }
  707. if RestartInitiatedByThisProcess then
  708. Message.Result := 1;
  709. end;
  710. procedure TSetupForm.WndProc(var Message: TMessage);
  711. begin
  712. { When we receive a 'QueryCancelAutoPlay' message as a result of a new CD
  713. being inserted, return 1 to prevent it from being 'autoplayed'.
  714. Note: According to the docs, this message is only sent on Shell version
  715. 4.70 and later. }
  716. if (WM_QueryCancelAutoPlay <> 0) and (Message.Msg = WM_QueryCancelAutoPlay) then
  717. Message.Result := 1
  718. else
  719. inherited;
  720. end;
  721. initialization
  722. BidiUtils.IsParentFlippedFunc := IsParentSetupFormFlipped;
  723. WM_QueryCancelAutoPlay := RegisterWindowMessage('QueryCancelAutoPlay');
  724. end.