The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

349 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce::detail
  19. {
  20. #if JUCE_MSVC
  21. // required to enable the newer dialog box on vista and above
  22. #pragma comment(linker, \
  23. "\"/MANIFESTDEPENDENCY:type='Win32' " \
  24. "name='Microsoft.Windows.Common-Controls' " \
  25. "version='6.0.0.0' " \
  26. "processorArchitecture='*' " \
  27. "publicKeyToken='6595b64144ccf1df' " \
  28. "language='*'\"" \
  29. )
  30. #endif
  31. std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
  32. {
  33. class WindowsMessageBoxBase : public ScopedMessageBoxInterface
  34. {
  35. public:
  36. explicit WindowsMessageBoxBase (Component* comp)
  37. : associatedComponent (comp) {}
  38. void runAsync (std::function<void (int)> recipient) override
  39. {
  40. future = std::async (std::launch::async, [showMessageBox = getShowMessageBox(), recipient]
  41. {
  42. const auto initComResult = CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
  43. if (initComResult != S_OK)
  44. return;
  45. const ScopeGuard scope { [] { CoUninitialize(); } };
  46. const auto messageResult = showMessageBox != nullptr ? showMessageBox() : 0;
  47. NullCheckedInvocation::invoke (recipient, messageResult);
  48. });
  49. }
  50. int runSync() override
  51. {
  52. if (auto showMessageBox = getShowMessageBox())
  53. return showMessageBox();
  54. return 0;
  55. }
  56. void close() override
  57. {
  58. if (auto* toClose = windowHandle.exchange (nullptr))
  59. EndDialog (toClose, 0);
  60. }
  61. void setDialogWindowHandle (HWND dialogHandle)
  62. {
  63. windowHandle = dialogHandle;
  64. }
  65. private:
  66. std::function<int()> getShowMessageBox()
  67. {
  68. const auto parent = associatedComponent != nullptr ? (HWND) associatedComponent->getWindowHandle() : nullptr;
  69. return getShowMessageBoxForParent (parent);
  70. }
  71. /* Returns a function that should display a message box and return the result.
  72. getShowMessageBoxForParent() will be called on the message thread.
  73. The returned function will be called on a separate thread, in order to avoid blocking the
  74. message thread.
  75. 'this' is guaranteed to be alive when the returned function is called.
  76. */
  77. virtual std::function<int()> getShowMessageBoxForParent (HWND parent) = 0;
  78. Component::SafePointer<Component> associatedComponent;
  79. std::atomic<HWND> windowHandle { nullptr };
  80. std::future<void> future;
  81. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase)
  82. };
  83. class PreVistaMessageBox final : public WindowsMessageBoxBase
  84. {
  85. public:
  86. PreVistaMessageBox (const MessageBoxOptions& opts, UINT extraFlags)
  87. : WindowsMessageBoxBase (opts.getAssociatedComponent()),
  88. flags (extraFlags | getMessageBoxFlags (opts.getIconType())),
  89. title (opts.getTitle()), message (opts.getMessage()) {}
  90. private:
  91. std::function<int()> getShowMessageBoxForParent (const HWND parent) override
  92. {
  93. JUCE_ASSERT_MESSAGE_THREAD
  94. static std::map<DWORD, PreVistaMessageBox*> map;
  95. static std::mutex mapMutex;
  96. return [this, parent]
  97. {
  98. const auto threadId = GetCurrentThreadId();
  99. {
  100. const std::scoped_lock scope { mapMutex };
  101. map.emplace (threadId, this);
  102. }
  103. const ScopeGuard eraseFromMap { [threadId]
  104. {
  105. const std::scoped_lock scope { mapMutex };
  106. map.erase (threadId);
  107. } };
  108. const auto hookCallback = [] (int nCode, const WPARAM wParam, const LPARAM lParam)
  109. {
  110. auto* params = reinterpret_cast<CWPSTRUCT*> (lParam);
  111. if (nCode >= 0
  112. && params != nullptr
  113. && (params->message == WM_INITDIALOG || params->message == WM_DESTROY))
  114. {
  115. const auto callbackThreadId = GetCurrentThreadId();
  116. const std::scoped_lock scope { mapMutex };
  117. if (const auto iter = map.find (callbackThreadId); iter != map.cend())
  118. iter->second->setDialogWindowHandle (params->message == WM_INITDIALOG ? params->hwnd : nullptr);
  119. }
  120. return CallNextHookEx ({}, nCode, wParam, lParam);
  121. };
  122. const auto hook = SetWindowsHookEx (WH_CALLWNDPROC,
  123. hookCallback,
  124. (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
  125. threadId);
  126. const ScopeGuard removeHook { [hook] { UnhookWindowsHookEx (hook); } };
  127. const auto result = MessageBox (parent, message.toWideCharPointer(), title.toWideCharPointer(), flags);
  128. if (result == IDYES || result == IDOK) return 0;
  129. if (result == IDNO && ((flags & 1) != 0)) return 1;
  130. return 2;
  131. };
  132. }
  133. static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept
  134. {
  135. // this window can get lost behind JUCE windows which are set to be alwaysOnTop
  136. // so if there are any set it to be topmost
  137. const auto topmostFlag = WindowUtils::areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0;
  138. const auto iconFlags = [&]() -> decltype (topmostFlag)
  139. {
  140. switch (iconType)
  141. {
  142. case MessageBoxIconType::QuestionIcon: return MB_ICONQUESTION;
  143. case MessageBoxIconType::WarningIcon: return MB_ICONWARNING;
  144. case MessageBoxIconType::InfoIcon: return MB_ICONINFORMATION;
  145. case MessageBoxIconType::NoIcon: break;
  146. }
  147. return 0;
  148. }();
  149. return static_cast<UINT> (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags);
  150. }
  151. const UINT flags;
  152. const String title, message;
  153. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox)
  154. };
  155. class WindowsTaskDialog final : public WindowsMessageBoxBase
  156. {
  157. static auto getTaskDialogFunc()
  158. {
  159. using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*);
  160. static const auto result = [&]() -> TaskDialogIndirectFunc
  161. {
  162. if (SystemStats::getOperatingSystemType() < SystemStats::WinVista)
  163. return nullptr;
  164. const auto comctl = "Comctl32.dll";
  165. LoadLibraryA (comctl);
  166. const auto comctlModule = GetModuleHandleA (comctl);
  167. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type")
  168. if (comctlModule != nullptr)
  169. return (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
  170. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  171. return nullptr;
  172. }();
  173. return result;
  174. }
  175. public:
  176. explicit WindowsTaskDialog (const MessageBoxOptions& opts)
  177. : WindowsMessageBoxBase (opts.getAssociatedComponent()),
  178. iconType (opts.getIconType()),
  179. title (opts.getTitle()), message (opts.getMessage()),
  180. buttons { opts.getButtonText (0), opts.getButtonText (1), opts.getButtonText (2) } {}
  181. static bool isAvailable()
  182. {
  183. return getTaskDialogFunc() != nullptr;
  184. }
  185. private:
  186. std::function<int()> getShowMessageBoxForParent (const HWND parent) override
  187. {
  188. JUCE_ASSERT_MESSAGE_THREAD
  189. return [this, parent]
  190. {
  191. TASKDIALOGCONFIG config{};
  192. config.cbSize = sizeof (config);
  193. config.hwndParent = parent;
  194. config.pszWindowTitle = title.toWideCharPointer();
  195. config.pszContent = message.toWideCharPointer();
  196. config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
  197. config.lpCallbackData = reinterpret_cast<LONG_PTR> (this);
  198. config.pfCallback = [] (HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR lpRefData)
  199. {
  200. if (auto* t = reinterpret_cast<WindowsTaskDialog*> (lpRefData))
  201. {
  202. switch (msg)
  203. {
  204. case TDN_CREATED:
  205. case TDN_DIALOG_CONSTRUCTED:
  206. t->setDialogWindowHandle (hwnd);
  207. break;
  208. case TDN_DESTROYED:
  209. t->setDialogWindowHandle (nullptr);
  210. break;
  211. }
  212. }
  213. return S_OK;
  214. };
  215. if (iconType == MessageBoxIconType::QuestionIcon)
  216. {
  217. if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
  218. {
  219. config.hMainIcon = questionIcon;
  220. config.dwFlags |= TDF_USE_HICON_MAIN;
  221. }
  222. }
  223. else
  224. {
  225. config.pszMainIcon = [&]() -> LPWSTR
  226. {
  227. switch (iconType)
  228. {
  229. case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON;
  230. case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON;
  231. case MessageBoxIconType::QuestionIcon: JUCE_FALLTHROUGH
  232. case MessageBoxIconType::NoIcon:
  233. break;
  234. }
  235. return nullptr;
  236. }();
  237. }
  238. std::vector<TASKDIALOG_BUTTON> buttonLabels;
  239. for (const auto& buttonText : buttons)
  240. if (buttonText.isNotEmpty())
  241. buttonLabels.push_back ({ (int) buttonLabels.size(), buttonText.toWideCharPointer() });
  242. config.pButtons = buttonLabels.data();
  243. config.cButtons = (UINT) buttonLabels.size();
  244. int buttonIndex = 0;
  245. if (auto* func = getTaskDialogFunc())
  246. func (&config, &buttonIndex, nullptr, nullptr);
  247. else
  248. jassertfalse;
  249. return buttonIndex;
  250. };
  251. }
  252. const MessageBoxIconType iconType;
  253. const String title, message;
  254. const std::array<String, 3> buttons;
  255. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
  256. };
  257. if (WindowsTaskDialog::isAvailable())
  258. return std::make_unique<WindowsTaskDialog> (options);
  259. const auto extraFlags = [&options]
  260. {
  261. const auto numButtons = options.getNumButtons();
  262. if (numButtons == 3)
  263. return MB_YESNOCANCEL;
  264. if (numButtons == 2)
  265. return options.getButtonText (0) == "OK" ? MB_OKCANCEL
  266. : MB_YESNO;
  267. return MB_OK;
  268. }();
  269. return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags);
  270. }
  271. } // namespace juce::detail