Dismiss PopupMenu when focus changes

How can I dissmiss all active menus when a user clicks away from the plugin GUI? As far as I can tell there is no callback that is recieved that allows me to determine that the focus has changed or that a click occured outside of the plugin window. Without this check users can move the plugin window after a menu has been clicked and the menu doesn't move with he plugin window or get dismissed!

For now I have it that i reguarly call getScreenPosition() in the editor to check if the editors window has been moved, if it has I call PopupMenu::dismissAllActiveMenus. However that is hardly an ideal solution and I've noticed Cytomic plugins have managed to solve this issue and they are using JUCE.

Alternatively it would be nice to have a wrapper class that allows the use of OS context menus.

1 Like

Previously reported.

https://forum.juce.com/t/combobox-modalcallbackfunction

I have a modified PopupMenu and ComboBox class which work… but Jules wouldn’t be happy with my implementation.

Rail

Thank you for the reply.

At first appearence, looking at your final solution, it seems covering the edge cases Jules mentions wouldn't be too difficult. I would probably add a virtual method to ComponentPeer something like shouldDismissMenusOnFocusLoss(), have the default implementation return true and override the method for any ComponetPeer classes that it causes an issue for. I haven't actually looked into this yet so maybe there is a good reason that can't be done, or an edge case that I'm not considering.

In regards to "why it would be a good idea", is because the menus should act 'exactly' as users expect them to act, they should at the very least replicate the basic funcionality of the a system menu; In my opinion, anything else will have a negative impact on user experience.

1 Like

Sorry to dig up an old thread, but we’re having trouble wrangling PopupMenu in our plugins. Is there a solution for dismissing a PopupMenu when a plugin window moves that doesn’t use a modal loop or require polling getScreenPosition()?

EDIT: polling getScreenPosition() isn’t really viable either because it doesn’t seem to update when the plugin window is being dragged.

The best solution is to set the popup as child of your plugin editor (or your main component or something) :
menu.showMenu (PopupMenu::Options().withParentComponent (&guthzPluginEditor))

edit: ah, note that it won’t dismiss the popup when you move the window, instead the popup will move with it. I think it’s a fine behaviour but perhaps not what you want?

Thanks! That’s interesting. Moving with the window would be just fine — basically anything that doesn’t leave a hanging popup floating in the middle of nowhere.

Would making it a child of the main component mean that it couldn’t display outside the bounds of the plugin window?

yes, the bounds would be constrained by the parent comp.
otherwise, regarding the dismiss when the window is moving I used a code like that in the past (a bit hacky, but that should work) :

void timerCallback() override
{
    if (topComp == nullptr)
        topComp = pluginEditor->getTopLevelComponent();

    if (topComp != nullptr)
    {
        if (ComponentPeer* peer = topComp->getPeer())
        {
            const Point<float> point = peer->localToGlobal (Point<float>());

            if (point != previousPoint)
            {
                 previousPoint = point;
                 PopupMenu::dismissAllActiveMenus();
            }
        }
    }
}

I really like your plugins btw.

Thanks! That’s essentially what I tried, but localToGlobal wasn’t updating while the window was being dragged. Is that what you’re seeing as well?

indeed, it’s not updating continuously while the mouse is dragged, it seems it’s updated just when the mouse movement stops.

Posting again here because some beta testers have reported this behavior to us as a bug. I was hoping it would be something no one would notice or care about, but that appears to not be the case.

Is there nothing that JUCE could do to fix the floating-in-mid-air PopupMenu issue?

2 Likes

ever find a nice solution for this? timers or anything else seems bit no bueno

2 Likes

I’ve been looking for a solution to this issue for almost a year now! To my knowledge, there are 3 problems with running Popup Menus asynchronously (what is recommended for plugins).

  1. As mentioned in this thread, moving the plugin window via dragging from the title bar does not dismiss open popup menus. The solution to make the popup a child of the plugin editor solves part of the problem but creates new ones when considering that it is now constrained to the bounds of the plugin editor. Displaying a large menu from a small plugin will frustrate users as they will have to scroll to find the option they want rather than seeing everything as expected.

  2. Closing the plugin while a popup menu is open does not dismiss the menu (on OSX) and causes a crash in most DAWs on both OSs. This is a major bug that should be investigated by the JUCE team because it is crashing DAWs and there is seemingly no way to handle it. I’ve tried adding “PopupMenu::dismissAllActiveMenus()” in the destructors of classes that use the popup menu and that does not help. Please let me know if you have a solution to this!

  3. Popup menus do not hide if I click somewhere off the menu that is not on the plugin or another app (ie: anywhere on the DAW itself). Since it does not hide it also means that it often gets in the way of other windows or tasks that a user may be using in the DAW. I already posted about this here: showMenuAsync() does not hide when clicked outside but received no response.

All this is to say that the behaviour of popup menus in plugins is not standard. Any reasonable user would expect that if they have a popup menu open, then clicking anywhere else on the screen will hide that popup menu. I’ve started looking into building my own popup menu dialog that takes the focus and is on top when opened, and (using focusLost()) will detect when focus is taken away and hides. However, I’m hesitant to commit to this direction because trying to build a popup menu that sizes correctly to fit in the bounds of the component and displays all submenus, icons, and other features that juce::PopupMenu has, and handles the result asynchronously sounds like such a nightmare… JUCE team, can you please take a look at fixing these issues so I don’t have to spend weeks essentially recreating your PopupMenu class to get one basic function that I need out of it… Many thanks!

