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.

347 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
  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 : 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 = juce_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 : 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. if (comctlModule != nullptr)
  168. return (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
  169. return nullptr;
  170. }();
  171. return result;
  172. }
  173. public:
  174. explicit WindowsTaskDialog (const MessageBoxOptions& opts)
  175. : WindowsMessageBoxBase (opts.getAssociatedComponent()),
  176. iconType (opts.getIconType()),
  177. title (opts.getTitle()), message (opts.getMessage()),
  178. buttons { opts.getButtonText (0), opts.getButtonText (1), opts.getButtonText (2) } {}
  179. static bool isAvailable()
  180. {
  181. return getTaskDialogFunc() != nullptr;
  182. }
  183. private:
  184. std::function<int()> getShowMessageBoxForParent (const HWND parent) override
  185. {
  186. JUCE_ASSERT_MESSAGE_THREAD
  187. return [this, parent]
  188. {
  189. TASKDIALOGCONFIG config{};
  190. config.cbSize = sizeof (config);
  191. config.hwndParent = parent;
  192. config.pszWindowTitle = title.toWideCharPointer();
  193. config.pszContent = message.toWideCharPointer();
  194. config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
  195. config.lpCallbackData = reinterpret_cast<LONG_PTR> (this);
  196. config.pfCallback = [] (HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR lpRefData)
  197. {
  198. if (auto* t = reinterpret_cast<WindowsTaskDialog*> (lpRefData))
  199. {
  200. switch (msg)
  201. {
  202. case TDN_CREATED:
  203. case TDN_DIALOG_CONSTRUCTED:
  204. t->setDialogWindowHandle (hwnd);
  205. break;
  206. case TDN_DESTROYED:
  207. t->setDialogWindowHandle (nullptr);
  208. break;
  209. }
  210. }
  211. return S_OK;
  212. };
  213. if (iconType == MessageBoxIconType::QuestionIcon)
  214. {
  215. if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
  216. {
  217. config.hMainIcon = questionIcon;
  218. config.dwFlags |= TDF_USE_HICON_MAIN;
  219. }
  220. }
  221. else
  222. {
  223. config.pszMainIcon = [&]() -> LPWSTR
  224. {
  225. switch (iconType)
  226. {
  227. case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON;
  228. case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON;
  229. case MessageBoxIconType::QuestionIcon: JUCE_FALLTHROUGH
  230. case MessageBoxIconType::NoIcon:
  231. break;
  232. }
  233. return nullptr;
  234. }();
  235. }
  236. std::vector<TASKDIALOG_BUTTON> buttonLabels;
  237. for (const auto& buttonText : buttons)
  238. if (buttonText.isNotEmpty())
  239. buttonLabels.push_back ({ (int) buttonLabels.size(), buttonText.toWideCharPointer() });
  240. config.pButtons = buttonLabels.data();
  241. config.cButtons = (UINT) buttonLabels.size();
  242. int buttonIndex = 0;
  243. if (auto* func = getTaskDialogFunc())
  244. func (&config, &buttonIndex, nullptr, nullptr);
  245. else
  246. jassertfalse;
  247. return buttonIndex;
  248. };
  249. }
  250. const MessageBoxIconType iconType;
  251. const String title, message;
  252. const std::array<String, 3> buttons;
  253. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
  254. };
  255. if (WindowsTaskDialog::isAvailable())
  256. return std::make_unique<WindowsTaskDialog> (options);
  257. const auto extraFlags = [&options]
  258. {
  259. const auto numButtons = options.getNumButtons();
  260. if (numButtons == 3)
  261. return MB_YESNOCANCEL;
  262. if (numButtons == 2)
  263. return options.getButtonText (0) == "OK" ? MB_OKCANCEL
  264. : MB_YESNO;
  265. return MB_OK;
  266. }();
  267. return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags);
  268. }
  269. } // namespace juce