Add additional submenus

There are many times when I've wanted to programmatically create new submenus within a PopUpContextualMenu, and to allocate items to different submenus. This doesn't seem to be possible right now -- although you can dynamically adjust Menu items, the submenus themselves must be hard-coded into the Menu definition from the start. Instead, I'd like the end user to be able to add submenus to an existing menu definition on the fly using something like AddSubMenu(submenuName,addToMenuName,itemListString).

They can already do this by writing their own menu definition using the same menu name.

Here's an example:

Menu "ForContext", contextualmenu, dynamic
    "Hello", Beep
    Submenu "Color"
        "*COLORPOP*", DoSomethingWithColor()
    End
    Submenu "A Waves"
        WaveList("A*",";",""), /Q, DoSomethingWithWave()
    End
End

Function DoSomethingWithColor()
    GetLastUserMenuInfo
    Print V_Red, V_Green, V_Blue, V_Alpha
End
Function DoSomethingWithWave()
    GetLastUserMenuInfo
    WAVE w = $S_value
    Print "User selected "+GetWavesDataFolder(w,2)
End

Macro tryIt()
    Make/O Awave0, Awave1
    Make/O Bwave0, bwave1
    PopupContextualMenu/N "ForContext"
    if( V_flag < 0 )
        Print "User did not select anything"
    endif
End

// End User writes their own addition to the ForContext menu
Menu "ForContext", contextualmenu, dynamic
    "This is Mine", /Q, Print "This is Mine"
    Submenu "B Waves"
        WaveList("B*",";",""), /Q, DoSomethingWithWave()
    End
End

 

Hi Jim,

That's definitely useful, but for this case I'm looking more for something where I can have a pre-written program dynamically resolve how many submenus to create, what they should be called, and what items to put into each one. 

For my purpose, I'm allowing the user to write their own functions that can be tagged with a submenu assignment in a commented line below the function definition (shown below). I can extract this code as a string using ProcedureText, and I want to resolve the submenu they wish to assign that function to, and then build it into a contextual pop up menu. The idea is to have a menu drop down with all of the user's functions organized as they please into various submenus. Every time a user writes a new function, that's all they have to do--the GUI will automatically find it and parse the functions into their appropriate submenus. Thanks for your help. 

Function UserFunction(input1,input2,input3)
    Variable input1,input2,input3
   
    //SUBMENU=Submenu1

   //code....

End

 

In reply to by Ben Murphy-Baum

In Jim's example, the menu with the 'dynamic' keyword is built on-the-fly. Instead of Wavelist you can supply an arbitrarily complex string function, with the caveat that adding many menus or using slow text processing will make things sluggish.

But, for what you describe, I don't think the menu needs to be dynamic. Building the menu at compile time will catch any new functions.

For global user functions that take no parameters, this works:

menu "Macros"  
    UserFuncList()
end

function /s UserFuncList()
    string s = functionlist("*", ";", "KIND:2,NPARAMS:0")
    s = removeFromList(functionlist("*", ";", "KIND:1,NPARAMS:0"), s)  
    return s
end

function test()
    print "hi"
end

You could supply a wrapper function that executes the selected user function if you need to collect parameters or something.

Use GetLastUserMenuInfo to retrieve the menu selection:

menu "Macros"  
    UserFuncList(), MyWrapperFunc()
end

function /s UserFuncList()
    string s = functionlist("*", ";", "KIND:2,NPARAMS:0")
    s = removeFromList(functionlist("*", ";", "KIND:1,NPARAMS:0"), s)  
    return s
end

function MyWrapperFunc()
    GetlastUserMenuInfo
    doalert 0, "you selected "+s_value
end

 

... but I don't think there's an option for programmatically adding or removing submenus, unfortunately.

Give this a try:

 

Menu "ForContext", contextualmenu, dynamic
    "Hello", Beep
    Submenu SubmenuStr(0)
        SubmenuItems(0)
    End
    Submenu SubmenuStr(1)
        SubmenuItems(1)
    End
    Submenu SubmenuStr(2)
        SubmenuItems(2)
    End
    Submenu SubmenuStr(3)
        SubmenuItems(3)
    End
    Submenu SubmenuStr(4)
        SubmenuItems(4)
    End
    Submenu SubmenuStr(5)
        SubmenuItems(5)
    End
    Submenu SubmenuStr(6)
        SubmenuItems(6)
    End
    Submenu SubmenuStr(7)
        SubmenuItems(7)
    End
    Submenu SubmenuStr(8)
        SubmenuItems(8)
    End
    Submenu SubmenuStr(9)
        SubmenuItems(9)
    End
   
End



Macro tryIt()
    PopupContextualMenu/N "ForContext"
    print V_flag, S_selection
End

Function/S SubmenuStr(Variable num)
    if (num < 4)
        return "Submenu" + num2istr(num)
    else
        return "-"
    endif
End

Function/S SubmenuItems(Variable submenuNum)
    Variable itemNum = 0
    String itemsList = ""
    For (itemNum = 0; itemNum < 5; itemNum++)
        itemsList = AddListItem("Submenu" + num2istr(submenuNum) + ", Item" + num2istr(itemNum), itemsList, ";", inf)
    EndFor
    return itemsList
End

 

If you can live with writing your "ForContext" menu definition to contain a maximum number of submenus (in the example, I made up to 10 possible) then I think you can do what you want.

In your situation you would modify SubmenuStr to handle all of the parsed submenus found in the code.

Note that this works because our menu code will remove any menu items that are dividers if they are not followed by a non-divider menu item. Returning "-" from SubmenuStr makes that submenu a divider.