I added the icon of my app to the system tray. When the user clicks on the tray icon a small window without a title bar is shown using a CallOutBox. That window should automatically hide when the user clicks anywhere outside the CallOutBox very much like a popup menu.
Within the main app window this works fine but when I tried to use it for the system tray window the window did not always hide when I clicked outside. It seems hiding of the CallOutBox is is related to the main app window because it always hides correctly if I click somewhere into the app window itself.
I am not sure if this is the expected behavior of a CallOutBox. Wouldn’t it make sense to also hide it when the user clicks anywhere outside the app itself? Maybe the CallOutBox is not the right tool for what I am trying to do. What else could be used to achieve a popup menu like behaviour?
I’d have expected it to also close if the app loses focus, but haven’t tested that. Might be something that needs a tweak…
If you simply click outside the window CallOutBox::inputAttemptWhenModal() is not called. Only if the main window is clicked CallOutBox::inputAttemptWhenModal() is called and the window will close.
I played around with toFront(), lostFocus() but was not able to achieve the desired effect. Any help would be very much appreciated.
Maybe try Desktop::addFocusChangeListener() ?
globalFocusChanged is not called if the main app window is not the key window and the status bar item is clicked which shows the small tool window.
If the main app window is the key window globalFocusChanged is called when the small tool window is shown.
But this is why the small tool window exists in the first place. To be shown instead of the main app window when the main app window is not shown
Well, you might need to check Process::isForegroundProcess to see if the app has lost focus
That was the missing ingredient. The following works for me.
Show the CallOutBox:
MiniPlayerPanel *miniPlayer = new MiniPlayerPanel(m_coreView);
juce::Rectangle area = event.eventComponent->getScreenBounds();
CallOutBox& calloutBox = CallOutBox::launchAsynchronously(miniPlayer, area, nullptr);
The displayed panel should listen to global focus changes:
where it should close the CallOutBox when it looses focus:
void MiniPlayerPanel::globalFocusChanged (Component* focusedComponent)
After more testing I noticed that Process::makeForegroundProcess() has two side effects which make the approach not really usable for a popup window of a status bar icon:
Clicking an icon should not change the main menu. makeForegroundProcess() will switch the main menu to the one of the application the popup window belongs to.
If the main application window is not minimized but only hidden behind other windows makeForegroundProcess will bring it to front. This means both the popup window and the main application windows are brought to the front. The correct behaviorwould be that the main app window stays in the background and only the popup window appears.
Another issue related to the main menu is that when the popup window is clicked the main menu is changed as well. A usual popup window should not switch the main menu.
On a side note, to get the correct highlighting of the nsstatusbaritem it is needed that the CallOutBox goes into a modal loop. Otherwise the highlighting disapears right after clicking the icon:
All this means I am still without a solution. It should be possible to do though. Here is a working example: http://blog.shpakovski.com/2011/07/cocoa-popup-window-in-status-bar.html the code is on github:
It seems a special kind of window is needed to make it work: “Next, we need a customized window to display it as a popover.” … “it configures popover appearance by removing title bar and standard window background.”
The Internet is wonderful. Here are more specifics.
It seems the solution is to make the window a NSPanel which don’t show up in the Window menu, disappear when the app becomes inactive, and shows up in the responder chain before the main window does. For the look of an utility window the NSUtilityWindowMask is needed.
Also the NSNonActivatingPanelMask is needed in the styleMask. That makes it a non activating panel that can become key without activating the application that owns it.
This is all specific to Mac OS of course. Is this possible at all with a JUCE component and its ComponentPeer?
While browsing through the JUCE code I found the NSViewComponent. This is used to show a NSView as a component. What I would need is pretty much the oposite. Show an component within a NSPanel that has the appropriate style flags as described in my previous post.
Is this possible at all?
You can add a component to any NSView by passing the pointer to addToDesktop