
Interface to choose a list item by typing, with autocompletion

A user-interface to select an item from a list, using keyboard entry to refine the selection options.
I used a notebook subwindow to create a control-like interface. Typing in the subwindow refines the list of possible matches within the provided list of values. The matches are displayed in a listbox. Autocompletion suggestions are shown as grey text. The tab key accepts the current autocomplete suggestion, and the up and down arrows step through the matches. The "Do It" button passes the notebook text on to the doSomething() function for further processing.
Execute GUIdemo() to create an example interface using the output from FunctionList to generate a string list, or pass your own list to ChooseFromListGUI(strList).
#pragma rtGlobals=3 #pragma IgorVersion=8 #pragma version=1.31 #pragma ModuleName=ListFilter #include <Resize Controls> // https: www.wavemetrics.com/user/tony // ---------------- edit this section --------------------- // edit this function to do something useful with selection static function doSomething(string selection) DoAlert 0, "You selected " + selection end // edit this function to choose what to do with a double click static function onDoubleClick(string selection) doSomething(selection) end // execute ChooseFromListGUI(strList) to create the GUI function GUIdemo() string strList=FunctionList("*", ";", "KIND:1") ChooseFromListGUI(strList, title="List Selector Demo") end // ---------------------------------------------------------- function ChooseFromListGUI(string strList, [string title]) title = SelectString(ParamIsDefault(title), title, "") // killing any old window also clears package data folder KillWindow /Z FilterPanel // create package data folder NewDataFolder /O root:Packages NewDataFolder /O root:Packages:ChooseFromListGUI DFREF dfr = root:Packages:ChooseFromListGUI variable /G dfr:stubLen=0 // create a sorted text wave wave w = ListToTextWave(strList, ";") Sort w, w Duplicate /O w dfr:displayList /WAVE=displayList, dfr:fullList // make a control panel GUI NewPanel/K=1/W=(100,50,310,240)/N=FilterPanel as title ModifyPanel/W=FilterPanel, noEdit=1 // insert a notebook subwindow to be used for filtering lists DefineGuide/W=FilterPanel nbR={FR,-28} NewNotebook/F=1/N=nb0/HOST=FilterPanel/W=(10,10,190,35)/FG=($"",$"",nbR,$"") /OPTS=3 Notebook FilterPanel#nb0 fSize=12, showRuler=0 Notebook FilterPanel#nb0 spacing={4,0,5} Notebook FilterPanel#nb0 margins={0,0,1000} SetWindow FilterPanel#nb0, activeChildFrame=0 ClearText(1) // sets notebook to its default appearance // make a button for clearing text in notebook subwindow Button buttonClear, win=FilterPanel,pos={185,14},size={15,15},title="" Button buttonClear, Picture=ListFilter#ClearTextPicture,Proc=ListFilter#ButtonProc, disable=1 ListBox listbox0, win=FilterPanel, pos={10,40}, size={190,120}, fsize=12, listwave=displayList ListBox listbox0, win=FilterPanel, mode=1, Proc=ListFilter#ListBoxProc, selRow=-1 Button buttonDoIt, win=FilterPanel,pos={150,165},size={50,20},title="Do It" Button buttonDoIt, win=FilterPanel,Proc=ListFilter#ButtonProc, disable=2 DoUpdate/W=FilterPanel // resizing userdata for controls Button buttonClear, win=FilterPanel,userdata(ResizeControlsInfo) = A"!!,GI!!#;m!!#<(!!#<(z!!#o2B4uAezzzzzzzzzzzzzz!!#o2B4uAezz" Button buttonClear, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzz!!#u:Du]k<zzzzzzzzzzz" Button buttonClear, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzz!!#u:Du]k<zzzzzzzzzzzzzz!!!" ListBox listbox0, win=FilterPanel,userdata(ResizeControlsInfo) = A"!!,A.!!#>.!!#AM!!#@Tz!!#](Aon\"Qzzzzzzzzzzzzzz!!#o2B4uAezz" ListBox listbox0, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzz!!#u:Du]k<zzzzzzzzzzz" ListBox listbox0, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzz!!#?(FEDG<zzzzzzzzzzzzzz!!!" Button buttonDoIt, win=FilterPanel,userdata(ResizeControlsInfo) = A"!!,G&!!#A4!!#>V!!#<Xz!!#o2B4uAezzzzzzzzzzzzzz!!#o2B4uAezz" Button buttonDoIt, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzz!!#?(FEDG<zzzzzzzzzzz" Button buttonDoIt, win=FilterPanel,userdata(ResizeControlsInfo) += A"zzz!!#?(FEDG<zzzzzzzzzzzzzz!!!" // resizing userdata for panel SetWindow FilterPanel,userdata(ResizeControlsInfo) = A"!!*'\"z!!#Aa!!#AMzzzzzzzzzzzzzzzzzzzzz" SetWindow FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzzzzzzzzzzzzzzz" SetWindow FilterPanel,userdata(ResizeControlsInfo) += A"zzzzzzzzzzzzzzzzzzz!!!" SetWindow FilterPanel,userdata(ResizeControlsGuides) = "nbR;" SetWindow FilterPanel,userdata(ResizeControlsInfonbR) = "NAME:nbR;WIN:FilterPanel;TYPE:User;HORIZONTAL:0;POSITION:182.00;GUIDE1:FR;GUIDE2:;RELPOSITION:-28;" // resizing panel hook SetWindow FilterPanel hook(ResizeControls)=ResizeControls#ResizeControlsHook // filter hook SetWindow FilterPanel hook(hFilterHook)=ListFilter#FilterHook end static function ClearText(variable doIt) if (doIt) Notebook FilterPanel#nb0 selection={startOfFile,endofFile}, textRGB=(50000,50000,50000), text="Filter" Notebook FilterPanel#nb0 selection={startOfFile,startOfFile} endif end static function ButtonProc(STRUCT WMButtonAction &s) if (s.eventCode != 2) return 0 endif strswitch(s.ctrlName) case "buttonClear" : ClearText(1) NVAR stubLen = root:Packages:ChooseFromListGUI:stubLen stubLen = 0 Button buttonClear, win=FilterPanel, disable=3 UpdateListboxWave("") break case "buttonDoIt" : ControlInfo/W=FilterPanel listbox0 if (V_Value >- 1) wave/T matchList = $(S_DataFolder+S_Value) doSomething(matchList[V_Value]) else Button buttonDoIt, win=FilterPanel, disable=2 endif break endswitch return 0 end static function ListBoxProc(STRUCT WMListboxAction &s) switch (s.eventCode) case 2: // mouseup - this captures deselection by shift-click ControlInfo/W=$(s.win) $(s.ctrlName) s.row = V_value case 4: case 5: // a list item is selected Button buttonDoIt, win=FilterPanel, disable=2*(s.row>=DimSize(s.listwave,0) || s.row<0) break case 3: // double click if (s.row<DimSize(s.listwave,0) && s.row>=0) string selection = s.listwave[s.row] onDoubleClick(selection) endif break endswitch return 0 end // update listbox wave based on string str static function UpdateListboxWave(string str) DFREF dfr = root:Packages:ChooseFromListGUI wave/SDFR=dfr/T displayList, fullList string regEx="(?i)"+str Grep/Z/E=regEx fullList as displayList ListBox listbox0, win=FilterPanel, selRow=-1 Button buttonDoIt, win=FilterPanel, disable=2 end // intercept and deal with keyboard events in notebook subwindow static function FilterHook(STRUCT WMWinHookStruct &s) if (s.eventcode == 2) // window is being killed KillDataFolder/Z root:Packages:ChooseFromListGUI return 1 endif GetWindow/Z FilterPanel#nb0 active if (V_Value == 0) // this check is not reliable return 0 endif if (s.eventCode==22 && cmpstr(s.winName, "FilterPanel#nb0")==0) return 1 // don't allow scrolling in notebook subwindow endif DFREF dfr = root:Packages:ChooseFromListGUI NVAR stubLen = dfr:stubLen if (s.eventcode==3 && stubLen==0) // mousedown return 1 endif if (s.eventcode == 5) // mouseup GetSelection Notebook, FilterPanel#nb0, 1 // get current position in notebook V_endPos = min(stubLen,V_endPos) V_startPos = min(stubLen,V_startPos) Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)} return 1 endif if (s.eventcode == 10) // menu strswitch(s.menuItem) case "Paste": GetSelection Notebook, FilterPanel#nb0, 1 // get current position in notebook string strScrap = GetScrapText() strScrap = ReplaceString("\r", strScrap, "") strScrap = ReplaceString("\n", strScrap, "") strScrap = ReplaceString("\t", strScrap, "") Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text=strScrap stubLen += strlen(strScrap)-abs(V_endPos-V_startPos) s.eventcode = 11 // pretend this was a keyboard event to allow execution to continue break case "Cut": GetSelection Notebook, FilterPanel#nb0, 3 // get current position in notebook PutScrapText s_selection Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text="" stubLen -= strlen(s_selection) s.eventcode = 11 break case "Clear": GetSelection Notebook, FilterPanel#nb0, 3 // get current position in notebook Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text="" // clear text stubLen -= strlen(s_selection) s.eventcode = 11 break endswitch Button buttonClear, win=FilterPanel, disable=3*(stublen==0) ClearText((stubLen == 0)) endif if (s.eventcode != 11) return 0 endif if (stubLen == 0) // Remove "Filter" text before starting to deal with keyboard activity Notebook FilterPanel#nb0 selection={startOfFile,endofFile}, text="" endif // deal with some non-printing characters switch(s.keycode) case 9: // tab: jump to end case 3: case 13: // enter or return: jump to end Notebook FilterPanel#nb0 selection={startOfFile,endofFile}, textRGB=(0,0,0) Notebook FilterPanel#nb0 selection={endOfFile,endofFile} GetSelection Notebook, FilterPanel#nb0, 1 // get current position in notebook stubLen = V_endPos break case 28: // left arrow ClearText((stubLen == 0)); return 0 case 29: // right arrow GetSelection Notebook, FilterPanel#nb0, 1 if(V_endPos >= stubLen) if (s.eventMod & 2) // shift key Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,stubLen)} else Notebook FilterPanel#nb0 selection={(0,stubLen),(0,stubLen)} endif ClearText((stubLen==0)); return 1 endif ClearText((stubLen==0)); return 0 case 8: case 127: // delete or forward delete GetSelection Notebook, FilterPanel#nb0, 1 if (V_startPos == V_endPos) V_startPos -= (s.keycode==8) V_endPos += (s.keycode==127) endif V_startPos = min(stubLen,V_startPos); V_endPos = min(stubLen,V_endPos) V_startPos = max(0, V_startPos); V_endPos = max(0, V_endPos) Notebook FilterPanel#nb0 selection={(0,V_startPos),(0,V_endPos)}, text="" stubLen -= abs(V_endPos-V_startPos) break endswitch // find and save current position GetSelection Notebook, FilterPanel#nb0, 1 variable selEnd = V_endPos if (strlen(s.keyText) == 1) // a one-byte printing character // insert character into current selection Notebook FilterPanel#nb0 text=s.keyText, textRGB=(0,0,0) stubLen += 1 - abs(V_endPos-V_startPos) // find out where we want to leave cursor GetSelection Notebook, FilterPanel#nb0, 1 selEnd = V_endPos endif string strStub = "", strInsert = "", strEnding = "" // select and format stub Notebook FilterPanel#nb0 selection={startOfFile,(0,stubLen)}, textRGB=(0,0,0) // get stub text GetSelection Notebook, FilterPanel#nb0, 3 strStub = s_selection // get matches based on stub text UpdateListboxWave(strStub) // do auto-completion based on stubLen characters wave /T matchList = dfr:DisplayList if (s.keycode==30 || s.keycode==31) // up or down arrow Notebook FilterPanel#nb0 selection={(0,stubLen),endOfFile} GetSelection Notebook, FilterPanel#nb0, 3 strEnding = s_selection strInsert = arrowKey(strStub, strEnding, 1-2*(s.keycode==30), matchList) else strInsert = completeStr(strStub, matchList) endif // insert completion text in grey Notebook FilterPanel#nb0 selection={(0,stubLen),endOfFile}, textRGB=(50000,50000,50000), text=strInsert Notebook FilterPanel#nb0 selection={startOfFile,startOfFile}, findText={"",1} Notebook FilterPanel#nb0 selection={(0,selEnd),(0,selEnd)}, findText={"",1} Button buttonClear, win=FilterPanel, disable=3*(stublen==0) ClearText((stubLen == 0)) return 1 // tell Igor we've handled all keyboard events end // returns completion text for first match of string s in text wave w static function/T completeStr(string stub, wave /T w) int stubLen = strlen(stub) if (stubLen == 0) return "" endif Make/free/T/N=1 w_out Grep/Z/E="(?i)^"+stub w as w_out if (DimSize(w_out,0) == 0) return "" endif return (w_out[0])[stubLen,Inf] end // find next or previous matching entry in wList and return completion text static function /T arrowKey(string stub, string ending, variable increment, wave /T wList) int stubLen = strlen(stub) if (stubLen == 0) return "" endif Make/free/T/N=0 w_out Grep/Z/E="(?i)^"+stub wList as w_out if (numpnts(w_out) == 0) return "" endif FindValue/TEXT=stub+ending /TXOP=4/Z w_out if (v_value >- 1) v_value += increment v_value = V_value<0 ? numpnts(w_out)-1 : v_value v_value = V_value >= numpnts(w_out) ? 0 : v_value else return (w_out[0])[stubLen,Inf] endif return (w_out[v_value])[stubLen,Inf] end // PNG: width= 90, height= 30 static Picture ClearTextPicture ASCII85Begin M,6r;%14!\!!!!.8Ou6I!!!"&!!!!?#R18/!3BT8GQ7^D&TgHDFAm*iFE_/6AH5;7DfQssEc39jTBQ =U"$&q@5u_NKm@(_/W^%DU?TFO*%Pm('G1+?)0-OWfgsSqYDhC]>ST`Z)0"D)K8@Ncp@>C,GnA#([A Jb0q`hu`4_P;#bpi`?T]j@medQ0%eKjbh8pO.^'LcCD,L*6P)3#odh%+r"J\$n:)LVlTrTIOm/oL'r #E&ce=k6Fiu8aXm1/:;hm?p#L^qI6J;D8?ZBMB_D14&ANkg9GMLR*Xs"/?@4VWUdJ,1MBB0[bECn33 KZ1__A<"/u9(o<Sf@<$^stNom5GmA@5SIJ$^\D=(p8G;&!HNh)6lgYLfW6>#jE3aT_'W?L>Xr73'A# m<7:2<I)2%%Jk$'i-7@>Ml+rPk4?-&B7ph6*MjH9&DV+Uo=D(4f6)f(Z9"SdCXSlj^V?0?8][X1#pG [0-Dbk^rVg[Rc^PBH/8U_8QFCWdi&3#DT?k^_gU>S_]F^9g'7.>5F'hcYV%X?$[g4KPRF0=NW^$Z(L G'1aoAKLpqS!ei0kB29iHZJgJ(_`LbUX%/C@J6!+"mVIs6V)A,gbdt$K*g_X+Um(2\?XI=m'*tR%i" ,kQIh51]UETI+HBA)DV:t/sl4<N*#^^=N.<B%00/$P>lNVlic"'Jc$p^ou^SLA\BS?`$Jla/$38;!# Q+K;T6T_?\3*d?$+27Ri'PYY-u]-gEMR^<d.ElNUY$#A@tX-ULd\IU&bfX]^T)a;<u7HgR!i2]GBpt SiZ1;JHl$jf3!k*jJlX$(ZroR:&!&8D[<-`g,)N96+6gSFVi$$Gr%h:1ioG%bZgmgbcp;2_&rF/[l" Qr^V@O-"j&UsEk)HgI'9`W31Wh"3^O,KrI/W'chm_@T!!^1"Y*Hknod`FiW&N@PIluYQdKILa)RK=W Ub4(Y)ao_5hG\K-+^73&AnBNQ+'D,6!KY/`F@6)`,V<qS#*-t?,F98]@h"8Y7Kj.%``Q=h4.L(m=Nd ,%6Vs`ptRkJNBdbpk]$\>hR4"[5SF8$^:q=W([+TB`,%?4h7'ET[Y6F!KJ3fH"9BpILuUI#GoI.rl( _DAn[OiS_GkcL7QT`\p%Sos;F.W#_'g]3!!!!j78?7R6=>B ASCII85End end

Forum

Support

Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
Version 1.10 cleans up the interface.
Panel is resizable.
September 16, 2020 at 11:48 am - Permalink
Tony- I haven't tried your demo code yet; I'm sure it's terrific!
Are you aware of the ListBox keyword "keySelectCol"? A listbox control will move the selection to a row that matches what you have typed, as long as you type quickly enough. By default it looks at column zero, this keyword will change the column used for the purpose.
September 16, 2020 at 03:16 pm - Permalink
Yes, that functionality is not affected by adding a 'filtering' option.
The demo GUI is a bit like the command tab of the help browser, where you can either type-to-select or type in the filter area to refine the list.
September 17, 2020 at 12:34 am - Permalink