Detecting that plugin window has lost focus

I’ve made a custom dropdown menu class similar to juce’s combobox. On PC any clicks outside the plugin window or on the header of the window are detected, and my code closes the dropdown menu. I’m currently trying to fix an issue on mac where the user can move the plugin window around or click around in other areas of the daw without the dropdown menu closing. This results in a free floating menu that shouldn’t be there. On mac, only clicks immediately inside the plugin window are detected. Only then does my code close the dropdown menu.

I’ve messed around with:
Process::isForegroundProcess()
Component::getCurrentlyFocusedComponent()
desktop::addFocusChangeListener
desktop::addGlobalMouseListener

as well as checking for mouse clicks in a timerCallback:

void timerCallback()
{
int clicks = desktop->getMouseButtonClickCounter();
if (clicks > clickCounter)
{
if (!isMouseOver() && !parentComponent->isMouseOver())
{
closeMenu();
}
}
clickCounter = clicks;
}

None of these seem to work once the user clicks outside the plugin window on mac.

I’ve been digging around the juce_PopupMenu for ideas since this popup does close once the user clicks outside, so this is clearly possible to achieve in juce. But I still haven’t found any answers looking through that code.

Has anyone addressed this issue in their own plugins before? Are there effective ways of detecting that the plugin window is no longer in focus?

Thanks.

I’ve been doing more digging and it appears that on mac, inside ableton and garage band, the desktop class really doesn’t track anything unless it’s happening inside the plugin window. Mouse clicks and focus change, which I’ve been using to close my menus, are useless in this context.

Also it seems like this block of code (inside juce_gui_basics.cpp) used by juce’s popup menus is probably responsible for handling this edge case:

namespace juce
{
    bool juce_areThereAnyAlwaysOnTopWindows();

    bool isEmbeddedInForegroundProcess (Component* c);

   #if ! JUCE_WINDOWS
    bool isEmbeddedInForegroundProcess (Component*) { return false; }
   #endif

    /*  Returns true if this process is in the foreground, or if the viewComponent
        is embedded into a window owned by the foreground process.
    */
    static bool isForegroundOrEmbeddedProcess (Component* viewComponent)
    {
        return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent);
    }
}

I’m not sure how this code is accessible from juce_PopupMenu.cpp since it doesn’t appear to be included in that file. My understanding of c++ architecture still pretty limited. Also the definition of isEmbeddedInForegroundProcess exists inside juce_Win32_Windowing.cpp. It seems windows specific and so maybe its not what I’m looking for.

Anyways, still haven’t found a way to fix my issue. Really not picky about what a solution looks like, I just need some way of detecting that the user is clicking elsewhere so I can close my dropdown menus.

Any information on this area would be much appreciated.

The way this works for the PopupMenu is to make the menu window ‘modal’ when it is displaying. When the plugin window loses focus, or when it is moved, the window will receive a message telling it to dismiss any modal components, and the menu will hide. I’d recommend trying out an approach like that initially.

1 Like

Cool. Definitely down to try that out. What would be some good documentation to read up on and build my familiarity with that?

I’d recommend searching for the word “Modal” on the documentation page for Component. enterModalState is probably the most important function to check. You could also look at the PopupMenu implementaiton to see how enterModalState is used there.

1 Like

Awesome. Thanks!

Okay. Well… I’ve implemented modal states for my dropdown menus and so far it has solved none of my issues and created new ones too.

Clicking outside the plugin window in a daw does not appear to call exitModalState().

Moving the plugin window in a daw also does not appear to call exitModalState(). Instead, dragging repeatedly calls negative action events while doing nothing to my menu. In ableton I get pop sounds, in garage band the screen flashes white, over and over and over.

Alt-tabbing away from the window doesn’t even call exitModalState().

So in all of these cases my menus remain open.

According to reuk, exitModalState() should be called when the window is moved or the user clicks outside. I assume there’s something wrong with my implementation because that’s not what’s happening.

Also the popping and flashing is pretty abrasive and not what I see happening with juce’s menu’s.
Any time I click outside my menu within the plugin window and exitModalState() is called, I get that annoying pop or a flash depending on the daw. I need some way of not triggering the OS with these negative action events.

So with all that being said. Is there something beyond implementing the modal state functions within the component class that I need to do in order to get this to work properly?

I’ve done my best to follow along in the juce_PopupMenu code and implement things that way, but I’m clearly missing something.

As a side note:
I also get weird behavior where if I click on another UI element (like a slider or a button) while having a menu open, about half the time the new UI element does not receive a mouseDown event. The other half of the time it does. I assume there’s something asynchronous going on here with events around exitModalState(). I need some way of getting UI elements to always respond to mouse clicks which close a menu.

I decided to ditch the modal state stuff, more trouble than its worth.

I just ended up closing the menus when the mouse exits the plugin window. Not the 100% best solution but it will work for now. Detecting mouse position outside the plugin window in a daw on a mac is doable, but for whatever reason, detecting mouse down events in the exact same situation is impossible. :face_with_diagonal_mouth:

If anyone knows how to achieve this illusive mouse down event outside the plugin window I’d love to know about it.

Also for anyone reading this who is trying to solve this kind of problem, here’s the info I’ve got:

!Process::isForegroundProcess()

Detects events like alt-tab that push the plugin window into the background.

ModifierKeys::currentModifiers.isAnyMouseButtonDown() || ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown()

Detects mouse down events but fails in the daw/mac case outside the plugin window.

The desktop class’s focus change listener and global mouse listener can be useful here. But they also fail in the daw/mac case.

Component* mainWindow = parentComponent->getTopLevelComponent();
//in my code, parentComponent exists in the main component hierarchy
Rectangle<int> windowBounds (mainWindow->getScreenBounds());

Simple way to get the plugin window bounds

 int x = desktop->getMousePosition().getX();
 int y = desktop->getMousePosition().getY();

Getting mouse position. For whatever reason this works in the daw/mac case even though the desktop class’s other mouse related stuff does not.