2
0

Setup.TaskDialogForm.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. unit Setup.TaskDialogForm;
  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. Task Dialog form which can be styled
  8. Supports up to three command links
  9. }
  10. interface
  11. uses
  12. Windows, Messages, SysUtils, Variants, Classes, Graphics,
  13. Controls, Forms, Dialogs, WinXPanels, ExtCtrls, StdCtrls,
  14. NewCtrls, BitmapImage, NewStaticText,
  15. Setup.SetupForm;
  16. const
  17. TD_TASKFORM_HELP_ICON = MAKEINTRESOURCEW(Word(-100));
  18. type
  19. TCopyFormat = (cfTaskDialog, cfMessageBox);
  20. TTaskDialogForm = class(TSetupForm)
  21. BottomPanel: TPanel;
  22. MainPanel: TPanel;
  23. LeftPanel: TPanel;
  24. BitmapImage: TBitmapImage;
  25. MainStackPanel: TStackPanel;
  26. InstructionText: TNewStaticText;
  27. TextText: TNewStaticText;
  28. MainButton1: TNewButton;
  29. MainButton2: TNewButton;
  30. MainButton3: TNewButton;
  31. BottomStackPanel: TStackPanel;
  32. OkButton: TNewButton;
  33. YesButton: TNewButton;
  34. NoButton: TNewButton;
  35. RetryButton: TNewButton;
  36. CancelButton: TNewButton;
  37. BottomPanel2: TPanel;
  38. VerificationCheck: TNewCheckBox;
  39. procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  40. private
  41. FCommonButtons: array of TNewButton;
  42. FCommonButtonFlags: array of Cardinal;
  43. FMainButtons: array of TNewButton;
  44. FCopyFormat: TCopyFormat;
  45. procedure Finish(const DefCommonButton: Integer);
  46. procedure UpdateCommonButtons(const CommonButtons: Cardinal);
  47. procedure UpdateIcon(const Icon: PChar);
  48. procedure UpdateInstructionAndText(const Instruction, Text: String);
  49. procedure UpdateHeight;
  50. procedure UpdateMainButtonsAndBorderIcons(const CommonButtons: Cardinal;
  51. const ButtonLabels: array of String; const ButtonIDs: array of Integer; const ShieldButton: Integer);
  52. procedure UpdateVerificationText(const VerificationText: String; const pfVerificationFlagChecked: PBOOL);
  53. public
  54. constructor Create(AOwner: TComponent; const ACopyFormat: TCopyFormat; const ASetForeground: Boolean); reintroduce;
  55. end;
  56. function TaskDialogForm(const Instruction, Text, Caption: String; const Icon: PChar;
  57. const CommonButtons: Cardinal; const ButtonLabels: array of String; const ButtonIDs: array of Integer;
  58. const DefCommonButton, ShieldButton: Integer; const TriggerMessageBoxCallbackFuncFlags: Cardinal;
  59. const VerificationText: String; const pfVerificationFlagChecked: PBOOL; const CopyFormat: TCopyFormat;
  60. const SetForeground: Boolean): Integer;
  61. implementation
  62. uses
  63. CommCtrl, Clipbrd, Themes, ShellAPI,
  64. Shared.SetupMessageIDs, Shared.CommonFunc, Shared.CommonFunc.Vcl,
  65. SetupLdrAndSetup.Messages, Setup.WizardForm, Setup.MainFunc;
  66. {$R *.dfm}
  67. function TaskDialogForm(const Instruction, Text, Caption: String; const Icon: PChar;
  68. const CommonButtons: Cardinal; const ButtonLabels: array of String; const ButtonIDs: array of Integer;
  69. const DefCommonButton, ShieldButton: Integer; const TriggerMessageBoxCallbackFuncFlags: Cardinal;
  70. const VerificationText: String; const pfVerificationFlagChecked: PBOOL; const CopyFormat: TCopyFormat;
  71. const SetForeground: Boolean): Integer;
  72. begin
  73. const Form = TTaskDialogForm.Create(nil, CopyFormat, SetForeground);
  74. try
  75. Form.Caption := Caption;
  76. Form.UpdateInstructionAndText(Instruction, Text);
  77. Form.UpdateIcon(Icon);
  78. Form.UpdateCommonButtons(CommonButtons);
  79. Form.UpdateVerificationText(VerificationText, pfVerificationFlagChecked);
  80. if (Pos(':\', Text) <> 0) or (Pos('\\', Text) <> 0) then
  81. Form.Width := MulDiv(Form.Width, 125, 100);
  82. if Form.InstructionText.Visible then
  83. Form.InstructionText.AdjustHeight;
  84. if Form.TextText.Visible then
  85. Form.TextText.AdjustHeight;
  86. Form.UpdateMainButtonsAndBorderIcons(CommonButtons, ButtonLabels, ButtonIDs, ShieldButton);
  87. Form.UpdateHeight;
  88. Form.Finish(DefCommonButton);
  89. TriggerMessageBoxCallbackFunc(TriggerMessageBoxCallbackFuncFlags, False);
  90. try
  91. Result := Form.ShowModal;
  92. if pfVerificationFlagChecked <> nil then
  93. pfVerificationFlagChecked^ := Form.VerificationCheck.Checked;
  94. finally
  95. TriggerMessageBoxCallbackFunc(TriggerMessageBoxCallbackFuncFlags, True);
  96. end;
  97. finally
  98. Form.Free;
  99. end;
  100. end;
  101. { TTaskDialogForm }
  102. constructor TTaskDialogForm.Create(AOwner: TComponent; const ACopyFormat: TCopyFormat; const ASetForeground: Boolean);
  103. begin
  104. inherited Create(AOwner);
  105. FCommonButtons := [OkButton, YesButton, NoButton, RetryButton, CancelButton];
  106. FCommonButtonFlags := [TDCBF_OK_BUTTON, TDCBF_YES_BUTTON, TDCBF_NO_BUTTON, TDCBF_RETRY_BUTTON, TDCBF_CANCEL_BUTTON];
  107. FMainButtons := [MainButton1, MainButton2, MainButton3];
  108. FCopyFormat := ACopyFormat;
  109. SetForeground := ASetForeground;
  110. if not CustomWizardBackground or (SetupHeader.WizardBackColor = clWindow) then begin
  111. var LStyle := StyleServices(Self);
  112. if not LStyle.Enabled or LStyle.IsSystemStyle then
  113. LStyle := nil;
  114. if LStyle <> nil then begin
  115. { Make MainPanel look the same as WizardForm's main area }
  116. MainPanel.StyleElements := [];
  117. MainPanel.Color := LStyle.GetStyleColor(scWindow);
  118. end;
  119. end else
  120. MainPanel.ParentBackground := True;
  121. { KeepSizeX: Already bit wider than regular task dialogs
  122. KeepSizeY: UpdateHeight will set height }
  123. InitializeFont(True, True);
  124. const Pad = 10;
  125. const PadX = TMarginSize(ScalePixelsX(Pad));
  126. const PadY = TMarginSize(ScalePixelsY(Pad));
  127. MainPanel.Padding.Left := PadX;
  128. MainPanel.Padding.Top := PadY;
  129. MainPanel.Padding.Right := PadX;
  130. MainPanel.Padding.Bottom := PadY;
  131. MainStackPanel.Padding.Left := PadX; { Also see Finish }
  132. MainStackPanel.Spacing := PadY;
  133. BottomStackPanel.Spacing := PadX;
  134. BottomStackPanel.Padding.Right := PadX; { Also see Finish }
  135. VerificationCheck.Left := PadX;
  136. OkButton.Caption := SetupMessages[msgButtonOK];
  137. YesButton.Caption := SetupMessages[msgButtonYes];
  138. NoButton.Caption := SetupMessages[msgButtonNo];
  139. RetryButton.Caption := SetupMessages[msgAbortRetryIgnoreRetry];
  140. CancelButton.Caption := SetupMessages[msgButtonCancel];
  141. end;
  142. procedure TTaskDialogForm.Finish(const DefCommonButton: Integer);
  143. begin
  144. if RightToLeft then begin
  145. { FlipSizeAndCenterIfNeeded does not update Align or Padding }
  146. if LeftPanel.Visible then
  147. LeftPanel.Align := alRight;
  148. MainStackPanel.Padding.Right := MainStackPanel.Padding.Left;
  149. MainStackPanel.Padding.Left := 0;
  150. if BottomPanel.Visible then begin
  151. BottomStackPanel.Align := alLeft;
  152. BottomStackPanel.Padding.Left := BottomStackPanel.Padding.Right;
  153. BottomStackPanel.Padding.Right := 0;
  154. end;
  155. end;
  156. if DefCommonButton > 0 then begin
  157. var I := DefCommonButton;
  158. for var CommonButton in FCommonButtons do begin
  159. if CommonButton.Visible then begin
  160. Dec(I);
  161. if I = 0 then begin
  162. ActiveControl := CommonButton;
  163. Exit;
  164. end;
  165. end;
  166. end;
  167. end;
  168. end;
  169. procedure TTaskDialogForm.UpdateCommonButtons(const CommonButtons: Cardinal);
  170. var
  171. VisibleCaptions: array of String;
  172. begin
  173. var NVisibleCaptions := 0;
  174. for var I := 0 to Length(FCommonButtons)-1 do begin
  175. const CommonButton = FCommonButtons[I];
  176. const CommonButtonFlag = FCommonButtonFlags[I];
  177. CommonButton.Visible := CommonButtons and CommonButtonFlag <> 0;
  178. if CommonButton.Visible then begin
  179. Inc(NVisibleCaptions);
  180. SetLength(VisibleCaptions, NVisibleCaptions);
  181. VisibleCaptions[NVisibleCaptions-1] := CommonButton.Caption;
  182. end;
  183. end;
  184. BottomPanel.Visible := NVisibleCaptions > 0;
  185. if BottomPanel.Visible then begin
  186. const W = CalculateButtonWidth(VisibleCaptions);
  187. for var CommonButton in FCommonButtons do
  188. if CommonButton.Visible then
  189. CommonButton.Width := W;
  190. end;
  191. end;
  192. procedure TTaskDialogForm.UpdateHeight;
  193. begin
  194. var BottomControl: TControl := nil;
  195. if MainButton3.Visible then
  196. BottomControl := MainButton3
  197. else if MainButton2.Visible then
  198. BottomControl := MainButton2
  199. else if MainButton1.Visible then
  200. BottomControl := MainButton1
  201. else if TextText.Visible then
  202. BottomControl := TextText
  203. else if InstructionText.Visible then
  204. BottomControl := InstructionText;
  205. var NewClientHeight := MainPanel.Padding.Top + MainStackPanel.Top;
  206. if BottomControl <> nil then
  207. NewClientHeight := NewClientHeight + BottomControl.Top + BottomControl.Height;
  208. if LeftPanel.Visible then begin
  209. { Make sure the height is enough to fit the icon }
  210. const MinimumClientHeight = MainPanel.Padding.Top + LeftPanel.Top + BitmapImage.Top + BitmapImage.Height + MainPanel.Padding.Bottom;
  211. if MinimumClientHeight > NewClientHeight then
  212. NewClientHeight := MinimumClientHeight;
  213. end;
  214. if BottomPanel.Visible then
  215. NewClientHeight := NewClientHeight + BottomPanel.Height;
  216. if BottomPanel2.Visible then
  217. NewClientHeight := NewClientHeight + BottomPanel2.Height;
  218. ClientHeight := NewClientHeight;
  219. end;
  220. procedure TTaskDialogForm.UpdateIcon(const Icon: PChar);
  221. begin
  222. var Siid: SHSTOCKICONID;
  223. if Icon = TD_ERROR_ICON then
  224. Siid := SIID_ERROR
  225. else if Icon = TD_TASKFORM_HELP_ICON then
  226. Siid := SIID_HELP
  227. else if Icon = TD_INFORMATION_ICON then
  228. Siid := SIID_INFO
  229. else if Icon = TD_WARNING_ICON then
  230. Siid := SIID_WARNING
  231. else
  232. Siid := SIID_INVALID;
  233. if Siid <> SIID_INVALID then
  234. BitmapImage.InitializeFromStockIcon(Siid, clNone, [32, 48, 64])
  235. else if Icon <> nil then
  236. BitmapImage.InitializeFromIcon(HInstance, Icon, clNone, [32, 48, 64])
  237. else
  238. LeftPanel.Visible := False;
  239. end;
  240. procedure TTaskDialogForm.UpdateInstructionAndText(const Instruction, Text: String);
  241. begin
  242. InstructionText.Visible := Instruction <> '';
  243. if InstructionText.Visible then begin
  244. InstructionText.Caption := Instruction;
  245. InstructionText.Font.Height := MulDiv(InstructionText.Font.Height, 12, 9);
  246. end;
  247. TextText.Visible := Text <> '';
  248. if TextText.Visible then
  249. TextText.Caption := Text;
  250. end;
  251. procedure TTaskDialogForm.UpdateMainButtonsAndBorderIcons(const CommonButtons: Cardinal;
  252. const ButtonLabels: array of String; const ButtonIDs: array of Integer; const ShieldButton: Integer);
  253. begin
  254. var HaveCancel := False;
  255. for var I := 0 to Length(FMainButtons)-1 do begin
  256. const MainButton = FMainButtons[I];
  257. MainButton.Visible := I < Length(ButtonLabels);
  258. if MainButton.Visible then begin
  259. var Caption := ButtonLabels[I];
  260. var Hint: String;
  261. const P = Pos(#10, Caption);
  262. if P <> 0 then begin
  263. Hint := Copy(Caption, P+1, MaxInt);
  264. Delete(Caption, P, MaxInt);
  265. end else
  266. Hint := '';
  267. MainButton.Caption := Caption;
  268. MainButton.Font.Height := MulDiv(MainButton.Font.Height, 12, 9);
  269. MainButton.CommandLinkHint := Hint;
  270. MainButton.ModalResult := ButtonIDs[I];
  271. if MainButton.ModalResult = IDCANCEL then begin
  272. MainButton.Cancel := True;
  273. HaveCancel := True;
  274. end;
  275. MainButton.ElevationRequired := MainButton.ModalResult = ShieldButton;
  276. MainButton.AdjustHeightIfCommandLink;
  277. end;
  278. end;
  279. if not HaveCancel and (CommonButtons and TDCBF_CANCEL_BUTTON = 0) then begin
  280. const SystemMenu = GetSystemMenu(Handle, False);
  281. if SystemMenu <> 0 then
  282. EnableMenuItem(SystemMenu, SC_CLOSE, MF_BYCOMMAND or MF_GRAYED);
  283. end;
  284. end;
  285. procedure TTaskDialogForm.UpdateVerificationText(const VerificationText: String;
  286. const pfVerificationFlagChecked: PBOOL);
  287. begin
  288. if VerificationText <> '' then begin
  289. VerificationCheck.Caption := VerificationText;
  290. if pfVerificationFlagChecked <> nil then
  291. VerificationCheck.Checked := pfVerificationFlagChecked^;
  292. end else
  293. BottomPanel2.Visible := False;
  294. end;
  295. procedure TTaskDialogForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  296. procedure AddButtonCaption(const SB: TStringBuilder; const MessageBoxFormat: Boolean;
  297. const Button: TButton);
  298. begin
  299. const Caption = RemoveAccelChar(Button.Caption);
  300. if MessageBoxFormat then
  301. SB.Append(Format('%s ', [Caption]))
  302. else
  303. SB.Append(Format('[%s] ', [Caption]));
  304. end;
  305. begin
  306. if (Shift = [ssCtrl]) and (Key = Ord('C')) then begin
  307. Key := 0;
  308. const SB = TStringBuilder.Create;
  309. try
  310. { Do not localize }
  311. const SLine = '---------------------------';
  312. const SLineAndNewLine = SLine + SNewLine;
  313. const MessageBoxFormat = FCopyFormat = cfMessageBox;
  314. if MessageBoxFormat then
  315. SB.Append(SLine)
  316. else
  317. SB.Append('[Window Title]');
  318. SB.Append(SNewLine);
  319. SB.Append(Caption);
  320. SB.Append(SNewLine);
  321. if MessageBoxFormat then
  322. SB.Append(SLineAndNewLine)
  323. else
  324. SB.Append(SNewLine);
  325. if InstructionText.Visible then begin
  326. if not MessageBoxFormat then begin
  327. SB.Append('[Main Instruction]');
  328. SB.Append(SNewLine);
  329. end;
  330. SB.Append(InstructionText.Caption);
  331. SB.Append(SNewLine);
  332. if not MessageBoxFormat or TextText.Visible then
  333. SB.Append(SNewLine);
  334. end;
  335. if TextText.Visible then begin
  336. if not MessageBoxFormat then begin
  337. SB.Append('[Content]');
  338. SB.Append(SNewLine);
  339. end;
  340. SB.Append(TextText.Caption);
  341. SB.Append(SNewLine);
  342. if not MessageBoxFormat then
  343. SB.Append(SNewLine);
  344. end;
  345. if MessageBoxFormat then
  346. SB.Append(SLineAndNewLine);
  347. for var MainButton in FMainButtons do
  348. if MainButton.Visible then
  349. AddButtonCaption(SB, MessageBoxFormat, MainButton);
  350. for var CommonButton in FCommonButtons do
  351. if CommonButton.Visible then
  352. AddButtonCaption(SB, MessageBoxFormat, CommonButton);
  353. if MessageBoxFormat then begin
  354. SB.Append(SNewLine);
  355. SB.Append(SLine); { Causes the spaces after the last button caption not to be trimmed, but this is same as with native MessageBox }
  356. end;
  357. Clipboard.AsText := SB.ToString.Trim;
  358. finally
  359. SB.Free;
  360. end;
  361. end;
  362. end;
  363. end.