6 Likes

BUMP!!

Can anyone from JUCE please weigh in on this? I’m sure this would be a headache to fix but it would improve the user experience for all juce plugins which use popup menus (I assume thats a lot of plugins). Thank you in advance!

2 Likes

YES PLEASE!

@jules, @ed95, @reuk … anyone??

1 Like

I just tried modifying the DSPModulePluginDemo like so, to add a text button which triggers a popup menu to the top left corner of the editor:

diff --git a/examples/Plugins/DSPModulePluginDemo.h b/examples/Plugins/DSPModulePluginDemo.h
index 4dd0f8d87..459819327 100644
--- a/examples/Plugins/DSPModulePluginDemo.h
+++ b/examples/Plugins/DSPModulePluginDemo.h
@@ -1532,6 +1532,24 @@ public:

         setSize (800, 430);
         setResizable (false, false);
+^M
+        addAndMakeVisible (button);^M
+        button.setBounds (getLocalBounds().withSize (80, 24));^M
+        button.onClick = [this]^M
+        {^M
+            PopupMenu menu;^M
+            menu.addItem ("a", nullptr);^M
+            menu.addItem ("b", nullptr);^M
+            menu.addItem ("c", nullptr);^M
+            menu.showMenuAsync (PopupMenu::Options{}.withTargetComponent (button));^M
+        };^M
+    }^M
+^M
+    TextButton button { "click me" };^M
+^M
+    ~DspModulePluginDemoEditor() override^M
+    {^M
+        PopupMenu::dismissAllActiveMenus();^M
     }

     //==============================================================================

I clicked the “click me” button to display the popup menu, and then dragged the window titlebar. The popup menu hid automatically. This no longer appears to be an issue.

When I open the VST3 plugin and its editor in the AudioPluginHost or Live, and then delete the plugin instance while the editor is open, there is no crash. This issue also seems to be resolved.

If there is a specific setup which causes problems, please let us know the plugin format, host, OS, and ideally a minimal code sample which causes the issue. In the meantime, I’m considering these initial two issues resolved.

I’ve added this to my to-do list, but this will take some time to investigate, as it will require custom handling on each platform. As a result, I can’t promise when I’ll be able to investigate this.

2 Likes

I assume you are trying this on the Mac AudioPluginHost? Yes, it’s fine. However, that is not the case in Windows AudioPluginHost, I just tried it. I pop open a ComboBox, the menu is open, I click on the title bar and drag it and the menu stays open and does not move with the window.

I myself have not been able to encounter this one, on either platform. Thanks.

I’ve been experimenting with the following approach. I’ve not tested extensively, only AudioPluginHost and Reaper, but it seems you get a nullptr focus change when clicking outside of the plugin window (at least on Windows), like in the DAW or another plugin window, and you can use that to dismiss popups:

(Setup a globalFocusChange Listener on your Editor), and:

WeakReference<Component> prevFocusedComponent = nullptr;
virtual void globalFocusChanged(Component* focusedComponent) override
{
    if (focusedComponent != prevFocusedComponent)
    {
        prevFocusedComponent = focusedComponent;

        if (focusedComponent == nullptr)
        {
            PopupMenu::dismissAllActiveMenus();
        }
    }
}

This also works to fix the dragging the titlebar issue on Windows. But it doesn’t work on the Mac in AudioPluginHost to dismiss the popups when you click outside of the window (you get no focus changes), but as I said I’m just experimenting…

Thank you for the response @reuk!

I can see that this works in the juce plugin host app but it does not work for me when testing in Reaper, Studio One 4/5 or any other DAW… My implementation for buttons with popup menus is nearly identical to yours apart from the fact that I trigger button clicks by overriding buttonClicked(Button* button) .

The issue occurs when the plugin window is closed while a popup menu is open (not when you delete the plugin instance). I can confirm that this happens on Mac (Reaper, Studio One 4/5, Ableton Live), and Windows (Reaper, Studio One 4/5, Cakewalk). It will crash the entire DAW when opening menus/submenus but it does not happen consistently.

@stephenk I’ve gone down this road before and although it works for clicking off the plugin to hide the menu, the major issue with this approach is that moving between a menu and one of it’s submenus will cause the entire menu to disappear because the focus is changed when a submenu is open. In my testing it also did not resolve the issue of clicking or moving the plugin from the title bar. In Reaper, the popup menu stays open while the plugin window is being moved from the title bar.

Are you definitely using the very latest develop branch?

I just tested again in REAPER, Live, and Logic, and it works in all of those DAWs. I’ve attached some gifs of the current behaviour:

trimmed trimmed2 trimmed3

I just tried opening the DSPModulePluginDemo editor in Live, opening a menu, and then closing the editor. I then repeated these steps 10 times in a row, and didn’t encounter any crashes.

If you’ve updated to the latest version of JUCE and are still encountering issues, please provide a minimal code example which reproduces the issue. Alternatively, if you are able to trigger the issues with any of the JUCE demos, please provide detailed instructions to trigger the issue, including the git hash of the JUCE commit used, the OS version, DAW version, and plugin format used.