PopupMenu with submenu result ID question


#1

I have a few places in my app where I use a PopupMenu with submenus. When I get the result from the show() method there is no way to tell which submenu the item was selected from that I am aware of. What I have been doing up to this point is something like this:

    enum MenuCommandID {
        section1 = 1000,
        section2 = 2000,
        section3 = 3000
    };

Then I loop through my application objects that are in each section and store the object IDs in a map where the key is the section ID plus an index that is incremented for each item, and the value is my objects ID. Then I have to check the result to see what range it is in so that I can determine the section. KnownPluginList::addToMenu() deals with this using a menuIdBase variable. Am I missing something? Is there a more direct way to do this? It seems like it would be very useful if each menu item could carry a juce::var instance or something similar.

UPDATE:
Adding a juce::var to PopupMenu::Item, and PopupMenu::Item* getItemForId (int itemId) const noexcept; from the ComboBox class did the trick for me. Hopefully someone has a better idea so I don’t have to touch my local JUCE code.


#2

What’s wrong with keeping base IDs for each top-level menu? This is what we do in the Projucer for the menu items - see here


#3

I did look at the code and that is precisely what I was doing. To clarify my submenus contain items from lists that contain integer IDs that are insert IDs from a database. So without generating unique item IDs across all lists using the technique discussed I have no way of determining which item was selected. Thank you for the response. The code below shows the two approaches. If you have an app that has many instances of this pattern, implementing without the juce::var can get a little tedious. But I can see how this wouldn’t be something high on the list of priorities.

void AClass::withItemVar()
{
    PopupMenu mainMenu;
    PopupMenu instrumentSubmenu;
    PopupMenu midiSubmenu;
    PopupMenu audioSubmenu;


    enum TrackMenuBase {
        instrument = 1000,
        midi = 2000,
        audio = 3000
    };

    std::vector<Instrument> instruments = getInstruments();
    std::vector<EffectRack> effectRacks = getEffectRacks();
    std::vector<MIDIOutput> midiOutputs = getMIDIOutputs();

    for (const auto& instrument: instruments)
    {
        instrumentSubmenu.addItem(instrument.getName(), instrument.getID(), juce::var(TrackMenuBase::instrument));
    }

    for (const auto& effect: effectRacks)
    {
        audioSubmenu.addItem(effect.getName(), effect.getID(), juce::var(TrackMenuBase::audio));
    }

    for (const auto& midiOut: midiOutputs)
    {
        midiSubmenu.addItem(midiOut.getName(), midiOut.getID(), juce::var(TrackMenuBase::midi));
    }


    mainMenu.addSubMenu("Add Instrument Track", instrumentSubmenu);
    mainMenu.addSubMenu("Add Audio Track", audioSubmenu);
    mainMenu.addSubMenu("Add MIDI Track", midiSubmenu);

    const int destinationID = mainMenu.show();
    if (destinationID != 0)
    {
        const int subMenuID = (int)mainMenu.getItemForId(destinationID)->userVar;
        if (subMenuID == TrackMenuBase::instrument)
        {
        }
        else if (subMenuID == TrackMenuBase::midi)
        {

        }
        else if (subMenuID == TrackMenuBase::audio)
        {

        }
    }

}


void AClass::withoutItemVar()
{
    PopupMenu mainMenu;
    PopupMenu instrumentSubmenu;
    PopupMenu midiSubmenu;
    PopupMenu audioSubmenu;


    enum TrackMenuBase {
        instrument = 1000,
        midi = 2000,
        audio = 3000
    };

    std::vector<Instrument> instruments = getInstruments();
    std::vector<EffectRack> effectRacks = getEffectRacks();
    std::vector<MIDIOutput> midiOutputs = getMIDIOutputs();

    std::unordered_map<int, int> idMap;

    int counter = 0;
    for (const auto& instrument: instruments)
    {
        int itemID = TrackMenuBase::instrument + counter;
        instrumentSubmenu.addItem(instrument.getName(), itemID);
        idMap[itemID] = instrument.getID();
        ++counter;
    }

    counter = 0;
    for (const auto& effect: effectRacks)
    {
        int itemID = TrackMenuBase::effect + counter;
        audioSubmenu.addItem(effect.getName(), itemID);
        idMap[itemID] = effect.getID();
        ++counter;
    }

    counter = 0;
    for (const auto& midiOut: midiOutputs)
    {
        int itemID = TrackMenuBase::midi + counter;
        midiSubmenu.addItem(midiOut.getName(), itemID);
        idMap[itemID] = midiOut.getID();
        ++counter;
   }

    mainMenu.addSubMenu("Add Instrument Track", instrumentSubmenu);
    mainMenu.addSubMenu("Add Audio Track", audioSubmenu);
    mainMenu.addSubMenu("Add MIDI Track", midiSubmenu);

    const int selectionID = mainMenu.show();
    int destinationID = idMap[selectionID];
    if (selectionID > 0 && selectionID < TrackMenuBase::midi) // Intrument Selected
    {
        // Create An Instrument Track
    }
    else if (selectionID >= TrackMenuBase::midi && selectionID < TrackMenuBase::audio ) // MIDI Selected
    {
        // Create An MIDI Track
    }
    else if (selectionID >= TrackMenuBase::audio) // Audio Selected
    {
        // Create An Audio Track
    }

}


#4

… And now I take back my suggestion. If the distinct lists have the same ID then adding the juce::var is of no help. Sorry for the noise.


#5

With the id of each sub-menu item (which I suppose is unique in its sub-menu) and the index of the sub-menu it should go it, you could encode an id that’s unique across the whole menu this way:

const int uniqueMenuId = uniqueIdInSubMenu * numSubMenus + subMenuIndex;

and, when an item is selected, you could get back those two values this way:

const int uniqueIdInSubMenu = uniqueMenuId / numSubMenus;
const int subMenuIndex = uniqueMenuId % numSubMenus

it is a little counter-intuitive to encode the sub-menu (that’s hierarchically more important) in the least significant part of the id, but aside from that it should just work.