//--------------------------------------------------------------------------- #include #pragma hdrstop #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MessageDlg.h" //--------------------------------------------------------------------------- #pragma resource "*.dfm" //--------------------------------------------------------------------------- const UnicodeString MessagePanelName(L"Panel"); const UnicodeString MainMessageLabelName(L"MainMessage"); const UnicodeString MessageLabelName(L"Message"); const UnicodeString YesButtonName(L"Yes"); const UnicodeString OKButtonName(L"OK"); //--------------------------------------------------------------------------- class TMessageButton : public TButton { public: __fastcall TMessageButton(TComponent * Owner); protected: virtual void __fastcall Dispatch(void * Message); private: void __fastcall WMGetDlgCode(TWMGetDlgCode & Message); }; //--------------------------------------------------------------------------- __fastcall TMessageButton::TMessageButton(TComponent * Owner) : TButton(Owner) { } //--------------------------------------------------------------------------- void __fastcall TMessageButton::Dispatch(void * Message) { TMessage * M = reinterpret_cast(Message); if (M->Msg == WM_GETDLGCODE) { WMGetDlgCode(*((TWMGetDlgCode *)Message)); } else { TButton::Dispatch(Message); } } //--------------------------------------------------------------------------- void __fastcall TMessageButton::WMGetDlgCode(TWMGetDlgCode & Message) { TButton::Dispatch(&Message); // WORKAROUND // Windows default handler returns DLGC_WANTARROWS for split buttons, // what prevent left/right keys from being used for focusing next/previous buttons/controls. // Overrwide that. Though note that we need to pass the up/down keys back to button // to allow drop down, see TMessageForm::CMDialogKey Message.Result = Message.Result & ~DLGC_WANTARROWS; } //--------------------------------------------------------------------------- __fastcall TMessageForm::TMessageForm(TComponent * AOwner) : TForm(AOwner) { FShowNoActivate = false; MessageMemo = NULL; MessageBrowserPanel = NULL; MessageBrowser = NULL; NeverAskAgainCheck = NULL; FUpdateForShiftStateTimer = NULL; UseSystemSettingsPre(this); } //--------------------------------------------------------------------------- __fastcall TMessageForm::~TMessageForm() { SAFE_DESTROY(FUpdateForShiftStateTimer); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::HelpButtonSubmit(TObject * /*Sender*/, unsigned int & /*Answer*/) { if (HelpKeyword != HELP_NONE) { FormHelp(this); } else { MessageWithNoHelp(GetReportText()); } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::ReportButtonSubmit(TObject * /*Sender*/, unsigned int & /*Answer*/) { // Report text goes last, as it may exceed URL parameters limit (2048) and get truncated. // And we need to preserve the other parameters. UnicodeString Url = FMTLOAD(ERROR_REPORT_URL2, (Configuration->ProductVersion, GUIConfiguration->AppliedLocaleHex, EncodeUrlString(GetReportText()))); OpenBrowser(Url); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::UpdateForShiftState() { TShiftState ShiftState = KeyboardStateToShiftState() * AllKeyShiftStates(); if (FShiftState != ShiftState) { FShiftState = ShiftState; for (int ComponentIndex = 0; ComponentIndex < ComponentCount - 1; ComponentIndex++) { TButton * Button = dynamic_cast(Components[ComponentIndex]); if ((Button != NULL) && (Button->DropDownMenu != NULL)) { TMenuItem * MenuItems = Button->DropDownMenu->Items; for (int ItemIndex = 0; ItemIndex < MenuItems->Count; ItemIndex++) { TMenuItem * Item = MenuItems->Items[ItemIndex]; TShiftState GrouppedShiftState(Item->Tag >> 16); if (Item->Enabled && ((ShiftState.Empty() && Item->Default) || (!ShiftState.Empty() && (ShiftState == GrouppedShiftState)))) { Button->Caption = CopyToChar(Item->Caption, L'\t', false); Button->ModalResult = Item->Tag & 0xFFFF; DebugAssert(Button->OnClick == NULL); DebugAssert(Item->OnClick == MenuItemClick); break; } } } } } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::KeyUp(Word & Key, TShiftState Shift) { UpdateForShiftState(); TForm::KeyUp(Key, Shift); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::KeyDown(Word & Key, TShiftState Shift) { if (Shift.Contains(ssCtrl) && (Key == L'C')) { AppLog(L"Copying message to clipboard"); TInstantOperationVisualizer Visualizer; CopyToClipboard(GetFormText()); } else { if (!Shift.Contains(ssCtrl)) { for (int ComponentIndex = 0; ComponentIndex < ComponentCount - 1; ComponentIndex++) { TButton * Button = dynamic_cast(Components[ComponentIndex]); if ((Button != NULL) && (Button->DropDownMenu != NULL)) { TMenuItem * MenuItems = Button->DropDownMenu->Items; for (int ItemIndex = 0; ItemIndex < MenuItems->Count; ItemIndex++) { TMenuItem * Item = MenuItems->Items[ItemIndex]; if (IsAccel(Key, MenuItems->Items[ItemIndex]->Caption)) { Item->OnClick(Item); Key = 0; break; } } } if (Key == 0) { break; } } } UpdateForShiftState(); TForm::KeyDown(Key, Shift); } } //--------------------------------------------------------------------------- UnicodeString __fastcall TMessageForm::NormalizeNewLines(UnicodeString Text) { Text = ReplaceStr(Text, L"\r", L""); Text = ReplaceStr(Text, L"\n", L"\r\n"); return Text; } //--------------------------------------------------------------------------- UnicodeString __fastcall TMessageForm::GetFormText() { UnicodeString DividerLine, ButtonCaptions; DividerLine = GetDividerLine() + sLineBreak; for (int i = 0; i < ComponentCount - 1; i++) { if (dynamic_cast(Components[i]) != NULL) { ButtonCaptions += dynamic_cast(Components[i])->Caption + UnicodeString::StringOfChar(L' ', 3); } } ButtonCaptions = ReplaceStr(ButtonCaptions, L"&", L""); UnicodeString MoreMessages; if (MessageMemo != NULL) { MoreMessages = MessageMemo->Text + DividerLine; } else if ((MessageBrowser != NULL) && CopyTextFromBrowser(MessageBrowser, MoreMessages)) { if (!EndsStr(sLineBreak, MoreMessages)) { MoreMessages += sLineBreak; } MoreMessages += DividerLine; } UnicodeString MessageCaption = NormalizeNewLines(MessageText); UnicodeString Result = FORMAT(L"%s%s%s%s%s%s%s%s%s%s%s", (DividerLine, Caption, sLineBreak, DividerLine, MessageCaption, sLineBreak, DividerLine, MoreMessages, ButtonCaptions, sLineBreak, DividerLine)); return Result; } //--------------------------------------------------------------------------- UnicodeString __fastcall TMessageForm::GetReportText() { UnicodeString Text = MessageText; Text = Text.TrimRight(); if (MessageMemo != NULL) { Text += L"\n\n" + MessageMemo->Text; } // Currently we use browser for updates box only and it has help context, // and does not have Report button, so we cannot get here. DebugAssert(MessageBrowser == NULL); Text = NormalizeNewLines(Text); UnicodeString ReportErrorText = NormalizeNewLines(FMTLOAD(REPORT_ERROR, (L""))); Text = ReplaceStr(Text, ReportErrorText, L""); Text = Trim(Text); return Text; } //--------------------------------------------------------------------------- void __fastcall TMessageForm::CMDialogKey(TWMKeyDown & Message) { // this gets used in WinInterface.cpp SetTimeoutEvents if (OnKeyDown != NULL) { OnKeyDown(this, Message.CharCode, KeyDataToShiftState(Message.KeyData)); } if (Message.CharCode == VK_MENU) { bool AnyButtonWithGrouppedCommandsWithShiftState = false; for (int ComponentIndex = 0; ComponentIndex < ComponentCount - 1; ComponentIndex++) { TButton * Button = dynamic_cast(Components[ComponentIndex]); if ((Button != NULL) && (Button->DropDownMenu != NULL)) { // we should check if there are any commands with shift state, // but it's bit overkill AnyButtonWithGrouppedCommandsWithShiftState = true; break; } } // this is to make Alt only alter button meaning (if there is any // alternable button) and not popup system menu if (AnyButtonWithGrouppedCommandsWithShiftState) { Message.Result = 1; UpdateForShiftState(); } else { TForm::Dispatch(&Message); } } else if ((Message.CharCode == VK_UP) || (Message.CharCode == VK_DOWN)) { // WORKAROUND // noop to make up/down be passed back to button to allow drop down, // see TMessageButton::WMGetDlgCode } else { TForm::Dispatch(&Message); } } //--------------------------------------------------------------------------- int __fastcall TMessageForm::ShowModal() { if (IsApplicationMinimized()) { FShowNoActivate = true; } int Result = TForm::ShowModal(); UnhookFormActivation(this); return Result; } //--------------------------------------------------------------------------- void __fastcall TMessageForm::SetZOrder(bool TopMost) { // WORKAROUND: If application is minimized, // swallow call to BringToFront() from TForm::ShowModal() if (FShowNoActivate && TopMost) { // noop } else { TForm::SetZOrder(TopMost); } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::CMShowingChanged(TMessage & Message) { if (Showing && FShowNoActivate) { ShowFormNoActivate(this); } else { TForm::Dispatch(&Message); } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::Dispatch(void * Message) { TMessage * M = reinterpret_cast(Message); if (M->Msg == CM_DIALOGKEY) { CMDialogKey(*((TWMKeyDown *)Message)); } else if (M->Msg == CM_SHOWINGCHANGED) { CMShowingChanged(*M); } else { TForm::Dispatch(Message); } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::FormAfterMonitorDpiChanged(TObject *, int OldDPI, int NewDPI) { DebugUsedParam2(OldDPI, NewDPI); if (MessageBrowser != NULL) { LoadMessageBrowser(); } if (NeverAskAgainCheck != NULL) { AutoSizeCheckBox(NeverAskAgainCheck); } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::CreateParams(TCreateParams & Params) { TForm::CreateParams(Params); if ((Screen != NULL) && (Screen->ActiveForm != NULL) && Screen->ActiveForm->HandleAllocated()) { Params.WndParent = Screen->ActiveForm->Handle; } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::LoadMessageBrowser() { NavigateToUrl(MessageBrowserUrl); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::DoShow() { UseSystemSettingsPost(this); if (!MessageBrowserUrl.IsEmpty() && // Guard against repeated calls to DoOpen() (MessageBrowser == NULL)) { // Web Browser component does not seem to work, // when created before any window is shown. // I.e. when the message dialog is the first window (like when /update is used). // So we have to delay its creation until at least the dialog box is shown. MessageBrowser = CreateBrowserViewer(MessageBrowserPanel, LoadStr(MESSAGE_LOADING)); MessageBrowser->SendToBack(); LoadMessageBrowser(); } // Need OnShow to be called only after MessageBrowser is created, // so that the implementation (InsertDonateLink) can call InsertPanelToMessageDialog TForm::DoShow(); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::ButtonSubmit(TObject * Sender) { unsigned int Answer = 0; FButtonSubmitEvents[Sender](Sender, Answer); if (Answer != 0) { ModalResult = Answer; } } //--------------------------------------------------------------------------- void __fastcall TMessageForm::MenuItemClick(TObject * Sender) { TMenuItem * Item = DebugNotNull(dynamic_cast(Sender)); ModalResult = (Item->Tag & 0xFFFF); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::UpdateForShiftStateTimer(TObject * /*Sender*/) { // this is needed to reflect shift state, even when we do not have a keyboard // focus, what happens when drop down menu is popped up UpdateForShiftState(); } //--------------------------------------------------------------------------- void __fastcall TMessageForm::ButtonDropDownClick(TObject * /*Sender*/) { // as optimization, do not waste time running timer, unless // user pops up drop down menu. we do not have a way to stop timer, once // it closes, but functionaly it does not matter if (FUpdateForShiftStateTimer == NULL) { FUpdateForShiftStateTimer = new TTimer(this); FUpdateForShiftStateTimer->Interval = 50; FUpdateForShiftStateTimer->OnTimer = UpdateForShiftStateTimer; } } //--------------------------------------------------------------------------- const ResourceString * Captions[] = { &_SMsgDlgWarning, &_SMsgDlgError, &_SMsgDlgInformation, &_SMsgDlgConfirm, NULL }; const wchar_t * ImageNames[] = { L"Warning", L"Error", L"Information", L"Help Blue", NULL }; const int mcHorzMargin = 8; const int mcVertMargin = 13; const int mcHorzSpacing = 12; const int mcButtonVertMargin = 7; const int mcButtonSpacing = 5; // includes mcVertMargin const int mcMoreMessageHeight = 86; // approximately what Windows Vista task dialogs use, // actually they probably has fixed width const int mcMaxDialogWidth = 340; const int mcMinDialogWidth = 310; const int mcMinDialogwithMoreMessagesWidth = 400; //--------------------------------------------------------------------------- static UnicodeString __fastcall GetKeyNameStr(int Key) { wchar_t Buf[MAX_PATH]; LONG VirtualKey = MapVirtualKey(Key, MAPVK_VK_TO_VSC); VirtualKey <<= 16; if (GetKeyNameText(VirtualKey, Buf, LENOF(Buf)) > 0) { NULL_TERMINATE(Buf); } else { Buf[0] = L'\0'; } return Buf; } //--------------------------------------------------------------------------- TButton * __fastcall TMessageForm::CreateButton( UnicodeString Name, UnicodeString Caption, unsigned int Answer, TButtonSubmitEvent OnSubmit, bool IsTimeoutButton, int GroupWith, TShiftState GrouppedShiftState, bool ElevationRequired, bool MenuButton, TAnswerButtons & AnswerButtons, bool HasMoreMessages, int & ButtonWidths) { UnicodeString MeasureCaption = Caption; if (IsTimeoutButton) { MeasureCaption = FMTLOAD(TIMEOUT_BUTTON, (MeasureCaption, 99)); } TRect TextRect; DrawText(Canvas->Handle, UnicodeString(MeasureCaption).c_str(), -1, &TextRect, DT_CALCRECT | DT_LEFT | DT_SINGLELINE | DrawTextBiDiModeFlagsReadingOnly()); int CurButtonWidth = TextRect.Right - TextRect.Left + ScaleByTextHeightRunTime(this, 16); if (ElevationRequired) { // Elevation icon CurButtonWidth += ScaleByTextHeightRunTime(this, 16); } if (MenuButton) { CurButtonWidth += ScaleByTextHeightRunTime(this, 16); } TButton * Button = NULL; if ((GroupWith >= 0) && DebugAlwaysTrue(AnswerButtons.find(GroupWith) != AnswerButtons.end())) { TButton * GroupWithButton = AnswerButtons[GroupWith]; if (GroupWithButton->DropDownMenu == NULL) { GroupWithButton->Style = TCustomButton::bsSplitButton; GroupWithButton->DropDownMenu = new TPopupMenu(this); // cannot handle subitems with shift state, // if the button has its own handler // (though it may not be the case still here) DebugAssert(GroupWithButton->OnClick == NULL); TMenuItem * Item = new TMenuItem(GroupWithButton->DropDownMenu); GroupWithButton->DropDownMenu->Items->Add(Item); GroupWithButton->OnDropDownClick = ButtonDropDownClick; Item->Caption = GroupWithButton->Caption; Item->OnClick = MenuItemClick; DebugAssert(GroupWithButton->ModalResult <= 0xFFFF); Item->Tag = GroupWithButton->ModalResult; Item->Default = true; } TMenuItem * Item = new TMenuItem(GroupWithButton->DropDownMenu); GroupWithButton->DropDownMenu->Items->Add(Item); // See ShortCutToText in Vcl.Menus.pas if (GrouppedShiftState == (TShiftState() << ssAlt)) { Caption = Caption + L"\t" + GetKeyNameStr(VK_MENU); } else if (GrouppedShiftState == (TShiftState() << ssCtrl)) { Caption = Caption + L"\t" + GetKeyNameStr(VK_CONTROL); } else if (GrouppedShiftState == (TShiftState() << ssShift)) { Caption = Caption + L"\t" + GetKeyNameStr(VK_SHIFT); } else { // do not support combined shift states yet DebugAssert(GrouppedShiftState == TShiftState()); } Item->Caption = Caption; if (OnSubmit != NULL) { Item->OnClick = ButtonSubmit; FButtonSubmitEvents[Item] = OnSubmit; } else { Item->OnClick = MenuItemClick; DebugAssert((Answer <= 0xFFFF) && (GrouppedShiftState.ToInt() <= 0xFFFF)); Item->Tag = Answer + (GrouppedShiftState.ToInt() << 16); } // Hard-coded drop down button width (do not know how to ask for system width). // Also we do not update the max button width for the default groupped // button caption. We just blindly hope that captions of advanced commands // are always longer than the caption of simple default command CurButtonWidth += ScaleByTextHeightRunTime(this, 15); // never shrink buttons below their default width if (GroupWithButton->Width < CurButtonWidth) { ButtonWidths += CurButtonWidth - GroupWithButton->Width; GroupWithButton->Width = CurButtonWidth; } DebugAssert(!ElevationRequired); } else { Button = new TMessageButton(this); Button->Name = Name; Button->Parent = this; Button->Caption = Caption; if (OnSubmit != NULL) { Button->OnClick = ButtonSubmit; FButtonSubmitEvents[Button] = OnSubmit; } else { Button->ModalResult = Answer; } if (HasMoreMessages) { Button->Anchors = TAnchors() << akBottom << akLeft; } // never shrink buttons below our default UI button with Button->Width = std::max(ScaleByTextHeightRunTime(this, 80), CurButtonWidth); Button->ElevationRequired = ElevationRequired; ButtonWidths += Button->Width; if (MenuButton) { ::MenuButton(Button); } } return Button; } //--------------------------------------------------------------------------- void __fastcall TMessageForm::InsertPanel(TPanel * Panel) { if (DebugAlwaysTrue(MessageBrowser != NULL)) { // we currently use this for updates message box only TControl * ContentsControl = static_cast(DebugNotNull(MessageBrowser))->Parent; Panel->Parent = ContentsPanel; Panel->Width = ContentsControl->Width; Panel->Left = ContentsControl->Left; int ContentsBottom = ContentsControl->Top + ContentsControl->Height; Panel->Top = ContentsBottom + ((ContentsPanel->Height - ContentsBottom) / 2); Height = Height + Panel->Height; ContentsPanel->Height = ContentsPanel->Height + Panel->Height; // The panel itself does not need this, as the ParentBackground (true by default) // has the same effect, but an eventual TStaticText on the panel // uses a wrong background color, if panel's ParentColor is not set. Panel->ParentColor = true; } } //--------------------------------------------------------------------------- int __fastcall TMessageForm::GetContentWidth() { int Result = 0; if (DebugAlwaysTrue(MessageBrowser != NULL)) { // we currently use this for updates message box only TControl * ContentsControl = static_cast(DebugNotNull(MessageBrowser))->Parent; Result = ContentsControl->Width; } return Result; } //--------------------------------------------------------------------------- void __fastcall TMessageForm::NavigateToUrl(const UnicodeString & Url) { if (DebugAlwaysTrue(MessageBrowser != NULL)) { UnicodeString FontSizeParam = FORMAT(L"fontsize=%d", (Font->Size)); UnicodeString FullUrl = AppendUrlParams(Url, FontSizeParam); NavigateBrowserToUrl(MessageBrowser, FullUrl); } } //--------------------------------------------------------------------------- void __fastcall AnswerNameAndCaption( unsigned int Answer, UnicodeString & Name, UnicodeString & Caption) { switch (Answer) { case qaYes: Caption = LoadStr(_SMsgDlgYes.Identifier); Name = YesButtonName; break; case qaNo: Caption = LoadStr(_SMsgDlgNo.Identifier); Name = L"No"; break; case qaOK: Caption = LoadStr(_SMsgDlgOK.Identifier); Name = OKButtonName; break; case qaCancel: Caption = LoadStr(_SMsgDlgCancel.Identifier); Name = L"Cancel"; break; case qaAbort: Caption = LoadStr(_SMsgDlgAbort.Identifier); Name = L"Abort"; break; case qaRetry: Caption = LoadStr(_SMsgDlgRetry.Identifier); Name = L"Retry"; break; case qaIgnore: Caption = LoadStr(_SMsgDlgIgnore.Identifier); Name = L"Ignore"; break; // Own variant to avoid accelerator conflict with "Abort" button. // Note that as of now, ALL_BUTTON is never actually used, // because qaAll is always aliased case qaAll: Caption = LoadStr(ALL_BUTTON); Name = L"All"; break; case qaNoToAll: Caption = LoadStr(_SMsgDlgNoToAll.Identifier); Name = L"NoToAll"; break; // Own variant to avoid accelerator conflict with "Abort" button. case qaYesToAll: Caption = LoadStr(YES_TO_ALL_BUTTON); Name = L"YesToAll"; break; case qaHelp: Caption = LoadStr(_SMsgDlgHelp.Identifier); Name = L"Help"; break; case qaSkip: Caption = LoadStr(SKIP_BUTTON); Name = L"Skip"; break; case qaReport: Caption = LoadStr(REPORT_BUTTON); Name = L"Report"; break; default: DebugFail(); throw Exception(L"Undefined answer"); } } //--------------------------------------------------------------------------- static int __fastcall CalculateWidthOnCanvas(UnicodeString Text, void * Arg) { TCanvas * Canvas = static_cast(Arg); return Canvas->TextWidth(Text); } //--------------------------------------------------------------------------- TForm * __fastcall TMessageForm::Create(const UnicodeString & Msg, TStrings * MoreMessages, TMsgDlgType DlgType, unsigned int Answers, const TQueryButtonAlias * Aliases, unsigned int AliasesCount, unsigned int TimeoutAnswer, TButton ** TimeoutButton, const UnicodeString & AImageName, const UnicodeString & NeverAskAgainCaption, const UnicodeString & MoreMessagesUrl, TSize MoreMessagesSize, const UnicodeString & CustomCaption) { unsigned int DefaultAnswer; if (FLAGSET(Answers, qaOK)) { DefaultAnswer = qaOK; } else if (FLAGSET(Answers, qaYes)) { DefaultAnswer = qaYes; } else { DefaultAnswer = qaRetry; } unsigned int CancelAnswer = ::CancelAnswer(Answers); if (TimeoutButton != NULL) { *TimeoutButton = NULL; } TColor MainInstructionColor; HFONT MainInstructionFont; HFONT InstructionFont; GetInstrutionsTheme(MainInstructionColor, MainInstructionFont, InstructionFont); TMessageForm * Result = SafeFormCreate(); if (InstructionFont != 0) { Result->Font->Handle = InstructionFont; } else { Result->Font->Assign(Screen->MessageFont); } // Can be possibly nul when error occurs before configuration is created if (Configuration != NULL) { Configuration->Usage->Set(L"ThemeMessageFontSize", Result->Font->Size); } // make sure we consider sizes of the monitor, // that is set in DoFormWindowProc(CM_SHOWINGCHANGED) later. Forms::TMonitor * Monitor = FormMonitor(Result); bool HasMoreMessages = (MoreMessages != NULL) || !MoreMessagesUrl.IsEmpty(); Result->BiDiMode = Application->BiDiMode; Result->BorderStyle = bsDialog; Result->Canvas->Font = Result->Font; Result->KeyPreview = true; int HorzMargin = ScaleByTextHeightRunTime(Result, mcHorzMargin); int VertMargin = ScaleByTextHeightRunTime(Result, mcVertMargin); int HorzSpacing = ScaleByTextHeightRunTime(Result, mcHorzSpacing); int ButtonVertMargin = ScaleByTextHeightRunTime(Result, mcButtonVertMargin); int ButtonWidths = 0; int ButtonHeight = -1; std::vector ButtonControls; TStaticText * LinkControl = NULL; TAnswerButtons AnswerButtons; for (unsigned int Answer = qaFirst; Answer <= qaLast; Answer = Answer << 1) { if (FLAGSET(Answers, Answer)) { DebugAssert(Answer != mrCancel); UnicodeString Caption; UnicodeString Name; AnswerNameAndCaption(Answer, Name, Caption); TButtonSubmitEvent OnSubmit = NULL; int GroupWith = -1; TShiftState GrouppedShiftState; bool ElevationRequired = false; bool MenuButton = false; UnicodeString ActionAlias; if (Aliases != NULL) { for (unsigned int i = 0; i < AliasesCount; i++) { if (Answer == Aliases[i].Button) { if (!Aliases[i].Alias.IsEmpty()) { Caption = Aliases[i].Alias; } OnSubmit = Aliases[i].OnSubmit; GroupWith = Aliases[i].GroupWith; GrouppedShiftState = Aliases[i].GrouppedShiftState; ElevationRequired = Aliases[i].ElevationRequired; MenuButton = Aliases[i].MenuButton; ActionAlias = Aliases[i].ActionAlias; DebugAssert((OnSubmit == NULL) || (GrouppedShiftState == TShiftState())); break; } } } // implemented for a one link only for now if (!ActionAlias.IsEmpty() && DebugAlwaysTrue(LinkControl == NULL) && DebugAlwaysTrue(OnSubmit != NULL) && DebugAlwaysTrue(GroupWith < 0)) { LinkControl = new TStaticText(Result); LinkControl->Name = Name; LinkControl->Caption = ActionAlias; LinkControl->Alignment = taRightJustify; LinkControl->Anchors = TAnchors() << akRight << akTop; LinkControl->OnClick = Result->ButtonSubmit; Result->FButtonSubmitEvents[LinkControl] = OnSubmit; } else { // we hope that all grouped-with buttons are for answer with greater // value that the answer to be grouped with if (GroupWith >= 0) { if (DebugAlwaysFalse(GroupWith >= static_cast(Answer)) || DebugAlwaysFalse(Answer == TimeoutAnswer) && DebugAlwaysFalse(Answer == DefaultAnswer) && DebugAlwaysFalse(Answer == CancelAnswer)) { GroupWith = -1; } } bool IsTimeoutButton = (TimeoutButton != NULL) && (Answer == TimeoutAnswer); if (Answer == qaHelp) { DebugAssert(OnSubmit == NULL); OnSubmit = Result->HelpButtonSubmit; } if (Answer == qaReport) { DebugAssert(OnSubmit == NULL); OnSubmit = Result->ReportButtonSubmit; } TButton * Button = Result->CreateButton( Name, Caption, Answer, OnSubmit, IsTimeoutButton, GroupWith, GrouppedShiftState, ElevationRequired, MenuButton, AnswerButtons, HasMoreMessages, ButtonWidths); if (Button != NULL) { ButtonControls.push_back(Button); Button->Default = (Answer == DefaultAnswer); Button->Cancel = (Answer == CancelAnswer); if (ButtonHeight < 0) { ButtonHeight = Button->Height; } DebugAssert(ButtonHeight == Button->Height); AnswerButtons.insert(TAnswerButtons::value_type(Answer, Button)); if (IsTimeoutButton) { *TimeoutButton = Button; } } } } } int NeverAskAgainWidth = 0; int NeverAskAgainBaseWidth = 0; if (!NeverAskAgainCaption.IsEmpty()) { NeverAskAgainBaseWidth = CalculateCheckBoxWidth(Result, NeverAskAgainCaption); NeverAskAgainWidth = NeverAskAgainBaseWidth + ScaleByTextHeightRunTime(Result, 8); // even more margin } int ButtonSpacing = ScaleByTextHeightRunTime(Result, mcButtonSpacing); int ButtonGroupWidth = NeverAskAgainWidth; if (!ButtonControls.empty()) { ButtonGroupWidth += ButtonWidths + ButtonSpacing * (ButtonControls.size() - 1); } DebugAssert((ButtonHeight > 0) && (ButtonWidths > 0)); TPanel * Panel = CreateBlankPanel(Result); Result->ContentsPanel = Panel; Panel->Name = MessagePanelName; Panel->Parent = Result; Panel->Color = clWindow; Panel->ParentBackground = false; Panel->Anchors = TAnchors() << akLeft << akRight << akTop; Panel->Caption = L""; int IconWidth = 0; int IconHeight = 0; UnicodeString ImageName = AImageName; if (ImageName.IsEmpty() && DebugAlwaysTrue(ImageNames[DlgType] != NULL)) { ImageName = ImageNames[DlgType]; } if (DebugAlwaysTrue(!ImageName.IsEmpty())) { TImage * Image = new TImage(Result); Image->Name = L"Image"; Image->Parent = Panel; LoadDialogImage(Image, ImageName); Image->SetBounds(HorzMargin, VertMargin, Image->Picture->Width, Image->Picture->Height); IconWidth = Image->Width + HorzSpacing; IconHeight = Image->Height; } int MaxTextWidth = ScaleByTextHeightRunTime(Result, mcMaxDialogWidth); // If the message contains SHA-256 hex fingerprint (CERT_TEXT2 on TLS/SSL certificate verification dialog), // allow wider box to fit it if (TRegEx::IsMatch(Msg, L"([0-9a-fA-F]{2}[:\-]){31}[0-9a-fA-F]{2}")) { MaxTextWidth = MaxTextWidth * 3 / 2; } // if the dialog would be wide anyway (overwrite confirmation on Windows XP), // to fit the buttons, do not restrict the text if (MaxTextWidth < ButtonGroupWidth - IconWidth) { MaxTextWidth = ButtonGroupWidth - IconWidth; } UnicodeString BodyMsg = Msg; BodyMsg = RemoveInteractiveMsgTag(BodyMsg); UnicodeString MainMsg; if (ExtractMainInstructions(BodyMsg, MainMsg)) { Result->MessageText = MainMsg + BodyMsg; BodyMsg = BodyMsg.TrimLeft(); } else { Result->MessageText = BodyMsg; } ApplyTabs(Result->MessageText, L' ', NULL, NULL); // Windows XP (not sure about Vista) does not support Hair space. // For Windows XP, we still keep the existing hack by using hard-coded spaces // in resource string if (IsWin7()) { // Have to be padding with spaces (the smallest space defined, hair space = 1px), // as tabs actually do not tab, just expand to 8 spaces. // Otherwise we would have to do custom drawing // (using GetTabbedTextExtent and TabbedTextOut) const wchar_t HairSpace = L'\x200A'; ApplyTabs(BodyMsg, HairSpace, CalculateWidthOnCanvas, Result->Canvas); } DebugAssert(MainMsg.Pos(L"\t") == 0); int IconTextWidth = -1; int IconTextHeight = 0; int ALeft = IconWidth + HorzMargin; for (int MessageIndex = 0; MessageIndex <= 1; MessageIndex++) { UnicodeString LabelMsg; UnicodeString LabelName; TColor LabelColor = Graphics::clNone; HFONT LabelFont = 0; switch (MessageIndex) { case 0: LabelMsg = MainMsg; LabelName = MainMessageLabelName; LabelColor = MainInstructionColor; LabelFont = MainInstructionFont; break; case 1: LabelMsg = BodyMsg; LabelName = MessageLabelName; break; default: DebugFail(); break; } if (!LabelMsg.IsEmpty()) { TLabel * Message = new TLabel(Panel); Message->Parent = Panel; Message->Name = LabelName; Message->WordWrap = true; Message->Caption = LabelMsg; Message->BiDiMode = Result->BiDiMode; // added to show & as & for messages containing !& pattern of custom commands // (suppose that we actually never want to use & as accel in message text) Message->ShowAccelChar = false; if (LabelFont != 0) { Message->Font->Handle = LabelFont; if (DebugAlwaysTrue(LabelFont == MainInstructionFont) && // When showing an early error message (Configuration != NULL)) { Configuration->Usage->Set(L"ThemeMainInstructionFontSize", Message->Font->Size); } } if (LabelColor != Graphics::clNone) { Message->Font->Color = LabelColor; } TRect TextRect; SetRect(&TextRect, 0, 0, MaxTextWidth, 0); DrawText(Message->Canvas->Handle, LabelMsg.c_str(), LabelMsg.Length() + 1, &TextRect, DT_EXPANDTABS | DT_CALCRECT | DT_WORDBREAK | DT_NOPREFIX | Result->DrawTextBiDiModeFlagsReadingOnly()); int MaxWidth = Monitor->Width - HorzMargin * 2 - IconWidth - 30; // 5% buffer for potential WM_DPICHANGED, as after re-scaling the text can otherwise narrowly not fit in. // Though note that the buffer is lost on the first re-scale due to the AutoSize TextRect.right = MulDiv(TextRect.right, 105, 100); // this will truncate the text, we should implement something smarter eventually TextRect.right = Min(TextRect.right, MaxWidth); IconTextWidth = Max(IconTextWidth, IconWidth + TextRect.Right); if (IconTextHeight > 0) { IconTextHeight += VertMargin; } Message->SetBounds(ALeft, VertMargin + IconTextHeight, TextRect.Right, TextRect.Bottom); IconTextHeight += TextRect.Bottom; } } if (LinkControl != NULL) { LinkControl->Parent = Panel; LinkActionLabel(LinkControl); LinkControl->Left = Panel->ClientWidth - HorzMargin - LinkControl->Width; LinkControl->Top = VertMargin + IconTextHeight + VertMargin; IconTextHeight += VertMargin + LinkControl->Height; } DebugAssert((IconTextWidth > 0) && (IconTextHeight > 0)); IconTextHeight = Max(IconTextHeight, IconHeight); int MoreMessageHeight = (HasMoreMessages ? ScaleByTextHeightRunTime(Result, (MoreMessagesSize.Height > 0 ? MoreMessagesSize.Height : mcMoreMessageHeight)) : 0); Panel->SetBounds(0, 0, Result->ClientWidth, VertMargin + IconTextHeight + VertMargin + MoreMessageHeight); TControl * MoreMessagesControl = NULL; if (HasMoreMessages) { if (MoreMessages != NULL) { DebugAssert(MoreMessagesUrl.IsEmpty()); TMemo * MessageMemo = new TMemo(Panel); MoreMessagesControl = MessageMemo; MessageMemo->Name = L"MessageMemo"; MessageMemo->Parent = Panel; MessageMemo->ReadOnly = true; MessageMemo->WantReturns = False; MessageMemo->ScrollBars = ssVertical; MessageMemo->Anchors = TAnchors() << akLeft << akRight << akTop; MessageMemo->Lines->Text = MoreMessages->Text; Result->MessageMemo = MessageMemo; } else if (DebugAlwaysTrue(!MoreMessagesUrl.IsEmpty())) { TPanel * MessageBrowserPanel = CreateBlankPanel(Panel); MessageBrowserPanel->Parent = Panel; MessageBrowserPanel->Anchors = TAnchors() << akLeft << akRight << akTop; MessageBrowserPanel->BevelKind = bkTile; // flat border Result->MessageBrowserPanel = MessageBrowserPanel; MoreMessagesControl = Result->MessageBrowserPanel; Result->MessageBrowserUrl = CampaignUrl(MoreMessagesUrl); } } int MinClientWidth = ScaleByTextHeightRunTime(Result, HasMoreMessages ? (MoreMessagesSize.Width > 0 ? MoreMessagesSize.Width : mcMinDialogwithMoreMessagesWidth) : mcMinDialogWidth); int AClientWidth = Max( (IconTextWidth > ButtonGroupWidth ? IconTextWidth : ButtonGroupWidth) + HorzMargin * 2, MinClientWidth); Result->ClientWidth = AClientWidth; Result->ClientHeight = Panel->Height + ButtonVertMargin + ButtonHeight + ButtonVertMargin; if (!CustomCaption.IsEmpty()) { Result->Caption = CustomCaption; } else if (DebugAlwaysTrue(DlgType != mtCustom)) { Result->Caption = LoadResourceString(Captions[DlgType]); } else { Result->Caption = Application->Title; } if (MoreMessagesControl != NULL) { MoreMessagesControl->SetBounds( ALeft, Panel->Height - MoreMessageHeight, Result->ClientWidth - ALeft - HorzMargin, MoreMessageHeight - VertMargin); } int ButtonTop = Panel->Height + ButtonVertMargin; int X = Result->ClientWidth - ButtonGroupWidth + NeverAskAgainWidth - HorzMargin; for (unsigned int i = 0; i < ButtonControls.size(); i++) { ButtonControls[i]->SetBounds( X, ButtonTop, ButtonControls[i]->Width, ButtonControls[i]->Height); X += ButtonControls[i]->Width + ButtonSpacing; } if (!NeverAskAgainCaption.IsEmpty() && !ButtonControls.empty()) { Result->NeverAskAgainCheck = new TCheckBox(Result); Result->NeverAskAgainCheck->Name = L"NeverAskAgainCheck"; Result->NeverAskAgainCheck->Parent = Result; Result->NeverAskAgainCheck->Caption = NeverAskAgainCaption; // Previously we set anchor to akBottom, but as we do not do that for buttons, we removed that. // When showing window on 100% DPI monitor, with system DPI 100%, but main monitor 150%, // the title bar seems to start on 150% DPI reducing later, leaving the form client height // sligtly higher than needed and the checkbox being aligned differently than the button. // Removing the akBottom aligning improves this sligtly, while the main problem still should be fixed. TButton * FirstButton = ButtonControls[0]; int NeverAskAgainHeight = ScaleByTextHeightRunTime(Result, Result->NeverAskAgainCheck->Height); int NeverAskAgainTop = FirstButton->Top + ((FirstButton->Height - NeverAskAgainHeight) / 2); int NeverAskAgainLeft = HorzMargin + ScaleByTextHeightRunTime(Result, 2); // checkbox indentation Result->NeverAskAgainCheck->SetBounds( NeverAskAgainLeft, NeverAskAgainTop, NeverAskAgainBaseWidth, NeverAskAgainHeight); } return Result; } //--------------------------------------------------------------------------- TForm * __fastcall CreateMoreMessageDialog(const UnicodeString & Msg, TStrings * MoreMessages, TMsgDlgType DlgType, unsigned int Answers, const TQueryButtonAlias * Aliases, unsigned int AliasesCount, unsigned int TimeoutAnswer, TButton ** TimeoutButton, const UnicodeString & ImageName, const UnicodeString & NeverAskAgainCaption, const UnicodeString & MoreMessagesUrl, TSize MoreMessagesSize, const UnicodeString & CustomCaption) { return TMessageForm::Create(Msg, MoreMessages, DlgType, Answers, Aliases, AliasesCount, TimeoutAnswer, TimeoutButton, ImageName, NeverAskAgainCaption, MoreMessagesUrl, MoreMessagesSize, CustomCaption); } //--------------------------------------------------------------------------- void __fastcall InsertPanelToMessageDialog(TCustomForm * Form, TPanel * Panel) { TMessageForm * MessageForm = DebugNotNull(dynamic_cast(Form)); MessageForm->InsertPanel(Panel); } //--------------------------------------------------------------------------- void __fastcall NavigateMessageDialogToUrl(TCustomForm * Form, const UnicodeString & Url) { TMessageForm * MessageForm = DebugNotNull(dynamic_cast(Form)); MessageForm->NavigateToUrl(Url); } //--------------------------------------------------------------------------- int __fastcall GetMessageDialogContentWidth(TCustomForm * Form) { TMessageForm * MessageForm = DebugNotNull(dynamic_cast(Form)); return MessageForm->GetContentWidth(); }