From 85facf6d6e24ebb9c9bdfda4e0c5246fd02392c4 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 7 Jan 2021 18:00:13 +0000 Subject: [PATCH] HWNDComponentPeer: Dismiss modals more proactively This change makes heavyweight views listen to window events from higher in the window hierarchy. If a move or resize event from higher in the hierarchy is encountered, blocking modal components will be dismissed. This patch should resolve an issue where the popupmenu for a combobox could become 'stranded' if the plugin window was moved while the box was open. --- .../native/juce_win32_Windowing.cpp | 79 +++++++++++++++++-- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index cda94b0ca4..a69619f79e 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -3841,15 +3841,13 @@ private: bool sendInputAttemptWhenModalMessage() { - if (component.isCurrentlyBlockedByAnotherModalComponent()) - { - if (Component* const current = Component::getCurrentlyModalComponent()) - current->inputAttemptWhenModal(); + if (! component.isCurrentlyBlockedByAnotherModalComponent()) + return false; - return true; - } + if (auto* current = Component::getCurrentlyModalComponent()) + current->inputAttemptWhenModal(); - return false; + return true; } //============================================================================== @@ -4074,6 +4072,73 @@ private: stopTimer(); } + static bool isAncestor (HWND outer, HWND inner) + { + if (outer == nullptr || inner == nullptr) + return false; + + if (outer == inner) + return true; + + return isAncestor (outer, GetAncestor (inner, GA_PARENT)); + } + + void windowShouldDismissModals (HWND originator) + { + if (isAncestor (originator, hwnd)) + sendInputAttemptWhenModalMessage(); + } + + // Unfortunately SetWindowsHookEx only allows us to register a static function as a hook. + // To get around this, we keep a static list of listeners which are interested in + // top-level window events, and notify all of these listeners from the callback. + class TopLevelModalDismissBroadcaster + { + public: + TopLevelModalDismissBroadcaster() + : hook (SetWindowsHookEx (WH_CALLWNDPROC, + callWndProc, + (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(), + GetCurrentThreadId())) + {} + + ~TopLevelModalDismissBroadcaster() noexcept + { + UnhookWindowsHookEx (hook); + } + + private: + static void processMessage (int nCode, const CWPSTRUCT* info) + { + if (nCode < 0 || info == nullptr) + return; + + constexpr UINT events[] { WM_MOVE, + WM_SIZE, + WM_WINDOWPOSCHANGED, + WM_NCPOINTERDOWN, + WM_NCLBUTTONDOWN, + WM_NCRBUTTONDOWN, + WM_NCMBUTTONDOWN }; + + if (std::find (std::begin (events), std::end (events), info->message) == std::end (events)) + return; + + for (auto i = ComponentPeer::getNumPeers(); --i >= 0;) + if (auto* hwndPeer = dynamic_cast (ComponentPeer::getPeer (i))) + hwndPeer->windowShouldDismissModals (info->hwnd); + } + + static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam) + { + processMessage (nCode, reinterpret_cast (lParam)); + return CallNextHookEx ({}, nCode, wParam, lParam); + } + + HHOOK hook; + }; + + SharedResourcePointer modalDismissBroadcaster; IMEHandler imeHandler; //==============================================================================