Need PopupMenu, ShowMenuAsync, ModalComponentManager example or explanation or tutorial

I need to show a popup menu in an audio plugin which means I can’t show it modally. So, I need to use PopupMenu::showMenuAsync() which requries a `ModalComponentManager’ but I have no idea how this stuff works. I looked at the JUCE demo and I’m not finding an example.

Used long title for search purposes.

Xenakios on Discord made suggestions and I came up with this example:

class PresetNameAndList : public Component
{
public:
    class  ModalManager : public ModalComponentManager::Callback
    {
    public:
        ModalManager(std::function<void(int)> doStuff_) : doStuff(doStuff_) {}

        void modalStateFinished(int returnValue)
        {
            doStuff(returnValue);
        }

        std::function<void(int)> doStuff;
    };

    PresetNameAndList()
    {
        ScopedPointer<PopupMenu> bassSubMenu = new PopupMenu();
        bassSubMenu->addItem(1, "preset1", true, true);
        bassSubMenu->addItem(2, "preset2", true, false);
        bassSubMenu->addItem(3, "preset3", true, false);
        popupMenu.addSubMenu("BASS", *bassSubMenu, true);

        popupMenu.addSeparator();
        popupMenu.addSectionHeader("Section Header");
        
        ScopedPointer<PopupMenu> leadSubMenu = new PopupMenu();
        leadSubMenu->addItem(4, "preset1", true, true);
        leadSubMenu->addItem(5, "preset2", true, false);
        leadSubMenu->addItem(6, "preset3", true, false);
        popupMenu.addSubMenu("LEAD", *leadSubMenu, true);
    };
    ~PresetNameAndList() = default;

protected:
    void mouseDown(const MouseEvent & e)
    {
        popupMenu.showMenuAsync(PopupMenu::Options(), new ModalManager([this](int choice)
        {
            // do work here
        }));
    }

    PopupMenu popupMenu;
};

Rather than making an object for that you can also pass showAsyncMenu() a callback function:

// A function to handle the choice result of showSomeMenu()'s menu
void handleMenu(int choice) { ... }

void handleMenuModalCallback(int choice, juce::Component*) { ... }

void showSomeMenu() {
    juce::PopupMenu menu;

    // Add some items
    menu.addItem(...);

    // This will pass a component pointer along via "this"
    menu.showMenuAsync(
        juce::PopupMenu::Options(), 
        juce::ModalCallbackFunction::forComponent(handleMenuModalCallback, this)
    ); 

    // But showMenuAsync() can also just take a std::function (lambda, function pointer, functor, etc.)
    menu.showMenuAsync(juce::PopupMenu::Options(), handleMenu);
    menu.showMenuAsync(juce::PopupMenu::Options(), &handleMenu);
    menu.showMenuAsync(juce::PopupMenu::Options(), [](int choice) { ... });
    menu.showMenuAsync(juce::PopupMenu::Options(), SomeFunctorObject());
}
2 Likes

You can also avoid the unnecessary heap allocation new PopupMenu() since juce::PopupMenu() copyable anyway:

PresetNameAndList()
{
    // PopupMenus on the stack
    juce::PopupMenu bassSubMenu;
    bassSubMenu.addItem(1, "preset1", true, true);
    ...
    popupMenu.addSubMenu("BASS", bassSubMenu, true);
}

Is that stack allocation thing going to work even when the menu is shown async? Does something in the async system make a copy of it?

Yep, it’s what I’ve been using in our plugins

addSubMenu() adds copies of the items to a member array of juce::PopupMenu, which is then passed to the creation of the window during showMenuAsync()

Looking at the code it actually seems like adding items uses new internally anyway, so the items are always copied to heap allocated objects managed by the PopupMenu (even though Item seems to be trivially copyable…? Not sure why it’s not just juce::Array<Item>)

OK, cool. It’s a bit surprising behavior really. I expected the stack allocated local menu variable to just be destroyed and the subsequent menu showing not to work.