Popupmenu enhancements for very many entries

Problem

The popup menus in Igor are currently not usable for more than > 50 entries.

 

NewPanel
make/T/N=256/O data = num2char(97 + mod(p, 26)) + "_" + num2str(p)
string/G str = Textwavetolist(data, ";")
PopupMenu pop, value=str

Reasons

  • User has to do too much scrolling for selecting an entry.
  • When selecting an entry from very far off from the currently selected entry there is also a lot of scrolling involved too.
  • Unresolved issues in multi monitor setups where no entries are shown at all 

Proposed solution

Provide a mechanism for creating subgroups for the popup menus like in 1.

API

PopupMenu subMenu(SubMenuName)="a_.*", subMenu(...)=...

Inner working

All popupmenu list items matching the regular expression would be collected and moved into a submenu named SubMenuName. The order of giving the keywords determines the order of collection for multiple submenus. No entry can be in more than one submenu. The position of the submenu can be set by adding an entry named SubMenuName into the item list. There is no resorting done so the order of the entries is completely controllable by the user.

GUI control procedure

The submenus are transparent for the GUI procedure this means the list indizes passed into it are the linear indizes without using submenus. There is no event send when selecting a submenu entry.

Advanced usage

Recursion can be achieved by adding entries like SubMenuA#SubMenuB. There SubMenuB would be a submenu in SubMenuA. The items for SubMenuB would be collected just from the subset of items of SubMenuA.

Alternative ways of specifying the list

There are cases where a regular expression is not the right choice. For these cases the user can specify a semicolon separated list of items for each submenu or one could invent new Special Characters in Menu Item Strings specifiers for denoting a submenu name and to which submenu an item belongs.

 

Can you use PopupContextualMenu/N=nameOfMenuDefinition, instead?

--Jim Prouty
Software Engineer, WaveMetrics, Inc.
Thanks Jim for the hint!

Looks promising

Function YourFunction()
    PopupContextualMenu/ASYN=Callback "first;second;third;"
    // (YourFunction continues...)
End

// Routine called when/if popup menu item is selected. selectedItem=1 is the first item.
Function Callback(String list, String selectedText, Variable selectedItem)
    Print "Callback: ", list, selectedText, selectedItem
End

Function Setup()

    string/G list = ""
    variable i

    make/T/N=26/O data = num2char(97 + mod(p, 26)) + "_" + num2str(p)

    for(i = 0; i < 26; i += 1)
        if(i == 25)
            list = AddListItem("submenuA ➨", list, ";", Inf)
        else       
            list = AddListItem(data[i], list, ";", Inf)
        endif
    endfor
   
    Killwindow/Z panel0
    newPanel/N=$"panel0"
    Popupmenu pop, value=list, proc=PopMenuProc,  bodyWidth=105
End

Function PopMenuProc(pa) : PopupMenuControl
    STRUCT WMPopupAction &pa

    switch(pa.eventCode)
        case 2: // mouse up
            Variable popNum = pa.popNum
            String popStr = pa.popStr
           
            if(!cmpstr(popStr, "submenuA ➨"))
                YourFunction()
            endif
            break
        default:
            print pa.eventCode 
    endswitch

    return 0
End


but is quite unintuitive as the user has to click the submenu and then also the main popup menu is hidden. This is contrary to the common way a submenu works (try out File->Recent Experiments).

Has this (or any other scheme for adding sub-menus to PopupMenus) been implemented in Igor 8?

Thanks.

This is how I would implement this:

Menu "FakePopupMenu", contextualmenu, dynamic
    SubMenu "Submenu A"
        FakePopupMenuSubmenu("A")
    End
    Submenu "Submenu B"
        FakePopupMenuSubmenu("B")
    End
    Submenu "Submenu C"
        FakePopupMenuSubmenu("C")
    End

End

Function/S FakePopupMenuSubmenu(String startsWith)
    WAVE/T listTextWave
    Variable n
    Variable numInWave = numpnts(listTextWave)
    String thisItem
    String matchingItemsList = ""
    startsWith = LowerStr(startsWith)
    For (n=0; n < numInWave; n++)
        thisItem = listTextWave[n]
        if (CmpStr(LowerStr(thisItem[0]), startsWith) == 0)
            // Add to the list
            matchingItemsList = AddListItem(thisItem, matchingItemsList, ";", inf)
        endif
    EndFor
   
    matchingItemsList = SortList(matchingItemsList, ";", 16)
    return matchingItemsList

End

Function YourFunction()
    PopupContextualMenu/N "FakePopupMenu"
    print V_flag, S_selection
End



Function Setup()

    Make/O/N=300/T listTextWave = num2char(97 + mod(p, 26)) + "_" + num2str(p)

   
    Killwindow/Z panel0
    newPanel/N=$"panel0"/K=1
    Button button0,pos={3.00,4.00},size={200.00,20.00},proc=ButtonProc,title="\\JL▼ Fake Popup Menu"
End


Function ButtonProc(ba) : ButtonControl
    STRUCT WMButtonAction &ba

    switch( ba.eventCode )
        case 2: // mouse up
            YourFunction()
            break
        case -1: // control being killed
            break
    endswitch

    return 0
End

 

This creates a button that simulates the popup menu, though as I have written it the text of the button does not change, unlike a PopupMenu. However you should be able to get that as well if you used dynamic text for the button's title, or if you force the title to change when the user selects an item. This also isn't quite the same user interface as a regular PopupMenu, but it's pretty close. Writing the menu definition is tedious, but shouldn't be difficult.

And now I thought I'd share what we have done.

Attached is a screenshot. The menu used to be larger than my rotated 16:9 monitor and now look how nicely cascaded it is. The switch is a one line change as we have generic code to do handle that. You even don't have to change the popup menu procedure.


The code is available at https://github.com/AllenInstitute/MIES/blob/master/Packages/MIES/MIES_G….