Setup.TaskDialogForm.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  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. BidiCtrls, 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: LongInt;
  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: LongInt;
  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. var LStyle := StyleServices(Self);
  111. if not LStyle.Enabled or LStyle.IsSystemStyle then
  112. LStyle := nil;
  113. if LStyle <> nil then begin
  114. { Make MainPanel look the same as WizardForm's main area }
  115. MainPanel.StyleElements := []; { Must be done before InitializeFont, see its comments }
  116. MainPanel.Color := LStyle.GetStyleColor(scWindow);
  117. end;
  118. { KeepSizeX: Already bit wider than regular task dialogs
  119. KeepSizeY: UpdateHeight will set height }
  120. InitializeFont(True, True);
  121. const Pad = 10;
  122. const PadX = ScalePixelsX(Pad);
  123. const PadY = ScalePixelsY(Pad);
  124. MainPanel.Padding.Left := PadX;
  125. MainPanel.Padding.Top := PadY;
  126. MainPanel.Padding.Right := PadX;
  127. MainPanel.Padding.Bottom := PadY;
  128. MainStackPanel.Padding.Left := PadX; { Also see Finish }
  129. MainStackPanel.Spacing := PadY;
  130. BottomStackPanel.Spacing := PadX;
  131. BottomStackPanel.Padding.Right := PadX; { Also see Finish }
  132. VerificationCheck.Left := PadX;
  133. OkButton.Caption := SetupMessages[msgButtonOK];
  134. YesButton.Caption := SetupMessages[msgButtonYes];
  135. NoButton.Caption := SetupMessages[msgButtonNo];
  136. RetryButton.Caption := SetupMessages[msgAbortRetryIgnoreRetry];
  137. CancelButton.Caption := SetupMessages[msgButtonCancel];
  138. end;
  139. procedure TTaskDialogForm.Finish(const DefCommonButton: Integer);
  140. begin
  141. if RightToLeft then begin
  142. { FlipSizeAndCenterIfNeeded does not update Align or Padding }
  143. if LeftPanel.Visible then
  144. LeftPanel.Align := alRight;
  145. MainStackPanel.Padding.Right := MainStackPanel.Padding.Left;
  146. MainStackPanel.Padding.Left := 0;
  147. if BottomPanel.Visible then begin
  148. BottomStackPanel.Align := alLeft;
  149. BottomStackPanel.Padding.Left := BottomStackPanel.Padding.Right;
  150. BottomStackPanel.Padding.Right := 0;
  151. end;
  152. end;
  153. if DefCommonButton > 0 then begin
  154. var I := DefCommonButton;
  155. for var CommonButton in FCommonButtons do begin
  156. if CommonButton.Visible then begin
  157. Dec(I);
  158. if I = 0 then begin
  159. ActiveControl := CommonButton;
  160. Exit;
  161. end;
  162. end;
  163. end;
  164. end;
  165. end;
  166. procedure TTaskDialogForm.UpdateCommonButtons(const CommonButtons: Cardinal);
  167. var
  168. VisibleCaptions: array of String;
  169. begin
  170. var NVisibleCaptions := 0;
  171. for var I := 0 to Length(FCommonButtons)-1 do begin
  172. const CommonButton = FCommonButtons[I];
  173. const CommonButtonFlag = FCommonButtonFlags[I];
  174. CommonButton.Visible := CommonButtons and CommonButtonFlag <> 0;
  175. if CommonButton.Visible then begin
  176. Inc(NVisibleCaptions);
  177. SetLength(VisibleCaptions, NVisibleCaptions);
  178. VisibleCaptions[NVisibleCaptions-1] := CommonButton.Caption;
  179. end;
  180. end;
  181. BottomPanel.Visible := NVisibleCaptions > 0;
  182. if BottomPanel.Visible then begin
  183. const W = CalculateButtonWidth(VisibleCaptions);
  184. for var CommonButton in FCommonButtons do
  185. if CommonButton.Visible then
  186. CommonButton.Width := W;
  187. end;
  188. end;
  189. procedure TTaskDialogForm.UpdateHeight;
  190. begin
  191. var BottomControl: TControl := nil;
  192. if MainButton3.Visible then
  193. BottomControl := MainButton3
  194. else if MainButton2.Visible then
  195. BottomControl := MainButton2
  196. else if MainButton1.Visible then
  197. BottomControl := MainButton1
  198. else if TextText.Visible then
  199. BottomControl := TextText
  200. else if InstructionText.Visible then
  201. BottomControl := InstructionText;
  202. var NewClientHeight := MainPanel.Padding.Top + MainStackPanel.Top;
  203. if BottomControl <> nil then
  204. NewClientHeight := NewClientHeight + BottomControl.Top + BottomControl.Height;
  205. if LeftPanel.Visible then begin
  206. { Make sure the height is enough to fit the icon }
  207. const MinimumClientHeight = MainPanel.Padding.Top + LeftPanel.Top + BitmapImage.Top + BitmapImage.Height + MainPanel.Padding.Bottom;
  208. if MinimumClientHeight > NewClientHeight then
  209. NewClientHeight := MinimumClientHeight;
  210. end;
  211. if BottomPanel.Visible then
  212. NewClientHeight := NewClientHeight + BottomPanel.Height;
  213. if BottomPanel2.Visible then
  214. NewClientHeight := NewClientHeight + BottomPanel2.Height;
  215. ClientHeight := NewClientHeight;
  216. end;
  217. procedure TTaskDialogForm.UpdateIcon(const Icon: PChar);
  218. begin
  219. var Siid: SHSTOCKICONID;
  220. if Icon = TD_ERROR_ICON then
  221. Siid := SIID_ERROR
  222. else if Icon = TD_TASKFORM_HELP_ICON then
  223. Siid := SIID_HELP
  224. else if Icon = TD_INFORMATION_ICON then
  225. Siid := SIID_INFO
  226. else if Icon = TD_WARNING_ICON then
  227. Siid := SIID_WARNING
  228. else
  229. Siid := SIID_INVALID;
  230. if Siid <> SIID_INVALID then
  231. BitmapImage.InitializeFromStockIcon(Siid, clNone, [32, 48, 64])
  232. else if Icon <> nil then
  233. BitmapImage.InitializeFromIcon(HInstance, Icon, clNone, [32, 48, 64])
  234. else
  235. LeftPanel.Visible := False;
  236. end;
  237. procedure TTaskDialogForm.UpdateInstructionAndText(const Instruction, Text: String);
  238. begin
  239. InstructionText.Visible := Instruction <> '';
  240. if InstructionText.Visible then begin
  241. InstructionText.Caption := Instruction;
  242. InstructionText.Font.Height := MulDiv(InstructionText.Font.Height, 12, 9);
  243. end;
  244. TextText.Visible := Text <> '';
  245. if TextText.Visible then
  246. TextText.Caption := Text;
  247. end;
  248. procedure TTaskDialogForm.UpdateMainButtonsAndBorderIcons(const CommonButtons: Cardinal;
  249. const ButtonLabels: array of String; const ButtonIDs: array of Integer; const ShieldButton: Integer);
  250. begin
  251. var HaveCancel := False;
  252. for var I := 0 to Length(FMainButtons)-1 do begin
  253. const MainButton = FMainButtons[I];
  254. MainButton.Visible := I < Length(ButtonLabels);
  255. if MainButton.Visible then begin
  256. var Caption := ButtonLabels[I];
  257. var Hint: String;
  258. const P = Pos(#10, Caption);
  259. if P <> 0 then begin
  260. Hint := Copy(Caption, P+1, MaxInt);
  261. Delete(Caption, P, MaxInt);
  262. end else
  263. Hint := '';
  264. MainButton.Caption := Caption;
  265. MainButton.Font.Height := MulDiv(MainButton.Font.Height, 12, 9);
  266. MainButton.CommandLinkHint := Hint;
  267. MainButton.ModalResult := ButtonIDs[I];
  268. if MainButton.ModalResult = IDCANCEL then begin
  269. MainButton.Cancel := True;
  270. HaveCancel := True;
  271. end;
  272. MainButton.ElevationRequired := MainButton.ModalResult = ShieldButton;
  273. MainButton.AdjustHeightIfCommandLink;
  274. end;
  275. end;
  276. if not HaveCancel and (CommonButtons and TDCBF_CANCEL_BUTTON = 0) then begin
  277. const SystemMenu = GetSystemMenu(Handle, False);
  278. if SystemMenu <> 0 then
  279. EnableMenuItem(SystemMenu, SC_CLOSE, MF_BYCOMMAND or MF_GRAYED);
  280. end;
  281. end;
  282. procedure TTaskDialogForm.UpdateVerificationText(const VerificationText: String;
  283. const pfVerificationFlagChecked: PBOOL);
  284. begin
  285. if VerificationText <> '' then begin
  286. VerificationCheck.Caption := VerificationText;
  287. if pfVerificationFlagChecked <> nil then
  288. VerificationCheck.Checked := pfVerificationFlagChecked^;
  289. end else
  290. BottomPanel2.Visible := False;
  291. end;
  292. procedure TTaskDialogForm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
  293. procedure AddButtonCaption(const SB: TStringBuilder; const MessageBoxFormat: Boolean;
  294. const Button: TButton);
  295. begin
  296. const Caption = RemoveAccelChar(Button.Caption);
  297. if MessageBoxFormat then
  298. SB.Append(Format('%s ', [Caption]))
  299. else
  300. SB.Append(Format('[%s] ', [Caption]));
  301. end;
  302. begin
  303. if (Shift = [ssCtrl]) and (Key = Ord('C')) then begin
  304. Key := 0;
  305. const SB = TStringBuilder.Create;
  306. try
  307. { Do not localize }
  308. const SLine = '---------------------------';
  309. const SLineAndNewLine = SLine + SNewLine;
  310. const MessageBoxFormat = FCopyFormat = cfMessageBox;
  311. if MessageBoxFormat then
  312. SB.Append(SLine)
  313. else
  314. SB.Append('[Window Title]');
  315. SB.Append(SNewLine);
  316. SB.Append(Caption);
  317. SB.Append(SNewLine);
  318. if MessageBoxFormat then
  319. SB.Append(SLineAndNewLine)
  320. else
  321. SB.Append(SNewLine);
  322. if InstructionText.Visible then begin
  323. if not MessageBoxFormat then begin
  324. SB.Append('[Main Instruction]');
  325. SB.Append(SNewLine);
  326. end;
  327. SB.Append(InstructionText.Caption);
  328. SB.Append(SNewLine);
  329. if not MessageBoxFormat or TextText.Visible then
  330. SB.Append(SNewLine);
  331. end;
  332. if TextText.Visible then begin
  333. if not MessageBoxFormat then begin
  334. SB.Append('[Content]');
  335. SB.Append(SNewLine);
  336. end;
  337. SB.Append(TextText.Caption);
  338. SB.Append(SNewLine);
  339. if not MessageBoxFormat then
  340. SB.Append(SNewLine);
  341. end;
  342. if MessageBoxFormat then
  343. SB.Append(SLineAndNewLine);
  344. for var MainButton in FMainButtons do
  345. if MainButton.Visible then
  346. AddButtonCaption(SB, MessageBoxFormat, MainButton);
  347. for var CommonButton in FCommonButtons do
  348. if CommonButton.Visible then
  349. AddButtonCaption(SB, MessageBoxFormat, CommonButton);
  350. if MessageBoxFormat then begin
  351. SB.Append(SNewLine);
  352. SB.Append(SLine); { Causes the spaces after the last button caption not to be trimmed, but this is same as with native MessageBox }
  353. end;
  354. Clipboard.AsText := SB.ToString.Trim;
  355. finally
  356. SB.Free;
  357. end;
  358. end;
  359. end;
  360. end.