ApplicationCommandManager issue with modal component


#1

I have a DocumentWindow containing some Buttons. These Buttons have attached commands via setCommandToTrigger(). If I run a DialogWindow in modal mode the DocumentWindow gets blocked and the Buttons get disabled, because their commands are currently deactivated due to the modal dialog. Until here everything works as expected.

If the whole application loses focus, because I activate the window of a different application the disabled buttons get enabled, even with the modal dialog blocking them.

The problem comes from ApplicationCommandManager::findDefaultComponentTarget():

[code]ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
{
Component* c = Component::getCurrentlyFocusedComponent();

if (c == 0) // first
{
    TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow();

    if (activeWindow != 0)
    {
        c = activeWindow->getPeer()->getLastFocusedSubcomponent();

        if (c == 0)
            c = activeWindow;
    }
}

if (c == 0) // second
{
    // getting a bit desperate now - try all desktop comps..
    for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
    {
        ApplicationCommandTarget* const target
            = findTargetForComponent (Desktop::getInstance().getComponent (i)
                                          ->getPeer()->getLastFocusedSubcomponent());

        if (target != 0)
            return target;
    }
}
[...][/code]

Because the whole application has no focus there is no currently focused component and the first if block does nothing. In the second if block the first matching target is choosen. This way the commands attached to the buttons find an available target. But the modal dialog should block them.

[code]ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
{
Component* c = Component::getCurrentlyFocusedComponent();

if (c == 0) // new first
    c = Component::getCurrentlyModalComponent();

if (c == 0) // old first
{
    TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow();

    if (activeWindow != 0)
    {
        c = activeWindow->getPeer()->getLastFocusedSubcomponent();

        if (c == 0)
            c = activeWindow;
    }
}

if (c == 0) // old second
{
[...][/code]

To solve this problem the search must check for the currently modal component before trying anything else, if there is no currently focused component. This way a modal component blocks the commands attached to the Buttons even if the whole application has no focus.


#2

Ok, I see the problem, but not sure I agree with your solution. Surely a better plan would just to avoid anything getting the command if the entire app has lost focus, e.g.

[code]ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
{
Component* c = Component::getCurrentlyFocusedComponent();

if (c == 0)
{
    TopLevelWindow* const activeWindow = TopLevelWindow::getActiveTopLevelWindow();

    if (activeWindow != 0)
    {
        c = activeWindow->getPeer()->getLastFocusedSubcomponent();

        if (c == 0)
            c = activeWindow;
    }
}

if (c == 0 && Process::isForegroundProcess())
{
    // getting a bit desperate now - try all desktop comps..
    for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
    {

[/code]

?


#3

Well, your solution fixes the problem too, but introduces a new one on Linux, but only on Linux, not on Windows.

Beside the Buttons that have commands attached I have a Button that opens a PopupMenu when clicked. This menu contains items (added via PopupMenu::addCommandItem()) that trigger commands from the command manager. With your solution the items in this menu get disabled, if and only if the menu is visible. If the menu is hidden I can trigger the commands in this menu via the attached shortcuts, if it’s visible the commands can’t even be triggered via shortcuts.

With my solution the menu works as expected on Linux and Windows.

All commands I use here are provided by the main DocumentWindow.

On Windows Process::isForegroundProcess() uses WinAPI to check if the current process is the foreground process, on Linux this information is deduced from the focus of the componentpeers. I suspect this difference to be the cause for the new problem. If I change the isForegroundProcess() body in juce_linux_Windowing.cpp to this

bool Process::isForegroundProcess() throw() { return true; }

then your solution works on Linux too, with the popup menu. I’m investigating this right now.

Update:
Okay your solution works on Linux too (without any further changes) and is the better one.

The second problem was my fault. I constructed the popup menu in the main window constructor, but this doesn’t work because the command status (enabled/disabled) is only checked when the command is added to the menu via addCommandItem(). This gives me disabled commands in the popup menu on Linux because the mainwindows hasn’t been mapped and has no focus at this point.

So I can’t create the popup menu once and use it then multiple times, but I have to create it anew each time I want to display it to reflect the correct state of the commands added via addCommandItem().


#4

Ok, thanks for that - I’ll check in my fix then.