MessageDlg.cpp 33 KB

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