SetupForm.pas 16 KB

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