Index: mptrack/dlg_misc.cpp =================================================================== --- mptrack/dlg_misc.cpp (revision 24083) +++ mptrack/dlg_misc.cpp (working copy) @@ -1573,27 +1573,29 @@ if((TrackerSettings::Instance().gnMsgBoxVisiblityFlags & msg.mask) == 0) return; -#if MPT_WINNT_AT_LEAST(MPT_WIN_VISTA) && defined(UNICODE) - if(CTaskDialog::IsSupported() - && !(mpt::OS::Windows::IsWine() && theApp.GetWineVersion()->Version().IsBefore(mpt::OS::Wine::Version(3, 13, 0)))) + TaskDlg dlg = TaskDlg{} + .WindowTitle(msg.mainTitle) + .Icon(MB_ICONINFORMATION); + + bool dontShowAgain; + if(TaskDlg::ModernTaskDialogSupported()) { - CTaskDialog taskDialog(msg.message, msg.mainTitle ? CString{msg.mainTitle} : CString{}, AfxGetAppName(), TDCBF_OK_BUTTON); - taskDialog.SetVerificationCheckboxText(_T("Do not show this message again")); - taskDialog.SetVerificationCheckbox(msg.defaultDontShowAgainStatus); - taskDialog.DoModal(); - - if(taskDialog.GetVerificationCheckboxState()) - TrackerSettings::Instance().gnMsgBoxVisiblityFlags &= ~msg.mask; - else - TrackerSettings::Instance().gnMsgBoxVisiblityFlags |= msg.mask; + dlg.Description(msg.message) + .VerificationText(_T("Do not show this message again")) + .VerificationChecked(msg.defaultDontShowAgainStatus); + dontShowAgain = dlg.DoModal().verificationChecked; } else -#endif { - if(Reporting::Confirm(msg.message + CString(_T("\n\nShow this message again?")), msg.mainTitle ? CString{msg.mainTitle} : CString{}, msg.defaultDontShowAgainStatus) == cnfNo) - TrackerSettings::Instance().gnMsgBoxVisiblityFlags &= ~msg.mask; - else - TrackerSettings::Instance().gnMsgBoxVisiblityFlags |= msg.mask; + dlg.Description(CString{msg.message} + _T("\n\nShow this message again?")) + .Buttons({{_T("Yes"), IDYES}, {_T("No"), IDNO} }) + .DefaultButton(msg.defaultDontShowAgainStatus ? IDNO : IDYES); + dontShowAgain = dlg.DoModal().buttonId == IDNO; } + + if(dontShowAgain) + TrackerSettings::Instance().gnMsgBoxVisiblityFlags &= ~msg.mask; + else + TrackerSettings::Instance().gnMsgBoxVisiblityFlags |= msg.mask; } Index: mptrack/Reporting.cpp =================================================================== --- mptrack/Reporting.cpp (revision 24083) +++ mptrack/Reporting.cpp (working copy) @@ -10,23 +10,27 @@ #include "stdafx.h" #include "Reporting.h" -#include "../mptrack/Mainfrm.h" +#include "Mainfrm.h" +#if MPT_WINNT_AT_LEAST(MPT_WIN_VISTA) && defined(UNICODE) +#include "Mptrack.h" +#include +#endif OPENMPT_NAMESPACE_BEGIN -static inline UINT LogLevelToFlags(LogLevel level) +static inline UINT LogLevelToIcon(LogLevel level) { switch(level) { - case LogDebug: return MB_OK; break; - case LogNotification: return MB_OK; break; - case LogInformation: return MB_OK | MB_ICONINFORMATION; break; - case LogWarning: return MB_OK | MB_ICONWARNING; break; - case LogError: return MB_OK | MB_ICONERROR; break; + case LogDebug: return 0; + case LogNotification: return 0; + case LogInformation: return MB_ICONINFORMATION; + case LogWarning: return MB_ICONWARNING; + case LogError: return MB_ICONERROR; } - return MB_OK; + return 0; } @@ -80,70 +84,264 @@ text.SetAt(uint16_max - 2, _T('.')); text.SetAt(uint16_max - 3, _T('.')); } - UINT result = ::MessageBox(parent->GetSafeHwnd(), text, caption.IsEmpty() ? CString(MAINFRAME_TITLE) : caption, flags); + UINT result = ::MessageBox(parent->GetSafeHwnd(), text, caption.IsEmpty() ? CString{MAINFRAME_TITLE} : caption, flags); return result; } +static const TCHAR *GetButtonString(UINT id) +{ + mpt::Library user32{mpt::LibraryPath::System(P_("user32"))}; + using PMB_GETSTRING = LPCWSTR(WINAPI *)(UINT); + PMB_GETSTRING MB_GetString = nullptr; + user32.Bind(MB_GetString, "MB_GetString"); + if(MB_GetString) + return MB_GetString(id - 1); + + switch(id) + { + case IDOK: return _T("&OK"); + case IDCANCEL: return _T("&Cancel"); + case IDABORT: return _T("&Abort"); + case IDRETRY: return _T("&Retry"); + case IDIGNORE: return _T("&Ignore"); + case IDYES: return _T("&Yes"); + case IDNO: return _T("&No"); + case IDCLOSE: return _T("&Close"); + case IDHELP: return _T("Help"); + case IDTRYAGAIN: return _T("&Try Again"); + case IDCONTINUE: return _T("&Continue"); + default: return _T(""); + } +} + + +bool TaskDlg::ModernTaskDialogSupported() +{ +#if MPT_WINNT_AT_LEAST(MPT_WIN_VISTA) && defined(UNICODE) + return CTaskDialog::IsSupported() + && !(mpt::OS::Windows::IsWine() && theApp.GetWineVersion()->Version().IsBefore(mpt::OS::Wine::Version(3, 13, 0))); +#else + return false; +#endif +} + + +TaskDlg::Result TaskDlg::DoModal(const CWnd *parent) const +{ + if(!parent) + parent = CMainFrame::GetActiveWindow(); + +#if MPT_WINNT_AT_LEAST(MPT_WIN_VISTA) && defined(UNICODE) + if(ModernTaskDialogSupported()) + { + CTaskDialog taskDialog{ + description, + headline, + windowTitle.IsEmpty() ? GetTitle() : windowTitle, + buttons.empty() ? TDCBF_OK_BUTTON : 0, + bigButtons ? TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS : 0}; + + if(icon == MB_ICONINFORMATION) + taskDialog.SetMainIcon(TD_INFORMATION_ICON); + else if(icon == MB_ICONWARNING) + taskDialog.SetMainIcon(TD_WARNING_ICON); + else if(icon == MB_ICONERROR) + taskDialog.SetMainIcon(TD_ERROR_ICON); + else if(icon == MB_ICONQUESTION) + taskDialog.SetMainIcon(TD_INFORMATION_ICON); // theApp.LoadStandardIcon(IDI_QUESTION) - this icon hasn't been updated since Vista, and shouldn't be used according to guidelines anyway... + + for(const auto &button : buttons) + { + taskDialog.AddCommandControl(button.commandId, button.text); + } + + if(!verificationText.IsEmpty()) + { + taskDialog.SetVerificationCheckboxText(verificationText); + taskDialog.SetVerificationCheckbox(verificationChecked); + } + + if(defaultButton) + taskDialog.SetDefaultCommandControl(defaultButton); + + UINT result = static_cast(taskDialog.DoModal(parent->GetSafeHwnd())); + return {result, !verificationText.IsEmpty() ? !!taskDialog.GetVerificationCheckboxState() : false}; + } +#endif + + // Fallback using regular MessageBox + UINT flags = MB_OK; + if(buttons.size() <= 1) + { + flags = MB_OK; + } else if(buttons.size() == 2) + { + uint32 hasButton = 0; + for(const auto &button : buttons) + { + hasButton |= (1 << button.commandId); + } + if(hasButton == ((1 << IDOK) | (1 << IDCANCEL))) + flags = MB_OKCANCEL; + else if(hasButton == ((1 << IDYES) | (1 << IDNO))) + flags = MB_YESNO; + else if(hasButton == ((1 << IDRETRY) | (1 << IDCANCEL))) + flags = MB_RETRYCANCEL; + else + flags = MB_OKCANCEL; + } else if(buttons.size() == 3) + { + flags = MB_YESNOCANCEL; + } + + if(defaultButton != IDOK) + { + if(defaultButton == IDCANCEL) + flags |= MB_DEFBUTTON3; + else if(defaultButton == IDNO) + flags |= MB_DEFBUTTON2; + } + + CString messageText = description; + if(!headline.IsEmpty()) + messageText = headline + _T("\n\n") + messageText; + + // Workaround MessageBox text length limitation: Better show a truncated string than no message at all. + if(messageText.GetLength() > uint16_max) + { + messageText.Truncate(uint16_max); + messageText.SetAt(uint16_max - 1, _T('.')); + messageText.SetAt(uint16_max - 2, _T('.')); + messageText.SetAt(uint16_max - 3, _T('.')); + } + UINT result = ::MessageBox(parent->GetSafeHwnd(), messageText, windowTitle.IsEmpty() ? GetTitle() : windowTitle, flags | icon); + return {result, false}; +} + + void Reporting::Notification(const AnyStringLocale &text, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(CString(), LogNotification), LogLevelToFlags(LogNotification), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption({}, LogNotification)) + .AddButton(GetButtonString(IDOK), IDOK) + .DoModal(parent); } void Reporting::Notification(const AnyStringLocale &text, const AnyStringLocale &caption, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption), LogNotification), LogLevelToFlags(LogNotification), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption), LogNotification)) + .AddButton(GetButtonString(IDOK), IDOK) + .DoModal(parent); } void Reporting::Information(const AnyStringLocale &text, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(CString(), LogInformation), LogLevelToFlags(LogInformation), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption({}, LogInformation)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(MB_ICONINFORMATION) + .DoModal(parent); } void Reporting::Information(const AnyStringLocale &text, const AnyStringLocale &caption, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption), LogInformation), LogLevelToFlags(LogInformation), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption), LogInformation)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(MB_ICONINFORMATION) + .DoModal(parent); } void Reporting::Warning(const AnyStringLocale &text, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(CString(), LogWarning), LogLevelToFlags(LogWarning), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption({}, LogWarning)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(MB_ICONWARNING) + .DoModal(parent); } void Reporting::Warning(const AnyStringLocale &text, const AnyStringLocale &caption, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption), LogWarning), LogLevelToFlags(LogWarning), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption), LogWarning)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(MB_ICONWARNING) + .DoModal(parent); } void Reporting::Error(const AnyStringLocale &text, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(CString(), LogError), LogLevelToFlags(LogError), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption({}, LogError)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(MB_ICONERROR) + .DoModal(parent); } void Reporting::Error(const AnyStringLocale &text, const AnyStringLocale &caption, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption), LogError), LogLevelToFlags(LogError), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption), LogError)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(MB_ICONERROR) + .DoModal(parent); } void Reporting::Message(LogLevel level, const AnyStringLocale &text, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(CString(), level), LogLevelToFlags(level), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption({}, level)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(LogLevelToIcon(level)) + .DoModal(parent); } void Reporting::Message(LogLevel level, const AnyStringLocale &text, const AnyStringLocale &caption, const CWnd *parent) { - ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption), level), LogLevelToFlags(level), parent); + TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption), level)) + .AddButton(GetButtonString(IDOK), IDOK) + .Icon(LogLevelToIcon(level)) + .DoModal(parent); } ConfirmAnswer Reporting::Confirm(const AnyStringLocale &text, bool showCancel, bool defaultNo, const CWnd *parent) { - return Confirm(mpt::ToCString(text), GetTitle() + _T(" - Confirmation"), showCancel, defaultNo, parent); + return Confirm(text, GetTitle() + _T(" - Confirmation"), showCancel, defaultNo, parent); } +ConfirmAnswer Reporting::Confirm(const AnyStringLocale &text, const AnyStringLocale &caption, bool showCancel, bool defaultNo, const CWnd *parent) +{ + std::vector buttonList = + { + {GetButtonString(IDYES), IDYES}, + {GetButtonString(IDNO), IDNO} + }; + if(showCancel) + buttonList.push_back({GetButtonString(IDCANCEL), IDCANCEL}); + TaskDlg params = TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption))) + .Buttons(buttonList) + .Icon(MB_ICONQUESTION); -ConfirmAnswer Reporting::Confirm(const AnyStringLocale &text, const AnyStringLocale &caption, bool showCancel, bool defaultNo, const CWnd *parent) -{ - UINT result = ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption)), (showCancel ? MB_YESNOCANCEL : MB_YESNO) | MB_ICONQUESTION | (defaultNo ? MB_DEFBUTTON2 : 0), parent); + if(defaultNo) + params.DefaultButton(IDNO); + + auto result = params.DoModal(parent).buttonId; switch(result) { case IDYES: @@ -155,25 +353,18 @@ return cnfCancel; } } - - RetryAnswer Reporting::RetryCancel(const AnyStringLocale &text, const CWnd *parent) { - return RetryCancel(mpt::ToCString(text), GetTitle(), parent); + return RetryCancel(text, GetTitle(), parent); } - - RetryAnswer Reporting::RetryCancel(const AnyStringLocale &text, const AnyStringLocale &caption, const CWnd *parent) { - UINT result = ShowNotificationImpl(mpt::ToCString(text), FillEmptyCaption(mpt::ToCString(caption)), MB_RETRYCANCEL, parent); - switch(result) - { - case IDRETRY: - return rtyRetry; - default: - case IDCANCEL: - return rtyCancel; - } + auto result = TaskDlg{} + .Description(mpt::ToCString(text)) + .WindowTitle(FillEmptyCaption(mpt::ToCString(caption))) + .Buttons({{GetButtonString(IDRETRY), IDRETRY}, {GetButtonString(IDCANCEL), IDCANCEL}}) + .DoModal(parent).buttonId; + return (result == IDRETRY) ? rtyRetry : rtyCancel; } Index: mptrack/Reporting.h =================================================================== --- mptrack/Reporting.h (revision 24083) +++ mptrack/Reporting.h (working copy) @@ -12,10 +12,55 @@ #include "openmpt/all/BuildSettings.hpp" +#include OPENMPT_NAMESPACE_BEGIN +struct TaskDlg +{ + // When assigning a custom command ID instead of IDOK/IDCANCEL/..., use this base offset + static constexpr int CUSTOM_ID_BASE = 200; + + struct Button + { + CString text; + UINT commandId; + }; + + struct Result + { + UINT buttonId; + bool verificationChecked; + }; + + Result DoModal(const CWnd *parent = nullptr) const; + + static bool ModernTaskDialogSupported(); + + TaskDlg &WindowTitle(CString title) { windowTitle = std::move(title); return *this; } + TaskDlg &Headline(CString text) { headline = std::move(text); return *this; } + TaskDlg &Description(CString text) { description = std::move(text); return *this; } + TaskDlg &AddButton(CString text, UINT commandID) { buttons.push_back({std::move(text), commandID}); return *this; } + TaskDlg &Buttons(std::vector