MessageDlg.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. //---------------------------------------------------------------------------
  2. #include <vcl.h>
  3. #pragma hdrstop
  4. #include <Consts.hpp>
  5. #include <GUITools.h>
  6. #include <Common.h>
  7. #include <VCLCommon.h>
  8. #include <CoreMain.h>
  9. #include <WinInterface.h>
  10. #include <Tools.h>
  11. #include <TextsWin.h>
  12. #include <TextsCore.h>
  13. #include <Vcl.Imaging.pngimage.hpp>
  14. #include <StrUtils.hpp>
  15. #include <PasTools.hpp>
  16. //---------------------------------------------------------------------------
  17. #pragma package(smart_init)
  18. //---------------------------------------------------------------------------
  19. class TMessageButton : public TButton
  20. {
  21. public:
  22. __fastcall TMessageButton(TComponent * Owner);
  23. protected:
  24. virtual void __fastcall Dispatch(void * Message);
  25. private:
  26. void __fastcall WMGetDlgCode(TWMGetDlgCode & Message);
  27. };
  28. //---------------------------------------------------------------------------
  29. __fastcall TMessageButton::TMessageButton(TComponent * Owner) :
  30. TButton(Owner)
  31. {
  32. }
  33. //---------------------------------------------------------------------------
  34. void __fastcall TMessageButton::Dispatch(void * Message)
  35. {
  36. TMessage * M = reinterpret_cast<TMessage*>(Message);
  37. if (M->Msg == WM_GETDLGCODE)
  38. {
  39. WMGetDlgCode(*((TWMGetDlgCode *)Message));
  40. }
  41. else
  42. {
  43. TButton::Dispatch(Message);
  44. }
  45. }
  46. //---------------------------------------------------------------------------
  47. void __fastcall TMessageButton::WMGetDlgCode(TWMGetDlgCode & Message)
  48. {
  49. TButton::Dispatch(&Message);
  50. // WORKAROUND
  51. // Windows default handler returns DLGC_WANTARROWS for split buttons,
  52. // what prevent left/right keys from being used for focusing next/previous buttons/controls.
  53. // Overrwide that. Though note that we need to pass the up/down keys back to button
  54. // to allow drop down, see TMessageForm::CMDialogKey
  55. Message.Result = Message.Result & ~DLGC_WANTARROWS;
  56. }
  57. //---------------------------------------------------------------------------
  58. class TMessageForm : public TForm
  59. {
  60. public:
  61. static TForm * __fastcall Create(const UnicodeString & Msg, TStrings * MoreMessages,
  62. TMsgDlgType DlgType, unsigned int Answers,
  63. const TQueryButtonAlias * Aliases, unsigned int AliasesCount,
  64. unsigned int TimeoutAnswer, TButton ** TimeoutButton, const UnicodeString & ImageName,
  65. const UnicodeString & NeverAskAgainCaption);
  66. protected:
  67. __fastcall TMessageForm(TComponent * AOwner);
  68. virtual __fastcall ~TMessageForm();
  69. DYNAMIC void __fastcall KeyDown(Word & Key, TShiftState Shift);
  70. DYNAMIC void __fastcall KeyUp(Word & Key, TShiftState Shift);
  71. UnicodeString __fastcall GetFormText();
  72. UnicodeString __fastcall GetReportText();
  73. UnicodeString __fastcall NormalizeNewLines(UnicodeString Text);
  74. virtual void __fastcall CreateParams(TCreateParams & Params);
  75. DYNAMIC void __fastcall DoShow();
  76. virtual void __fastcall Dispatch(void * Message);
  77. void __fastcall MenuItemClick(TObject * Sender);
  78. void __fastcall ButtonDropDownClick(TObject * Sender);
  79. void __fastcall UpdateForShiftStateTimer(TObject * Sender);
  80. private:
  81. typedef std::map<unsigned int, TButton *> TAnswerButtons;
  82. TLabel * Message;
  83. TMemo * MessageMemo;
  84. TShiftState FShiftState;
  85. TTimer * FUpdateForShiftStateTimer;
  86. TForm * FDummyForm;
  87. void __fastcall HelpButtonClick(TObject * Sender);
  88. void __fastcall ReportButtonClick(TObject * Sender);
  89. void __fastcall CMDialogKey(TWMKeyDown & Message);
  90. void __fastcall UpdateForShiftState();
  91. TButton * __fastcall CreateButton(
  92. UnicodeString Name, UnicodeString Caption, unsigned int Answer,
  93. TNotifyEvent OnClick, bool IsTimeoutButton,
  94. int GroupWith, TShiftState GrouppedShiftState,
  95. TAnswerButtons & AnswerButtons, bool HasMoreMessages, int & ButtonWidth);
  96. };
  97. //---------------------------------------------------------------------------
  98. __fastcall TMessageForm::TMessageForm(TComponent * AOwner) : TForm(AOwner, 0)
  99. {
  100. Message = NULL;
  101. MessageMemo = NULL;
  102. FUpdateForShiftStateTimer = NULL;
  103. Position = poOwnerFormCenter;
  104. UseSystemSettingsPre(this);
  105. FDummyForm = new TForm(this);
  106. UseSystemSettings(FDummyForm);
  107. }
  108. //---------------------------------------------------------------------------
  109. __fastcall TMessageForm::~TMessageForm()
  110. {
  111. SAFE_DESTROY(FDummyForm);
  112. SAFE_DESTROY(FUpdateForShiftStateTimer);
  113. }
  114. //---------------------------------------------------------------------------
  115. void __fastcall TMessageForm::HelpButtonClick(TObject * /*Sender*/)
  116. {
  117. if (HelpKeyword != HELP_NONE)
  118. {
  119. FormHelp(this);
  120. }
  121. else
  122. {
  123. MessageWithNoHelp(GetReportText());
  124. }
  125. }
  126. //---------------------------------------------------------------------------
  127. void __fastcall TMessageForm::ReportButtonClick(TObject * /*Sender*/)
  128. {
  129. UnicodeString Url =
  130. FMTLOAD(ERROR_REPORT_URL,
  131. (EncodeUrlString(GetReportText()), Configuration->ProductVersion,
  132. IntToHex(__int64(GUIConfiguration->Locale), 4)));
  133. OpenBrowser(Url);
  134. }
  135. //---------------------------------------------------------------------------
  136. void __fastcall TMessageForm::UpdateForShiftState()
  137. {
  138. TShiftState ShiftState =
  139. KeyboardStateToShiftState() *
  140. (TShiftState() << ssShift << ssCtrl << ssAlt);
  141. if (FShiftState != ShiftState)
  142. {
  143. FShiftState = ShiftState;
  144. for (int ComponentIndex = 0; ComponentIndex < ComponentCount - 1; ComponentIndex++)
  145. {
  146. TButton * Button = dynamic_cast<TButton*>(Components[ComponentIndex]);
  147. if ((Button != NULL) && (Button->DropDownMenu != NULL))
  148. {
  149. TMenuItem * MenuItems = Button->DropDownMenu->Items;
  150. for (int ItemIndex = 0; ItemIndex < MenuItems->Count; ItemIndex++)
  151. {
  152. TMenuItem * Item = MenuItems->Items[ItemIndex];
  153. TShiftState GrouppedShiftState(Item->Tag >> 16);
  154. if (Item->Enabled &&
  155. ((ShiftState.Empty() && Item->Default) ||
  156. (!ShiftState.Empty() && (ShiftState == GrouppedShiftState))))
  157. {
  158. int From = 1;
  159. Button->Caption = CopyToChars(Item->Caption, From, L"\t", false);
  160. Button->ModalResult = Item->Tag & 0xFFFF;
  161. assert(Button->OnClick == NULL);
  162. assert(Item->OnClick == MenuItemClick);
  163. break;
  164. }
  165. }
  166. }
  167. }
  168. }
  169. }
  170. //---------------------------------------------------------------------------
  171. void __fastcall TMessageForm::KeyUp(Word & Key, TShiftState Shift)
  172. {
  173. UpdateForShiftState();
  174. TForm::KeyUp(Key, Shift);
  175. }
  176. //---------------------------------------------------------------------------
  177. void __fastcall TMessageForm::KeyDown(Word & Key, TShiftState Shift)
  178. {
  179. if (Shift.Contains(ssCtrl) && (Key == L'C'))
  180. {
  181. CopyToClipboard(GetFormText());
  182. }
  183. else
  184. {
  185. if (!Shift.Contains(ssCtrl))
  186. {
  187. for (int ComponentIndex = 0; ComponentIndex < ComponentCount - 1; ComponentIndex++)
  188. {
  189. TButton * Button = dynamic_cast<TButton*>(Components[ComponentIndex]);
  190. if ((Button != NULL) && (Button->DropDownMenu != NULL))
  191. {
  192. TMenuItem * MenuItems = Button->DropDownMenu->Items;
  193. for (int ItemIndex = 0; ItemIndex < MenuItems->Count; ItemIndex++)
  194. {
  195. TMenuItem * Item = MenuItems->Items[ItemIndex];
  196. if (IsAccel(Key, MenuItems->Items[ItemIndex]->Caption))
  197. {
  198. Item->OnClick(Item);
  199. Key = 0;
  200. break;
  201. }
  202. }
  203. }
  204. if (Key == 0)
  205. {
  206. break;
  207. }
  208. }
  209. }
  210. UpdateForShiftState();
  211. TForm::KeyDown(Key, Shift);
  212. }
  213. }
  214. //---------------------------------------------------------------------------
  215. UnicodeString __fastcall TMessageForm::NormalizeNewLines(UnicodeString Text)
  216. {
  217. Text = ReplaceStr(Text, L"\r", L"");
  218. Text = ReplaceStr(Text, L"\n", L"\r\n");
  219. return Text;
  220. }
  221. //---------------------------------------------------------------------------
  222. UnicodeString __fastcall TMessageForm::GetFormText()
  223. {
  224. UnicodeString DividerLine, ButtonCaptions;
  225. DividerLine = UnicodeString::StringOfChar(L'-', 27) + sLineBreak;
  226. for (int i = 0; i < ComponentCount - 1; i++)
  227. {
  228. if (dynamic_cast<TButton*>(Components[i]) != NULL)
  229. {
  230. ButtonCaptions += dynamic_cast<TButton*>(Components[i])->Caption +
  231. UnicodeString::StringOfChar(L' ', 3);
  232. }
  233. }
  234. ButtonCaptions = ReplaceStr(ButtonCaptions, L"&", L"");
  235. UnicodeString MoreMessages;
  236. if (MessageMemo != NULL)
  237. {
  238. MoreMessages = MessageMemo->Text + DividerLine;
  239. }
  240. UnicodeString MessageCaption = NormalizeNewLines(Message->Caption);
  241. UnicodeString Result = FORMAT(L"%s%s%s%s%s%s%s%s%s%s%s", (DividerLine, Caption, sLineBreak,
  242. DividerLine, MessageCaption, sLineBreak, DividerLine, MoreMessages,
  243. ButtonCaptions, sLineBreak, DividerLine));
  244. return Result;
  245. }
  246. //---------------------------------------------------------------------------
  247. UnicodeString __fastcall TMessageForm::GetReportText()
  248. {
  249. UnicodeString Text = Message->Caption;
  250. if (MessageMemo != NULL)
  251. {
  252. Text += L"\n" + MessageMemo->Text;
  253. }
  254. Text = NormalizeNewLines(Text);
  255. UnicodeString ReportErrorText = NormalizeNewLines(FMTLOAD(REPORT_ERROR, (L"")));
  256. Text = ReplaceStr(Text, ReportErrorText, L"");
  257. Text = Trim(Text);
  258. return Text;
  259. }
  260. //---------------------------------------------------------------------------
  261. void __fastcall TMessageForm::CMDialogKey(TWMKeyDown & Message)
  262. {
  263. // this gets used in WinInterface.cpp SetTimeoutEvents
  264. if (OnKeyDown != NULL)
  265. {
  266. OnKeyDown(this, Message.CharCode, KeyDataToShiftState(Message.KeyData));
  267. }
  268. if (Message.CharCode == VK_MENU)
  269. {
  270. bool AnyButtonWithGrouppedCommandsWithShiftState = false;
  271. for (int ComponentIndex = 0; ComponentIndex < ComponentCount - 1; ComponentIndex++)
  272. {
  273. TButton * Button = dynamic_cast<TButton*>(Components[ComponentIndex]);
  274. if ((Button != NULL) && (Button->DropDownMenu != NULL))
  275. {
  276. // we should check if there are any commands with shift state,
  277. // but it's bit overkill
  278. AnyButtonWithGrouppedCommandsWithShiftState = true;
  279. break;
  280. }
  281. }
  282. // this is to make Alt only alter button meaning (if there is any
  283. // alternable button) and not popup system menu
  284. if (AnyButtonWithGrouppedCommandsWithShiftState)
  285. {
  286. Message.Result = 1;
  287. UpdateForShiftState();
  288. }
  289. else
  290. {
  291. TForm::Dispatch(&Message);
  292. }
  293. }
  294. else if ((Message.CharCode == VK_UP) || (Message.CharCode == VK_DOWN))
  295. {
  296. // WORKAROUND
  297. // noop to make up/down be passed back to button to allow drop down,
  298. // see TMessageButton::WMGetDlgCode
  299. }
  300. else
  301. {
  302. TForm::Dispatch(&Message);
  303. }
  304. }
  305. //---------------------------------------------------------------------------
  306. void __fastcall TMessageForm::Dispatch(void * Message)
  307. {
  308. TMessage * M = reinterpret_cast<TMessage*>(Message);
  309. if (M->Msg == CM_DIALOGKEY)
  310. {
  311. CMDialogKey(*((TWMKeyDown *)Message));
  312. }
  313. else
  314. {
  315. TForm::Dispatch(Message);
  316. }
  317. }
  318. //---------------------------------------------------------------------------
  319. void __fastcall TMessageForm::CreateParams(TCreateParams & Params)
  320. {
  321. TForm::CreateParams(Params);
  322. if ((Screen != NULL) && (Screen->ActiveForm != NULL) &&
  323. Screen->ActiveForm->HandleAllocated())
  324. {
  325. Params.WndParent = Screen->ActiveForm->Handle;
  326. }
  327. }
  328. //---------------------------------------------------------------------------
  329. void __fastcall TMessageForm::DoShow()
  330. {
  331. UseSystemSettingsPost(this);
  332. TForm::DoShow();
  333. }
  334. //---------------------------------------------------------------------------
  335. void __fastcall TMessageForm::MenuItemClick(TObject * Sender)
  336. {
  337. TMenuItem * Item = NOT_NULL(dynamic_cast<TMenuItem *>(Sender));
  338. ModalResult = (Item->Tag & 0xFFFF);
  339. }
  340. //---------------------------------------------------------------------------
  341. void __fastcall TMessageForm::UpdateForShiftStateTimer(TObject * /*Sender*/)
  342. {
  343. // this is needed to reflect shift state, even when we do not have a keyboard
  344. // focus, what happens when drop down menu is popped up
  345. UpdateForShiftState();
  346. }
  347. //---------------------------------------------------------------------------
  348. void __fastcall TMessageForm::ButtonDropDownClick(TObject * /*Sender*/)
  349. {
  350. // as optimization, do not waste time running timer, unless
  351. // user pops up drop down menu. we do not have a way to stop timer, once
  352. // it closes, but functionaly is does not matter
  353. if (FUpdateForShiftStateTimer == NULL)
  354. {
  355. FUpdateForShiftStateTimer = new TTimer(this);
  356. FUpdateForShiftStateTimer->Interval = 50;
  357. FUpdateForShiftStateTimer->OnTimer = UpdateForShiftStateTimer;
  358. }
  359. }
  360. //---------------------------------------------------------------------------
  361. const ResourceString * Captions[] = { &_SMsgDlgWarning, &_SMsgDlgError, &_SMsgDlgInformation,
  362. &_SMsgDlgConfirm, NULL };
  363. const wchar_t * IconIDs[] = { IDI_EXCLAMATION, IDI_HAND, IDI_ASTERISK,
  364. IDI_QUESTION, NULL };
  365. const int mcHorzMargin = 10;
  366. const int mcVertMargin = 13;
  367. const int mcHorzSpacing = 12;
  368. const int mcVertSpacing = 24;
  369. const int mcButtonSpacing = 5;
  370. const int mcMoreMessageWidth = 400;
  371. const int mcMoreMessageHeight = 86;
  372. // approximately what Windows Vista task dialogs use
  373. const int mcMaxDialogWidth = 390;
  374. //---------------------------------------------------------------------------
  375. static UnicodeString __fastcall GetKeyNameStr(int Key)
  376. {
  377. wchar_t Buf[MAX_PATH];
  378. LONG VirtualKey = MapVirtualKey(Key, MAPVK_VK_TO_VSC);
  379. VirtualKey <<= 16;
  380. if (GetKeyNameText(VirtualKey, Buf, LENOF(Buf)) > 0)
  381. {
  382. Buf[LENOF(Buf) - 1] = L'\0';
  383. }
  384. else
  385. {
  386. Buf[0] = L'\0';
  387. }
  388. return Buf;
  389. }
  390. //---------------------------------------------------------------------------
  391. TButton * __fastcall TMessageForm::CreateButton(
  392. UnicodeString Name, UnicodeString Caption, unsigned int Answer,
  393. TNotifyEvent OnClick, bool IsTimeoutButton,
  394. int GroupWith, TShiftState GrouppedShiftState,
  395. TAnswerButtons & AnswerButtons, bool HasMoreMessages, int & ButtonWidth)
  396. {
  397. UnicodeString MeasureCaption = Caption;
  398. if (IsTimeoutButton)
  399. {
  400. MeasureCaption = FMTLOAD(TIMEOUT_BUTTON, (MeasureCaption, 99));
  401. }
  402. TRect TextRect;
  403. DrawText(Canvas->Handle,
  404. UnicodeString(MeasureCaption).c_str(), -1,
  405. &TextRect, DT_CALCRECT | DT_LEFT | DT_SINGLELINE |
  406. DrawTextBiDiModeFlagsReadingOnly());
  407. int CurButtonWidth = TextRect.Right - TextRect.Left + ScaleByTextHeightRunTime(this, 16);
  408. TButton * Button = NULL;
  409. if (SupportsSplitButton() &&
  410. (GroupWith >= 0) &&
  411. ALWAYS_TRUE(AnswerButtons.find(GroupWith) != AnswerButtons.end()))
  412. {
  413. TButton * GroupWithButton = AnswerButtons[GroupWith];
  414. if (GroupWithButton->DropDownMenu == NULL)
  415. {
  416. GroupWithButton->Style = TCustomButton::bsSplitButton;
  417. GroupWithButton->DropDownMenu = new TPopupMenu(this);
  418. // cannot handle subitems with shift state,
  419. // if the button has its own handler
  420. // (though it may not be the case still here)
  421. assert(GroupWithButton->OnClick == NULL);
  422. TMenuItem * Item = new TMenuItem(GroupWithButton->DropDownMenu);
  423. GroupWithButton->DropDownMenu->Items->Add(Item);
  424. GroupWithButton->OnDropDownClick = ButtonDropDownClick;
  425. Item->Caption = GroupWithButton->Caption;
  426. Item->OnClick = MenuItemClick;
  427. assert(GroupWithButton->ModalResult <= 0xFFFF);
  428. Item->Tag = GroupWithButton->ModalResult;
  429. Item->Default = true;
  430. }
  431. TMenuItem * Item = new TMenuItem(GroupWithButton->DropDownMenu);
  432. GroupWithButton->DropDownMenu->Items->Add(Item);
  433. // See ShortCutToText in Vcl.Menus.pas
  434. if (GrouppedShiftState == (TShiftState() << ssAlt))
  435. {
  436. Caption = Caption + L"\t" + GetKeyNameStr(VK_MENU);
  437. }
  438. else if (GrouppedShiftState == (TShiftState() << ssCtrl))
  439. {
  440. Caption = Caption + L"\t" + GetKeyNameStr(VK_CONTROL);
  441. }
  442. else if (GrouppedShiftState == (TShiftState() << ssShift))
  443. {
  444. Caption = Caption + L"\t" + GetKeyNameStr(VK_SHIFT);
  445. }
  446. else
  447. {
  448. // do not support combined shift states yet
  449. assert(GrouppedShiftState == TShiftState());
  450. }
  451. Item->Caption = Caption;
  452. if (OnClick != NULL)
  453. {
  454. Item->OnClick = OnClick;
  455. }
  456. else
  457. {
  458. Item->OnClick = MenuItemClick;
  459. assert((Answer <= 0xFFFF) && (GrouppedShiftState.ToInt() <= 0xFFFF));
  460. Item->Tag = Answer + (GrouppedShiftState.ToInt() << 16);
  461. }
  462. // Hard-coded drop down button width (do not know how to ask for system width).
  463. // Also we do not update the max button width for the default groupped
  464. // button caption. We just blindly hope that captions of advanced commands
  465. // are always longer than the caption of simple default command
  466. CurButtonWidth += ScaleByTextHeightRunTime(this, 15);
  467. }
  468. else
  469. {
  470. Button = new TMessageButton(this);
  471. Button->Name = Name;
  472. Button->Parent = this;
  473. Button->Caption = Caption;
  474. // Scale buttons using regular font, so that they are as large as buttons
  475. // on other dialogs (note that they are still higher than Windows Task dialog
  476. // buttons)
  477. Button->Height = ScaleByTextHeightRunTime(FDummyForm, Button->Height);
  478. Button->Width = ScaleByTextHeightRunTime(FDummyForm, Button->Width);
  479. if (OnClick != NULL)
  480. {
  481. Button->OnClick = OnClick;
  482. }
  483. else
  484. {
  485. Button->ModalResult = Answer;
  486. }
  487. if (HasMoreMessages)
  488. {
  489. Button->Anchors = TAnchors() << akBottom << akLeft;
  490. }
  491. // never shrink buttons below their default width
  492. if (Button->Width > CurButtonWidth)
  493. {
  494. CurButtonWidth = Button->Width;
  495. }
  496. }
  497. if (CurButtonWidth > ButtonWidth)
  498. {
  499. ButtonWidth = CurButtonWidth;
  500. }
  501. return Button;
  502. }
  503. //---------------------------------------------------------------------------
  504. void __fastcall AnswerNameAndCaption(
  505. unsigned int Answer, UnicodeString & Name, UnicodeString & Caption)
  506. {
  507. switch (Answer)
  508. {
  509. case qaYes:
  510. Caption = LoadStr(_SMsgDlgYes.Identifier);
  511. Name = L"Yes";
  512. break;
  513. case qaNo:
  514. Caption = LoadStr(_SMsgDlgNo.Identifier);
  515. Name = L"No";
  516. break;
  517. case qaOK:
  518. Caption = LoadStr(_SMsgDlgOK.Identifier);
  519. Name = L"OK";
  520. break;
  521. case qaCancel:
  522. Caption = LoadStr(_SMsgDlgCancel.Identifier);
  523. Name = L"Cancel";
  524. break;
  525. case qaAbort:
  526. Caption = LoadStr(_SMsgDlgAbort.Identifier);
  527. Name = L"Abort";
  528. break;
  529. case qaRetry:
  530. Caption = LoadStr(_SMsgDlgRetry.Identifier);
  531. Name = L"Retry";
  532. break;
  533. case qaIgnore:
  534. Caption = LoadStr(_SMsgDlgIgnore.Identifier);
  535. Name = L"Ignore";
  536. break;
  537. // Own variant to avoid accelerator conflict with "Abort" button.
  538. // Note that as of now, ALL_BUTTON is never actually used,
  539. // because qaAll is always aliased
  540. case qaAll:
  541. Caption = LoadStr(ALL_BUTTON);
  542. Name = L"All";
  543. break;
  544. case qaNoToAll:
  545. Caption = LoadStr(_SMsgDlgNoToAll.Identifier);
  546. Name = L"NoToAll";
  547. break;
  548. // Own variant to avoid accelerator conflict with "Abort" button.
  549. case qaYesToAll:
  550. Caption = LoadStr(YES_TO_ALL_BUTTON);
  551. Name = L"YesToAll";
  552. break;
  553. case qaHelp:
  554. Caption = LoadStr(_SMsgDlgHelp.Identifier);
  555. Name = L"Help";
  556. break;
  557. case qaSkip:
  558. Caption = LoadStr(SKIP_BUTTON);
  559. Name = L"Skip";
  560. break;
  561. case qaReport:
  562. Caption = LoadStr(REPORT_BUTTON);
  563. Name = L"Report";
  564. break;
  565. default:
  566. FAIL;
  567. throw Exception(L"Undefined answer");
  568. }
  569. }
  570. //---------------------------------------------------------------------------
  571. static int __fastcall CalculateWidthOnCanvas(UnicodeString Text, void * Arg)
  572. {
  573. TCanvas * Canvas = static_cast<TCanvas *>(Arg);
  574. return Canvas->TextWidth(Text);
  575. }
  576. //---------------------------------------------------------------------------
  577. TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg,
  578. TStrings * MoreMessages, TMsgDlgType DlgType, unsigned int Answers,
  579. const TQueryButtonAlias * Aliases, unsigned int AliasesCount,
  580. unsigned int TimeoutAnswer, TButton ** TimeoutButton, const UnicodeString & ImageName,
  581. const UnicodeString & NeverAskAgainCaption)
  582. {
  583. unsigned int DefaultAnswer;
  584. if (FLAGSET(Answers, qaOK))
  585. {
  586. DefaultAnswer = qaOK;
  587. }
  588. else if (FLAGSET(Answers, qaYes))
  589. {
  590. DefaultAnswer = qaYes;
  591. }
  592. else
  593. {
  594. DefaultAnswer = qaRetry;
  595. }
  596. unsigned int CancelAnswer = ::CancelAnswer(Answers);
  597. if (TimeoutButton != NULL)
  598. {
  599. *TimeoutButton = NULL;
  600. }
  601. TMessageForm * Result = SafeFormCreate<TMessageForm>();
  602. UseDesktopFont(Result);
  603. Result->BiDiMode = Application->BiDiMode;
  604. Result->BorderStyle = bsDialog;
  605. Result->Canvas->Font = Result->Font;
  606. Result->KeyPreview = true;
  607. int HorzMargin = ScaleByTextHeightRunTime(Result, mcHorzMargin);
  608. int VertMargin = ScaleByTextHeightRunTime(Result, mcVertMargin);
  609. int HorzSpacing = ScaleByTextHeightRunTime(Result, mcHorzSpacing);
  610. int VertSpacing = ScaleByTextHeightRunTime(Result, mcVertSpacing);
  611. int ButtonWidth = -1;
  612. int ButtonHeight = -1;
  613. std::vector<TButton *> ButtonControls;
  614. TAnswerButtons AnswerButtons;
  615. for (unsigned int Answer = qaFirst; Answer <= qaLast; Answer = Answer << 1)
  616. {
  617. if (FLAGSET(Answers, Answer))
  618. {
  619. assert(Answer != mrCancel);
  620. UnicodeString Caption;
  621. UnicodeString Name;
  622. AnswerNameAndCaption(Answer, Name, Caption);
  623. TNotifyEvent OnClick = NULL;
  624. int GroupWith = -1;
  625. TShiftState GrouppedShiftState;
  626. if (Aliases != NULL)
  627. {
  628. for (unsigned int i = 0; i < AliasesCount; i++)
  629. {
  630. if (Answer == Aliases[i].Button)
  631. {
  632. if (!Aliases[i].Alias.IsEmpty())
  633. {
  634. Caption = Aliases[i].Alias;
  635. }
  636. OnClick = Aliases[i].OnClick;
  637. GroupWith = Aliases[i].GroupWith;
  638. GrouppedShiftState = Aliases[i].GrouppedShiftState;
  639. assert((OnClick == NULL) || (GrouppedShiftState == TShiftState()));
  640. break;
  641. }
  642. }
  643. }
  644. // we hope that all grouped-with buttons are for answer with greater
  645. // value that the answer to be grouped with
  646. if (GroupWith >= 0)
  647. {
  648. if (ALWAYS_FALSE(GroupWith >= static_cast<int>(Answer)) ||
  649. ALWAYS_FALSE(Answer == TimeoutAnswer) &&
  650. ALWAYS_FALSE(Answer == DefaultAnswer) &&
  651. ALWAYS_FALSE(Answer == CancelAnswer))
  652. {
  653. GroupWith = -1;
  654. }
  655. }
  656. bool IsTimeoutButton = (TimeoutButton != NULL) && (Answer == TimeoutAnswer);
  657. if (Answer == qaHelp)
  658. {
  659. assert(OnClick == NULL);
  660. OnClick = Result->HelpButtonClick;
  661. }
  662. if (Answer == qaReport)
  663. {
  664. assert(OnClick == NULL);
  665. OnClick = Result->ReportButtonClick;
  666. }
  667. TButton * Button = Result->CreateButton(
  668. Name, Caption, Answer,
  669. OnClick, IsTimeoutButton, GroupWith, GrouppedShiftState,
  670. AnswerButtons, (MoreMessages != NULL), ButtonWidth);
  671. if (Button != NULL)
  672. {
  673. ButtonControls.push_back(Button);
  674. Button->Default = (Answer == DefaultAnswer);
  675. Button->Cancel = (Answer == CancelAnswer);
  676. if (ButtonHeight < 0)
  677. {
  678. ButtonHeight = Button->Height;
  679. }
  680. assert(ButtonHeight == Button->Height);
  681. AnswerButtons.insert(TAnswerButtons::value_type(Answer, Button));
  682. if (IsTimeoutButton)
  683. {
  684. *TimeoutButton = Button;
  685. }
  686. }
  687. }
  688. }
  689. int NeverAskAgainWidth = 0;
  690. if (!NeverAskAgainCaption.IsEmpty())
  691. {
  692. NeverAskAgainWidth =
  693. ScaleByTextHeightRunTime(Result, 16) + // checkbox
  694. Result->Canvas->TextWidth(NeverAskAgainCaption) +
  695. ScaleByTextHeightRunTime(Result, 16); // margin
  696. }
  697. int ButtonSpacing = ScaleByTextHeightRunTime(Result, mcButtonSpacing);
  698. int ButtonGroupWidth = NeverAskAgainWidth;
  699. if (!ButtonControls.empty())
  700. {
  701. ButtonGroupWidth += ButtonWidth * ButtonControls.size() +
  702. ButtonSpacing * (ButtonControls.size() - 1);
  703. }
  704. UnicodeString NormalizedMsg = Msg;
  705. // Windows XP (not sure about Vista) does not support Hair space.
  706. // For Windows XP, we still keep the existing hack by using hard-coded spaces
  707. // in resource string
  708. if (CheckWin32Version(6, 1))
  709. {
  710. // Have to be padding with spaces (the smallest space defined, hair space = 1px),
  711. // as tabs actually do not tab, just expand to 8 spaces.
  712. // Otherwise we would have to do custom drawing
  713. // (using GetTabbedTextExtent and TabbedTextOut)
  714. const wchar_t HairSpace = L'\x200A';
  715. ApplyTabs(NormalizedMsg, HairSpace, CalculateWidthOnCanvas, Result->Canvas);
  716. }
  717. int IconWidth = 0;
  718. const wchar_t * IconID = IconIDs[DlgType];
  719. bool HasIcon = (IconID != NULL) || !ImageName.IsEmpty();
  720. if (HasIcon)
  721. {
  722. IconWidth = 32 + HorzSpacing;
  723. }
  724. assert((ButtonHeight > 0) && (ButtonWidth > 0));
  725. int MaxTextWidth = ScaleByTextHeightRunTime(Result, mcMaxDialogWidth);
  726. // if the dialog would be wide anyway (overwrite confirmation on Windows XP),
  727. // to fit the buttons, do not restrict the text
  728. if (MaxTextWidth < ButtonGroupWidth - IconWidth)
  729. {
  730. MaxTextWidth = ButtonGroupWidth - IconWidth;
  731. }
  732. TRect TextRect;
  733. SetRect(&TextRect, 0, 0, MaxTextWidth, 0);
  734. DrawText(Result->Canvas->Handle, NormalizedMsg.c_str(), NormalizedMsg.Length() + 1, &TextRect,
  735. DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK |
  736. Result->DrawTextBiDiModeFlagsReadingOnly());
  737. int MaxWidth = Screen->Width - HorzMargin * 2 - IconWidth - 30;
  738. if (TextRect.right > MaxWidth)
  739. {
  740. // this will truncate the text, we should implement something smarter eventually
  741. TextRect.right = MaxWidth;
  742. }
  743. int IconTextWidth = TextRect.Right + IconWidth;
  744. int IconTextHeight = TextRect.Bottom;
  745. if (HasIcon && (IconTextHeight < 32))
  746. {
  747. IconTextHeight = 32;
  748. }
  749. int MoreMessageWidth = (MoreMessages != NULL ?
  750. ScaleByTextHeightRunTime(Result, mcMoreMessageWidth) : 0);
  751. int MoreMessageHeight = (MoreMessages != NULL ?
  752. ScaleByTextHeightRunTime(Result, mcMoreMessageHeight) : 0);
  753. int ButtonTop = IconTextHeight + VertMargin + VertSpacing + MoreMessageHeight;
  754. TPanel * Panel = new TPanel(Result);
  755. Panel->Name = L"Panel";
  756. Panel->Parent = Result;
  757. Panel->Color = clWindow;
  758. Panel->ParentBackground = false;
  759. Panel->SetBounds(0, 0, Result->ClientWidth, ButtonTop - (VertSpacing / 2));
  760. Panel->Anchors = TAnchors() << akLeft << akRight << akTop;
  761. Panel->BevelOuter = bvNone;
  762. Panel->BevelKind = bkNone;
  763. Panel->Caption = L"";
  764. if (MoreMessages != NULL)
  765. {
  766. TMemo * MessageMemo = new TMemo(Panel);
  767. MessageMemo->Name = L"MessageMemo";
  768. MessageMemo->Parent = Panel;
  769. MessageMemo->ReadOnly = true;
  770. MessageMemo->WantReturns = False;
  771. MessageMemo->ScrollBars = ssVertical;
  772. MessageMemo->Anchors = TAnchors() << akLeft << akRight << akTop;
  773. MessageMemo->Lines->Text = MoreMessages->Text;
  774. Result->MessageMemo = MessageMemo;
  775. }
  776. int AClientWidth =
  777. (IconTextWidth > ButtonGroupWidth ? IconTextWidth : ButtonGroupWidth) +
  778. HorzMargin * 2;
  779. Result->ClientWidth = (AClientWidth > MoreMessageWidth ?
  780. AClientWidth : MoreMessageWidth);
  781. Result->ClientHeight = IconTextHeight + ButtonHeight + VertSpacing +
  782. VertMargin * 2 + MoreMessageHeight;
  783. Result->Left = (Screen->Width / 2) - (Result->Width / 2);
  784. Result->Top = (Screen->Height / 2) - (Result->Height / 2);
  785. if (DlgType != mtCustom)
  786. {
  787. Result->Caption = LoadResourceString(Captions[DlgType]);
  788. }
  789. else
  790. {
  791. Result->Caption = Application->Title;
  792. }
  793. if ((IconID != NULL) || !ImageName.IsEmpty())
  794. {
  795. TImage * Image = new TImage(Panel);
  796. Image->Name = L"Image";
  797. Image->Parent = Panel;
  798. if (!ImageName.IsEmpty())
  799. {
  800. std::auto_ptr<TPngImage> Png(new TPngImage());
  801. Png->LoadFromResourceName(0, ImageName);
  802. Image->Picture->Assign(Png.get());
  803. }
  804. else
  805. {
  806. Image->Picture->Icon->Handle = LoadIcon(0, IconID);
  807. }
  808. Image->SetBounds(HorzMargin, VertMargin, 32, 32);
  809. }
  810. TLabel * Message = new TLabel(Panel);
  811. Result->Message = Message;
  812. Message->Name = L"Message";
  813. Message->Parent = Panel;
  814. Message->WordWrap = true;
  815. Message->Caption = NormalizedMsg;
  816. Message->BoundsRect = TextRect;
  817. Message->BiDiMode = Result->BiDiMode;
  818. // added to show & as & for messages containing !& pattern of custom commands
  819. // (suppose that we actually never want to use & as accel in message text)
  820. Message->ShowAccelChar = false;
  821. int ALeft = IconTextWidth - TextRect.Right + HorzMargin;
  822. Message->SetBounds(ALeft, VertMargin, TextRect.Right, TextRect.Bottom);
  823. if (Result->MessageMemo != NULL)
  824. {
  825. Result->MessageMemo->BoundsRect = TRect(Message->Left,
  826. Message->Top + Message->Height + VertSpacing,
  827. Result->ClientWidth - HorzMargin,
  828. Message->Top + Message->Height + VertSpacing + MoreMessageHeight);
  829. // rather hack, whole control positioning is wrong
  830. if (Result->MessageMemo->Top + Result->MessageMemo->Height > ButtonTop - VertSpacing)
  831. {
  832. Result->MessageMemo->Height =
  833. (ButtonTop - VertSpacing) - Result->MessageMemo->Top;
  834. }
  835. }
  836. int X = Result->ClientWidth - ButtonGroupWidth + NeverAskAgainWidth - HorzMargin;
  837. for (unsigned int i = 0; i < ButtonControls.size(); i++)
  838. {
  839. ButtonControls[i]->SetBounds(X,
  840. ButtonTop, ButtonWidth, ButtonControls[i]->Height);
  841. X += ButtonWidth + ButtonSpacing;
  842. }
  843. if (!NeverAskAgainCaption.IsEmpty() &&
  844. !ButtonControls.empty())
  845. {
  846. TCheckBox * NeverAskAgainCheck = new TCheckBox(Result);
  847. NeverAskAgainCheck->Name = L"NeverAskAgainCheck";
  848. NeverAskAgainCheck->Parent = Result;
  849. NeverAskAgainCheck->Caption = NeverAskAgainCaption;
  850. NeverAskAgainCheck->Anchors = TAnchors() << akBottom << akLeft;
  851. TButton * FirstButton = ButtonControls[0];
  852. int NeverAskAgainHeight = ScaleByTextHeightRunTime(Result, NeverAskAgainCheck->Height);
  853. int NeverAskAgainTop = FirstButton->Top + ((FirstButton->Height - NeverAskAgainHeight) / 2);
  854. int NeverAskAgainLeft = ScaleByTextHeightRunTime(Result, mcHorzMargin);
  855. NeverAskAgainCheck->BoundsRect =
  856. TRect(
  857. NeverAskAgainLeft,
  858. NeverAskAgainTop,
  859. NeverAskAgainLeft + NeverAskAgainWidth,
  860. NeverAskAgainTop + NeverAskAgainHeight);
  861. }
  862. return Result;
  863. }
  864. //---------------------------------------------------------------------------
  865. TForm * __fastcall CreateMoreMessageDialog(const UnicodeString & Msg,
  866. TStrings * MoreMessages, TMsgDlgType DlgType, unsigned int Answers,
  867. const TQueryButtonAlias * Aliases, unsigned int AliasesCount,
  868. unsigned int TimeoutAnswer, TButton ** TimeoutButton, const UnicodeString & ImageName,
  869. const UnicodeString & NeverAskAgainCaption)
  870. {
  871. return TMessageForm::Create(Msg, MoreMessages, DlgType, Answers,
  872. Aliases, AliasesCount, TimeoutAnswer, TimeoutButton, ImageName,
  873. NeverAskAgainCaption);
  874. }