mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
Mostly – explicit static global variables – const, sign and down casts safety – same-named variables in nested scopes – incorrectly identified uninitialized variables – semicolon after function implementation – unused variables, fields, arguments and macros – exact include filename case – field initialization order – typename use – struct vs class declarations – incorrectly identified uninitialized variables – refactoring to solve Clang internal compiler error when using new variable in __finally block Source commit: 3da995ff5169eefcfe3b3b7cc0d019c660096c07
1290 lines
42 KiB
C++
1290 lines
42 KiB
C++
//---------------------------------------------------------------------------
|
|
#include <vcl.h>
|
|
#pragma hdrstop
|
|
|
|
#include <Consts.hpp>
|
|
#include <GUITools.h>
|
|
|
|
#include <Common.h>
|
|
#include <VCLCommon.h>
|
|
#include <CoreMain.h>
|
|
#include <WinInterface.h>
|
|
#include <Tools.h>
|
|
#include <TextsWin.h>
|
|
#include <TextsCore.h>
|
|
#include <Vcl.Imaging.pngimage.hpp>
|
|
#include <StrUtils.hpp>
|
|
#include <PasTools.hpp>
|
|
#include <Math.hpp>
|
|
#include <WebBrowserEx.hpp>
|
|
#include <RegularExpressions.hpp>
|
|
#include <Setup.h>
|
|
#include <WinApi.h>
|
|
#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);
|
|
virtual void __fastcall CreateWnd();
|
|
private:
|
|
void __fastcall WMGetDlgCode(TWMGetDlgCode & Message);
|
|
};
|
|
//---------------------------------------------------------------------------
|
|
__fastcall TMessageButton::TMessageButton(TComponent * Owner) :
|
|
TButton(Owner)
|
|
{
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TMessageButton::Dispatch(void * Message)
|
|
{
|
|
TMessage * M = reinterpret_cast<TMessage*>(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;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TMessageButton::CreateWnd()
|
|
{
|
|
TButton::CreateWnd();
|
|
SetExplorerTheme(this);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
__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<TButton*>(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<TButton*>(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<TButton*>(Components[i]) != NULL)
|
|
{
|
|
ButtonCaptions += dynamic_cast<TButton*>(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<TButton*>(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<TMessage*>(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::CreateWnd()
|
|
{
|
|
TForm::CreateWnd();
|
|
ApplyColorMode(this);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
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<TMenuItem *>(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;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
static const ResourceString * Captions[] = { &_SMsgDlgWarning, &_SMsgDlgError, &_SMsgDlgInformation,
|
|
&_SMsgDlgConfirm, NULL };
|
|
static 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;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
TControl * TMessageForm::GetContentsControls()
|
|
{
|
|
return static_cast<TControl *>(DebugNotNull(MessageBrowser))->Parent;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TMessageForm::InsertPanel(TPanel * Panel)
|
|
{
|
|
if (DebugAlwaysTrue(MessageBrowser != NULL))
|
|
{
|
|
// we currently use this for updates message box only
|
|
TControl * ContentsControl = GetContentsControls();
|
|
|
|
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
|
|
Result = GetContentsControls()->Width;
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
static UnicodeString UrlColor(TColor Color)
|
|
{
|
|
UnicodeString Result = ColorToWebColorStr(Color);
|
|
DebugAssert(Result.Length() == 7);
|
|
if (DebugAlwaysTrue(StartsStr(L"#", Result)))
|
|
{
|
|
Result.Delete(1, 1);
|
|
}
|
|
return Result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall TMessageForm::NavigateToUrl(const UnicodeString & Url)
|
|
{
|
|
if (DebugAlwaysTrue(MessageBrowser != NULL))
|
|
{
|
|
UnicodeString StyleParams =
|
|
FORMAT(L"fontsize=%d&textcolor=%s&backcolor=%s", (Font->Size, UrlColor(Font->Color), UrlColor(GetControlColor(GetContentsControls()))));
|
|
UnicodeString FullUrl = AppendUrlParams(Url, StyleParams);
|
|
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<TCanvas *>(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;
|
|
// In general, we should not have Yes and OK on the same query.
|
|
// But we do for "hostkey" prompt, where they are aliased and OK is groupped with Yes.
|
|
// There we need Yes to be the default.
|
|
if (FLAGSET(Answers, qaYes))
|
|
{
|
|
DefaultAnswer = qaYes;
|
|
}
|
|
else if (FLAGSET(Answers, qaOK))
|
|
{
|
|
DefaultAnswer = qaOK;
|
|
}
|
|
else
|
|
{
|
|
DefaultAnswer = qaRetry;
|
|
}
|
|
|
|
unsigned int CancelAnswer = ::CancelAnswer(Answers);
|
|
|
|
if (TimeoutButton != NULL)
|
|
{
|
|
*TimeoutButton = NULL;
|
|
}
|
|
|
|
TMessageForm * Result = SafeFormCreate<TMessageForm>();
|
|
|
|
TColor MainInstructionColor;
|
|
HFONT MainInstructionFont;
|
|
HFONT InstructionFont;
|
|
GetInstrutionsTheme(MainInstructionColor, MainInstructionFont, InstructionFont);
|
|
|
|
// There is probably some theme we can load the load dark mode color from
|
|
if (UseDarkModeForControl(Result))
|
|
{
|
|
MainInstructionColor = GetLinkColor(Result);
|
|
}
|
|
|
|
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<TButton *> 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<int>(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->ParentBackground = false;
|
|
Panel->Anchors = TAnchors() << akLeft << akRight << akTop;
|
|
Panel->Caption = L"";
|
|
|
|
int IconWidth = 0;
|
|
int IconHeight = 0;
|
|
|
|
UnicodeString ImageName = AImageName;
|
|
int DlgTypeIndex = static_cast<int>(DlgType);
|
|
if (ImageName.IsEmpty() &&
|
|
DebugAlwaysTrue(ImageNames[DlgTypeIndex] != NULL))
|
|
{
|
|
ImageName = ImageNames[DlgTypeIndex];
|
|
}
|
|
|
|
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 = CreateMemo(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[DlgTypeIndex]);
|
|
}
|
|
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 = CreateCheckBox(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<TMessageForm *>(Form));
|
|
MessageForm->InsertPanel(Panel);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void __fastcall NavigateMessageDialogToUrl(TCustomForm * Form, const UnicodeString & Url)
|
|
{
|
|
TMessageForm * MessageForm = DebugNotNull(dynamic_cast<TMessageForm *>(Form));
|
|
MessageForm->NavigateToUrl(Url);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
int __fastcall GetMessageDialogContentWidth(TCustomForm * Form)
|
|
{
|
|
TMessageForm * MessageForm = DebugNotNull(dynamic_cast<TMessageForm *>(Form));
|
|
return MessageForm->GetContentWidth();
|
|
}
|