diff --git a/modules/juce_gui_basics/juce_gui_basics.cpp b/modules/juce_gui_basics/juce_gui_basics.cpp index 933ab8128b..718928ea82 100644 --- a/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/modules/juce_gui_basics/juce_gui_basics.cpp @@ -114,6 +114,16 @@ #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "dwmapi.lib") + // Link a newer version of the side-by-side comctl32 dll. + // Required to enable the newer native message box and visual styles on vista and above. + #pragma comment(linker, \ + "\"/MANIFESTDEPENDENCY:type='Win32' " \ + "name='Microsoft.Windows.Common-Controls' " \ + "version='6.0.0.0' " \ + "processorArchitecture='*' " \ + "publicKeyToken='6595b64144ccf1df' " \ + "language='*'\"" \ + ) #if JUCE_OPENGL #pragma comment(lib, "OpenGL32.Lib") #pragma comment(lib, "GlU32.Lib") diff --git a/modules/juce_gui_basics/native/juce_NativeMessageBox_windows.cpp b/modules/juce_gui_basics/native/juce_NativeMessageBox_windows.cpp index 45213130ac..813a343bf8 100644 --- a/modules/juce_gui_basics/native/juce_NativeMessageBox_windows.cpp +++ b/modules/juce_gui_basics/native/juce_NativeMessageBox_windows.cpp @@ -35,28 +35,13 @@ namespace juce::detail { -#if JUCE_MSVC - // required to enable the newer dialog box on vista and above - #pragma comment(linker, \ - "\"/MANIFESTDEPENDENCY:type='Win32' " \ - "name='Microsoft.Windows.Common-Controls' " \ - "version='6.0.0.0' " \ - "processorArchitecture='*' " \ - "publicKeyToken='6595b64144ccf1df' " \ - "language='*'\"" \ - ) -#endif - -template -constexpr int intArray[] { ids... }; - std::unique_ptr ScopedMessageBoxInterface::create (const MessageBoxOptions& options) { - class WindowsMessageBoxBase : public ScopedMessageBoxInterface + class WindowsTaskDialog : public ScopedMessageBoxInterface { public: - explicit WindowsMessageBoxBase (Component* comp) - : associatedComponent (comp) {} + explicit WindowsTaskDialog (const MessageBoxOptions& opts) + : associatedComponent (opts.getAssociatedComponent()), options (opts) {} void runAsync (std::function recipient) override { @@ -109,158 +94,7 @@ std::unique_ptr ScopedMessageBoxInterface::create (co 'this' is guaranteed to be alive when the returned function is called. */ - virtual std::function getShowMessageBoxForParent (HWND parent) = 0; - - Component::SafePointer associatedComponent; - std::atomic windowHandle { nullptr }; - std::future future; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase) - }; - - class PreVistaMessageBox final : public WindowsMessageBoxBase - { - public: - PreVistaMessageBox (const MessageBoxOptions& opts, UINT extraFlags) - : WindowsMessageBoxBase (opts.getAssociatedComponent()), - flags (extraFlags | getMessageBoxFlags (opts.getIconType())), - title (opts.getTitle()), message (opts.getMessage()) {} - - private: - std::function getShowMessageBoxForParent (const HWND parent) override - { - JUCE_ASSERT_MESSAGE_THREAD - - static std::map map; - static std::mutex mapMutex; - - return [this, parent] - { - const auto threadId = GetCurrentThreadId(); - - { - const std::scoped_lock scope { mapMutex }; - map.emplace (threadId, this); - } - - const ScopeGuard eraseFromMap { [threadId] - { - const std::scoped_lock scope { mapMutex }; - map.erase (threadId); - } }; - - const auto hookCallback = [] (int nCode, const WPARAM wParam, const LPARAM lParam) - { - auto* params = reinterpret_cast (lParam); - - if (nCode >= 0 - && params != nullptr - && (params->message == WM_INITDIALOG || params->message == WM_DESTROY)) - { - const auto callbackThreadId = GetCurrentThreadId(); - - const std::scoped_lock scope { mapMutex }; - - if (const auto iter = map.find (callbackThreadId); iter != map.cend()) - iter->second->setDialogWindowHandle (params->message == WM_INITDIALOG ? params->hwnd : nullptr); - } - - return CallNextHookEx ({}, nCode, wParam, lParam); - }; - - const auto hook = SetWindowsHookEx (WH_CALLWNDPROC, - hookCallback, - (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(), - threadId); - const ScopeGuard removeHook { [hook] { UnhookWindowsHookEx (hook); } }; - - const auto result = MessageBox (parent, message.toWideCharPointer(), title.toWideCharPointer(), flags); - - const auto buttons = [&]() -> Span - { - switch (flags & 0xf) - { - case MB_OK: return intArray; - case MB_OKCANCEL: return intArray; - case MB_ABORTRETRYIGNORE: return intArray; - case MB_YESNOCANCEL: return intArray; - case MB_YESNO: return intArray; - case MB_RETRYCANCEL: return intArray; - case MB_CANCELTRYCONTINUE: return intArray; - } - - return {}; - }(); - - return (int) std::distance (buttons.begin(), std::find (buttons.begin(), buttons.end(), result)); - }; - } - - static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept - { - // this window can get lost behind JUCE windows which are set to be alwaysOnTop - // so if there are any set it to be topmost - const auto topmostFlag = WindowUtils::areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0; - - const auto iconFlags = [&]() -> decltype (topmostFlag) - { - switch (iconType) - { - case MessageBoxIconType::QuestionIcon: return MB_ICONQUESTION; - case MessageBoxIconType::WarningIcon: return MB_ICONWARNING; - case MessageBoxIconType::InfoIcon: return MB_ICONINFORMATION; - case MessageBoxIconType::NoIcon: break; - } - - return 0; - }(); - - return static_cast (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags); - } - - const UINT flags; - const String title, message; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox) - }; - - class WindowsTaskDialog final : public WindowsMessageBoxBase - { - static auto getTaskDialogFunc() - { - using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*); - - static const auto result = [&]() -> TaskDialogIndirectFunc - { - const auto comctl = "Comctl32.dll"; - LoadLibraryA (comctl); - const auto comctlModule = GetModuleHandleA (comctl); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type") - if (comctlModule != nullptr) - return (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - return nullptr; - }(); - - return result; - } - - public: - explicit WindowsTaskDialog (const MessageBoxOptions& opts) - : WindowsMessageBoxBase (opts.getAssociatedComponent()), - iconType (opts.getIconType()), - title (opts.getTitle()), message (opts.getMessage()), - buttons { opts.getButtonText (0), opts.getButtonText (1), opts.getButtonText (2) } {} - - static bool isAvailable() - { - return getTaskDialogFunc() != nullptr; - } - - private: - std::function getShowMessageBoxForParent (const HWND parent) override + std::function getShowMessageBoxForParent (const HWND parent) { JUCE_ASSERT_MESSAGE_THREAD @@ -270,8 +104,8 @@ std::unique_ptr ScopedMessageBoxInterface::create (co config.cbSize = sizeof (config); config.hwndParent = parent; - config.pszWindowTitle = title.toWideCharPointer(); - config.pszContent = message.toWideCharPointer(); + config.pszWindowTitle = options.getTitle().toWideCharPointer(); + config.pszContent = options.getMessage().toWideCharPointer(); config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle(); config.lpCallbackData = reinterpret_cast (this); config.pfCallback = [] (HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR lpRefData) @@ -294,7 +128,7 @@ std::unique_ptr ScopedMessageBoxInterface::create (co return S_OK; }; - if (iconType == MessageBoxIconType::QuestionIcon) + if (options.getIconType() == MessageBoxIconType::QuestionIcon) { if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION)) { @@ -306,7 +140,7 @@ std::unique_ptr ScopedMessageBoxInterface::create (co { config.pszMainIcon = [&]() -> LPWSTR { - switch (iconType) + switch (options.getIconType()) { case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON; case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON; @@ -322,49 +156,29 @@ std::unique_ptr ScopedMessageBoxInterface::create (co std::vector buttonLabels; - for (const auto& buttonText : buttons) - if (buttonText.isNotEmpty()) + for (auto i = 0; i < options.getNumButtons(); ++i) + if (const auto buttonText = options.getButtonText (i); buttonText.isNotEmpty()) buttonLabels.push_back ({ (int) buttonLabels.size(), buttonText.toWideCharPointer() }); config.pButtons = buttonLabels.data(); config.cButtons = (UINT) buttonLabels.size(); int buttonIndex = 0; - - if (auto* func = getTaskDialogFunc()) - func (&config, &buttonIndex, nullptr, nullptr); - else - jassertfalse; + TaskDialogIndirect (&config, &buttonIndex, nullptr, nullptr); return buttonIndex; }; } - const MessageBoxIconType iconType; - const String title, message; - const std::array buttons; + Component::SafePointer associatedComponent; + std::atomic windowHandle { nullptr }; + std::future future; + MessageBoxOptions options; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog) }; - if (WindowsTaskDialog::isAvailable()) - return std::make_unique (options); - - const auto extraFlags = [&options] - { - const auto numButtons = options.getNumButtons(); - - if (numButtons == 3) - return MB_YESNOCANCEL; - - if (numButtons == 2) - return options.getButtonText (0) == "OK" ? MB_OKCANCEL - : MB_YESNO; - - return MB_OK; - }(); - - return std::make_unique (options, (UINT) extraFlags); + return std::make_unique (options); } } // namespace juce::detail