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….

 

 

cascaded-popupmenu.png (67.41 KB)